@@ -39,6 +39,7 @@ import type {
3939 ExcludesFalse ,
4040 Format ,
4141 GetAsyncFunctionFromUnion ,
42+ JsRedirect ,
4243 LibConfig ,
4344 LibOnlyConfig ,
4445 PkgJson ,
@@ -958,90 +959,112 @@ const composeEntryConfig = async (
958959 } ;
959960} ;
960961
961- const composeBundleConfig = (
962+ const composeBundlelessExternalConfig = (
962963 jsExtension : string ,
963964 redirect : Redirect ,
964965 cssModulesAuto : CssLoaderOptionsAuto ,
965966 bundle : boolean ,
966- ) : RsbuildConfig => {
967- if ( bundle ) return { } ;
967+ ) : {
968+ config : RsbuildConfig ;
969+ resolvedJsRedirect ?: DeepRequired < JsRedirect > ;
970+ } => {
971+ if ( bundle ) return { config : { } } ;
968972
969- const isStyleRedirect = redirect . style ?? true ;
973+ const doesRedirectStyle = redirect . style ?? true ;
974+ const jsRedirectPath = redirect . js ?. path ?? true ;
975+ const jsRedirectExtension = redirect . js ?. extension ?? true ;
970976
971977 type Resolver = GetAsyncFunctionFromUnion <
972978 ReturnType < NonNullable < Rspack . ExternalItemFunctionData [ 'getResolve' ] > >
973979 > ;
974980 let resolver : Resolver | undefined ;
975981
976982 return {
977- output : {
978- externals : [
979- async ( data , callback ) => {
980- const { request, getResolve, context, contextInfo } = data ;
981- if ( ! request || ! getResolve || ! context || ! contextInfo ) {
982- return callback ( ) ;
983- }
984-
985- if ( ! resolver ) {
986- resolver = ( await getResolve ( ) ) as Resolver ;
987- }
983+ resolvedJsRedirect : {
984+ path : jsRedirectPath ,
985+ extension : jsRedirectExtension ,
986+ } ,
987+ config : {
988+ output : {
989+ externals : [
990+ async ( data , callback ) => {
991+ const { request, getResolve, context, contextInfo } = data ;
992+ if ( ! request || ! getResolve || ! context || ! contextInfo ) {
993+ return callback ( ) ;
994+ }
988995
989- // Issuer is not empty string when the module is imported by another module.
990- // Prevent from externalizing entry modules here.
991- if ( contextInfo . issuer ) {
992- // Node.js ECMAScript module loader does no extension searching.
993- // Add a file extension according to autoExtension config
994- // when data.request is a relative path and do not have an extension.
995- // If data.request already have an extension, we replace it with new extension
996- // This may result in a change in semantics,
997- // user should use copy to keep origin file or use another separate entry to deal this
998- let resolvedRequest : string = request ;
999-
1000- const cssExternal = cssExternalHandler (
1001- resolvedRequest ,
1002- callback ,
1003- jsExtension ,
1004- cssModulesAuto ,
1005- isStyleRedirect ,
1006- ) ;
1007-
1008- if ( cssExternal !== false ) {
1009- return cssExternal ;
996+ if ( ! resolver ) {
997+ resolver = ( await getResolve ( ) ) as Resolver ;
1010998 }
1011999
1012- if ( resolvedRequest [ 0 ] === '.' ) {
1013- const resolved = await resolver ( context , resolvedRequest ) ;
1014- resolvedRequest = normalizeSlash (
1015- path . relative ( path . dirname ( contextInfo . issuer ) , resolved ) ,
1000+ // Issuer is not empty string when the module is imported by another module.
1001+ // Prevent from externalizing entry modules here.
1002+ if ( contextInfo . issuer ) {
1003+ let resolvedRequest : string = request ;
1004+
1005+ const cssExternal = cssExternalHandler (
1006+ resolvedRequest ,
1007+ callback ,
1008+ jsExtension ,
1009+ cssModulesAuto ,
1010+ doesRedirectStyle ,
10161011 ) ;
10171012
1018- if ( resolvedRequest [ 0 ] !== '.' ) {
1019- resolvedRequest = `./ ${ resolvedRequest } ` ;
1013+ if ( cssExternal !== false ) {
1014+ return cssExternal ;
10201015 }
10211016
1022- const ext = extname ( resolvedRequest ) ;
1017+ // Node.js ECMAScript module loader does no extension searching.
1018+ // Add a file extension according to autoExtension config
1019+ // when data.request is a relative path and do not have an extension.
1020+ // If data.request already have an extension, we replace it with new extension
1021+ // This may result in a change in semantics,
1022+ // user should use copy to keep origin file or use another separate entry to deal this
1023+
1024+ if ( jsRedirectPath ) {
1025+ try {
1026+ resolvedRequest = await resolver ( context , resolvedRequest ) ;
1027+ } catch ( e ) {
1028+ // Do nothing, fallthrough to other external matches.
1029+ }
1030+
1031+ resolvedRequest = normalizeSlash (
1032+ path . relative (
1033+ path . dirname ( contextInfo . issuer ) ,
1034+ resolvedRequest ,
1035+ ) ,
1036+ ) ;
10231037
1024- if ( ext ) {
1025- if ( JS_EXTENSIONS_PATTERN . test ( resolvedRequest ) ) {
1026- resolvedRequest = resolvedRequest . replace (
1027- / \. [ ^ . ] + $ / ,
1028- jsExtension ,
1029- ) ;
1038+ if ( resolvedRequest [ 0 ] !== '.' ) {
1039+ resolvedRequest = `./${ resolvedRequest } ` ;
1040+ }
1041+ }
1042+
1043+ if ( jsRedirectExtension ) {
1044+ const ext = extname ( resolvedRequest ) ;
1045+ if ( ext ) {
1046+ if ( JS_EXTENSIONS_PATTERN . test ( resolvedRequest ) ) {
1047+ resolvedRequest = resolvedRequest . replace (
1048+ / \. [ ^ . ] + $ / ,
1049+ jsExtension ,
1050+ ) ;
1051+ } else {
1052+ // If it does not match jsExtensionsPattern, we should do nothing, eg: ./foo.png
1053+ return callback ( ) ;
1054+ }
10301055 } else {
1031- // If it does not match jsExtensionsPattern, we should do nothing, eg: ./foo.png
1032- return callback ( ) ;
1056+ // TODO: add redirect.extension option
1057+ resolvedRequest = ` ${ resolvedRequest } ${ jsExtension } ` ;
10331058 }
1034- } else {
1035- // TODO: add redirect.extension option
1036- resolvedRequest = `${ resolvedRequest } ${ jsExtension } ` ;
10371059 }
1060+
1061+ return callback ( undefined , resolvedRequest ) ;
10381062 }
10391063
1040- return callback ( undefined , resolvedRequest ) ;
1041- }
1042- callback ( ) ;
1043- } ,
1044- ] as Rspack . ExternalItem [ ] ,
1064+ callback ( ) ;
1065+ } ,
1066+ ] as Rspack . ExternalItem [ ] ,
1067+ } ,
10451068 } ,
10461069 } ;
10471070} ;
@@ -1217,7 +1240,7 @@ async function composeLibRsbuildConfig(
12171240 externalHelpers ,
12181241 pkgJson ,
12191242 ) ;
1220- const externalsConfig = composeExternalsConfig (
1243+ const userExternalConfig = composeExternalsConfig (
12211244 format ! ,
12221245 config . output ?. externals ,
12231246 ) ;
@@ -1226,7 +1249,7 @@ async function composeLibRsbuildConfig(
12261249 jsExtension,
12271250 dtsExtension,
12281251 } = composeAutoExtensionConfig ( config , autoExtension , pkgJson ) ;
1229- const bundleConfig = composeBundleConfig (
1252+ const { config : bundlelessExternalConfig } = composeBundlelessExternalConfig (
12301253 jsExtension ,
12311254 redirect ,
12321255 cssModulesAuto ,
@@ -1257,7 +1280,7 @@ async function composeLibRsbuildConfig(
12571280 const externalsWarnConfig = composeExternalsWarnConfig (
12581281 format ! ,
12591282 autoExternalConfig ?. output ?. externals ,
1260- externalsConfig ?. output ?. externals ,
1283+ userExternalConfig ?. output ?. externals ,
12611284 ) ;
12621285 const minifyConfig = composeMinifyConfig ( config ) ;
12631286 const bannerFooterConfig = composeBannerFooterConfig ( banner , footer ) ;
@@ -1269,15 +1292,20 @@ async function composeLibRsbuildConfig(
12691292 return mergeRsbuildConfig (
12701293 formatConfig ,
12711294 shimsConfig ,
1295+ syntaxConfig ,
12721296 externalHelpersConfig ,
1273- // externalsWarnConfig should before other externals config
1297+ autoExtensionConfig ,
1298+
1299+ // `externalsWarnConfig` should before other externals config.
12741300 externalsWarnConfig ,
1275- externalsConfig ,
12761301 autoExternalConfig ,
1277- autoExtensionConfig ,
1278- syntaxConfig ,
1279- bundleConfig ,
12801302 targetConfig ,
1303+ // The externals config in `bundleConfig` should present after all externals config as
1304+ // it relies on other externals config to bail out the externalized modules first then resolve
1305+ // the correct path for relative imports.
1306+ userExternalConfig ,
1307+ bundlelessExternalConfig ,
1308+
12811309 entryConfig ,
12821310 cssConfig ,
12831311 entryChunkConfig ,
0 commit comments