@@ -38,6 +38,7 @@ import type {
3838 ExcludesFalse ,
3939 Format ,
4040 GetAsyncFunctionFromUnion ,
41+ JsRedirect ,
4142 LibConfig ,
4243 LibOnlyConfig ,
4344 PkgJson ,
@@ -942,90 +943,112 @@ const composeEntryConfig = async (
942943 } ;
943944} ;
944945
945- const composeBundleConfig = (
946+ const composeBundlelessExternalConfig = (
946947 jsExtension : string ,
947948 redirect : Redirect ,
948949 cssModulesAuto : CssLoaderOptionsAuto ,
949950 bundle : boolean ,
950- ) : RsbuildConfig => {
951- if ( bundle ) return { } ;
951+ ) : {
952+ config : RsbuildConfig ;
953+ resolvedJsRedirect ?: DeepRequired < JsRedirect > ;
954+ } => {
955+ if ( bundle ) return { config : { } } ;
952956
953- const isStyleRedirect = redirect . style ?? true ;
957+ const doesRedirectStyle = redirect . style ?? true ;
958+ const jsRedirectPath = redirect . js ?. path ?? true ;
959+ const jsRedirectExtension = redirect . js ?. extension ?? true ;
954960
955961 type Resolver = GetAsyncFunctionFromUnion <
956962 ReturnType < NonNullable < Rspack . ExternalItemFunctionData [ 'getResolve' ] > >
957963 > ;
958964 let resolver : Resolver | undefined ;
959965
960966 return {
961- output : {
962- externals : [
963- async ( data , callback ) => {
964- const { request, getResolve, context, contextInfo } = data ;
965- if ( ! request || ! getResolve || ! context || ! contextInfo ) {
966- return callback ( ) ;
967- }
968-
969- if ( ! resolver ) {
970- resolver = ( await getResolve ( ) ) as Resolver ;
971- }
967+ resolvedJsRedirect : {
968+ path : jsRedirectPath ,
969+ extension : jsRedirectExtension ,
970+ } ,
971+ config : {
972+ output : {
973+ externals : [
974+ async ( data , callback ) => {
975+ const { request, getResolve, context, contextInfo } = data ;
976+ if ( ! request || ! getResolve || ! context || ! contextInfo ) {
977+ return callback ( ) ;
978+ }
972979
973- // Issuer is not empty string when the module is imported by another module.
974- // Prevent from externalizing entry modules here.
975- if ( contextInfo . issuer ) {
976- // Node.js ECMAScript module loader does no extension searching.
977- // Add a file extension according to autoExtension config
978- // when data.request is a relative path and do not have an extension.
979- // If data.request already have an extension, we replace it with new extension
980- // This may result in a change in semantics,
981- // user should use copy to keep origin file or use another separate entry to deal this
982- let resolvedRequest : string = request ;
983-
984- const cssExternal = cssExternalHandler (
985- resolvedRequest ,
986- callback ,
987- jsExtension ,
988- cssModulesAuto ,
989- isStyleRedirect ,
990- ) ;
991-
992- if ( cssExternal !== false ) {
993- return cssExternal ;
980+ if ( ! resolver ) {
981+ resolver = ( await getResolve ( ) ) as Resolver ;
994982 }
995983
996- if ( resolvedRequest [ 0 ] === '.' ) {
997- const resolved = await resolver ( context , resolvedRequest ) ;
998- resolvedRequest = normalizeSlash (
999- path . relative ( path . dirname ( contextInfo . issuer ) , resolved ) ,
984+ // Issuer is not empty string when the module is imported by another module.
985+ // Prevent from externalizing entry modules here.
986+ if ( contextInfo . issuer ) {
987+ let resolvedRequest : string = request ;
988+
989+ const cssExternal = cssExternalHandler (
990+ resolvedRequest ,
991+ callback ,
992+ jsExtension ,
993+ cssModulesAuto ,
994+ doesRedirectStyle ,
1000995 ) ;
1001996
1002- if ( resolvedRequest [ 0 ] !== '.' ) {
1003- resolvedRequest = `./ ${ resolvedRequest } ` ;
997+ if ( cssExternal !== false ) {
998+ return cssExternal ;
1004999 }
10051000
1006- const ext = extname ( resolvedRequest ) ;
1001+ // Node.js ECMAScript module loader does no extension searching.
1002+ // Add a file extension according to autoExtension config
1003+ // when data.request is a relative path and do not have an extension.
1004+ // If data.request already have an extension, we replace it with new extension
1005+ // This may result in a change in semantics,
1006+ // user should use copy to keep origin file or use another separate entry to deal this
1007+
1008+ if ( jsRedirectPath ) {
1009+ try {
1010+ resolvedRequest = await resolver ( context , resolvedRequest ) ;
1011+ } catch ( e ) {
1012+ // Do nothing, fallthrough to other external matches.
1013+ }
1014+
1015+ resolvedRequest = normalizeSlash (
1016+ path . relative (
1017+ path . dirname ( contextInfo . issuer ) ,
1018+ resolvedRequest ,
1019+ ) ,
1020+ ) ;
10071021
1008- if ( ext ) {
1009- if ( JS_EXTENSIONS_PATTERN . test ( resolvedRequest ) ) {
1010- resolvedRequest = resolvedRequest . replace (
1011- / \. [ ^ . ] + $ / ,
1012- jsExtension ,
1013- ) ;
1022+ if ( resolvedRequest [ 0 ] !== '.' ) {
1023+ resolvedRequest = `./${ resolvedRequest } ` ;
1024+ }
1025+ }
1026+
1027+ if ( jsRedirectExtension ) {
1028+ const ext = extname ( resolvedRequest ) ;
1029+ if ( ext ) {
1030+ if ( JS_EXTENSIONS_PATTERN . test ( resolvedRequest ) ) {
1031+ resolvedRequest = resolvedRequest . replace (
1032+ / \. [ ^ . ] + $ / ,
1033+ jsExtension ,
1034+ ) ;
1035+ } else {
1036+ // If it does not match jsExtensionsPattern, we should do nothing, eg: ./foo.png
1037+ return callback ( ) ;
1038+ }
10141039 } else {
1015- // If it does not match jsExtensionsPattern, we should do nothing, eg: ./foo.png
1016- return callback ( ) ;
1040+ // TODO: add redirect.extension option
1041+ resolvedRequest = ` ${ resolvedRequest } ${ jsExtension } ` ;
10171042 }
1018- } else {
1019- // TODO: add redirect.extension option
1020- resolvedRequest = `${ resolvedRequest } ${ jsExtension } ` ;
10211043 }
1044+
1045+ return callback ( undefined , resolvedRequest ) ;
10221046 }
10231047
1024- return callback ( undefined , resolvedRequest ) ;
1025- }
1026- callback ( ) ;
1027- } ,
1028- ] as Rspack . ExternalItem [ ] ,
1048+ callback ( ) ;
1049+ } ,
1050+ ] as Rspack . ExternalItem [ ] ,
1051+ } ,
10291052 } ,
10301053 } ;
10311054} ;
@@ -1198,7 +1221,7 @@ async function composeLibRsbuildConfig(config: LibConfig) {
11981221 externalHelpers ,
11991222 pkgJson ,
12001223 ) ;
1201- const externalsConfig = composeExternalsConfig (
1224+ const userExternalConfig = composeExternalsConfig (
12021225 format ! ,
12031226 config . output ?. externals ,
12041227 ) ;
@@ -1207,7 +1230,7 @@ async function composeLibRsbuildConfig(config: LibConfig) {
12071230 jsExtension,
12081231 dtsExtension,
12091232 } = composeAutoExtensionConfig ( config , autoExtension , pkgJson ) ;
1210- const bundleConfig = composeBundleConfig (
1233+ const { config : bundlelessExternalConfig } = composeBundlelessExternalConfig (
12111234 jsExtension ,
12121235 redirect ,
12131236 cssModulesAuto ,
@@ -1237,7 +1260,7 @@ async function composeLibRsbuildConfig(config: LibConfig) {
12371260 const externalsWarnConfig = composeExternalsWarnConfig (
12381261 format ! ,
12391262 autoExternalConfig ?. output ?. externals ,
1240- externalsConfig ?. output ?. externals ,
1263+ userExternalConfig ?. output ?. externals ,
12411264 ) ;
12421265 const minifyConfig = composeMinifyConfig ( config ) ;
12431266 const bannerFooterConfig = composeBannerFooterConfig ( banner , footer ) ;
@@ -1249,15 +1272,20 @@ async function composeLibRsbuildConfig(config: LibConfig) {
12491272 return mergeRsbuildConfig (
12501273 formatConfig ,
12511274 shimsConfig ,
1275+ syntaxConfig ,
12521276 externalHelpersConfig ,
1253- // externalsWarnConfig should before other externals config
1277+ autoExtensionConfig ,
1278+
1279+ // `externalsWarnConfig` should before other externals config.
12541280 externalsWarnConfig ,
1255- externalsConfig ,
12561281 autoExternalConfig ,
1257- autoExtensionConfig ,
1258- syntaxConfig ,
1259- bundleConfig ,
12601282 targetConfig ,
1283+ // The externals config in `bundleConfig` should present after all externals config as
1284+ // it relies on other externals config to bail out the externalized modules first then resolve
1285+ // the correct path for relative imports.
1286+ userExternalConfig ,
1287+ bundlelessExternalConfig ,
1288+
12611289 entryConfig ,
12621290 cssConfig ,
12631291 entryChunkConfig ,
0 commit comments