Skip to content

Commit 1745141

Browse files
committed
feat(commonjs): reconstruct real es module from __esModule marker
1 parent e4d21ba commit 1745141

File tree

20 files changed

+258
-339
lines changed

20 files changed

+258
-339
lines changed

β€Žpackages/commonjs/src/index.jsβ€Ž

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,8 @@ export default function commonjs(options = {}) {
7676
const sourceMap = options.sourceMap !== false;
7777

7878
function transformAndCheckExports(code, id) {
79-
const { isEsModule, hasDefaultExport, hasNamedExports, ast } = checkEsModule(
80-
this.parse,
81-
code,
82-
id
83-
);
79+
const checkResult = checkEsModule(this.parse, code, id);
80+
const { isEsModule, isCompiledEsModule, hasDefaultExport, hasNamedExports, ast } = checkResult;
8481
if (hasDefaultExport) {
8582
esModulesWithDefaultExport.add(id);
8683
}
@@ -101,6 +98,7 @@ export default function commonjs(options = {}) {
10198
code,
10299
id,
103100
isEsModule,
101+
isCompiledEsModule,
104102
ignoreGlobal || isEsModule,
105103
ignoreRequire,
106104
sourceMap,
@@ -110,7 +108,7 @@ export default function commonjs(options = {}) {
110108
ast
111109
);
112110

113-
setIsCjsPromise(id, isEsModule ? false : Boolean(transformed));
111+
setIsCjsPromise(id, isEsModule || isCompiledEsModule ? false : Boolean(transformed));
114112
return transformed;
115113
}
116114

β€Žpackages/commonjs/src/transform.jsβ€Ž

Lines changed: 129 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ import {
1717
} from './helpers';
1818
import { getName } from './utils';
1919

20+
const KEY_COMPILED_ESM = '__esModule';
2021
const reserved = 'process location abstract arguments boolean break byte case catch char class const continue debugger default delete do double else enum eval export extends false final finally float for from function goto if implements import in instanceof int interface let long native new null package private protected public return short static super switch synchronized this throw throws transient true try typeof var void volatile while with yield'.split(
2122
' '
2223
);
23-
const blacklist = { __esModule: true };
24+
const blacklist = { [KEY_COMPILED_ESM]: true };
2425
reserved.forEach((word) => (blacklist[word] = true));
2526

2627
const exportsPattern = /^(?:module\.)?exports(?:\.([a-zA-Z_$][a-zA-Z_$0-9]*))?$/;
@@ -60,10 +61,51 @@ export function hasCjsKeywords(code, ignoreGlobal) {
6061
return firstpass.test(code);
6162
}
6263

64+
function isTrueNode(node) {
65+
if (!node) return false;
66+
67+
switch (node.type) {
68+
case 'Literal':
69+
return node.value;
70+
case 'UnaryExpression':
71+
return node.operator === '!' && node.argument && node.argument.value === 0;
72+
default:
73+
return false;
74+
}
75+
}
76+
77+
function getAssignedMember(node) {
78+
const { left, operator, right } = node.expression;
79+
if (operator !== '=' || left.type !== 'MemberExpression') {
80+
return null;
81+
}
82+
let assignedIdentifier;
83+
if (left.object.type === 'Identifier') {
84+
// exports.foo = ...
85+
assignedIdentifier = left.object;
86+
} else if (
87+
left.object.type === 'MemberExpression' &&
88+
left.object.property.type === 'Identifier'
89+
) {
90+
// module.exports.foo = ...
91+
assignedIdentifier = left.object.property;
92+
} else {
93+
return null;
94+
}
95+
96+
if (assignedIdentifier.name !== 'exports') {
97+
return null;
98+
}
99+
100+
const key = left.property ? left.property.name : null;
101+
return { key, value: right };
102+
}
103+
63104
export function checkEsModule(parse, code, id) {
64105
const ast = tryParse(parse, code, id);
65106

66107
let isEsModule = false;
108+
let isCompiledEsModule = false;
67109
let hasDefaultExport = false;
68110
let hasNamedExports = false;
69111
for (const node of ast.body) {
@@ -93,9 +135,36 @@ export function checkEsModule(parse, code, id) {
93135
} else if (node.type === 'ImportDeclaration') {
94136
isEsModule = true;
95137
}
138+
139+
if (node.type === 'ExpressionStatement' && node.expression) {
140+
let compiledEsmValueNode;
141+
142+
if (node.expression.type === 'CallExpression') {
143+
// detect Object.defineProperty(exports, '__esModule', { value: true });
144+
const p = getDefinePropertyCallName(node.expression, 'exports');
145+
if (p && p.key === KEY_COMPILED_ESM) {
146+
compiledEsmValueNode = p.value;
147+
}
148+
} else if (node.expression.type === 'AssignmentExpression') {
149+
// detect exports.__esModule = true;
150+
const assignedMember = getAssignedMember(node);
151+
if (assignedMember && assignedMember.key === KEY_COMPILED_ESM) {
152+
compiledEsmValueNode = assignedMember.value;
153+
}
154+
}
155+
156+
if (compiledEsmValueNode) {
157+
isCompiledEsModule = isTrueNode(compiledEsmValueNode);
158+
}
159+
}
96160
}
97161

98-
return { isEsModule, hasDefaultExport, hasNamedExports, ast };
162+
// don't treat mixed es modules as compiled es mdoules
163+
if (isEsModule) {
164+
isCompiledEsModule = false;
165+
}
166+
167+
return { isEsModule, isCompiledEsModule, hasDefaultExport, hasNamedExports, ast };
99168
}
100169

101170
function getDefinePropertyCallName(node, targetName) {
@@ -111,17 +180,23 @@ function getDefinePropertyCallName(node, targetName) {
111180

112181
if (node.arguments.length !== 3) return;
113182

114-
const [target, val] = node.arguments;
183+
const [target, key, value] = node.arguments;
115184
if (target.type !== 'Identifier' || target.name !== targetName) return;
185+
186+
if (value.type !== 'ObjectExpression' || !value.properties) return;
187+
const valueProperty = value.properties.find((p) => p.key && p.key.name === 'value');
188+
if (!valueProperty || !valueProperty.value) return;
189+
116190
// eslint-disable-next-line consistent-return
117-
return val.value;
191+
return { key: key.value, value: valueProperty.value };
118192
}
119193

120194
export function transformCommonjs(
121195
parse,
122196
code,
123197
id,
124198
isEsModule,
199+
isCompiledEsModule,
125200
ignoreGlobal,
126201
ignoreRequire,
127202
sourceMap,
@@ -154,8 +229,7 @@ export function transformCommonjs(
154229

155230
const namedExports = {};
156231

157-
// TODO handle transpiled modules
158-
let shouldWrap = /__esModule/.test(code);
232+
let shouldWrap = false;
159233
let usesCommonjsHelpers = false;
160234

161235
function isRequireStatement(node) {
@@ -457,8 +531,11 @@ export function transformCommonjs(
457531
return;
458532
}
459533

460-
const name = getDefinePropertyCallName(node, 'exports');
461-
if (name && name === makeLegalIdentifier(name)) namedExports[name] = true;
534+
if (node.type === 'ExpressionStatement' && node.expression) {
535+
const p = getDefinePropertyCallName(node.expression, 'exports');
536+
if (p && p.key === makeLegalIdentifier(p.key)) namedExports[p.key] = true;
537+
if (p && p.key === KEY_COMPILED_ESM) node._shouldRemove = true;
538+
}
462539

463540
// if this is `var x = require('x')`, we can do `import x from 'x'`
464541
if (
@@ -541,6 +618,10 @@ export function transformCommonjs(
541618
if (!keepDeclaration) {
542619
magicString.remove(node.start, node.end);
543620
}
621+
} else if (node.type === 'ExpressionStatement') {
622+
if (node._shouldRemove) {
623+
magicString.remove(node.start, node.end);
624+
}
544625
}
545626
}
546627
});
@@ -556,9 +637,8 @@ export function transformCommonjs(
556637
return null;
557638
}
558639

559-
// If `isEsModule` is on, it means it has ES6 import/export statements,
560-
// which just can't be wrapped in a function.
561-
if (isEsModule) shouldWrap = false;
640+
// if this is an es module or a comiled es module, we don't need to wrap it
641+
if (isEsModule || isCompiledEsModule) shouldWrap = false;
562642

563643
usesCommonjsHelpers = usesCommonjsHelpers || shouldWrap;
564644

@@ -589,7 +669,7 @@ export function transformCommonjs(
589669
let wrapperEnd = '';
590670

591671
const moduleName = deconflict(scope, globals, getName(id));
592-
if (!isEsModule) {
672+
if (!isEsModule && !isCompiledEsModule) {
593673
const exportModuleExports = {
594674
str: `export { ${moduleName} as __moduleExports };`,
595675
name: '__moduleExports'
@@ -600,6 +680,7 @@ export function transformCommonjs(
600680

601681
const defaultExportPropertyAssignments = [];
602682
let hasDefaultExport = false;
683+
let deconflictedDefaultExportName;
603684

604685
if (shouldWrap) {
605686
const args = `module${uses.exports ? ', exports' : ''}`;
@@ -636,30 +717,37 @@ export function transformCommonjs(
636717
magicString.overwrite(left.start, left.end, `var ${moduleName}`);
637718
} else {
638719
const [, name] = match;
639-
const deconflicted = deconflict(scope, globals, name);
640720

641-
names.push({ name, deconflicted });
721+
if (name !== KEY_COMPILED_ESM) {
722+
const deconflicted = deconflict(scope, globals, name);
642723

643-
magicString.overwrite(node.start, left.end, `var ${deconflicted}`);
724+
names.push({ name, deconflicted });
644725

645-
const declaration =
646-
name === deconflicted
647-
? `export { ${name} };`
648-
: `export { ${deconflicted} as ${name} };`;
726+
magicString.overwrite(node.start, left.end, `var ${deconflicted}`);
649727

650-
if (name !== 'default') {
651-
namedExportDeclarations.push({
652-
str: declaration,
653-
name
654-
});
655-
}
728+
const declaration =
729+
name === deconflicted
730+
? `export { ${name} };`
731+
: `export { ${deconflicted} as ${name} };`;
732+
733+
if (name !== 'default') {
734+
namedExportDeclarations.push({
735+
str: declaration,
736+
name
737+
});
738+
} else {
739+
deconflictedDefaultExportName = deconflicted;
740+
}
656741

657-
defaultExportPropertyAssignments.push(`${moduleName}.${name} = ${deconflicted};`);
742+
defaultExportPropertyAssignments.push(`${moduleName}.${name} = ${deconflicted};`);
743+
} else {
744+
magicString.remove(node.start, node.end);
745+
}
658746
}
659747
}
660748
}
661749

662-
if (!(isEsModule || hasDefaultExport)) {
750+
if (!(isEsModule || isCompiledEsModule || hasDefaultExport)) {
663751
wrapperEnd = `\n\nvar ${moduleName} = {\n${names
664752
.map(({ name, deconflicted }) => `\t${name}: ${deconflicted}`)
665753
.join(',\n')}\n};`;
@@ -672,17 +760,21 @@ export function transformCommonjs(
672760
.trim()
673761
.append(wrapperEnd);
674762

675-
const defaultExport =
676-
code.indexOf('__esModule') >= 0
677-
? `export default /*@__PURE__*/${HELPERS_NAME}.getDefaultExportFromCjs(${moduleName});`
678-
: `export default ${moduleName};`;
763+
const defaultExport = [];
764+
if (isCompiledEsModule) {
765+
if (deconflictedDefaultExportName) {
766+
defaultExport.push(`export default ${deconflictedDefaultExportName};`);
767+
}
768+
} else if (!isEsModule) {
769+
defaultExport.push(`export default ${moduleName};`);
770+
}
679771

680772
const named = namedExportDeclarations
681773
.filter((x) => x.name !== 'default' || !hasDefaultExport)
682774
.map((x) => x.str);
683775

684776
magicString.append(
685-
`\n\n${(isEsModule ? [] : [defaultExport])
777+
`\n\n${defaultExport
686778
.concat(named)
687779
.concat(hasDefaultExport ? defaultExportPropertyAssignments : [])
688780
.join('\n')}`
@@ -691,5 +783,9 @@ export function transformCommonjs(
691783
code = magicString.toString();
692784
const map = sourceMap ? magicString.generateMap() : null;
693785

694-
return { code, map, syntheticNamedExports: isEsModule ? false : '__moduleExports' };
786+
return {
787+
code,
788+
map,
789+
syntheticNamedExports: isEsModule || isCompiledEsModule ? false : '__moduleExports'
790+
};
695791
}

β€Žpackages/commonjs/test/fixtures/.eslintrcβ€Ž

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
"import/prefer-default-export": "off",
1111
"import/extensions": "off",
1212
"import/no-unresolved": "off",
13-
"@typescript-eslint/no-unused-vars": "off"
13+
"@typescript-eslint/no-unused-vars": "off",
14+
"camelcase": "off",
15+
"no-underscore-dangle": "off"
1416
}
1517
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
exports.__esModule = true;
2+
exports.default = 'x';
3+
exports.foo = 'foo';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
var _default = 'x';
2+
var foo = 'foo';
3+
4+
export default _default;
5+
export { foo };
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports.__esModule = true;
2+
module.exports.default = 'x';
3+
module.exports.foo = 'foo';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
var _default = 'x';
2+
var foo = 'foo';
3+
4+
export default _default;
5+
export { foo };
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Object.defineProperty(exports, '__esModule', { value: true });
2+
exports.foo = 'bar';
3+
4+
const foo = 'also bar';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const foo_1 = 'bar';
2+
3+
const foo = 'also bar';
4+
5+
export { foo_1 as foo };
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Object.defineProperty(exports, '__esModule', { value: true });
2+
exports.default = 'x';
3+
exports.foo = 'foo';

0 commit comments

Comments
Β (0)