Skip to content

Commit

Permalink
refactor(commonjs): some clean-up
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed Nov 8, 2021
1 parent 1417a4e commit cb65386
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 136 deletions.
73 changes: 43 additions & 30 deletions packages/commonjs/src/generate-imports.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export function getRequireHandlers() {
exportMode,
resolveRequireSourcesAndGetMeta,
usesRequireWrapper,
isEsModule,
usesRequire
) {
const imports = [];
Expand All @@ -134,38 +135,12 @@ export function getRequireHandlers() {
);
}
const requiresBySource = collectSources(requireExpressions);
// TODO Lukas consider extracting stuff
const result = await resolveRequireSourcesAndGetMeta(
usesRequireWrapper ? IS_WRAPPED_COMMONJS : true,
const requireTargets = await resolveRequireSourcesAndGetMeta(
id,
usesRequireWrapper ? IS_WRAPPED_COMMONJS : !isEsModule,
Object.keys(requiresBySource)
);
let uid = 0;
for (const { source, id: resolveId, isCommonJS } of result) {
const requires = requiresBySource[source];
let usesRequired = false;
let name;
const hasNameConflict = ({ scope }) => scope.contains(name);
do {
name = `require$$${uid}`;
uid += 1;
} while (requires.some(hasNameConflict));
if (isCommonJS === IS_WRAPPED_COMMONJS) {
for (const { node } of requires) {
magicString.overwrite(node.start, node.end, `${name}()`);
}
imports.push(`import { __require as ${name} } from ${JSON.stringify(resolveId)};`);
} else {
for (const { node, usesReturnValue, toBeRemoved } of requires) {
if (usesReturnValue) {
usesRequired = true;
magicString.overwrite(node.start, node.end, name);
} else {
magicString.remove(toBeRemoved.start, toBeRemoved.end);
}
}
imports.push(`import ${usesRequired ? `${name} from ` : ''}${JSON.stringify(resolveId)};`);
}
}
processRequireExpressions(imports, requireTargets, requiresBySource, magicString);
return imports.length ? `${imports.join('\n')}\n\n` : '';
}

Expand All @@ -186,3 +161,41 @@ function collectSources(requireExpressions) {
}
return requiresBySource;
}

function processRequireExpressions(imports, requireTargets, requiresBySource, magicString) {
const generateRequireName = getGenerateRequireName();
for (const { source, id: resolveId, isCommonJS } of requireTargets) {
const requires = requiresBySource[source];
const name = generateRequireName(requires);
if (isCommonJS === IS_WRAPPED_COMMONJS) {
for (const { node } of requires) {
magicString.overwrite(node.start, node.end, `${name}()`);
}
imports.push(`import { __require as ${name} } from ${JSON.stringify(resolveId)};`);
} else {
let usesRequired = false;
for (const { node, usesReturnValue, toBeRemoved } of requires) {
if (usesReturnValue) {
usesRequired = true;
magicString.overwrite(node.start, node.end, name);
} else {
magicString.remove(toBeRemoved.start, toBeRemoved.end);
}
}
imports.push(`import ${usesRequired ? `${name} from ` : ''}${JSON.stringify(resolveId)};`);
}
}
}

function getGenerateRequireName() {
let uid = 0;
return (requires) => {
let name;
const hasNameConflict = ({ scope }) => scope.contains(name);
do {
name = `require$$${uid}`;
uid += 1;
} while (requires.some(hasNameConflict));
return name;
};
}
115 changes: 23 additions & 92 deletions packages/commonjs/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,26 @@ import {
EXTERNAL_SUFFIX,
getHelpersModule,
HELPERS_ID,
IS_WRAPPED_COMMONJS,
isWrappedId,
MODULE_SUFFIX,
PROXY_SUFFIX,
unwrapId,
wrapId
unwrapId
} from './helpers';
import { hasCjsKeywords } from './parse';
import { getStaticRequireProxy, getUnknownRequireProxy } from './proxies';
import getResolveId, { resolveExtensions } from './resolve-id';
import { getEsImportProxy, getStaticRequireProxy, getUnknownRequireProxy } from './proxies';
import getResolveId from './resolve-id';
import { getResolveRequireSourcesAndGetMeta } from './resolve-require-sources';
import validateRollupVersion from './rollup-version';
import transformCommonjs from './transform-commonjs';
import { capitalize, getName, normalizePathSlashes } from './utils';
import { getName, normalizePathSlashes } from './utils';

