Skip to content

Commit eda2925

Browse files
feat!: support redirect.style.path and redirect.style.extension (#618)
Co-authored-by: Timeless0911 <[email protected]>
1 parent 55767af commit eda2925

File tree

20 files changed

+520
-140
lines changed

20 files changed

+520
-140
lines changed

packages/core/src/config.ts

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,7 +1007,8 @@ const composeBundlelessExternalConfig = (
10071007
} => {
10081008
if (bundle) return { config: {} };
10091009

1010-
const isStyleRedirected = redirect.style ?? true;
1010+
const styleRedirectPath = redirect.style?.path ?? true;
1011+
const styleRedirectExtension = redirect.style?.extension ?? true;
10111012
const jsRedirectPath = redirect.js?.path ?? true;
10121013
const jsRedirectExtension = redirect.js?.extension ?? true;
10131014

@@ -1026,60 +1027,72 @@ const composeBundlelessExternalConfig = (
10261027
if (!request || !getResolve || !context || !contextInfo) {
10271028
return callback();
10281029
}
1030+
const { issuer } = contextInfo;
10291031

10301032
if (!resolver) {
10311033
resolver = (await getResolve()) as RspackResolver;
10321034
}
10331035

1036+
async function redirectPath(
1037+
request: string,
1038+
): Promise<string | undefined> {
1039+
try {
1040+
let resolvedRequest = request;
1041+
// use resolver to resolve the request
1042+
resolvedRequest = await resolver!(context!, resolvedRequest);
1043+
1044+
// only handle the request that is not in node_modules
1045+
if (!resolvedRequest.includes('node_modules')) {
1046+
resolvedRequest = normalizeSlash(
1047+
path.relative(path.dirname(issuer), resolvedRequest),
1048+
);
1049+
// Requests that fall through here cannot be matched by any other externals config ahead.
1050+
// Treat all these requests as relative import of source code. Node.js won't add the
1051+
// leading './' to the relative path resolved by `path.relative`. So add manually it here.
1052+
if (resolvedRequest[0] !== '.') {
1053+
resolvedRequest = `./${resolvedRequest}`;
1054+
}
1055+
return resolvedRequest;
1056+
}
1057+
// NOTE: If request is a phantom dependency, which means it can be resolved but not specified in dependencies or peerDependencies in package.json, the output will be incorrect to use when the package is published
1058+
// return the original request instead of the resolved request
1059+
return undefined;
1060+
} catch (e) {
1061+
// catch error when request can not be resolved by resolver
1062+
// e.g. A react component library importing and using 'react' but while not defining
1063+
// it in devDependencies and peerDependencies. Preserve 'react' as-is if so.
1064+
logger.debug(
1065+
`Failed to resolve module ${color.green(`"${request}"`)} from ${color.green(issuer)}. If it's an npm package, consider adding it to dependencies or peerDependencies in package.json to make it externalized.`,
1066+
);
1067+
return request;
1068+
}
1069+
}
1070+
10341071
// Issuer is not empty string when the module is imported by another module.
10351072
// Prevent from externalizing entry modules here.
1036-
if (contextInfo.issuer) {
1073+
if (issuer) {
10371074
let resolvedRequest: string = request;
10381075

1039-
const cssExternal = cssExternalHandler(
1076+
const cssExternal = await cssExternalHandler(
10401077
resolvedRequest,
10411078
callback,
10421079
jsExtension,
10431080
cssModulesAuto,
1044-
isStyleRedirected,
1081+
styleRedirectPath,
1082+
styleRedirectExtension,
1083+
redirectPath,
10451084
);
10461085

10471086
if (cssExternal !== false) {
10481087
return cssExternal;
10491088
}
10501089

10511090
if (jsRedirectPath) {
1052-
try {
1053-
// use resolver to resolve the request
1054-
resolvedRequest = await resolver(context, resolvedRequest);
1055-
1056-
// only handle the request that is not in node_modules
1057-
if (!resolvedRequest.includes('node_modules')) {
1058-
resolvedRequest = normalizeSlash(
1059-
path.relative(
1060-
path.dirname(contextInfo.issuer),
1061-
resolvedRequest,
1062-
),
1063-
);
1064-
// Requests that fall through here cannot be matched by any other externals config ahead.
1065-
// Treat all these requests as relative import of source code. Node.js won't add the
1066-
// leading './' to the relative path resolved by `path.relative`. So add manually it here.
1067-
if (resolvedRequest[0] !== '.') {
1068-
resolvedRequest = `./${resolvedRequest}`;
1069-
}
1070-
} else {
1071-
// NOTE: If request is a phantom dependency, which means it can be resolved but not specified in dependencies or peerDependencies in package.json, the output will be incorrect to use when the package is published
1072-
// return the original request instead of the resolved request
1073-
return callback(undefined, request);
1074-
}
1075-
} catch (e) {
1076-
// catch error when request can not be resolved by resolver
1077-
// e.g. A react component library importing and using 'react' but while not defining
1078-
// it in devDependencies and peerDependencies. Preserve 'react' as-is if so.
1079-
logger.debug(
1080-
`Failed to resolve module ${color.green(`"${resolvedRequest}"`)} from ${color.green(contextInfo.issuer)}. If it's an npm package, consider adding it to dependencies or peerDependencies in package.json to make it externalized.`,
1081-
);
1091+
const redirectedPath = await redirectPath(resolvedRequest);
1092+
if (redirectedPath === undefined) {
1093+
return callback(undefined, request);
10821094
}
1095+
resolvedRequest = redirectedPath;
10831096
}
10841097

10851098
// Node.js ECMAScript module loader does no extension searching.

packages/core/src/css/cssConfig.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -79,35 +79,48 @@ export function isCssGlobalFile(
7979

8080
type ExternalCallback = (arg0?: undefined, arg1?: string) => void;
8181

82-
export function cssExternalHandler(
82+
export async function cssExternalHandler(
8383
request: string,
8484
callback: ExternalCallback,
8585
jsExtension: string,
8686
auto: CssLoaderOptionsAuto,
87-
isStyleRedirect: boolean,
88-
): void | false {
89-
const isCssModulesRequest = isCssModulesFile(request, auto);
90-
87+
styleRedirectPath: boolean,
88+
styleRedirectExtension: boolean,
89+
redirectPath: (request: string) => Promise<string | undefined>,
90+
): Promise<false | void> {
9191
// cssExtract would execute the file handled by css-loader, so we cannot external the "helper import" from css-loader
9292
// do not external @rsbuild/core/compiled/css-loader/noSourceMaps.js, sourceMaps.js, api.mjs etc.
9393
if (/compiled\/css-loader\//.test(request)) {
9494
return callback();
9595
}
9696

97+
let resolvedRequest = request;
98+
99+
if (styleRedirectPath) {
100+
const resolved = await redirectPath(resolvedRequest);
101+
if (resolved) {
102+
resolvedRequest = resolved;
103+
}
104+
}
105+
106+
if (!isCssFile(resolvedRequest)) {
107+
return false;
108+
}
109+
97110
// 1. css modules: import './CounterButton.module.scss' -> import './CounterButton.module.mjs'
98111
// 2. css global: import './CounterButton.scss' -> import './CounterButton.css'
99-
if (request[0] === '.' && isCssFile(request)) {
100-
// preserve import './CounterButton.module.scss'
101-
if (!isStyleRedirect) {
102-
return callback(undefined, request);
103-
}
112+
if (styleRedirectExtension) {
113+
const isCssModulesRequest = isCssModulesFile(resolvedRequest, auto);
104114
if (isCssModulesRequest) {
105-
return callback(undefined, request.replace(/\.[^.]+$/, jsExtension));
115+
return callback(
116+
undefined,
117+
resolvedRequest.replace(/\.[^.]+$/, jsExtension),
118+
);
106119
}
107-
return callback(undefined, request.replace(/\.[^.]+$/, '.css'));
120+
return callback(undefined, resolvedRequest.replace(/\.[^.]+$/, '.css'));
108121
}
109122

110-
return false;
123+
return callback(undefined, resolvedRequest);
111124
}
112125

113126
const PLUGIN_NAME = 'rsbuild:lib-css';

packages/core/src/types/config.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,19 @@ export type JsRedirect = {
9191
extension?: boolean;
9292
};
9393

94+
export type StyleRedirect = {
95+
/**
96+
* Whether to automatically redirect the import paths of style output files.
97+
* @defaultValue `true`
98+
*/
99+
path?: boolean;
100+
/**
101+
* Whether to automatically add the file extension to import paths based on the style output files.
102+
* @defaultValue `true`
103+
*/
104+
extension?: boolean;
105+
};
106+
94107
// @ts-expect-error TODO: support dts redirect in the future
95108
type DtsRedirect = {
96109
path?: boolean;
@@ -101,7 +114,7 @@ export type Redirect = {
101114
/** Controls the redirect of the import paths of output JavaScript files. */
102115
js?: JsRedirect;
103116
/** Whether to redirect the import path of the style file. */
104-
style?: boolean;
117+
style?: StyleRedirect;
105118
// TODO: support other redirects
106119
// asset?: boolean;
107120
// dts?: DtsRedirect;

pnpm-lock.yaml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/integration/redirect/style-false/rslib.config.ts

Lines changed: 0 additions & 28 deletions
This file was deleted.

tests/integration/redirect/style-false/src/index.ts

Lines changed: 0 additions & 4 deletions
This file was deleted.

0 commit comments

Comments
 (0)