Skip to content

Commit c325e22

Browse files
committed
fix: do not use CJS methods in ESM
1 parent 86f16f9 commit c325e22

File tree

51 files changed

+629
-158
lines changed

Some content is hidden

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

51 files changed

+629
-158
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
"test": "pnpm run test:unit && pnpm run test:integration && pnpm run test:e2e",
2121
"test:benchmark": "cd ./tests && pnpm run test:benchmark",
2222
"test:e2e": "pnpm run build:examples && cd tests && pnpm run test:e2e",
23-
"test:integration": "vitest run --project integration",
24-
"test:integration:watch": "vitest --project integration",
23+
"test:integration": "NODE_OPTIONS='--experimental-vm-modules' vitest run --project integration",
24+
"test:integration:watch": "NODE_OPTIONS='--experimental-vm-modules' vitest --project integration",
2525
"test:unit": "vitest run --project unit*",
2626
"test:unit:watch": "vitest --project unit*",
2727
"testu": "pnpm run test:unit -u && pnpm run test:integration -u",

packages/core/src/config.ts

Lines changed: 104 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,24 @@ import {
2424
cssExternalHandler,
2525
isCssGlobalFile,
2626
} from './css/cssConfig';
27-
import { pluginCjsShim } from './plugins/cjsShim';
27+
import {
28+
pluginCjsImportMetaUrlShim,
29+
pluginEsmRequireShim,
30+
} from './plugins/shims';
2831
import type {
2932
AutoExternal,
3033
BannerAndFooter,
3134
Format,
3235
LibConfig,
3336
PkgJson,
3437
Redirect,
38+
ResolvedShims,
3539
RsbuildConfigOutputTarget,
3640
RslibConfig,
3741
RslibConfigAsyncFn,
3842
RslibConfigExport,
3943
RslibConfigSyncFn,
44+
Shims,
4045
Syntax,
4146
} from './types';
4247
import { getDefaultExtension } from './utils/extension';
@@ -447,12 +452,16 @@ export async function createConstantRsbuildConfig(): Promise<RsbuildConfig> {
447452

448453
const composeFormatConfig = (format: Format): RsbuildConfig => {
449454
const jsParserOptions = {
450-
importMeta: false,
451-
requireResolve: false,
452-
requireDynamic: false,
453-
requireAsExpression: false,
454-
importDynamic: false,
455-
};
455+
cjs: {
456+
requireResolve: false,
457+
requireDynamic: false,
458+
requireAsExpression: false,
459+
},
460+
esm: {
461+
importMeta: false,
462+
importDynamic: false,
463+
},
464+
} as const;
456465

457466
switch (format) {
458467
case 'esm':
@@ -461,7 +470,10 @@ const composeFormatConfig = (format: Format): RsbuildConfig => {
461470
rspack: {
462471
module: {
463472
parser: {
464-
javascript: jsParserOptions,
473+
javascript: {
474+
...jsParserOptions.esm,
475+
...jsParserOptions.cjs,
476+
},
465477
},
466478
},
467479
optimization: {
@@ -486,12 +498,11 @@ const composeFormatConfig = (format: Format): RsbuildConfig => {
486498
};
487499
case 'cjs':
488500
return {
489-
plugins: [pluginCjsShim()],
490501
tools: {
491502
rspack: {
492503
module: {
493504
parser: {
494-
javascript: jsParserOptions,
505+
javascript: { ...jsParserOptions.esm, ...jsParserOptions.cjs },
495506
},
496507
},
497508
output: {
@@ -531,6 +542,85 @@ const composeFormatConfig = (format: Format): RsbuildConfig => {
531542
}
532543
};
533544

545+
const resolveShims = (shims?: Shims) => {
546+
const resolvedShims = {
547+
cjs: {
548+
'import.meta.url': true,
549+
},
550+
esm: {
551+
__filename: true,
552+
__dirname: true,
553+
require: false,
554+
},
555+
};
556+
557+
if (!shims) {
558+
return resolvedShims;
559+
}
560+
561+
if (shims.cjs) {
562+
if (typeof shims.cjs === 'boolean') {
563+
if (shims.cjs === true) {
564+
resolvedShims.cjs['import.meta.url'] = true;
565+
} else {
566+
resolvedShims.cjs['import.meta.url'] = false;
567+
}
568+
} else {
569+
resolvedShims.cjs['import.meta.url'] =
570+
shims.cjs['import.meta.url'] ?? false;
571+
}
572+
}
573+
574+
if (shims.esm) {
575+
if (typeof shims.esm === 'boolean') {
576+
if (shims.esm === true) {
577+
resolvedShims.esm.__filename = true;
578+
resolvedShims.esm.__dirname = true;
579+
resolvedShims.esm.require = true;
580+
}
581+
} else {
582+
resolvedShims.esm.__filename = shims.esm.__filename ?? false;
583+
resolvedShims.esm.__dirname = shims.esm.__dirname ?? false;
584+
resolvedShims.esm.require = shims.esm.require ?? false;
585+
}
586+
}
587+
588+
return resolvedShims;
589+
};
590+
591+
const composeShimsConfig = (
592+
format: Format,
593+
resolvedShims: ResolvedShims,
594+
): RsbuildConfig => {
595+
switch (format) {
596+
case 'esm':
597+
return {
598+
tools: {
599+
rspack: {
600+
node: {
601+
// "__dirname" and "__filename" shims will automatically be enabled when `output.module` is `true`
602+
__dirname: resolvedShims.esm.__dirname ? 'node-module' : false,
603+
__filename: resolvedShims.esm.__filename ? 'node-module' : false,
604+
},
605+
},
606+
},
607+
plugins: [resolvedShims.esm.require && pluginEsmRequireShim()].filter(
608+
Boolean,
609+
),
610+
};
611+
case 'cjs':
612+
return {
613+
plugins: [
614+
resolvedShims.cjs['import.meta.url'] && pluginCjsImportMetaUrlShim(),
615+
].filter(Boolean),
616+
};
617+
case 'umd':
618+
return {};
619+
default:
620+
throw new Error(`Unsupported format: ${format}`);
621+
}
622+
};
623+
534624
export const composeModuleImportWarn = (request: string): string => {
535625
return `The externalized commonjs request ${color.green(`"${request}"`)} will use ${color.blue('"module"')} external type in ESM format. If you want to specify other external type, considering set the request and type with ${color.blue('"output.externals"')}.`;
536626
};
@@ -832,9 +922,6 @@ const composeTargetConfig = (
832922
tools: {
833923
rspack: {
834924
target: ['node'],
835-
// "__dirname" and "__filename" shims will automatically be enabled when `output.module` is `true`,
836-
// and leave them as-is in the rest of the cases. Leave the comments here to explain the behavior.
837-
// { node: { __dirname: ..., __filename: ... } }
838925
},
839926
},
840927
output: {
@@ -915,6 +1002,8 @@ async function composeLibRsbuildConfig(config: LibConfig, configPath: string) {
9151002
externalHelpers = false,
9161003
redirect = {},
9171004
} = config;
1005+
const resolvedShims = resolveShims(config.shims);
1006+
const shimsConfig = composeShimsConfig(format!, resolvedShims);
9181007
const formatConfig = composeFormatConfig(format!);
9191008
const externalHelpersConfig = composeExternalHelpersConfig(
9201009
externalHelpers,
@@ -967,6 +1056,7 @@ async function composeLibRsbuildConfig(config: LibConfig, configPath: string) {
9671056

9681057
return mergeRsbuildConfig(
9691058
formatConfig,
1059+
shimsConfig,
9701060
externalHelpersConfig,
9711061
// externalsWarnConfig should before other externals config
9721062
externalsWarnConfig,
@@ -1046,6 +1136,7 @@ export async function composeCreateRsbuildConfig(
10461136
'banner',
10471137
'footer',
10481138
'dts',
1139+
'shims',
10491140
]),
10501141
),
10511142
};

packages/core/src/plugins/cjsShim.ts renamed to packages/core/src/plugins/shims.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { RsbuildPlugin } from '@rsbuild/core';
1+
import { type RsbuildPlugin, rspack } from '@rsbuild/core';
22

33
const importMetaUrlShim = `/*#__PURE__*/ (function () {
44
return typeof document === 'undefined'
@@ -11,9 +11,8 @@ const importMetaUrlShim = `/*#__PURE__*/ (function () {
1111
// - Replace `import.meta.url` with `importMetaUrl`.
1212
// - Inject `importMetaUrl` to the end of the module (can't inject at the beginning because of `"use strict";`).
1313
// This is a short-term solution, and we hope to provide built-in polyfills like `node.__filename` on Rspack side.
14-
export const pluginCjsShim = (): RsbuildPlugin => ({
14+
export const pluginCjsImportMetaUrlShim = (): RsbuildPlugin => ({
1515
name: 'rsbuild-plugin-cjs-shim',
16-
1716
setup(api) {
1817
api.modifyEnvironmentConfig((config) => {
1918
config.source.define = {
@@ -23,3 +22,26 @@ export const pluginCjsShim = (): RsbuildPlugin => ({
2322
});
2423
},
2524
});
25+
26+
const requireShim = `// Rslib ESM shims
27+
import __rslib_shim_module__ from 'module';
28+
const require = /*#__PURE__*/ __rslib_shim_module__.createRequire(import.meta.url);
29+
`;
30+
31+
export const pluginEsmRequireShim = (): RsbuildPlugin => ({
32+
name: 'rsbuild-plugin-esm-shim',
33+
setup(api) {
34+
api.modifyRspackConfig((config) => {
35+
config.plugins ??= [];
36+
config.plugins.push(
37+
new rspack.BannerPlugin({
38+
banner: requireShim,
39+
// Just before minify stage, to perform tree shaking.
40+
stage: rspack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE - 1,
41+
raw: true,
42+
include: /\.(js|cjs)$/,
43+
}),
44+
);
45+
});
46+
},
47+
});

packages/core/src/types/config/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,26 @@ export type BannerAndFooter = {
4848
dts?: string;
4949
};
5050

51+
export type Shims = {
52+
cjs?:
53+
| boolean
54+
| {
55+
'import.meta.url'?: boolean;
56+
};
57+
esm?:
58+
| boolean
59+
| {
60+
__filename?: boolean;
61+
__dirname?: boolean;
62+
require?: boolean;
63+
};
64+
};
65+
66+
export type ResolvedShims = {
67+
cjs: Required<NonNullable<Exclude<Shims['cjs'], boolean>>>;
68+
esm: Required<NonNullable<Exclude<Shims['esm'], boolean>>>;
69+
};
70+
5171
export type Redirect = {
5272
// TODO: support other redirects
5373
// alias?: boolean;
@@ -67,6 +87,7 @@ export interface LibConfig extends RsbuildConfig {
6787
externalHelpers?: boolean;
6888
banner?: BannerAndFooter;
6989
footer?: BannerAndFooter;
90+
shims?: Shims;
7091
dts?: Dts;
7192
}
7293

packages/core/tests/__snapshots__/config.test.ts.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config 1
4848
"not dead",
4949
],
5050
},
51+
"plugins": [],
5152
"source": {
5253
"alias": {
5354
"bar": "bar",
@@ -110,6 +111,10 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config 1
110111
},
111112
},
112113
},
114+
"node": {
115+
"__dirname": "node-module",
116+
"__filename": "node-module",
117+
},
113118
"optimization": {
114119
"concatenateModules": true,
115120
"sideEffects": "flag",

tests/benchmark/index.bench.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,23 @@ describe('run rslib in examples', () => {
66
'examples/express-plugin',
77
async () => {
88
const cwd = getCwdByExample('express-plugin');
9-
await rslibBuild(cwd);
9+
await rslibBuild({ cwd });
1010
},
1111
{ time: 5 },
1212
);
1313
bench(
1414
'examples/react-component-bundle',
1515
async () => {
1616
const cwd = getCwdByExample('react-component-bundle');
17-
await rslibBuild(cwd);
17+
await rslibBuild({ cwd });
1818
},
1919
{ time: 5 },
2020
);
2121
bench(
2222
'examples/react-component-bundle-false',
2323
async () => {
2424
const cwd = getCwdByExample('react-component-bundle-false');
25-
await rslibBuild(cwd);
25+
await rslibBuild({ cwd });
2626
},
2727
{ time: 5 },
2828
);

tests/integration/alias/index.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { expect, test } from 'vitest';
33

44
test('source.alias', async () => {
55
const fixturePath = __dirname;
6-
const { entries } = await buildAndGetResults(fixturePath);
6+
const { entries } = await buildAndGetResults({ fixturePath });
77

88
expect(entries.esm).toContain('hello world');
99
expect(entries.cjs).toContain('hello world');

tests/integration/asset/index.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { expect, test } from 'vitest';
44

55
test('set the size threshold to inline static assets', async () => {
66
const fixturePath = join(__dirname, 'limit');
7-
const { contents } = await buildAndGetResults(fixturePath);
7+
const { contents } = await buildAndGetResults({ fixturePath });
88

99
// inline when bundle
1010
expect(Object.values(contents.esm0!)[0]).toContain(
@@ -29,7 +29,7 @@ test('set the size threshold to inline static assets', async () => {
2929

3030
test('set the assets name', async () => {
3131
const fixturePath = join(__dirname, 'name');
32-
const { contents } = await buildAndGetResults(fixturePath);
32+
const { contents } = await buildAndGetResults({ fixturePath });
3333

3434
// bundle
3535
expect(Object.values(contents.esm0!)[0]).toContain(
@@ -44,7 +44,7 @@ test('set the assets name', async () => {
4444

4545
test('set the assets output path', async () => {
4646
const fixturePath = join(__dirname, 'path');
47-
const { contents } = await buildAndGetResults(fixturePath);
47+
const { contents } = await buildAndGetResults({ fixturePath });
4848

4949
// bundle
5050
expect(Object.values(contents.esm0!)[0]).toContain(
@@ -59,7 +59,7 @@ test('set the assets output path', async () => {
5959

6060
test('set the assets public path', async () => {
6161
const fixturePath = join(__dirname, 'public-path');
62-
const { contents } = await buildAndGetResults(fixturePath);
62+
const { contents } = await buildAndGetResults({ fixturePath });
6363

6464
// bundle
6565
expect(Object.values(contents.esm0!)[0]).toContain(
@@ -74,7 +74,7 @@ test('set the assets public path', async () => {
7474

7575
test('use svgr', async () => {
7676
const fixturePath = join(__dirname, 'svgr');
77-
const { contents } = await buildAndGetResults(fixturePath);
77+
const { contents } = await buildAndGetResults({ fixturePath });
7878

7979
// bundle -- default export with react query
8080
expect(Object.values(contents.esm0!)[0]).toMatchSnapshot();

tests/integration/async-chunks/index.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { expect, test } from 'vitest';
44

55
test('should get correct value from async chunks', async () => {
66
const fixturePath = join(__dirname, 'default');
7-
const { entryFiles } = await buildAndGetResults(fixturePath);
7+
const { entryFiles } = await buildAndGetResults({ fixturePath });
88

99
for (const format of ['esm', 'cjs']) {
1010
const { foo } = await import(entryFiles[format]);

0 commit comments

Comments
 (0)