Skip to content

Commit acadf07

Browse files
feat: use resolver function to resolve module (#535)
Co-authored-by: Timeless0911 <[email protected]>
1 parent 5bab1d5 commit acadf07

File tree

50 files changed

+824
-174
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+824
-174
lines changed

packages/core/src/config.ts

Lines changed: 130 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
type RsbuildConfig,
66
type RsbuildPlugin,
77
type RsbuildPlugins,
8+
type Rspack,
89
defineConfig as defineRsbuildConfig,
910
loadConfig as loadRsbuildConfig,
1011
mergeRsbuildConfig,
@@ -38,6 +39,7 @@ import type {
3839
DeepRequired,
3940
ExcludesFalse,
4041
Format,
42+
JsRedirect,
4143
LibConfig,
4244
LibOnlyConfig,
4345
PkgJson,
@@ -50,6 +52,7 @@ import type {
5052
RslibConfigAsyncFn,
5153
RslibConfigExport,
5254
RslibConfigSyncFn,
55+
RspackResolver,
5356
Shims,
5457
Syntax,
5558
} from './types';
@@ -63,6 +66,7 @@ import {
6366
isIntermediateOutputFormat,
6467
isObject,
6568
nodeBuiltInModules,
69+
normalizeSlash,
6670
omit,
6771
pick,
6872
readPackageJson,
@@ -956,64 +960,113 @@ const composeEntryConfig = async (
956960
};
957961
};
958962

959-
const composeBundleConfig = (
963+
const composeBundlelessExternalConfig = (
960964
jsExtension: string,
961965
redirect: Redirect,
962966
cssModulesAuto: CssLoaderOptionsAuto,
963967
bundle: boolean,
964-
): RsbuildConfig => {
965-
if (bundle) return {};
968+
): {
969+
config: RsbuildConfig;
970+
resolvedJsRedirect?: DeepRequired<JsRedirect>;
971+
} => {
972+
if (bundle) return { config: {} };
973+
974+
const isStyleRedirected = redirect.style ?? true;
975+
const jsRedirectPath = redirect.js?.path ?? true;
976+
const jsRedirectExtension = redirect.js?.extension ?? true;
966977

967-
const isStyleRedirect = redirect.style ?? true;
978+
let resolver: RspackResolver | undefined;
968979

969980
return {
970-
output: {
971-
externals: [
972-
(data: any, callback: any) => {
973-
// Issuer is not empty string when the module is imported by another module.
974-
// Prevent from externalizing entry modules here.
975-
if (data.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 request: string = data.request;
983-
984-
const cssExternal = cssExternalHandler(
985-
request,
986-
callback,
987-
jsExtension,
988-
cssModulesAuto,
989-
isStyleRedirect,
990-
);
991-
992-
if (cssExternal !== false) {
993-
return cssExternal;
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+
}
993+
994+
if (!resolver) {
995+
resolver = (await getResolve()) as RspackResolver;
994996
}
995997

996-
if (request[0] === '.') {
997-
const ext = extname(request);
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+
isStyleRedirected,
1009+
);
1010+
1011+
if (cssExternal !== false) {
1012+
return cssExternal;
1013+
}
1014+
1015+
if (jsRedirectPath) {
1016+
try {
1017+
resolvedRequest = await resolver(context, resolvedRequest);
1018+
} catch (e) {
1019+
// Do nothing, fallthrough to other external matches.
1020+
logger.debug(
1021+
`Failed to resolve ${resolvedRequest} with resolver`,
1022+
);
1023+
}
1024+
1025+
resolvedRequest = normalizeSlash(
1026+
path.relative(
1027+
path.dirname(contextInfo.issuer),
1028+
resolvedRequest,
1029+
),
1030+
);
1031+
1032+
// Requests that fall through here cannot be matched by any other externals config ahead.
1033+
// Treat all these requests as relative import of source code. Node.js won't add the
1034+
// leading './' to the relative path resolved by `path.relative`. So add manually it here.
1035+
if (resolvedRequest[0] !== '.') {
1036+
resolvedRequest = `./${resolvedRequest}`;
1037+
}
1038+
}
9981039

999-
if (ext) {
1000-
if (JS_EXTENSIONS_PATTERN.test(request)) {
1001-
request = request.replace(/\.[^.]+$/, jsExtension);
1040+
// Node.js ECMAScript module loader does no extension searching.
1041+
// Add a file extension according to autoExtension config
1042+
// when data.request is a relative path and do not have an extension.
1043+
// If data.request already have an extension, we replace it with new extension
1044+
// This may result in a change in semantics,
1045+
// user should use copy to keep origin file or use another separate entry to deal this
1046+
if (jsRedirectExtension) {
1047+
const ext = extname(resolvedRequest);
1048+
if (ext) {
1049+
if (JS_EXTENSIONS_PATTERN.test(resolvedRequest)) {
1050+
resolvedRequest = resolvedRequest.replace(
1051+
/\.[^.]+$/,
1052+
jsExtension,
1053+
);
1054+
} else {
1055+
// If it does not match jsExtensionsPattern, we should do nothing, eg: ./foo.png
1056+
return callback();
1057+
}
10021058
} else {
1003-
// If it does not match jsExtensionsPattern, we should do nothing, eg: ./foo.png
1004-
return callback();
1059+
resolvedRequest = `${resolvedRequest}${jsExtension}`;
10051060
}
1006-
} else {
1007-
// TODO: add redirect.extension option
1008-
request = `${request}${jsExtension}`;
10091061
}
1062+
1063+
return callback(undefined, resolvedRequest);
10101064
}
10111065

1012-
return callback(null, request);
1013-
}
1014-
callback();
1015-
},
1016-
],
1066+
callback();
1067+
},
1068+
] as Rspack.ExternalItem[],
1069+
},
10171070
},
10181071
};
10191072
};
@@ -1054,17 +1107,15 @@ const composeDtsConfig = async (
10541107
};
10551108

10561109
const composeTargetConfig = (
1057-
target: RsbuildConfigOutputTarget,
1110+
userTarget: RsbuildConfigOutputTarget,
10581111
format: Format,
10591112
): {
10601113
config: RsbuildConfig;
1114+
externalsConfig: RsbuildConfig;
10611115
target: RsbuildConfigOutputTarget;
10621116
} => {
1063-
let defaultTarget = target;
1064-
if (!defaultTarget) {
1065-
defaultTarget = format === 'mf' ? 'web' : 'node';
1066-
}
1067-
switch (defaultTarget) {
1117+
const target = userTarget ?? (format === 'mf' ? 'web' : 'node');
1118+
switch (target) {
10681119
case 'web':
10691120
return {
10701121
config: {
@@ -1075,6 +1126,7 @@ const composeTargetConfig = (
10751126
},
10761127
},
10771128
target: 'web',
1129+
externalsConfig: {},
10781130
};
10791131
case 'node':
10801132
return {
@@ -1084,15 +1136,19 @@ const composeTargetConfig = (
10841136
target: ['node'],
10851137
},
10861138
},
1139+
output: {
1140+
target: 'node',
1141+
},
1142+
},
1143+
target: 'node',
1144+
externalsConfig: {
10871145
output: {
10881146
// When output.target is 'node', Node.js's built-in will be treated as externals of type `node-commonjs`.
10891147
// Simply override the built-in modules to make them external.
10901148
// https://github.com/webpack/webpack/blob/dd44b206a9c50f4b4cb4d134e1a0bd0387b159a3/lib/node/NodeTargetPlugin.js#L81
10911149
externals: nodeBuiltInModules,
1092-
target: 'node',
10931150
},
10941151
},
1095-
target: 'node',
10961152
};
10971153
// TODO: Support `neutral` target, however Rsbuild don't list it as an option in the target field.
10981154
// case 'neutral':
@@ -1104,7 +1160,7 @@ const composeTargetConfig = (
11041160
// },
11051161
// };
11061162
default:
1107-
throw new Error(`Unsupported platform: ${defaultTarget}`);
1163+
throw new Error(`Unsupported platform: ${target}`);
11081164
}
11091165
};
11101166

@@ -1189,7 +1245,7 @@ async function composeLibRsbuildConfig(
11891245
externalHelpers,
11901246
pkgJson,
11911247
);
1192-
const externalsConfig = composeExternalsConfig(
1248+
const userExternalsConfig = composeExternalsConfig(
11931249
format!,
11941250
config.output?.externals,
11951251
);
@@ -1198,16 +1254,17 @@ async function composeLibRsbuildConfig(
11981254
jsExtension,
11991255
dtsExtension,
12001256
} = composeAutoExtensionConfig(config, autoExtension, pkgJson);
1201-
const bundleConfig = composeBundleConfig(
1257+
const { config: bundlelessExternalConfig } = composeBundlelessExternalConfig(
12021258
jsExtension,
12031259
redirect,
12041260
cssModulesAuto,
12051261
bundle,
12061262
);
1207-
const { config: targetConfig, target } = composeTargetConfig(
1208-
config.output?.target,
1209-
format!,
1210-
);
1263+
const {
1264+
config: targetConfig,
1265+
externalsConfig: targetExternalsConfig,
1266+
target,
1267+
} = composeTargetConfig(config.output?.target, format!);
12111268
const syntaxConfig = composeSyntaxConfig(target, config?.syntax);
12121269
const autoExternalConfig = composeAutoExternalConfig({
12131270
format: format!,
@@ -1231,7 +1288,7 @@ async function composeLibRsbuildConfig(
12311288
const externalsWarnConfig = composeExternalsWarnConfig(
12321289
format!,
12331290
autoExternalConfig?.output?.externals,
1234-
externalsConfig?.output?.externals,
1291+
userExternalsConfig?.output?.externals,
12351292
);
12361293
const minifyConfig = composeMinifyConfig(config);
12371294
const bannerFooterConfig = composeBannerFooterConfig(banner, footer);
@@ -1243,15 +1300,23 @@ async function composeLibRsbuildConfig(
12431300
return mergeRsbuildConfig(
12441301
formatConfig,
12451302
shimsConfig,
1303+
syntaxConfig,
12461304
externalHelpersConfig,
1247-
// externalsWarnConfig should before other externals config
1248-
externalsWarnConfig,
1249-
externalsConfig,
1250-
autoExternalConfig,
12511305
autoExtensionConfig,
1252-
syntaxConfig,
1253-
bundleConfig,
12541306
targetConfig,
1307+
// #region Externals configs
1308+
// The order of the externals config should come in the following order:
1309+
// 1. `externalsWarnConfig` should come before other externals config to touch the externalized modules first.
1310+
// 2. The externals config in `bundlelessExternalConfig` should present after other externals config as
1311+
// it relies on other externals config to bail out the externalized modules first then resolve
1312+
// the correct path for relative imports.
1313+
// 3. `userExternalsConfig` should present later to override the externals config of the ahead ones.
1314+
externalsWarnConfig,
1315+
autoExternalConfig,
1316+
targetExternalsConfig,
1317+
userExternalsConfig,
1318+
bundlelessExternalConfig,
1319+
// #endregion
12551320
entryConfig,
12561321
cssConfig,
12571322
assetConfig,

packages/core/src/css/cssConfig.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export function isCssGlobalFile(
7777
return !isCssModules;
7878
}
7979

80-
type ExternalCallback = (arg0?: null, arg1?: string) => void;
80+
type ExternalCallback = (arg0?: undefined, arg1?: string) => void;
8181

8282
export function cssExternalHandler(
8383
request: string,
@@ -99,12 +99,12 @@ export function cssExternalHandler(
9999
if (request[0] === '.' && isCssFile(request)) {
100100
// preserve import './CounterButton.module.scss'
101101
if (!isStyleRedirect) {
102-
return callback(null, request);
102+
return callback(undefined, request);
103103
}
104104
if (isCssModulesRequest) {
105-
return callback(null, request.replace(/\.[^.]+$/, jsExtension));
105+
return callback(undefined, request.replace(/\.[^.]+$/, jsExtension));
106106
}
107-
return callback(null, request.replace(/\.[^.]+$/, '.css'));
107+
return callback(undefined, request.replace(/\.[^.]+$/, '.css'));
108108
}
109109

110110
return false;

0 commit comments

Comments
 (0)