Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(commonjs): reconstruct real es module from __esModule marker #537

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f5b4fc5
feat(commonjs): reconstruct real es module from __esModule marker
LarsDenBakker Aug 12, 2020
f24824a
fix(commonjs): handle module.exports reassignment
LarsDenBakker Aug 16, 2020
ed323e8
fix(commonjs): preserve namespace default export fallback
LarsDenBakker Aug 16, 2020
d3b851d
fix(commonjs): mark redefined module.exports as CJS
LarsDenBakker Aug 16, 2020
7d2b278
chore(commonjs): fix tests
LarsDenBakker Aug 16, 2020
6a9a712
Make tests actually throw when there is an error in the code and skip…
lukastaegert Oct 20, 2020
853e004
chore(commonjs): Improve how AST branche are skipped
lukastaegert Oct 21, 2020
5e4596f
fix(commonjs): Fix Rollup peer dependency version check
lukastaegert Oct 23, 2020
fcc9c08
refactor(commonjs): Use a switch statement for top level analysis
lukastaegert Oct 23, 2020
8b46475
refactor(commonjs): Restructure transform slightly
lukastaegert Oct 23, 2020
3d08ced
refactor(commonjs): Extract helpers
lukastaegert Nov 2, 2020
10d082c
refactor(commonjs): Extract helpers
lukastaegert Nov 3, 2020
63563e7
refactor(commonjs): Move __esModule detection logic entirely within C…
lukastaegert Nov 4, 2020
ed93cf7
refactor(commonjs): Add __moduleExports back to compiled modules
lukastaegert Nov 5, 2020
9b707f6
refactor(commonjs): Move more parts into switch statement
lukastaegert Nov 6, 2020
9b05c1e
refactor(commonjs): Completely refactor require handling
lukastaegert Nov 9, 2020
2a6f913
fix(commonjs): Handle nested and multiple __esModule definitions
lukastaegert Nov 10, 2020
347b304
fix(commonjs): Handle shadowed imports for multiple requires
lukastaegert Nov 10, 2020
1725bc9
fix(commonjs): Handle double assignments to exports
lukastaegert Nov 11, 2020
bc6a9d9
chore(commonjs): Further cleanup
lukastaegert Nov 11, 2020
ba618c9
refactor(commonjs): extract logic to rewrite imports
lukastaegert Nov 11, 2020
f8dbc89
feat(commonjs): only add interop if necessary
lukastaegert Nov 12, 2020
a61f2b0
refactor(commonjs): Do not add require helper unless used
lukastaegert Nov 12, 2020
ef6f1ea
refactor(commonjs): Inline dynamic require handling into loop
lukastaegert Nov 13, 2020
45fa0b8
refactor(commonjs): Extract import logic
lukastaegert Nov 13, 2020
d15bc4a
refactor(commonjs): Extract export generation
lukastaegert Nov 13, 2020
b4389bb
refactor(commonjs): Avoid empty lines before wrapped code
lukastaegert Nov 14, 2020
09374a0
Do not remove leading comments in files
lukastaegert Nov 19, 2020
800667b
refactor(commonjs): Remove unused code
lukastaegert Nov 19, 2020
627f801
fix(commonjs): Improve error message for unsupported dynamic requires
lukastaegert Nov 24, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 48 additions & 0 deletions packages/commonjs/src/analyze-top-level-statements.js
@@ -0,0 +1,48 @@
/* eslint-disable no-underscore-dangle */

import { tryParse } from './parse';

export default function analyzeTopLevelStatements(parse, code, id) {
const ast = tryParse(parse, code, id);

let isEsModule = false;
let hasDefaultExport = false;
let hasNamedExports = false;

for (const node of ast.body) {
switch (node.type) {
case 'ExportDefaultDeclaration':
isEsModule = true;
hasDefaultExport = true;
break;
case 'ExportNamedDeclaration':
isEsModule = true;
if (node.declaration) {
hasNamedExports = true;
} else {
for (const specifier of node.specifiers) {
if (specifier.exported.name === 'default') {
hasDefaultExport = true;
} else {
hasNamedExports = true;
}
}
}
break;
case 'ExportAllDeclaration':
isEsModule = true;
if (node.exported && node.exported.name === 'default') {
hasDefaultExport = true;
} else {
hasNamedExports = true;
}
break;
case 'ImportDeclaration':
isEsModule = true;
break;
default:
}
}

return { isEsModule, hasDefaultExport, hasNamedExports, ast };
}
125 changes: 72 additions & 53 deletions packages/commonjs/src/ast-utils.js
@@ -1,4 +1,3 @@
/* eslint-disable no-undefined */
export { default as isReference } from 'is-reference';

