Skip to content

Commit

Permalink
feat(commonjs): Infer type for unidentified modules (#1038)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed Apr 24, 2022
1 parent 2b3d215 commit 2aa0ac9
Show file tree
Hide file tree
Showing 50 changed files with 220 additions and 63 deletions.
29 changes: 23 additions & 6 deletions packages/commonjs/src/index.js
Expand Up @@ -60,10 +60,11 @@ export default function commonjs(options = {}) {
: () =>
typeof defaultIsModuleExportsOption === 'boolean' ? defaultIsModuleExportsOption : 'auto';

const { resolveRequireSourcesAndGetMeta, getWrappedIds } = getResolveRequireSourcesAndGetMeta(
extensions,
detectCycles
);
const {
resolveRequireSourcesAndGetMeta,
getWrappedIds,
isRequiredId
} = getResolveRequireSourcesAndGetMeta(extensions, detectCycles);
const dynamicRequireModuleSet = getDynamicRequireModuleSet(options.dynamicRequireTargets);
const isDynamicRequireModulesEnabled = dynamicRequireModuleSet.size > 0;
const commonDir = isDynamicRequireModulesEnabled
Expand Down Expand Up @@ -115,7 +116,8 @@ export default function commonjs(options = {}) {

if (
!dynamicRequireModuleSet.has(normalizePathSlashes(id)) &&
(!hasCjsKeywords(code, ignoreGlobal) || (isEsModule && !options.transformMixedEsModules))
(!(hasCjsKeywords(code, ignoreGlobal) || isRequiredId(id)) ||
(isEsModule && !options.transformMixedEsModules))
) {
return { meta: { commonjs: { isCommonJS: false } } };
}
Expand All @@ -140,13 +142,28 @@ export default function commonjs(options = {}) {
ast,
getDefaultIsModuleExports(id),
needsRequireWrapper,
resolveRequireSourcesAndGetMeta(this)
resolveRequireSourcesAndGetMeta(this),
isRequiredId(id)
);
}

return {
name: 'commonjs',

options(options) {
// Always sort the node-resolve plugin after the commonjs plugin as otherwise CommonJS entries
// will not work with strictRequires: true
const { plugins } = options;
if (Array.isArray(plugins)) {
const cjsIndex = plugins.findIndex((plugin) => plugin.name === 'commonjs');
const nodeResolveIndex = plugins.findIndex((plugin) => plugin.name === 'node-resolve');
if (nodeResolveIndex >= 0 && nodeResolveIndex < cjsIndex) {
plugins.splice(cjsIndex + 1, 0, plugins[nodeResolveIndex]);
plugins.splice(nodeResolveIndex, 1);
}
}
},

buildStart() {
validateRollupVersion(this.meta.rollupVersion, peerDependencies.rollup);
if (options.namedExports != null) {
Expand Down
3 changes: 3 additions & 0 deletions packages/commonjs/src/resolve-require-sources.js
Expand Up @@ -3,6 +3,7 @@ import { resolveExtensions } from './resolve-id';

export function getResolveRequireSourcesAndGetMeta(extensions, detectCycles) {
const knownCjsModuleTypes = Object.create(null);
const requiredIds = Object.create(null);
const dependentModules = Object.create(null);
const getDependentModules = (id) =>
dependentModules[id] || (dependentModules[id] = Object.create(null));
Expand All @@ -12,6 +13,7 @@ export function getResolveRequireSourcesAndGetMeta(extensions, detectCycles) {
Object.keys(knownCjsModuleTypes).filter(
(id) => knownCjsModuleTypes[id] === IS_WRAPPED_COMMONJS
),
isRequiredId: (id) => requiredIds[id],
resolveRequireSourcesAndGetMeta: (rollupContext) => async (id, isParentCommonJS, sources) => {
knownCjsModuleTypes[id] = isParentCommonJS;
const requireTargets = await Promise.all(
Expand All @@ -34,6 +36,7 @@ export function getResolveRequireSourcesAndGetMeta(extensions, detectCycles) {
if (resolved.external) {
return { id: wrapId(childId, EXTERNAL_SUFFIX), allowProxy: false };
}
requiredIds[childId] = true;
const parentDependentModules = getDependentModules(id);
const childDependentModules = getDependentModules(childId);
childDependentModules[id] = true;
Expand Down
4 changes: 3 additions & 1 deletion packages/commonjs/src/transform-commonjs.js
Expand Up @@ -51,7 +51,8 @@ export default async function transformCommonjs(
astCache,
defaultIsModuleExports,
needsRequireWrapper,
resolveRequireSourcesAndGetMeta
resolveRequireSourcesAndGetMeta,
isRequired
) {
const ast = astCache || tryParse(parse, code, id);
const magicString = new MagicString(code);
Expand Down Expand Up @@ -418,6 +419,7 @@ export default async function transformCommonjs(
if (
!(
shouldWrap ||
isRequired ||
uses.module ||
uses.exports ||
uses.require ||
Expand Down
@@ -0,0 +1,31 @@
const assert = require('assert');

const { nodeResolve } = require('@rollup/plugin-node-resolve');

module.exports = {
description:
'strict require semantic modules can be entry points when the node-resolve plugin is used',
pluginOptions: {
strictRequires: true
},
options: {
plugins: [
{
name: 'before-node',
buildStart({ plugins }) {
assert.deepStrictEqual(
plugins.map((plugin) => plugin.name),
['before-node', 'after-node', 'commonjs', 'node-resolve']
);
}
},
nodeResolve(),
{
name: 'after-node'
}
]
},
exports(exports) {
assert.deepStrictEqual(exports, { foo: 'foo' });
}
};
@@ -1,6 +1,6 @@
module.exports = {
description: 'supports using function wrappers for modules for export mode "exports"',
pluginOptions: {
strictRequires: ['fixtures/function/strict-require-semantic-exportmode-exports/*E*.js']
strictRequires: ['fixtures/function/strict-requires-exportmode-exports/*E*.js']
}
};
@@ -1,6 +1,6 @@
module.exports = {
description: 'supports using function wrappers for modules for export mode "module"',
pluginOptions: {
strictRequires: ['fixtures/function/strict-require-semantic-exportmode-module/*E*.js']
strictRequires: ['fixtures/function/strict-requires-exportmode-module/*E*.js']
}
};
@@ -1,6 +1,6 @@
module.exports = {
description: 'supports using function wrappers for modules for export mode "replace"',
pluginOptions: {
strictRequires: ['fixtures/function/strict-require-semantic-exportmode-replace/*E*.js']
strictRequires: ['fixtures/function/strict-requires-exportmode-replace/*E*.js']
}
};
@@ -0,0 +1,7 @@
module.exports = {
description:
'identifies files without module features as commonjs if they are required by another file',
pluginOptions: {
strictRequires: true
}
};
@@ -0,0 +1 @@
throw new Error('FAIL');
@@ -0,0 +1,2 @@
// eslint-disable-next-line global-require
t.is(0 && require('./error.js'), 0);
@@ -1,6 +1,6 @@
module.exports = {
description: 'handles importing wrapped modules from ESM',
pluginOptions: {
strictRequires: ['fixtures/function/strict-require-semantic-from-esm/strict.js']
strictRequires: ['fixtures/function/strict-requires-from-esm/strict.js']
}
};
Expand Up @@ -4,15 +4,15 @@ module.exports = {
description: 'strict require semantic modules can be entry points',
options: {
input: [
'fixtures/function/strict-require-semantic-entry/main.js',
'fixtures/function/strict-require-semantic-entry/other.js'
'fixtures/function/strict-requires-multiple-entry/main.js',
'fixtures/function/strict-requires-multiple-entry/other.js'
],
output: {
chunkFileNames: 'generated-[name].js'
}
},
pluginOptions: {
strictRequires: ['fixtures/function/strict-require-semantic-entry/main.js']
strictRequires: ['fixtures/function/strict-requires-multiple-entry/main.js']
},
exports(exports) {
assert.deepStrictEqual(exports, { foo: 'foo' });
Expand Down
@@ -0,0 +1 @@
exports.foo = 'foo';

0 comments on commit 2aa0ac9

Please sign in to comment.