@@ -38,6 +38,7 @@ import type {
3838 ExcludesFalse ,
3939 Format ,
4040 GetAsyncFunctionFromUnion ,
41+ JsRedirect ,
4142 LibConfig ,
4243 LibOnlyConfig ,
4344 PkgJson ,
@@ -956,90 +957,112 @@ const composeEntryConfig = async (
956957 } ;
957958} ;
958959
959- const composeBundleConfig = (
960+ const composeBundlelessExternalConfig = (
960961 jsExtension : string ,
961962 redirect : Redirect ,
962963 cssModulesAuto : CssLoaderOptionsAuto ,
963964 bundle : boolean ,
964- ) : RsbuildConfig => {
965- if ( bundle ) return { } ;
965+ ) : {
966+ config : RsbuildConfig ;
967+ resolvedJsRedirect ?: DeepRequired < JsRedirect > ;
968+ } => {
969+ if ( bundle ) return { config : { } } ;
966970
967- const isStyleRedirect = redirect . style ?? true ;
971+ const doesRedirectStyle = redirect . style ?? true ;
972+ const jsRedirectPath = redirect . js ?. path ?? true ;
973+ const jsRedirectExtension = redirect . js ?. extension ?? true ;
968974
969975 type Resolver = GetAsyncFunctionFromUnion <
970976 ReturnType < NonNullable < Rspack . ExternalItemFunctionData [ 'getResolve' ] > >
971977 > ;
972978 let resolver : Resolver | undefined ;
973979
974980 return {
975- output : {
976- externals : [
977- async ( data , callback ) => {
978- const { request, getResolve, context, contextInfo } = data ;
979- if ( ! request || ! getResolve || ! context || ! contextInfo ) {
980- return callback ( ) ;
981- }
982-
983- if ( ! resolver ) {
984- resolver = ( await getResolve ( ) ) as Resolver ;
985- }
981+ resolvedJsRedirect : {
982+ path : jsRedirectPath ,
983+ extension : jsRedirectExtension ,
984+ } ,
985+ config : {
986+ output : {
987+ externals : [
988+ async ( data , callback ) => {
989+ const { request, getResolve, context, contextInfo } = data ;
990+ if ( ! request || ! getResolve || ! context || ! contextInfo ) {
991+ return callback ( ) ;
992+ }
986993
987- // Issuer is not empty string when the module is imported by another module.
988- // Prevent from externalizing entry modules here.
989- if ( contextInfo . issuer ) {
990- // Node.js ECMAScript module loader does no extension searching.
991- // Add a file extension according to autoExtension config
992- // when data.request is a relative path and do not have an extension.
993- // If data.request already have an extension, we replace it with new extension
994- // This may result in a change in semantics,
995- // user should use copy to keep origin file or use another separate entry to deal this
996- let resolvedRequest : string = request ;
997-
998- const cssExternal = cssExternalHandler (
999- resolvedRequest ,
1000- callback ,
1001- jsExtension ,
1002- cssModulesAuto ,
1003- isStyleRedirect ,
1004- ) ;
1005-
1006- if ( cssExternal !== false ) {
1007- return cssExternal ;
994+ if ( ! resolver ) {
995+ resolver = ( await getResolve ( ) ) as Resolver ;
1008996 }
1009997
1010- if ( resolvedRequest [ 0 ] === '.' ) {
1011- const resolved = await resolver ( context , resolvedRequest ) ;
1012- resolvedRequest = normalizeSlash (
1013- path . relative ( path . dirname ( contextInfo . issuer ) , resolved ) ,
998+ // Issuer is not empty string when the module is imported by another module.
999+ // Prevent from externalizing entry modules here.
1000+ if ( contextInfo . issuer ) {
1001+ let resolvedRequest : string = request ;
1002+
1003+ const cssExternal = cssExternalHandler (
1004+ resolvedRequest ,
1005+ callback ,
1006+ jsExtension ,
1007+ cssModulesAuto ,
1008+ doesRedirectStyle ,
10141009 ) ;
10151010
1016- if ( resolvedRequest [ 0 ] !== '.' ) {
1017- resolvedRequest = `./ ${ resolvedRequest } ` ;
1011+ if ( cssExternal !== false ) {
1012+ return cssExternal ;
10181013 }
10191014
1020- const ext = extname ( resolvedRequest ) ;
1015+ // Node.js ECMAScript module loader does no extension searching.
1016+ // Add a file extension according to autoExtension config
1017+ // when data.request is a relative path and do not have an extension.
1018+ // If data.request already have an extension, we replace it with new extension
1019+ // This may result in a change in semantics,
1020+ // user should use copy to keep origin file or use another separate entry to deal this
1021+
1022+ if ( jsRedirectPath ) {
1023+ try {
1024+ resolvedRequest = await resolver ( context , resolvedRequest ) ;
1025+ } catch ( e ) {
1026+ // Do nothing, fallthrough to other external matches.
1027+ }
1028+
1029+ resolvedRequest = normalizeSlash (
1030+ path . relative (
1031+ path . dirname ( contextInfo . issuer ) ,
1032+ resolvedRequest ,
1033+ ) ,
1034+ ) ;
10211035
1022- if ( ext ) {
1023- if ( JS_EXTENSIONS_PATTERN . test ( resolvedRequest ) ) {
1024- resolvedRequest = resolvedRequest . replace (
1025- / \. [ ^ . ] + $ / ,
1026- jsExtension ,
1027- ) ;
1036+ if ( resolvedRequest [ 0 ] !== '.' ) {
1037+ resolvedRequest = `./${ resolvedRequest } ` ;
1038+ }
1039+ }
1040+
1041+ if ( jsRedirectExtension ) {
1042+ const ext = extname ( resolvedRequest ) ;
1043+ if ( ext ) {
1044+ if ( JS_EXTENSIONS_PATTERN . test ( resolvedRequest ) ) {
1045+ resolvedRequest = resolvedRequest . replace (
1046+ / \. [ ^ . ] + $ / ,
1047+ jsExtension ,
1048+ ) ;
1049+ } else {
1050+ // If it does not match jsExtensionsPattern, we should do nothing, eg: ./foo.png
1051+ return callback ( ) ;
1052+ }
10281053 } else {
1029- // If it does not match jsExtensionsPattern, we should do nothing, eg: ./foo.png
1030- return callback ( ) ;
1054+ // TODO: add redirect.extension option
1055+ resolvedRequest = ` ${ resolvedRequest } ${ jsExtension } ` ;
10311056 }
1032- } else {
1033- // TODO: add redirect.extension option
1034- resolvedRequest = `${ resolvedRequest } ${ jsExtension } ` ;
10351057 }
1058+
1059+ return callback ( undefined , resolvedRequest ) ;
10361060 }
10371061
1038- return callback ( undefined , resolvedRequest ) ;
1039- }
1040- callback ( ) ;
1041- } ,
1042- ] as Rspack . ExternalItem [ ] ,
1062+ callback ( ) ;
1063+ } ,
1064+ ] as Rspack . ExternalItem [ ] ,
1065+ } ,
10431066 } ,
10441067 } ;
10451068} ;
@@ -1212,7 +1235,7 @@ async function composeLibRsbuildConfig(config: LibConfig) {
12121235 externalHelpers ,
12131236 pkgJson ,
12141237 ) ;
1215- const externalsConfig = composeExternalsConfig (
1238+ const userExternalConfig = composeExternalsConfig (
12161239 format ! ,
12171240 config . output ?. externals ,
12181241 ) ;
@@ -1221,7 +1244,7 @@ async function composeLibRsbuildConfig(config: LibConfig) {
12211244 jsExtension,
12221245 dtsExtension,
12231246 } = composeAutoExtensionConfig ( config , autoExtension , pkgJson ) ;
1224- const bundleConfig = composeBundleConfig (
1247+ const { config : bundlelessExternalConfig } = composeBundlelessExternalConfig (
12251248 jsExtension ,
12261249 redirect ,
12271250 cssModulesAuto ,
@@ -1252,7 +1275,7 @@ async function composeLibRsbuildConfig(config: LibConfig) {
12521275 const externalsWarnConfig = composeExternalsWarnConfig (
12531276 format ! ,
12541277 autoExternalConfig ?. output ?. externals ,
1255- externalsConfig ?. output ?. externals ,
1278+ userExternalConfig ?. output ?. externals ,
12561279 ) ;
12571280 const minifyConfig = composeMinifyConfig ( config ) ;
12581281 const bannerFooterConfig = composeBannerFooterConfig ( banner , footer ) ;
@@ -1264,15 +1287,20 @@ async function composeLibRsbuildConfig(config: LibConfig) {
12641287 return mergeRsbuildConfig (
12651288 formatConfig ,
12661289 shimsConfig ,
1290+ syntaxConfig ,
12671291 externalHelpersConfig ,
1268- // externalsWarnConfig should before other externals config
1292+ autoExtensionConfig ,
1293+
1294+ // `externalsWarnConfig` should before other externals config.
12691295 externalsWarnConfig ,
1270- externalsConfig ,
12711296 autoExternalConfig ,
1272- autoExtensionConfig ,
1273- syntaxConfig ,
1274- bundleConfig ,
12751297 targetConfig ,
1298+ // The externals config in `bundleConfig` should present after all externals config as
1299+ // it relies on other externals config to bail out the externalized modules first then resolve
1300+ // the correct path for relative imports.
1301+ userExternalConfig ,
1302+ bundlelessExternalConfig ,
1303+
12761304 entryConfig ,
12771305 cssConfig ,
12781306 entryChunkConfig ,
0 commit comments