Skip to content

Commit a6f8077

Browse files
committed
fix(commonjs): attach correct plugin meta-data to commonjs modules (#1038)
1 parent 6faff65 commit a6f8077

File tree

28 files changed

+122
-95
lines changed

28 files changed

+122
-95
lines changed

packages/commonjs/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,17 @@ Default: `false`
113113

114114
Instructs the plugin whether to enable mixed module transformations. This is useful in scenarios with modules that contain a mix of ES `import` statements and CommonJS `require` expressions. Set to `true` if `require` calls should be transformed to imports in mixed modules, or `false` if the `require` expressions should survive the transformation. The latter can be important if the code contains environment detection, or you are coding for an environment with special treatment for `require` calls such as [ElectronJS](https://www.electronjs.org/). See also the "ignore" option.
115115

116+
### `strictRequireSemantic`
117+
118+
Type: `boolean | string | string[]`<br>
119+
Default: `false`
120+
121+
By default, this plugin will try to hoist all `require` statements as imports to the top of each file. While this works well for many code bases and allows for very efficient ESM output, it does not perfectly capture CommonJS semantics. This is especially problematic when there are circular `require` calls between CommonJS modules as those often rely on the lazy execution of nested `require` calls.
122+
123+
Setting this option to `true` will wrap all CommonJS files in functions which are executed when they are required for the first time, preserving NodeJS semantics. Note that this can have a small impact on the size and performance of the generated code.
124+
125+
You can also provide a [minimatch pattern](https://github.com/isaacs/minimatch), or array of patterns, to only specify a subset of files which should be wrapped in functions for proper `require` semantics.
126+
116127
### `ignore`
117128

118129
Type: `string[] | ((id: string) => boolean)`<br>

packages/commonjs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"require"
4848
],
4949
"peerDependencies": {
50-
"rollup": "^2.38.3"
50+
"rollup": "^2.61.1"
5151
},
5252
"dependencies": {
5353
"@rollup/pluginutils": "^3.1.0",

packages/commonjs/src/generate-imports.js

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,7 @@ import { dirname, resolve } from 'path';
22

33
import { sync as nodeResolveSync } from 'resolve';
44

5-
import {
6-
EXPORTS_SUFFIX,
7-
HELPERS_ID,
8-
MODULE_SUFFIX,
9-
PROXY_SUFFIX,
10-
REQUIRE_SUFFIX,
11-
wrapId
12-
} from './helpers';
5+
import { EXPORTS_SUFFIX, HELPERS_ID, MODULE_SUFFIX, PROXY_SUFFIX, wrapId } from './helpers';
136
import { normalizePathSlashes } from './utils';
147

158
export function isRequireStatement(node, scope) {
@@ -153,12 +146,9 @@ export function getRequireHandlers() {
153146
);
154147
}
155148
for (const source of dynamicRegisterSources) {
156-
imports.push(`import ${JSON.stringify(wrapId(source, REQUIRE_SUFFIX))};`);
149+
imports.push(`import ${JSON.stringify(source)};`);
157150
}
158151
for (const source of requiredSources) {
159-
if (!source.startsWith('\0')) {
160-
imports.push(`import ${JSON.stringify(wrapId(source, REQUIRE_SUFFIX))};`);
161-
}
162152
const { name, nodesUsingRequired } = requiredBySource[source];
163153
imports.push(
164154
`import ${nodesUsingRequired.length ? `${name} from ` : ''}${JSON.stringify(

packages/commonjs/src/helpers.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ export const wrapId = (id, suffix) => `\0${id}${suffix}`;
33
export const unwrapId = (wrappedId, suffix) => wrappedId.slice(1, -suffix.length);
44

55
export const PROXY_SUFFIX = '?commonjs-proxy';
6-
export const REQUIRE_SUFFIX = '?commonjs-require';
76
export const EXTERNAL_SUFFIX = '?commonjs-external';
87
export const EXPORTS_SUFFIX = '?commonjs-exports';
98
export const MODULE_SUFFIX = '?commonjs-module';

packages/commonjs/src/index.js

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import {
2626
PROXY_SUFFIX,
2727
unwrapId
2828
} from './helpers';
29-
import { setCommonJSMetaPromise } from './is-cjs';
3029
import { hasCjsKeywords } from './parse';
3130
import {
3231
getDynamicJsonProxy,
@@ -43,6 +42,12 @@ import { getName, getVirtualPathForDynamicRequirePath, normalizePathSlashes } fr
4342
export default function commonjs(options = {}) {
4443
const extensions = options.extensions || ['.js'];
4544
const filter = createFilter(options.include, options.exclude);
45+
const strictRequireSemanticFilter =
46+
options.strictRequireSemantic === true
47+
? () => true
48+
: !options.strictRequireSemantic
49+
? () => false
50+
: createFilter(options.strictRequireSemantic);
4651
const {
4752
ignoreGlobal,
4853
ignoreDynamicRequires,
@@ -77,7 +82,6 @@ export default function commonjs(options = {}) {
7782

7883
const esModulesWithDefaultExport = new Set();
7984
const esModulesWithNamedExports = new Set();
80-
const commonJsMetaPromises = new Map();
8185

8286
const ignoreRequire =
8387
typeof options.ignore === 'function'
@@ -248,7 +252,7 @@ export default function commonjs(options = {}) {
248252
getRequireReturnsDefault(actualId),
249253
esModulesWithDefaultExport,
250254
esModulesWithNamedExports,
251-
commonJsMetaPromises
255+
this.load
252256
);
253257
}
254258

@@ -277,14 +281,6 @@ export default function commonjs(options = {}) {
277281
} catch (err) {
278282
return this.error(err, err.loc);
279283
}
280-
},
281-
282-
moduleParsed({ id, meta: { commonjs: commonjsMeta } }) {
283-
if (commonjsMeta && commonjsMeta.isCommonJS != null) {
284-
setCommonJSMetaPromise(commonJsMetaPromises, id, commonjsMeta);
285-
return;
286-
}
287-
setCommonJSMetaPromise(commonJsMetaPromises, id, null);
288284
}
289285
};
290286
}

packages/commonjs/src/is-cjs.js

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

packages/commonjs/src/proxies.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { readFileSync } from 'fs';
22

33
import { DYNAMIC_JSON_PREFIX, HELPERS_ID } from './helpers';
4-
import { getCommonJSMetaPromise } from './is-cjs';
54
import { getName, getVirtualPathForDynamicRequirePath, normalizePathSlashes } from './utils';
65

76
// e.g. id === "commonjsHelpers?commonjsRegister"
@@ -16,11 +15,11 @@ export function getUnknownRequireProxy(id, requireReturnsDefault) {
1615
const name = getName(id);
1716
const exported =
1817
requireReturnsDefault === 'auto'
19-
? `import {getDefaultExportFromNamespaceIfNotNamed} from "${HELPERS_ID}"; export default /*@__PURE__*/getDefaultExportFromNamespaceIfNotNamed(${name});`
18+
? `import { getDefaultExportFromNamespaceIfNotNamed } from "${HELPERS_ID}"; export default /*@__PURE__*/getDefaultExportFromNamespaceIfNotNamed(${name});`
2019
: requireReturnsDefault === 'preferred'
21-
? `import {getDefaultExportFromNamespaceIfPresent} from "${HELPERS_ID}"; export default /*@__PURE__*/getDefaultExportFromNamespaceIfPresent(${name});`
20+
? `import { getDefaultExportFromNamespaceIfPresent } from "${HELPERS_ID}"; export default /*@__PURE__*/getDefaultExportFromNamespaceIfPresent(${name});`
2221
: !requireReturnsDefault
23-
? `import {getAugmentedNamespace} from "${HELPERS_ID}"; export default /*@__PURE__*/getAugmentedNamespace(${name});`
22+
? `import { getAugmentedNamespace } from "${HELPERS_ID}"; export default /*@__PURE__*/getAugmentedNamespace(${name});`
2423
: `export default ${name};`;
2524
return `import * as ${name} from ${JSON.stringify(id)}; ${exported}`;
2625
}
@@ -47,13 +46,15 @@ export async function getStaticRequireProxy(
4746
requireReturnsDefault,
4847
esModulesWithDefaultExport,
4948
esModulesWithNamedExports,
50-
commonJsMetaPromises
49+
loadModule
5150
) {
5251
const name = getName(id);
53-
const commonjsMeta = await getCommonJSMetaPromise(commonJsMetaPromises, id);
52+
const {
53+
meta: { commonjs: commonjsMeta }
54+
} = await loadModule({ id });
5455
if (commonjsMeta && commonjsMeta.isCommonJS) {
5556
return `export { __moduleExports as default } from ${JSON.stringify(id)};`;
56-
} else if (commonjsMeta === null) {
57+
} else if (!commonjsMeta) {
5758
return getUnknownRequireProxy(id, requireReturnsDefault);
5859
} else if (!requireReturnsDefault) {
5960
return `import { getAugmentedNamespace } from "${HELPERS_ID}"; import * as ${name} from ${JSON.stringify(

packages/commonjs/src/resolve-id.js

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
isWrappedId,
1414
MODULE_SUFFIX,
1515
PROXY_SUFFIX,
16-
REQUIRE_SUFFIX,
1716
unwrapId,
1817
wrapId
1918
} from './helpers';
@@ -66,14 +65,11 @@ export default function getResolveId(extensions) {
6665
}
6766

6867
const isProxyModule = isWrappedId(importee, PROXY_SUFFIX);
69-
const isRequiredModule = isWrappedId(importee, REQUIRE_SUFFIX);
7068
let isModuleRegistration = false;
7169

7270
if (isProxyModule) {
7371
importee = unwrapId(importee, PROXY_SUFFIX);
74-
} else if (isRequiredModule) {
75-
importee = unwrapId(importee, REQUIRE_SUFFIX);
76-
72+
} else {
7773
isModuleRegistration = isWrappedId(importee, DYNAMIC_REGISTER_SUFFIX);
7874
if (isModuleRegistration) {
7975
importee = unwrapId(importee, DYNAMIC_REGISTER_SUFFIX);
@@ -98,20 +94,29 @@ export default function getResolveId(extensions) {
9894
Object.assign({}, resolveOptions, {
9995
skipSelf: true,
10096
custom: Object.assign({}, resolveOptions.custom, {
101-
'node-resolve': { isRequire: isProxyModule || isRequiredModule }
97+
'node-resolve': { isRequire: isProxyModule || isModuleRegistration }
10298
})
10399
})
104100
).then((resolved) => {
105101
if (!resolved) {
106102
resolved = resolveExtensions(importee, importer);
107103
}
108-
if (resolved && isProxyModule) {
109-
resolved.id = wrapId(resolved.id, resolved.external ? EXTERNAL_SUFFIX : PROXY_SUFFIX);
110-
resolved.external = false;
111-
} else if (resolved && isModuleRegistration) {
112-
resolved.id = wrapId(resolved.id, DYNAMIC_REGISTER_SUFFIX);
113-
} else if (!resolved && (isProxyModule || isRequiredModule)) {
114-
return { id: wrapId(importee, EXTERNAL_SUFFIX), external: false };
104+
if (isProxyModule) {
105+
if (!resolved || resolved.external) {
106+
return {
107+
id: wrapId(resolved ? resolved.id : importee, EXTERNAL_SUFFIX),
108+
external: false
109+
};
110+
}
111+
// This will make sure meta properties in "resolved" are correctly attached to the module
112+
this.load(resolved);
113+
return {
114+
id: wrapId(resolved.id, PROXY_SUFFIX),
115+
external: false
116+
};
117+
}
118+
if (resolved && isModuleRegistration) {
119+
return { id: wrapId(resolved.id, DYNAMIC_REGISTER_SUFFIX), external: false };
115120
}
116121
return resolved;
117122
});

packages/commonjs/src/transform-commonjs.js

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,8 @@ export default function transformCommonjs(
8282
const dynamicRegisterSources = new Set();
8383
let hasRemovedRequire = false;
8484

85-
const {
86-
addRequireStatement,
87-
requiredSources,
88-
rewriteRequireExpressionsAndGetImportBlock
89-
} = getRequireHandlers();
85+
const { addRequireStatement, requiredSources, rewriteRequireExpressionsAndGetImportBlock } =
86+
getRequireHandlers();
9087

9188
// See which names are assigned to. This is necessary to prevent
9289
// illegally replacing `var foo = require('foo')` with `import foo from 'foo'`,
@@ -235,10 +232,8 @@ export default function transformCommonjs(
235232
let shouldRemoveRequireStatement = false;
236233

237234
if (currentTryBlockEnd !== null) {
238-
({
239-
canConvertRequire,
240-
shouldRemoveRequireStatement
241-
} = getIgnoreTryCatchRequireStatementMode(node.arguments[0].value));
235+
({ canConvertRequire, shouldRemoveRequireStatement } =
236+
getIgnoreTryCatchRequireStatementMode(node.arguments[0].value));
242237

243238
if (shouldRemoveRequireStatement) {
244239
hasRemovedRequire = true;

packages/commonjs/test/fixtures/form/constant-template-literal/output.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import * as commonjsHelpers from "_commonjsHelpers.js";
22
import { __exports as input } from "\u0000fixtures/form/constant-template-literal/input.js?commonjs-exports"
3-
import "\u0000tape?commonjs-require";
43
import require$$0 from "\u0000tape?commonjs-proxy";
54

65
var foo = require$$0;

0 commit comments

Comments
 (0)