Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
681cd8c
fix(commonjs): attach correct plugin meta-data to commonjs modules (#…
lukastaegert Sep 21, 2021
f55ba96
feat(commonjs): reimplement dynamic import handling (requires Node 12…
lukastaegert Oct 28, 2021
003845f
feat(commonjs): add strictRequires option to wrap modules (#1038)
lukastaegert Nov 6, 2021
771ef4a
feat(commonjs): automatically wrap cyclic modules (#1038)
lukastaegert Nov 9, 2021
aae44a2
feat(commonjs): Infer type for unidentified modules (#1038)
lukastaegert Nov 12, 2021
1054074
feat(commonjs): make namespace callable when requiring ESM with funct…
lukastaegert Nov 12, 2021
4424686
feat(commonjs): limit ignoreTryCatch to external requires (#1038)
lukastaegert Nov 19, 2021
a9ed479
feat(commonjs): auto-detect conditional requires (#1038)
lukastaegert Nov 20, 2021
42e7196
feat(commonjs): add dynamicRequireRoot option (#1038)
lukastaegert Nov 21, 2021
06a74ae
feat(commonjs): throw for dynamic requires from outside the configure…
lukastaegert Nov 22, 2021
3cdad21
refactor(commonjs): deconflict helpers only once globals are known (#…
lukastaegert Nov 22, 2021
3b28947
feat(commonjs): expose plugin version (#1038)
lukastaegert Nov 24, 2021
a66d253
fix(commonjs): do not transform "typeof exports" for mixed modules (#…
lukastaegert Dec 2, 2021
29074f4
fix(commonjs): inject module name into dynamic require function (#1038)
lukastaegert Dec 14, 2021
804eb8e
fix(commonjs): validate node-resolve peer version (#1038)
lukastaegert Dec 14, 2021
d01238f
fix(commonjs): use correct version and add package exports (#1038)
lukastaegert Dec 14, 2021
017ea90
fix(commonjs): proxy all entries to not break legacy polyfill plugins…
lukastaegert Jan 14, 2022
a521f47
fix(commonjs): add heuristic to deoptimize requires after calling imp…
lukastaegert Feb 7, 2022
368f9c8
fix(commonjs): handle external dependencies when using the cache (#1038)
lukastaegert Feb 24, 2022
e5f1abd
fix(commonjs): Do not change semantics when removing requires in if s…
lukastaegert Apr 3, 2022
e2f26a4
fix(commonjs): Warn when plugins do not pass options to resolveId (#1…
lukastaegert Apr 18, 2022
5b3bd05
fix(commonjs): support CJS modules re-exporting transpiled ESM module…
fwouts Apr 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix(commonjs): Warn when plugins do not pass options to resolveId (#1038
)
  • Loading branch information
lukastaegert committed Apr 24, 2022
commit e2f26a43f2e76d6cbdf3d77989061162d2732d6e
2 changes: 2 additions & 0 deletions packages/commonjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ You can also provide a [minimatch pattern](https://github.com/isaacs/minimatch),
Type: `string | string[]`<br>
Default: `[]`

_Note: In previous versions, this option would spin up a rather comprehensive mock environment that was capable of handling modules that manipulate `require.cache`. This is no longer supported. If you rely on this e.g. when using request-promise-native, use version 21 of this plugin._

Some modules contain dynamic `require` calls, or require modules that contain circular dependencies, which are not handled well by static imports.
Including those modules as `dynamicRequireTargets` will simulate a CommonJS (NodeJS-like) environment for them with support for dynamic dependencies. It also enables `strictRequires` for those modules, see above.

Expand Down
18 changes: 10 additions & 8 deletions packages/commonjs/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export default function commonjs(options = {}) {
};
};

const resolveId = getResolveId(extensions);
const { currentlyResolving, resolveId } = getResolveId(extensions);

const sourceMap = options.sourceMap !== false;

Expand Down Expand Up @@ -204,7 +204,11 @@ export default function commonjs(options = {}) {
'The namedExports option from "@rollup/plugin-commonjs" is deprecated. Named exports are now handled automatically.'
);
}
requireResolver = getRequireResolver(extensions, detectCyclesAndConditional);
requireResolver = getRequireResolver(
extensions,
detectCyclesAndConditional,
currentlyResolving
);
},

buildEnd() {
Expand Down Expand Up @@ -260,15 +264,13 @@ export default function commonjs(options = {}) {

// entry suffix is just appended to not mess up relative external resolution
if (id.endsWith(ENTRY_SUFFIX)) {
return getEntryProxy(
id.slice(0, -ENTRY_SUFFIX.length),
defaultIsModuleExports,
this.getModuleInfo
);
const acutalId = id.slice(0, -ENTRY_SUFFIX.length);
return getEntryProxy(acutalId, getDefaultIsModuleExports(acutalId), this.getModuleInfo);
}

if (isWrappedId(id, ES_IMPORT_SUFFIX)) {
return getEsImportProxy(unwrapId(id, ES_IMPORT_SUFFIX), defaultIsModuleExports);
const actualId = unwrapId(id, ES_IMPORT_SUFFIX);
return getEsImportProxy(actualId, getDefaultIsModuleExports(actualId));
}

if (id === DYNAMIC_MODULES_ID) {
Expand Down
159 changes: 97 additions & 62 deletions packages/commonjs/src/resolve-id.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,79 +50,114 @@ export function resolveExtensions(importee, importer, extensions) {
}

export default function getResolveId(extensions) {
return async function resolveId(importee, importer, resolveOptions) {
// We assume that all requires are pre-resolved
const customOptions = resolveOptions.custom;
if (customOptions && customOptions['node-resolve'] && customOptions['node-resolve'].isRequire) {
return null;
}
if (isWrappedId(importee, WRAPPED_SUFFIX)) {
return unwrapId(importee, WRAPPED_SUFFIX);
}
const currentlyResolving = new Map();

if (
importee.endsWith(ENTRY_SUFFIX) ||
isWrappedId(importee, MODULE_SUFFIX) ||
isWrappedId(importee, EXPORTS_SUFFIX) ||
isWrappedId(importee, PROXY_SUFFIX) ||
isWrappedId(importee, ES_IMPORT_SUFFIX) ||
isWrappedId(importee, EXTERNAL_SUFFIX) ||
importee.startsWith(HELPERS_ID) ||
importee === DYNAMIC_MODULES_ID
) {
return importee;
}
return {
/**
* This is a Maps of importers to Sets of require sources being resolved at
* the moment by resolveRequireSourcesAndUpdateMeta
*/
currentlyResolving,
async resolveId(importee, importer, resolveOptions) {
const customOptions = resolveOptions.custom;
// All logic below is specific to ES imports.
// Also, if we do not skip this logic for requires that are resolved while
// transforming a commonjs file, it can easily lead to deadlocks.
if (
customOptions &&
customOptions['node-resolve'] &&
customOptions['node-resolve'].isRequire
) {
return null;
}
const currentlyResolvingForParent = currentlyResolving.get(importer);
if (currentlyResolvingForParent && currentlyResolvingForParent.has(importee)) {
this.warn({
code: 'THIS_RESOLVE_WITHOUT_OPTIONS',
message:
'It appears a plugin has implemented a "resolveId" hook that uses "this.resolve" without forwarding the third "options" parameter of "resolveId". This is problematic as it can lead to wrong module resolutions especially for the node-resolve plugin and in certain cases cause early exit errors for the commonjs plugin.\nIn rare cases, this warning can appear if the same file is both imported and required from the same mixed ES/CommonJS module, in which case it can be ignored.',
url: 'https://rollupjs.org/guide/en/#resolveid'
});
return null;
}

if (isWrappedId(importee, WRAPPED_SUFFIX)) {
return unwrapId(importee, WRAPPED_SUFFIX);
}

if (importer) {
if (
importer === DYNAMIC_MODULES_ID ||
// Proxies are only importing resolved ids, no need to resolve again
isWrappedId(importer, PROXY_SUFFIX) ||
isWrappedId(importer, ES_IMPORT_SUFFIX) ||
importer.endsWith(ENTRY_SUFFIX)
importee.endsWith(ENTRY_SUFFIX) ||
isWrappedId(importee, MODULE_SUFFIX) ||
isWrappedId(importee, EXPORTS_SUFFIX) ||
isWrappedId(importee, PROXY_SUFFIX) ||
isWrappedId(importee, ES_IMPORT_SUFFIX) ||
isWrappedId(importee, EXTERNAL_SUFFIX) ||
importee.startsWith(HELPERS_ID) ||
importee === DYNAMIC_MODULES_ID
) {
return importee;
}
if (isWrappedId(importer, EXTERNAL_SUFFIX)) {
// We need to return null for unresolved imports so that the proper warning is shown
if (!(await this.resolve(importee, importer, { skipSelf: true }))) {
return null;

if (importer) {
if (
importer === DYNAMIC_MODULES_ID ||
// Proxies are only importing resolved ids, no need to resolve again
isWrappedId(importer, PROXY_SUFFIX) ||
isWrappedId(importer, ES_IMPORT_SUFFIX) ||
importer.endsWith(ENTRY_SUFFIX)
) {
return importee;
}
if (isWrappedId(importer, EXTERNAL_SUFFIX)) {
// We need to return null for unresolved imports so that the proper warning is shown
if (
!(await this.resolve(
importee,
importer,
Object.assign({ skipSelf: true }, resolveOptions)
))
) {
return null;
}
// For other external imports, we need to make sure they are handled as external
return { id: importee, external: true };
}
// For other external imports, we need to make sure they are handled as external
return { id: importee, external: true };
}
}

if (importee.startsWith('\0')) {
return null;
}
if (importee.startsWith('\0')) {
return null;
}

// If this is an entry point or ESM import, we need to figure out if the importee is wrapped and
// if that is the case, we need to add a proxy.
const resolved =
(await this.resolve(importee, importer, Object.assign({ skipSelf: true }, resolveOptions))) ||
resolveExtensions(importee, importer, extensions);
// Make sure that even if other plugins resolve again, we ignore our own proxies
if (
!resolved ||
resolved.external ||
resolved.id.endsWith(ENTRY_SUFFIX) ||
isWrappedId(resolved.id, ES_IMPORT_SUFFIX)
) {
// If this is an entry point or ESM import, we need to figure out if the importee is wrapped and
// if that is the case, we need to add a proxy.
const resolved =
(await this.resolve(
importee,
importer,
Object.assign({ skipSelf: true }, resolveOptions)
)) || resolveExtensions(importee, importer, extensions);
// Make sure that even if other plugins resolve again, we ignore our own proxies
if (
!resolved ||
resolved.external ||
resolved.id.endsWith(ENTRY_SUFFIX) ||
isWrappedId(resolved.id, ES_IMPORT_SUFFIX)
) {
return resolved;
}
const moduleInfo = await this.load(resolved);
if (resolveOptions.isEntry) {
moduleInfo.moduleSideEffects = true;
// We must not precede entry proxies with a `\0` as that will mess up relative external resolution
return resolved.id + ENTRY_SUFFIX;
}
const {
meta: { commonjs: commonjsMeta }
} = moduleInfo;
if (commonjsMeta && commonjsMeta.isCommonJS === IS_WRAPPED_COMMONJS) {
return { id: wrapId(resolved.id, ES_IMPORT_SUFFIX), meta: { commonjs: { resolved } } };
}
return resolved;
}
const moduleInfo = await this.load(resolved);
if (resolveOptions.isEntry) {
moduleInfo.moduleSideEffects = true;
// We must not precede entry proxies with a `\0` as that will mess up relative external resolution
return resolved.id + ENTRY_SUFFIX;
}
const {
meta: { commonjs: commonjsMeta }
} = moduleInfo;
if (commonjsMeta && commonjsMeta.isCommonJS === IS_WRAPPED_COMMONJS) {
return { id: wrapId(resolved.id, ES_IMPORT_SUFFIX), meta: { commonjs: { resolved } } };
}
return resolved;
};
}
10 changes: 9 additions & 1 deletion packages/commonjs/src/resolve-require-sources.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from './helpers';
import { resolveExtensions } from './resolve-id';

export function getRequireResolver(extensions, detectCyclesAndConditional) {
export function getRequireResolver(extensions, detectCyclesAndConditional, currentlyResolving) {
const knownCjsModuleTypes = Object.create(null);
const requiredIds = Object.create(null);
const unconditionallyRequiredIds = Object.create(null);
Expand Down Expand Up @@ -161,16 +161,20 @@ export function getRequireResolver(extensions, detectCyclesAndConditional) {
parentMeta.requires = [];
parentMeta.isRequiredCommonJS = Object.create(null);
setInitialParentType(parentId, isParentCommonJS);
const currentlyResolvingForParent = currentlyResolving.get(parentId) || new Set();
currentlyResolving.set(parentId, currentlyResolvingForParent);
const requireTargets = await Promise.all(
sources.map(async ({ source, isConditional }) => {
// Never analyze or proxy internal modules
if (source.startsWith('\0')) {
return { id: source, allowProxy: false };
}
currentlyResolvingForParent.add(source);
const resolved =
(await rollupContext.resolve(source, parentId, {
custom: { 'node-resolve': { isRequire: true } }
})) || resolveExtensions(source, parentId, extensions);
currentlyResolvingForParent.delete(source);
if (!resolved) {
return { id: wrapId(source, EXTERNAL_SUFFIX), allowProxy: false };
}
Expand Down Expand Up @@ -201,6 +205,10 @@ export function getRequireResolver(extensions, detectCyclesAndConditional) {
isCommonJS
};
});
},
isCurrentlyResolving(source, parentId) {
const currentlyResolvingForParent = currentlyResolving.get(parentId);
return currentlyResolvingForParent && currentlyResolvingForParent.has(source);
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const assert = require('assert');

const warnings = [];

module.exports = {
description: 'Warns when another plugin uses this.resolve without forwarding options',
options: {
onwarn(warning) {
warnings.push(warning);
},
plugins: [
{
name: 'test',
resolveId(source, importer) {
return this.resolve(source, importer, { skipSelf: true });
},
buildEnd() {
assert.strictEqual(warnings.length, 1);
assert.strictEqual(
warnings[0].message,
'It appears a plugin has implemented a "resolveId" hook that uses "this.resolve" without forwarding the third "options" parameter of "resolveId". This is problematic as it can lead to wrong module resolutions especially for the node-resolve plugin and in certain cases cause early exit errors for the commonjs plugin.\nIn rare cases, this warning can appear if the same file is both imported and required from the same mixed ES/CommonJS module, in which case it can be ignored.'
);
assert.strictEqual(warnings[0].pluginCode, 'THIS_RESOLVE_WITHOUT_OPTIONS');
assert.strictEqual(warnings[0].url, 'https://rollupjs.org/guide/en/#resolveid');
}
}
]
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = 21;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const foo = require('./foo');

module.exports = foo * 2;
17 changes: 17 additions & 0 deletions packages/commonjs/test/snapshots/function.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -7713,3 +7713,20 @@ Generated by [AVA](https://avajs.dev).
module.exports = main;␊
`,
}

## warn-this-resolve-without-options

> Snapshot 1

{
'main.js': `'use strict';␊
var foo$1 = 21;␊
const foo = foo$1;␊
var main = foo * 2;␊
module.exports = main;␊
`,
}
Binary file modified packages/commonjs/test/snapshots/function.js.snap
Binary file not shown.