export default function commonjs(options = {}) {
const {
ignoreGlobal,
ignoreDynamicRequires,
requireReturnsDefault: requireReturnsDefaultOption,
esmExternals
} = options;
const extensions = options.extensions || ['.js'];
const filter = createFilter(options.include, options.exclude);
const strictRequireSemanticFilter =
Expand All @@ -39,27 +44,24 @@ export default function commonjs(options = {}) {
: !options.strictRequireSemantic
? () => false
: createFilter(options.strictRequireSemantic);
const {
ignoreGlobal,
ignoreDynamicRequires,
requireReturnsDefault: requireReturnsDefaultOption,
esmExternals
} = options;

const getRequireReturnsDefault =
typeof requireReturnsDefaultOption === 'function'
? requireReturnsDefaultOption
: () => requireReturnsDefaultOption;

let esmExternalIds;
const isEsmExternal =
typeof esmExternals === 'function'
? esmExternals
: Array.isArray(esmExternals)
? ((esmExternalIds = new Set(esmExternals)), (id) => esmExternalIds.has(id))
: () => esmExternals;

const defaultIsModuleExports =
typeof options.defaultIsModuleExports === 'boolean' ? options.defaultIsModuleExports : 'auto';
const knownCjsModuleTypes = Object.create(null);

const resolveRequireSourcesAndGetMeta = getResolveRequireSourcesAndGetMeta(extensions);
const dynamicRequireModuleSet = getDynamicRequireModuleSet(options.dynamicRequireTargets);
const isDynamicRequireModulesEnabled = dynamicRequireModuleSet.size > 0;
const commonDir = isDynamicRequireModulesEnabled
Expand Down Expand Up @@ -120,8 +122,6 @@ export default function commonjs(options = {}) {
!isEsModule &&
(dynamicRequireModuleSet.has(normalizePathSlashes(id)) || strictRequireSemanticFilter(id));

// TODO Lukas auto-detect circular dependencies
// TODO Lukas extract helpers
return transformCommonjs(
this.parse,
code,
Expand All @@ -138,53 +138,7 @@ export default function commonjs(options = {}) {
ast,
defaultIsModuleExports,
usesRequireWrapper,
// TODO Lukas extract
(isParentCommonJS, sources) => {
knownCjsModuleTypes[id] = isParentCommonJS;
return Promise.all(
sources.map(async (source) => {
// Never analyze or proxy internal modules
if (source.startsWith('\0')) {
return { source, id: source, isCommonJS: false };
}
const resolved =
(await this.resolve(source, id, {
skipSelf: true,
custom: {
'node-resolve': { isRequire: true }
}
})) || resolveExtensions(source, id, extensions);
if (!resolved) {
return { source, id: wrapId(source, EXTERNAL_SUFFIX), isCommonJS: false };
}
if (resolved.external) {
return { source, id: wrapId(resolved.id, EXTERNAL_SUFFIX), isCommonJS: false };
}
if (resolved.id in knownCjsModuleTypes) {
return {
source,
id:
knownCjsModuleTypes[resolved.id] === true
? wrapId(resolved.id, PROXY_SUFFIX)
: resolved.id,
isCommonJS: knownCjsModuleTypes[resolved.id]
};
}
const {
meta: { commonjs: commonjsMeta }
} = await this.load(resolved);
const isCommonJS = commonjsMeta && commonjsMeta.isCommonJS;
return {
source,
id:
isCommonJS === IS_WRAPPED_COMMONJS
? resolved.id
: wrapId(resolved.id, PROXY_SUFFIX),
isCommonJS
};
})
);
}
resolveRequireSourcesAndGetMeta(this)
);
}

Expand Down Expand Up @@ -232,40 +186,17 @@ export default function commonjs(options = {}) {
);
}

// TODO Lukas extract
if (isWrappedId(id, ES_IMPORT_SUFFIX)) {
const actualId = unwrapId(id, ES_IMPORT_SUFFIX);
const name = getName(actualId);
const exportsName = `${name}Exports`;
const requireModule = `require${capitalize(name)}`;
// TODO Lukas the ES wrapper might also just forward the exports object
let code =
`import { getDefaultExportFromCjs } from "${HELPERS_ID}";\n` +
`import { __require as ${requireModule} } from ${JSON.stringify(actualId)};\n` +
`var ${exportsName} = ${requireModule}();\n` +
`export { ${exportsName} as __moduleExports };`;
if (defaultIsModuleExports) {
code += `\nexport { ${exportsName} as default };`;
} else {
code += `export default /*@__PURE__*/getDefaultExportFromCjs(${exportsName});`;
}
return {
code,
syntheticNamedExports: '__moduleExports',
meta: { commonjs: { isCommonJS: false } }
};
return getEsImportProxy(unwrapId(id, ES_IMPORT_SUFFIX), defaultIsModuleExports);
}

if (id === DYNAMIC_MODULES_ID) {
return {
code: getDynamicRequireModules(
isDynamicRequireModulesEnabled,
dynamicRequireModuleSet,
commonDir,
ignoreDynamicRequires
),
meta: { commonjs: { isCommonJS: false } }
};
return getDynamicRequireModules(
isDynamicRequireModulesEnabled,
dynamicRequireModuleSet,
commonDir,
ignoreDynamicRequires
);
}

if (isWrappedId(id, PROXY_SUFFIX)) {
Expand Down
23 changes: 22 additions & 1 deletion packages/commonjs/src/proxies.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { HELPERS_ID } from './helpers';
import { getName } from './utils';
import { capitalize, getName } from './utils';

export function getUnknownRequireProxy(id, requireReturnsDefault) {
if (requireReturnsDefault === true || id.endsWith('.json')) {
Expand Down Expand Up @@ -46,3 +46,24 @@ export async function getStaticRequireProxy(
}
return `export { default } from ${JSON.stringify(id)};`;
}

export function getEsImportProxy(id, defaultIsModuleExports) {
const name = getName(id);
const exportsName = `${name}Exports`;
const requireModule = `require${capitalize(name)}`;
let code =
`import { getDefaultExportFromCjs } from "${HELPERS_ID}";\n` +
`import { __require as ${requireModule} } from ${JSON.stringify(id)};\n` +
`var ${exportsName} = ${requireModule}();\n` +
`export { ${exportsName} as __moduleExports };`;
if (defaultIsModuleExports) {
code += `\nexport { ${exportsName} as default };`;
} else {
code += `export default /*@__PURE__*/getDefaultExportFromCjs(${exportsName});`;
}
return {
code,
syntheticNamedExports: '__moduleExports',
meta: { commonjs: { isCommonJS: false } }
};
}
54 changes: 54 additions & 0 deletions packages/commonjs/src/resolve-require-sources.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { EXTERNAL_SUFFIX, IS_WRAPPED_COMMONJS, PROXY_SUFFIX, wrapId } from './helpers';
import { resolveExtensions } from './resolve-id';

// TODO Lukas auto-detect circular dependencies
// * only return once all dependencies have been analyzed
// * wait for this.load dependencies unless they already have an entry in knownCjsModuleTypes to avoid deadlocks
// as those have already started being processed
// * only analyze cycles if we do not have an explicit config
export function getResolveRequireSourcesAndGetMeta(extensions) {
const knownCjsModuleTypes = Object.create(null);
return (rollupContext) => (id, isParentCommonJS, sources) => {
knownCjsModuleTypes[id] = isParentCommonJS;
return Promise.all(
sources.map(async (source) => {
// Never analyze or proxy internal modules
if (source.startsWith('\0')) {
return { source, id: source, isCommonJS: false };
}
const resolved =
(await rollupContext.resolve(source, id, {
skipSelf: true,
custom: {
'node-resolve': { isRequire: true }
}
})) || resolveExtensions(source, id, extensions);
if (!resolved) {
return { source, id: wrapId(source, EXTERNAL_SUFFIX), isCommonJS: false };
}
if (resolved.external) {
return { source, id: wrapId(resolved.id, EXTERNAL_SUFFIX), isCommonJS: false };
}
if (resolved.id in knownCjsModuleTypes) {
return {
source,
id:
knownCjsModuleTypes[resolved.id] === IS_WRAPPED_COMMONJS
? resolved.id
: wrapId(resolved.id, PROXY_SUFFIX),
isCommonJS: knownCjsModuleTypes[resolved.id]
};
}
const {
meta: { commonjs: commonjsMeta }
} = await rollupContext.load(resolved);
const isCommonJS = commonjsMeta && commonjsMeta.isCommonJS;
return {
source,
id: isCommonJS === IS_WRAPPED_COMMONJS ? resolved.id : wrapId(resolved.id, PROXY_SUFFIX),
isCommonJS
};
})
);
};
}
Loading

0 comments on commit cb65386

Please sign in to comment.