Skip to content

Commit 112ed61

Browse files
committed
fix: treat module as normal variable in ESM
1 parent ff7ac33 commit 112ed61

File tree

29 files changed

+245
-15
lines changed

29 files changed

+245
-15
lines changed

packages/core/src/config.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,7 @@ const composeFormatConfig = ({
602602
bundle?: boolean;
603603
umdName?: Rspack.LibraryName;
604604
}): EnvironmentConfig => {
605-
const jsParserOptions = {
605+
const jsParserOptions: Record<string, Rspack.JavascriptParserOptions> = {
606606
cjs: {
607607
requireResolve: false,
608608
requireDynamic: false,
@@ -611,11 +611,14 @@ const composeFormatConfig = ({
611611
esm: {
612612
importMeta: false,
613613
importDynamic: false,
614+
commonjs: {
615+
exports: 'skipInEsm',
616+
},
614617
},
615618
others: {
616619
worker: false,
617620
},
618-
} as const;
621+
};
619622

620623
// The built-in Rslib plugin will apply to all formats except the `mf` format.
621624
// The `mf` format functions more like an application than a library and requires additional webpack runtime.

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,9 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i
461461
typeReexportsPresence: 'tolerant',
462462
importMeta: false,
463463
importDynamic: false,
464+
commonjs: {
465+
exports: 'skipInEsm'
466+
},
464467
requireResolve: false,
465468
requireDynamic: false,
466469
requireAsExpression: false,
@@ -1158,6 +1161,9 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i
11581161
typeReexportsPresence: 'tolerant',
11591162
importMeta: false,
11601163
importDynamic: false,
1164+
commonjs: {
1165+
exports: 'skipInEsm'
1166+
},
11611167
requireResolve: false,
11621168
requireDynamic: false,
11631169
requireAsExpression: false,
@@ -3658,6 +3664,9 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i
36583664
"module": {
36593665
"parser": {
36603666
"javascript": {
3667+
"commonjs": {
3668+
"exports": "skipInEsm",
3669+
},
36613670
"importDynamic": false,
36623671
"importMeta": false,
36633672
"requireAsExpression": false,
@@ -3944,6 +3953,9 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i
39443953
"module": {
39453954
"parser": {
39463955
"javascript": {
3956+
"commonjs": {
3957+
"exports": "skipInEsm",
3958+
},
39473959
"importDynamic": false,
39483960
"importMeta": false,
39493961
"requireAsExpression": false,

pnpm-lock.yaml

Lines changed: 12 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/integration/format/index.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ test("API plugin's api should be skipped in parser", async () => {
120120
fixturePath,
121121
});
122122

123-
expect(entries.esm).toMatchInlineSnapshot(`
123+
expect(entries.esm0!).toMatchInlineSnapshot(`
124124
"const a = require.cache;
125125
const b = require.extensions;
126126
const c = require.config;
@@ -149,3 +149,14 @@ test('ESM interop should be correct', async () => {
149149
expect(typeof cjsOutput.default.path1.basename).toBe('function');
150150
expect(cjsOutput.default.path1).toBe(cjsOutput.default.path2);
151151
});
152+
153+
test('`module` should be correctly handled by `parserOptions.commonjs.exports = "skipInEsm"`', async () => {
154+
const fixturePath = path.resolve(__dirname, 'esm-interop');
155+
const { entryFiles } = await buildAndGetResults({
156+
fixturePath,
157+
});
158+
159+
const cjsOutput = await import(entryFiles.cjs);
160+
expect(typeof cjsOutput.default.path1.basename).toBe('function');
161+
expect(cjsOutput.default.path1).toBe(cjsOutput.default.path2);
162+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import path from 'node:path';
2+
import { expect, test } from '@rstest/core';
3+
import { buildAndGetResults } from 'test-helper';
4+
5+
test("API plugin's api should be skipped in parser", async () => {
6+
const fixturePath = path.resolve(__dirname);
7+
const { entries } = await buildAndGetResults({
8+
fixturePath,
9+
});
10+
11+
expect(entries.esm).toMatchInlineSnapshot(`
12+
"const a = require.cache;
13+
const b = require.extensions;
14+
const c = require.config;
15+
const d = require.version;
16+
const e = require.include;
17+
const f = require.onError;
18+
export { a, b, c, d, e, f };
19+
"
20+
`);
21+
22+
expect(entries.cjs).toContain('const a = require.cache;');
23+
expect(entries.cjs).toContain('const b = require.extensions;');
24+
expect(entries.cjs).toContain('const c = require.config;');
25+
expect(entries.cjs).toContain('const d = require.version;');
26+
expect(entries.cjs).toContain('const e = require.include;');
27+
expect(entries.cjs).toContain('const f = require.onError;');
28+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "parser-javascript-api-plugin-test",
3+
"version": "1.0.0",
4+
"private": true,
5+
"type": "module"
6+
}
File renamed without changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const value = () => 42;
2+
3+
// Make the export immutable
4+
Object.defineProperty(module, 'exports', {
5+
enumerable: true,
6+
get: value,
7+
});
File renamed without changes.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import path from 'node:path';
2+
import { expect, test } from '@rstest/core';
3+
import { buildAndGetResults, queryContent } from 'test-helper';
4+
5+
test('`module` variable should be preserved as-is by `javascript.commonjs.exports = "false"`', async () => {
6+
const fixturePath = path.resolve(__dirname);
7+
const { contents } = await buildAndGetResults({
8+
fixturePath,
9+
});
10+
11+
const esm1 = queryContent(contents.esm, 'm1.mjs', { basename: true });
12+
const esm2 = queryContent(contents.esm, 'm2.mjs', { basename: true });
13+
const cjs1 = queryContent(contents.cjs, 'm1.js', { basename: true });
14+
const cjs2 = queryContent(contents.cjs, 'm2.js', { basename: true });
15+
16+
expect(
17+
(
18+
await Promise.all([
19+
import(esm1.path),
20+
import(esm2.path),
21+
import(cjs1.path),
22+
import(cjs2.path),
23+
])
24+
).map((m) => m.value),
25+
).toEqual([42, 42, 42, 42]);
26+
27+
const checksM1 = [
28+
'if (module.children) module.children = module.children.filter((item)=>item.filename !== path);',
29+
'module.exports = original',
30+
'if (module.exports && module.exports.test) return module.exports.test()',
31+
];
32+
33+
const checksEsm2 = [
34+
'if (node_module.children) node_module.children = node_module.children.filter((item)=>item.filename !== path);',
35+
'node_module.exports = original',
36+
'if (node_module.exports && node_module.exports.test) return node_module.exports.test()',
37+
];
38+
39+
const checksCjs2 = [
40+
'if (external_node_module_default().children) external_node_module_default().children = external_node_module_default().children.filter((item)=>item.filename !== path);',
41+
'external_node_module_default().exports = original',
42+
'if (external_node_module_default().exports && external_node_module_default().exports.test) return external_node_module_default().exports.test()',
43+
];
44+
45+
for (const check of checksM1) {
46+
expect(esm1.content).toContain(check);
47+
expect(cjs1.content).toContain(check);
48+
}
49+
50+
for (const check of checksEsm2) {
51+
expect(esm2.content).toContain(check);
52+
}
53+
54+
for (const check of checksCjs2) {
55+
expect(cjs2.content).toContain(check);
56+
}
57+
});

0 commit comments

Comments
 (0)