const operators = {
Expand All @@ -17,34 +16,30 @@ const operators = {
'||': (x) => isTruthy(x.left) || isTruthy(x.right)
};

const extractors = {
Identifier(names, node) {
names.push(node.name);
},

ObjectPattern(names, node) {
node.properties.forEach((prop) => {
getExtractor(prop.value.type)(names, prop.value);
});
},

ArrayPattern(names, node) {
node.elements.forEach((element) => {
if (!element) return;
getExtractor(element.type)(names, element);
});
},

RestElement(names, node) {
getExtractor(node.argument.type)(names, node.argument);
},

AssignmentPattern(names, node) {
getExtractor(node.left.type)(names, node.left);
}
};
function not(value) {
return value === null ? value : !value;
}

function equals(a, b, strict) {
if (a.type !== b.type) return null;
// eslint-disable-next-line eqeqeq
if (a.type === 'Literal') return strict ? a.value === b.value : a.value == b.value;
return null;
}

export function isTruthy(node) {
if (!node) return false;
if (node.type === 'Literal') return !!node.value;
if (node.type === 'ParenthesizedExpression') return isTruthy(node.expression);
if (node.operator in operators) return operators[node.operator](node);
return null;
}

export function flatten(node) {
export function isFalsy(node) {
return not(isTruthy(node));
}

export function getKeypath(node) {
const parts = [];

while (node.type === 'MemberExpression') {
Expand All @@ -63,36 +58,60 @@ export function flatten(node) {
return { name, keypath: parts.join('.') };
}

export function extractNames(node) {
const names = [];
extractors[node.type](names, node);
return names;
}
export const KEY_COMPILED_ESM = '__esModule';

function getExtractor(type) {
const extractor = extractors[type];
if (!extractor) throw new SyntaxError(`${type} pattern not supported.`);
return extractor;
export function isDefineCompiledEsm(node) {
const definedProperty =
getDefinePropertyCallName(node, 'exports') || getDefinePropertyCallName(node, 'module.exports');
if (definedProperty && definedProperty.key === KEY_COMPILED_ESM) {
return isTruthy(definedProperty.value);
}
return false;
}

export function isTruthy(node) {
if (node.type === 'Literal') return !!node.value;
if (node.type === 'ParenthesizedExpression') return isTruthy(node.expression);
if (node.operator in operators) return operators[node.operator](node);
return undefined;
}
function getDefinePropertyCallName(node, targetName) {
const targetNames = targetName.split('.');

const {
callee: { object, property }
} = node;
if (!object || object.type !== 'Identifier' || object.name !== 'Object') return;
if (!property || property.type !== 'Identifier' || property.name !== 'defineProperty') return;
if (node.arguments.length !== 3) return;

const [target, key, value] = node.arguments;
if (targetNames.length === 1) {
if (target.type !== 'Identifier' || target.name !== targetNames[0]) {
return;
}
}

export function isFalsy(node) {
return not(isTruthy(node));
}
if (targetNames.length === 2) {
if (
target.type !== 'MemberExpression' ||
target.object.name !== targetNames[0] ||
target.property.name !== targetNames[1]
) {
return;
}
}

function not(value) {
return value === undefined ? value : !value;
if (value.type !== 'ObjectExpression' || !value.properties) return;

const valueProperty = value.properties.find((p) => p.key && p.key.name === 'value');
if (!valueProperty || !valueProperty.value) return;

// eslint-disable-next-line consistent-return
return { key: key.value, value: valueProperty.value };
}

function equals(a, b, strict) {
if (a.type !== b.type) return undefined;
// eslint-disable-next-line eqeqeq
if (a.type === 'Literal') return strict ? a.value === b.value : a.value == b.value;
return undefined;
export function isLocallyShadowed(name, scope) {
while (scope.parent) {
if (scope.declarations[name]) {
return true;
}
// eslint-disable-next-line no-param-reassign
scope = scope.parent;
}
return false;
}
9 changes: 2 additions & 7 deletions packages/commonjs/src/dynamic-packages-manager.js
@@ -1,13 +1,8 @@
import { existsSync, readFileSync } from 'fs';
import { join } from 'path';

import {
DYNAMIC_PACKAGES_ID,
DYNAMIC_REGISTER_PREFIX,
getVirtualPathForDynamicRequirePath,
HELPERS_ID
} from './helpers';
import { normalizePathSlashes } from './transform';
import { DYNAMIC_PACKAGES_ID, DYNAMIC_REGISTER_PREFIX, HELPERS_ID } from './helpers';
import { getVirtualPathForDynamicRequirePath, normalizePathSlashes } from './utils';

export function getDynamicPackagesModule(dynamicRequireModuleDirPaths, commonDir) {
let code = `const commonjsRegister = require('${HELPERS_ID}?commonjsRegister');`;
Expand Down
2 changes: 1 addition & 1 deletion packages/commonjs/src/dynamic-require-paths.js
Expand Up @@ -4,7 +4,7 @@ import { resolve } from 'path';

import glob from 'glob';

import { normalizePathSlashes } from './transform';
import { normalizePathSlashes } from './utils';

export default function getDynamicRequirePaths(patterns) {
const dynamicRequireModuleSet = new Set();
Expand Down
97 changes: 97 additions & 0 deletions packages/commonjs/src/generate-exports.js
@@ -0,0 +1,97 @@
export function wrapCode(magicString, uses, moduleName, HELPERS_NAME, virtualDynamicRequirePath) {
const args = `module${uses.exports ? ', exports' : ''}`;

magicString
.trim()
.prepend(`var ${moduleName} = ${HELPERS_NAME}.createCommonjsModule(function (${args}) {\n`)
.append(
`\n}${virtualDynamicRequirePath ? `, ${JSON.stringify(virtualDynamicRequirePath)}` : ''});`
);
}

export function rewriteExportsAndGetExportsBlock(
magicString,
moduleName,
wrapped,
topLevelModuleExportsAssignments,
topLevelExportsAssignmentsByName,
defineCompiledEsmExpressions,
deconflict,
isRestorableCompiledEsm,
code,
uses,
HELPERS_NAME
) {
const namedExportDeclarations = [`export { ${moduleName} as __moduleExports };`];
const moduleExportsPropertyAssignments = [];
let deconflictedDefaultExportName;

if (!wrapped) {
let hasModuleExportsAssignment = false;
const namedExportProperties = [];

// Collect and rewrite module.exports assignments
for (const { left } of topLevelModuleExportsAssignments) {
hasModuleExportsAssignment = true;
magicString.overwrite(left.start, left.end, `var ${moduleName}`);
}

// Collect and rewrite named exports
for (const [exportName, node] of topLevelExportsAssignmentsByName) {
const deconflicted = deconflict(exportName);
magicString.overwrite(node.start, node.left.end, `var ${deconflicted}`);

if (exportName === 'default') {
deconflictedDefaultExportName = deconflicted;
} else {
namedExportDeclarations.push(
exportName === deconflicted
? `export { ${exportName} };`
: `export { ${deconflicted} as ${exportName} };`
);
}

if (hasModuleExportsAssignment) {
moduleExportsPropertyAssignments.push(`${moduleName}.${exportName} = ${deconflicted};`);
} else {
namedExportProperties.push(`\t${exportName}: ${deconflicted}`);
}
}

// Regenerate CommonJS namespace
if (!hasModuleExportsAssignment) {
const moduleExports = `{\n${namedExportProperties.join(',\n')}\n}`;
magicString
.trim()
.append(
`\n\nvar ${moduleName} = ${
isRestorableCompiledEsm
? `/*#__PURE__*/Object.defineProperty(${moduleExports}, '__esModule', {value: true})`
: moduleExports
};`
);
}
}

// Generate default export
const defaultExport = [];
if (isRestorableCompiledEsm) {
defaultExport.push(`export default ${deconflictedDefaultExportName || moduleName};`);
} else if (
(wrapped || deconflictedDefaultExportName) &&
(defineCompiledEsmExpressions.length > 0 || code.indexOf('__esModule') >= 0)
) {
// eslint-disable-next-line no-param-reassign
uses.commonjsHelpers = true;
defaultExport.push(
`export default /*@__PURE__*/${HELPERS_NAME}.getDefaultExportFromCjs(${moduleName});`
);
} else {
defaultExport.push(`export default ${moduleName};`);
}

return `\n\n${defaultExport
.concat(namedExportDeclarations)
.concat(moduleExportsPropertyAssignments)
.join('\n')}`;
}