@parcel/scope-hoisting
Advanced tools
Comparing version 2.0.0-nightly.522 to 2.0.0-nightly.524
@@ -8,12 +8,2 @@ "use strict"; | ||
function _parser() { | ||
const data = require("@babel/parser"); | ||
_parser = function () { | ||
return data; | ||
}; | ||
return data; | ||
} | ||
function _path() { | ||
@@ -89,2 +79,12 @@ const data = _interopRequireDefault(require("path")); | ||
function _template() { | ||
const data = _interopRequireDefault(require("@babel/template")); | ||
_template = function () { | ||
return data; | ||
}; | ||
return data; | ||
} | ||
function _utils2() { | ||
@@ -106,9 +106,9 @@ const data = require("./utils"); | ||
const HELPERS_PATH = _path().default.join(__dirname, 'helpers.js'); | ||
const PRELUDE_PATH = _path().default.join(__dirname, 'prelude.js'); | ||
const HELPERS = parse(_fs().default.readFileSync(_path().default.join(__dirname, 'helpers.js'), 'utf8'), HELPERS_PATH); | ||
const PRELUDE = (0, _utils2().parse)(_fs().default.readFileSync(_path().default.join(__dirname, 'prelude.js'), 'utf8'), PRELUDE_PATH); | ||
const PRELUDE_PATH = _path().default.join(__dirname, 'prelude.js'); | ||
const DEFAULT_INTEROP_TEMPLATE = _template().default.statement('var NAME = $parcel$interopDefault(MODULE);'); | ||
const PRELUDE = parse(_fs().default.readFileSync(_path().default.join(__dirname, 'prelude.js'), 'utf8'), PRELUDE_PATH); | ||
const ESMODULE_TEMPLATE = _template().default.statement(`$parcel$defineInteropFlag(EXPORTS);`); | ||
@@ -143,14 +143,10 @@ // eslint-disable-next-line no-unused-vars | ||
case 'asset': | ||
queue.add(() => processAsset(options, bundle, node.value, wrappedAssets)); | ||
queue.add(() => processAsset(options, bundleGraph, bundle, node.value, wrappedAssets)); | ||
} | ||
}); | ||
let outputs = new Map(await queue.run()); | ||
let result = [...HELPERS]; // Add a declaration for parcelRequire that points to the unique global name. | ||
let result = []; | ||
if (bundle.env.outputFormat === 'global') { | ||
result.push(...parse(`var parcelRequire = $parcel$global.${parcelRequireName};`, PRELUDE_PATH)); | ||
} | ||
if ((0, _utils2().needsPrelude)(bundle, bundleGraph)) { | ||
result.push(...parse(`var parcelRequireName = "${parcelRequireName}";`, PRELUDE_PATH), ...PRELUDE); | ||
result.push(...(0, _utils2().parse)(`var parcelRequireName = "${parcelRequireName}";`, PRELUDE_PATH), ...PRELUDE); | ||
} // Note: for each asset, the order of `$parcel$require` calls and the corresponding | ||
@@ -216,3 +212,3 @@ // `asset.getDependencies()` must be the same! | ||
async function processAsset(options, bundle, asset, wrappedAssets) { | ||
async function processAsset(options, bundleGraph, bundle, asset, wrappedAssets) { | ||
let statements; | ||
@@ -225,3 +221,25 @@ | ||
let code = await asset.getCode(); | ||
statements = parse(code, (0, _utils().relativeUrl)(options.projectRoot, asset.filePath)); | ||
statements = (0, _utils2().parse)(code, (0, _utils().relativeUrl)(options.projectRoot, asset.filePath)); | ||
} // If this is a CommonJS module, add an interop default declaration if there are any ES6 default | ||
// import dependencies in the same bundle for that module. | ||
if ((0, _utils2().needsDefaultInterop)(bundleGraph, bundle, asset)) { | ||
statements.push(DEFAULT_INTEROP_TEMPLATE({ | ||
NAME: (0, _utils2().getIdentifier)(asset, '$interop$default'), | ||
MODULE: t().identifier((0, _utils2().assertString)(asset.meta.exportsIdentifier)) | ||
})); | ||
} // If this is an ES6 module with a default export, and it's required by a | ||
// CommonJS module in the same bundle, then add an __esModule flag for interop with babel. | ||
if (asset.meta.isES6Module && asset.symbols.hasExportSymbol('default')) { | ||
let deps = bundleGraph.getIncomingDependencies(asset); | ||
let hasCJSDep = deps.some(dep => dep.meta.isCommonJS && !dep.isAsync && dep.symbols.hasExportSymbol('*')); | ||
if (hasCJSDep) { | ||
statements.push(ESMODULE_TEMPLATE({ | ||
EXPORTS: t().identifier((0, _utils2().assertString)(asset.meta.exportsIdentifier)) | ||
})); | ||
} | ||
} | ||
@@ -240,11 +258,2 @@ | ||
function parse(code, sourceFilename) { | ||
let ast = (0, _parser().parse)(code, { | ||
sourceFilename, | ||
allowReturnOutsideFunction: true, | ||
plugins: ['dynamicImport'] | ||
}); | ||
return ast.program.body; | ||
} | ||
function shouldSkipAsset(bundleGraph, bundle, asset) { | ||
@@ -305,10 +314,6 @@ return asset.sideEffects === false && bundleGraph.getUsedSymbols(asset).size == 0 && !bundleGraph.isAssetReferencedByDependant(bundle, asset); | ||
const WRAP_MODULE_VISITOR = { | ||
VariableDeclaration(path, { | ||
VariableDeclaration(node, { | ||
decls | ||
}) { | ||
// $FlowFixMe | ||
let { | ||
node, | ||
parent | ||
} = path; | ||
}, ancestors) { | ||
let parent = ancestors[ancestors.length - 2]; | ||
let isParentForX = (0, t().isForInStatement)(parent, { | ||
@@ -323,3 +328,3 @@ left: node | ||
if (node.kind === 'var' || (0, t().isProgram)(path.parent)) { | ||
if (node.kind === 'var' || (0, t().isProgram)(parent)) { | ||
let replace = []; | ||
@@ -359,26 +364,22 @@ | ||
path.replaceWith(n); | ||
return n; | ||
} else { | ||
path.remove(); | ||
return _babylonWalk().REMOVE; | ||
} | ||
} | ||
path.skip(); | ||
return _babylonWalk().SKIP; | ||
}, | ||
FunctionDeclaration(path, { | ||
FunctionDeclaration(node, { | ||
fns | ||
}) { | ||
fns.push(path.node); | ||
path.remove(); | ||
fns.push(node); | ||
return _babylonWalk().REMOVE; | ||
}, | ||
ClassDeclaration(path, { | ||
ClassDeclaration(node, { | ||
decls | ||
}) { | ||
// $FlowFixMe | ||
let { | ||
node | ||
} = path; | ||
let { | ||
id | ||
@@ -390,8 +391,7 @@ } = node; | ||
decls.push(t().variableDeclarator(id)); | ||
path.replaceWith(t().expressionStatement(t().assignmentExpression('=', id, t().toExpression(node)))); | ||
path.skip(); | ||
return t().expressionStatement(t().assignmentExpression('=', id, t().toExpression(node))); | ||
}, | ||
'Function|Class'(path) { | ||
path.skip(); | ||
'Function|Class'() { | ||
return _babylonWalk().SKIP; | ||
}, | ||
@@ -409,3 +409,4 @@ | ||
let program = t().program(statements); | ||
(0, _babylonWalk().traverse)(program, WRAP_MODULE_VISITOR, { | ||
(0, _babylonWalk().traverse2)(program, WRAP_MODULE_VISITOR, { | ||
asset, | ||
decls, | ||
@@ -412,0 +413,0 @@ fns |
@@ -8,3 +8,4 @@ "use strict"; | ||
exports.generateExternalImport = generateExternalImport; | ||
exports.generateExports = generateExports; | ||
exports.generateBundleExports = generateBundleExports; | ||
exports.generateMainExport = generateMainExport; | ||
@@ -41,22 +42,2 @@ function t() { | ||
function _nullthrows() { | ||
const data = _interopRequireDefault(require("nullthrows")); | ||
_nullthrows = function () { | ||
return data; | ||
}; | ||
return data; | ||
} | ||
function _path() { | ||
const data = require("path"); | ||
_path = function () { | ||
return data; | ||
}; | ||
return data; | ||
} | ||
function _utils() { | ||
@@ -72,12 +53,2 @@ const data = require("@parcel/utils"); | ||
function _renamer() { | ||
const data = _interopRequireDefault(require("../renamer")); | ||
_renamer = function () { | ||
return data; | ||
}; | ||
return data; | ||
} | ||
function _utils2() { | ||
@@ -156,6 +127,6 @@ const data = require("../utils"); | ||
function generateBundleImports(from, { | ||
function generateBundleImports(bundleGraph, from, { | ||
bundle, | ||
assets | ||
}, path) { | ||
}, scope) { | ||
let specifiers = [...assets].map(asset => { | ||
@@ -170,27 +141,10 @@ let id = (0, _utils2().getName)(asset, 'init'); | ||
if (specifiers.length > 0) { | ||
let decls = path.unshiftContainer('body', generateDestructuringAssignment(bundle.env, specifiers, expression, path.scope)); | ||
for (let decl of decls) { | ||
// every VariableDeclaration emitted by generateDestructuringAssignment has only | ||
// one VariableDeclarator | ||
let next = decl.get('declarations.0'); | ||
for (let [name] of Object.entries(decl.getBindingIdentifierPaths())) { | ||
if (path.scope.hasOwnBinding(name)) { | ||
(0, _utils2().removeReplaceBinding)(path.scope, name, next); | ||
} else { | ||
path.scope.registerDeclaration(decl); | ||
} | ||
} | ||
} | ||
return generateDestructuringAssignment(bundle.env, specifiers, expression, scope); | ||
} else { | ||
path.unshiftContainer('body', [t().expressionStatement(expression)]); | ||
return [t().expressionStatement(expression)]; | ||
} | ||
} | ||
function generateExternalImport(bundle, external, path) { | ||
function generateExternalImport(bundle, external, scope) { | ||
let { | ||
scope | ||
} = path; | ||
let { | ||
source, | ||
@@ -238,2 +192,3 @@ specifiers, | ||
}); | ||
scope.add('$parcel$exportWildcard'); | ||
} | ||
@@ -254,2 +209,3 @@ | ||
})); | ||
scope.add('$parcel$interopDefault'); | ||
} | ||
@@ -269,2 +225,3 @@ | ||
})); | ||
scope.add('$parcel$interopDefault'); | ||
} else if (specifiersWildcard) { | ||
@@ -280,2 +237,3 @@ let require = REQUIRE_TEMPLATE({ | ||
}); | ||
scope.add('$parcel$exportWildcard'); | ||
} | ||
@@ -297,47 +255,6 @@ | ||
let decls = path.unshiftContainer('body', statements); | ||
for (let decl of decls) { | ||
if ((0, t().isVariableDeclaration)(decl.node)) { | ||
let declarator = decl.get('declarations.0'); | ||
for (let [name] of Object.entries(decl.getBindingIdentifierPaths())) { | ||
if (path.scope.hasOwnBinding(name)) { | ||
(0, _utils2().removeReplaceBinding)(path.scope, name, declarator); | ||
} else { | ||
// $FlowFixMe | ||
path.scope.registerBinding(decl.node.kind, declarator); | ||
} | ||
} | ||
if ((0, t().isCallExpression)(declarator.node.init)) { | ||
if (!(0, t().isIdentifier)(declarator.node.init.callee, { | ||
name: 'require' | ||
})) { | ||
// $parcel$exportWildcard or $parcel$interopDefault | ||
let id = declarator.get('init.callee'); | ||
let { | ||
name | ||
} = id.node; | ||
(0, _nullthrows().default)(path.scope.getBinding(name)).reference(id); | ||
for (let arg of declarator.get('init.arguments')) { | ||
if ((0, t().isIdentifier)(arg.node)) { | ||
// $FlowFixMe | ||
(0, _nullthrows().default)(path.scope.getBinding(arg.node.name)).reference(arg); | ||
} | ||
} | ||
} | ||
} else if ((0, t().isIdentifier)(declarator.node.init)) { | ||
// a temporary variable for the transpiled destructuring assigment | ||
(0, _nullthrows().default)(path.scope.getBinding(declarator.node.init.name)).reference(declarator.get('init')); | ||
} else if ((0, t().isMemberExpression)(declarator.node.init) && (0, t().isIdentifier)(declarator.node.init.object)) { | ||
// (a temporary variable for the transpiled destructuring assigment).symbol | ||
(0, _nullthrows().default)(path.scope.getBinding(declarator.node.init.object.name)).reference(declarator.get('init.object')); | ||
} | ||
} | ||
} | ||
return statements; | ||
} | ||
function generateExports(bundleGraph, bundle, referencedAssets, path, replacements, options, maybeReplaceIdentifier) { | ||
function generateBundleExports(bundleGraph, bundle, referencedAssets, scope, reexports) { | ||
let exported = new Set(); | ||
@@ -355,104 +272,59 @@ let statements = []; | ||
let entry = bundle.getMainEntry(); | ||
for (let exp of reexports) { | ||
statements.push(EXPORT_TEMPLATE({ | ||
NAME: t().identifier(exp.exportAs), | ||
IDENTIFIER: t().identifier(exp.local) | ||
})); | ||
} | ||
if (entry) { | ||
if (entry.meta.isCommonJS) { | ||
let exportsId = (0, _utils2().assertString)(entry.meta.exportsIdentifier); | ||
let binding = path.scope.getBinding(exportsId); | ||
return statements; | ||
} | ||
if (binding) { | ||
// If the exports object is constant, then we can just remove it and rename the | ||
// references to the builtin CommonJS exports object. Otherwise, assign to module.exports. | ||
(0, _assert().default)((0, t().isVariableDeclarator)(binding.path.node)); | ||
let init = binding.path.node.init; | ||
let isEmptyObject = init && (0, t().isObjectExpression)(init) && init.properties.length === 0; | ||
function generateMainExport(node, exported) { | ||
let statements = [node]; | ||
if (binding.constant && isEmptyObject) { | ||
for (let path of binding.referencePaths) { | ||
// This is never a ExportNamedDeclaration | ||
(0, _assert().default)((0, t().isIdentifier)(path.node)); | ||
path.node.name = 'exports'; | ||
} | ||
for (let { | ||
exportAs, | ||
local | ||
} of exported) { | ||
if (exportAs === '*') { | ||
// Replace assignments to the `exports` object with `module.exports` | ||
if ((0, t().isExpressionStatement)(node)) { | ||
let expression = node.expression; | ||
(0, _assert().default)((0, t().isAssignmentExpression)(expression)); | ||
expression.left = t().memberExpression(t().identifier('module'), t().identifier('exports')); | ||
continue; | ||
} // Remove the `exports` declaration if set to an empty object. | ||
// Otherwise, assign to `module.exports`. | ||
binding.path.remove(); | ||
exported.add('exports'); | ||
} else { | ||
exported.add(exportsId); | ||
statements.push(MODULE_EXPORTS_TEMPLATE({ | ||
IDENTIFIER: t().identifier(exportsId) | ||
})); | ||
} | ||
} | ||
} else { | ||
for (let { | ||
exportAs, | ||
exportSymbol, | ||
symbol, | ||
asset, | ||
loc | ||
} of (0, _nullthrows().default)(bundleGraph.getExportedSymbols(entry, bundle))) { | ||
if (symbol === false) {// skipped | ||
} else if (symbol === null) { | ||
// TODO `meta.exportsIdentifier[exportSymbol]` should be exported | ||
let relativePath = (0, _path().relative)(options.projectRoot, asset.filePath); | ||
throw (0, _utils2().getThrowableDiagnosticForNode)(`${relativePath} couldn't be statically analyzed when importing '${exportSymbol}'`, entry.filePath, loc); | ||
} else if (symbol != null && symbol !== false) { | ||
let hasReplacement = replacements.get(symbol); | ||
symbol = hasReplacement !== null && hasReplacement !== void 0 ? hasReplacement : symbol; // If there is an existing binding with the exported name (e.g. an import), | ||
// rename it so we can use the name for the export instead. | ||
if (path.scope.hasBinding(exportAs, true) && exportAs !== symbol) { | ||
(0, _renamer().default)(path.scope, exportAs, path.scope.generateUid(exportAs)); | ||
} | ||
let isExports = false; | ||
let binding = path.scope.getBinding(symbol); | ||
if ((0, t().isVariableDeclaration)(node)) { | ||
let decl = node.declarations.find(decl => (0, t().isIdentifier)(decl.id) && decl.id.name === local); | ||
isExports = decl && decl.init && (0, t().isObjectExpression)(decl.init) && decl.init.properties.length === 0; | ||
} | ||
if (binding) { | ||
if (!hasReplacement) { | ||
let id = // We cannot use the name if it's already used as global (e.g. `Map`). | ||
!t().isValidIdentifier(exportAs) || path.scope.hasGlobal(exportAs) ? path.scope.generateUid(exportAs) : exportAs; // rename only once, avoid having to update `replacements` transitively | ||
if (!isExports) { | ||
statements.push(MODULE_EXPORTS_TEMPLATE({ | ||
IDENTIFIER: t().identifier(local) | ||
})); | ||
} else { | ||
statements.shift(); | ||
} | ||
} else { | ||
// Exports other than the default export are live bindings. | ||
// Only insert an assignment to module.exports for non-default exports. | ||
if ((0, t().isExpressionStatement)(node) && exportAs === 'default') { | ||
continue; | ||
} | ||
(0, _renamer().default)(path.scope, symbol, id); | ||
replacements.set(symbol, id); | ||
symbol = id; | ||
} | ||
let [stmt] = binding.path.getStatementParent().insertAfter(EXPORT_TEMPLATE({ | ||
NAME: t().identifier(exportAs), | ||
IDENTIFIER: t().identifier(symbol) | ||
})); | ||
binding.reference(stmt.get('expression.right')); // Exports other than the default export are live bindings. Insert an assignment | ||
// after each constant violation so this remains true. | ||
if (exportAs !== 'default') { | ||
for (let path of binding.constantViolations) { | ||
let [stmt] = path.insertAfter(EXPORT_TEMPLATE({ | ||
NAME: t().identifier(exportAs), | ||
IDENTIFIER: t().identifier(symbol) | ||
})); | ||
binding.reference(stmt.get('expression.right')); | ||
} | ||
} | ||
} else { | ||
// `getExportedSymbols` can return `$id$import$foo` symbols so that cross-bundle imports | ||
// are resolved correctly. There is no binding in that case. | ||
let [decl] = path.pushContainer('body', [EXPORT_TEMPLATE({ | ||
NAME: t().identifier(exportAs), | ||
IDENTIFIER: t().identifier(symbol) | ||
})]); | ||
maybeReplaceIdentifier(decl.get('expression.right')); | ||
} | ||
} | ||
} | ||
statements.push(EXPORT_TEMPLATE({ | ||
NAME: t().identifier(exportAs), | ||
IDENTIFIER: t().identifier(local) | ||
})); | ||
} | ||
} | ||
let stmts = path.pushContainer('body', statements); | ||
for (let stmt of stmts) { | ||
let id = stmt.get('expression.right'); | ||
(0, _nullthrows().default)(path.scope.getBinding(id.node.name)).reference(id); | ||
} | ||
return exported; | ||
return statements; | ||
} |
@@ -8,3 +8,4 @@ "use strict"; | ||
exports.generateExternalImport = generateExternalImport; | ||
exports.generateExports = generateExports; | ||
exports.generateBundleExports = generateBundleExports; | ||
exports.generateMainExport = generateMainExport; | ||
@@ -21,32 +22,2 @@ function t() { | ||
function _assert() { | ||
const data = _interopRequireDefault(require("assert")); | ||
_assert = function () { | ||
return data; | ||
}; | ||
return data; | ||
} | ||
function _nullthrows() { | ||
const data = _interopRequireDefault(require("nullthrows")); | ||
_nullthrows = function () { | ||
return data; | ||
}; | ||
return data; | ||
} | ||
function _path() { | ||
const data = require("path"); | ||
_path = function () { | ||
return data; | ||
}; | ||
return data; | ||
} | ||
function _utils() { | ||
@@ -62,12 +33,2 @@ const data = require("@parcel/utils"); | ||
function _renamer() { | ||
const data = _interopRequireDefault(require("../renamer")); | ||
_renamer = function () { | ||
return data; | ||
}; | ||
return data; | ||
} | ||
function _utils2() { | ||
@@ -83,4 +44,2 @@ const data = require("../utils"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } | ||
@@ -90,6 +49,6 @@ | ||
function generateBundleImports(from, { | ||
function generateBundleImports(bundleGraph, from, { | ||
bundle, | ||
assets | ||
}, path) { | ||
}) { | ||
let specifiers = [...assets].map(asset => { | ||
@@ -99,10 +58,6 @@ let id = (0, _utils2().getName)(asset, 'init'); | ||
}); | ||
let [decl] = path.unshiftContainer('body', [t().importDeclaration(specifiers, t().stringLiteral((0, _utils().relativeBundlePath)(from, bundle)))]); | ||
for (let spec of decl.get('specifiers')) { | ||
(0, _utils2().removeReplaceBinding)(path.scope, spec.node.local.name, spec, 'module'); | ||
} | ||
return [t().importDeclaration(specifiers, t().stringLiteral((0, _utils().relativeBundlePath)(from, bundle)))]; | ||
} | ||
function generateExternalImport(bundle, external, path) { | ||
function generateExternalImport(bundle, external) { | ||
let { | ||
@@ -145,259 +100,62 @@ source, | ||
let decls = path.unshiftContainer('body', statements); | ||
return statements; | ||
} | ||
for (let decl of decls) { | ||
let specifiers = decl.get('specifiers'); | ||
function generateBundleExports(bundleGraph, bundle, referencedAssets, scope, reexports) { | ||
let statements = []; | ||
for (let specifier of specifiers) { | ||
for (let name of Object.keys(specifier.getBindingIdentifiers())) { | ||
(0, _utils2().removeReplaceBinding)(path.scope, name, specifier, 'module'); | ||
} | ||
} | ||
} | ||
} | ||
if (referencedAssets.size > 0 || reexports.size > 0) { | ||
statements.push(t().exportNamedDeclaration(null, [...referencedAssets].map(asset => { | ||
let name = (0, _utils2().getName)(asset, 'init'); | ||
return t().exportSpecifier(t().identifier(name), t().identifier(name)); | ||
}).concat([...reexports].map(exp => t().exportSpecifier(t().identifier(exp.local), t().identifier(exp.exportAs)))))); | ||
} // If the main entry is a CommonJS asset, export its `module.exports` property as the `default` export | ||
function generateExports(bundleGraph, bundle, referencedAssets, programPath, replacements, options, maybeReplaceIdentifier) { | ||
// maps the bundles's export symbols to the bindings | ||
let exportedIdentifiers = new Map(); // let exportedIdentifiersBailout = new Map<Symbol, [Asset, Symbol]>(); | ||
let entry = bundle.getMainEntry(); | ||
if (entry) { | ||
// Get all used symbols for this bundle (= entry + subgraph) | ||
let usedSymbols = new Set(); | ||
if ((entry === null || entry === void 0 ? void 0 : entry.meta.isCommonJS) === true) { | ||
statements.push(t().exportDefaultDeclaration(t().identifier((0, _utils2().assertString)(entry.meta.exportsIdentifier)))); | ||
} | ||
for (let d of bundleGraph.getIncomingDependencies(entry)) { | ||
let used = bundleGraph.getUsedSymbols(d); | ||
return statements; | ||
} | ||
if (d.symbols.isCleared || used.has('*')) { | ||
usedSymbols = null; | ||
break; | ||
} | ||
used.forEach(s => (0, _nullthrows().default)(usedSymbols).add(s)); | ||
} | ||
for (let { | ||
exportAs, | ||
exportSymbol, | ||
symbol, | ||
asset, | ||
loc | ||
} of (0, _nullthrows().default)(bundleGraph.getExportedSymbols(entry, bundle))) { | ||
if (usedSymbols && !usedSymbols.has(exportAs)) { | ||
// an unused symbol | ||
continue; | ||
} | ||
if (symbol === false) {// skipped | ||
} else if (symbol === null) { | ||
// TODO `asset.meta.exportsIdentifier[exportSymbol]` should be exported | ||
let relativePath = (0, _path().relative)(options.projectRoot, asset.filePath); | ||
throw (0, _utils2().getThrowableDiagnosticForNode)(`${relativePath} couldn't be statically analyzed when importing '${exportSymbol}'`, entry.filePath, loc); // exportedIdentifiersBailout.set(exportAs, [asset, exportSymbol]); | ||
} else { | ||
(0, _assert().default)(symbol != null); | ||
symbol = replacements.get(symbol) || symbol; // Map CommonJS module.exports assignments to default ESM exports for interop | ||
if (exportAs === '*') { | ||
exportAs = 'default'; | ||
} // If there is an existing binding with the exported name (e.g. an import), | ||
// rename it so we can use the name for the export instead. | ||
if (programPath.scope.hasBinding(exportAs, true) && exportAs !== symbol) { | ||
(0, _renamer().default)(programPath.scope, exportAs, programPath.scope.generateUid(exportAs)); | ||
} | ||
exportedIdentifiers.set(exportAs, symbol); | ||
} | ||
} | ||
function generateMainExport(node, exported) { | ||
if ((0, t().isExpressionStatement)(node)) { | ||
return [node]; | ||
} | ||
for (let asset of referencedAssets) { | ||
let exportsId = (0, _utils2().getName)(asset, 'init'); | ||
exportedIdentifiers.set(exportsId, exportsId); | ||
} | ||
let statements = []; | ||
let bindingIdentifiers = t().getBindingIdentifiers(node); | ||
let ids = Object.keys(bindingIdentifiers); // Export '*' (re-exported CJS exports object) as default | ||
let exported = new Set(); | ||
programPath.traverse({ | ||
Declaration(path) { | ||
if (path.isExportDeclaration() || path.parentPath.isExportDeclaration()) { | ||
return; | ||
} | ||
let defaultExport = exported.find(e => e.exportAs === 'default' || e.exportAs === '*'); | ||
let namedExports = exported.filter(e => e.exportAs !== 'default' && e.exportAs !== '*'); | ||
let { | ||
node | ||
} = path; | ||
let bindingIdentifiers = path.getBindingIdentifierPaths(false, true); | ||
let ids = Object.keys(bindingIdentifiers); | ||
if (exported.length === 1 && defaultExport && !(0, t().isVariableDeclaration)(node)) { | ||
// If there's only a default export, then export the declaration directly. | ||
// $FlowFixMe - we don't need to worry about type declarations here. | ||
statements.push(t().exportDefaultDeclaration(node)); | ||
} else if (namedExports.length === exported.length && namedExports.length === ids.length && namedExports.every(({ | ||
exportAs, | ||
local | ||
}) => exportAs === local)) { | ||
// If there's only named exports, all of the ids are exported, | ||
// and none of them are renamed, export the declaration directly. | ||
statements.push(t().exportNamedDeclaration(node, [])); | ||
} else { | ||
// Otherwise, add a default export and named export for the identifiers after the original declaration. | ||
statements.push(node); | ||
if (ids.length === 0) { | ||
return; | ||
} | ||
ids.sort(); | ||
let exportedIdentifiersFiltered = [...exportedIdentifiers.entries()].filter(([exportSymbol, symbol]) => exportSymbol !== 'default' && ids.includes(symbol)).sort(([, a], [, b]) => a > b ? -1 : a < b ? 1 : 0); | ||
let exportedSymbolsBindings = exportedIdentifiersFiltered.map(([, symbol]) => symbol); | ||
let exportedSymbols = exportedIdentifiersFiltered.map(([exportSymbol]) => exportSymbol); | ||
let defaultExport = exportedIdentifiers.get('default'); | ||
if (!ids.includes(defaultExport)) { | ||
defaultExport = null; | ||
} else { | ||
exportedIdentifiers.delete('default'); | ||
} // If all exports in the binding are named exports, export the entire declaration. | ||
// Also rename all of the identifiers to their exported name. | ||
if (exportedSymbols.every(s => !path.scope.hasGlobal(s)) && areArraysStrictlyEqual(ids, exportedSymbolsBindings) && !path.isImportDeclaration()) { | ||
let [decl] = path.replaceWith(t().exportNamedDeclaration(node, [])); | ||
for (let sym of exportedSymbols) { | ||
let id = (0, _nullthrows().default)(exportedIdentifiers.get(sym)); | ||
id = replacements.get(id) || id; | ||
(0, _nullthrows().default)(path.scope.getBinding(id)).reference(decl); | ||
(0, _renamer().default)(path.scope, id, sym); | ||
replacements.set(id, sym); | ||
exported.add(sym); | ||
} // If the default export is part of the declaration, add it as well | ||
if (defaultExport != null) { | ||
defaultExport = replacements.get(defaultExport) || defaultExport; | ||
let binding = path.scope.getBinding(defaultExport); | ||
let insertPath = path; | ||
if (binding && !binding.constant) { | ||
insertPath = binding.constantViolations[binding.constantViolations.length - 1]; | ||
} | ||
let [decl] = insertPath.insertAfter(t().exportDefaultDeclaration(t().identifier(defaultExport))); | ||
binding === null || binding === void 0 ? void 0 : binding.reference(decl); | ||
} // If there is only a default export, export the entire declaration. | ||
} else if (ids.length === 1 && defaultExport != null && !(0, t().isVariableDeclaration)(node) && !(0, t().isImportDeclaration)(node)) { | ||
(0, _assert().default)((0, t().isFunctionDeclaration)(node) || (0, t().isClassDeclaration)(node)); | ||
let binding = (0, _nullthrows().default)(path.scope.getBinding((0, _nullthrows().default)(node.id).name)); // We don't update the references in `node` itself (e.g. function body), because this statement | ||
// will never be removed and therefore the shaking doesn't need correct | ||
// information. All existing references in `node` are "dead" but will also never be removed. | ||
let [decl] = path.replaceWith(t().exportDefaultDeclaration(node)); | ||
binding.path = decl.get('declaration'); | ||
binding.reference(decl); // Otherwise, add export statements after for each identifier. | ||
} else { | ||
if (defaultExport != null) { | ||
defaultExport = replacements.get(defaultExport) || defaultExport; | ||
let binding = path.scope.getBinding(defaultExport); | ||
let insertPath = path; | ||
if (binding && !binding.constant) { | ||
insertPath = binding.constantViolations[binding.constantViolations.length - 1]; | ||
} | ||
let node = t().exportDefaultDeclaration(t().identifier(defaultExport)); | ||
let decl; | ||
if (insertPath.parentPath.isProgram()) { | ||
[decl] = insertPath.insertAfter(node); | ||
} else { | ||
[decl] = programPath.pushContainer('body', node); | ||
} | ||
binding === null || binding === void 0 ? void 0 : binding.reference(decl.get('declaration')); | ||
} | ||
if (exportedSymbols.length > 0) { | ||
let [decl] = path.insertAfter(t().exportNamedDeclaration(null, [])); | ||
for (let sym of exportedSymbols) { | ||
var _path$scope$getBindin; | ||
let id = (0, _nullthrows().default)(exportedIdentifiers.get(sym)); | ||
id = replacements.get(id) || id; | ||
let symLocal = path.scope.hasGlobal(sym) ? path.scope.generateUid(sym) : sym; | ||
(0, _renamer().default)(path.scope, id, symLocal); | ||
replacements.set(id, symLocal); | ||
exported.add(symLocal); | ||
let [spec] = decl.unshiftContainer('specifiers', [t().exportSpecifier(t().identifier(symLocal), t().identifier(sym))]); | ||
(_path$scope$getBindin = path.scope.getBinding(symLocal)) === null || _path$scope$getBindin === void 0 ? void 0 : _path$scope$getBindin.reference(spec.get('local')); | ||
} | ||
} | ||
} | ||
exportedSymbols.forEach(s => exportedIdentifiers.delete(s)); | ||
if (defaultExport) { | ||
statements.push(t().exportDefaultDeclaration(t().identifier(defaultExport.local))); | ||
} | ||
}); | ||
if (exportedIdentifiers.size > 0) { | ||
let declarations = []; | ||
let exportedIdentifiersSpecifiers = []; // `export { $id$init().foo as foo};` is not valid, so instead do: | ||
// ``` | ||
// let syntheticExport$foo = $id$init().foo; | ||
// export { syntheticExport$foo as foo}; | ||
// ``` | ||
for (let [exportAs, symbol] of exportedIdentifiers) { | ||
declarations.push(t().variableDeclarator(t().identifier('syntheticExport$' + exportAs), t().identifier(symbol))); | ||
exportedIdentifiersSpecifiers.push(t().exportSpecifier(t().identifier('syntheticExport$' + exportAs), t().identifier(exportAs))); | ||
if (namedExports.length > 0) { | ||
statements.push(t().exportNamedDeclaration(null, namedExports.map(e => t().exportSpecifier(t().identifier(e.local), t().identifier(e.exportAs))))); | ||
} | ||
} | ||
let [decl, exports] = programPath.pushContainer('body', [t().variableDeclaration('var', declarations), t().exportNamedDeclaration(null, exportedIdentifiersSpecifiers)]); | ||
(0, _assert().default)((0, t().isVariableDeclaration)(decl.node)); | ||
programPath.scope.registerDeclaration(decl); | ||
for (let d of decl.get('declarations')) { | ||
maybeReplaceIdentifier(d.get('init')); | ||
} | ||
(0, _assert().default)((0, t().isExportNamedDeclaration)(exports.node)); | ||
programPath.scope.registerDeclaration(exports); | ||
for (let e of exports.get('specifiers')) { | ||
(0, _nullthrows().default)(programPath.scope.getBinding(e.node.local.name)).reference(e.get('local')); | ||
} | ||
} // This would be needed if we want to export symbols from different bundles, | ||
// but it's currently not possible to actually trigger this. | ||
// | ||
// if (exportedIdentifiersBailout.size > 0) { | ||
// let declarations = []; | ||
// let exportedIdentifiersBailoutSpecifiers = []; | ||
// for (let [exportAs, [asset, exportSymbol]] of exportedIdentifiersBailout) { | ||
// invariant( | ||
// !programPath.scope.hasBinding( | ||
// getExportIdentifier(asset, exportSymbol).name, | ||
// ), | ||
// ); | ||
// invariant(programPath.scope.hasBinding(getName(asset, 'init'))); | ||
// declarations.push( | ||
// t.variableDeclarator( | ||
// getExportIdentifier(asset, exportSymbol), | ||
// t.memberExpression( | ||
// t.callExpression(t.identifier(getName(asset, 'init')), []), // it isn't in this bundle, TODO import if not already there | ||
// t.identifier(exportSymbol), | ||
// ), | ||
// ), | ||
// ); | ||
// exportedIdentifiersBailoutSpecifiers.push( | ||
// t.exportSpecifier( | ||
// getExportIdentifier(asset, exportSymbol), | ||
// t.identifier(exportAs), | ||
// ), | ||
// ); | ||
// } | ||
// programPath.pushContainer('body', [ | ||
// t.variableDeclaration('var', declarations), | ||
// t.exportNamedDeclaration(null, exportedIdentifiersBailoutSpecifiers), | ||
// ]); | ||
// programPath.scope.crawl(); | ||
// } | ||
return exported; | ||
} | ||
function areArraysStrictlyEqual(a, b) { | ||
return a.length === b.length && a.every(function (a_v, i) { | ||
return a_v === b[i]; | ||
}); | ||
return statements; | ||
} |
@@ -8,14 +8,5 @@ "use strict"; | ||
exports.generateExternalImport = generateExternalImport; | ||
exports.generateExports = generateExports; | ||
exports.generateBundleExports = generateBundleExports; | ||
exports.generateMainExport = generateMainExport; | ||
function _assert() { | ||
const data = _interopRequireDefault(require("assert")); | ||
_assert = function () { | ||
return data; | ||
}; | ||
return data; | ||
} | ||
function t() { | ||
@@ -51,12 +42,2 @@ const data = _interopRequireWildcard(require("@babel/types")); | ||
function _nullthrows() { | ||
const data = _interopRequireDefault(require("nullthrows")); | ||
_nullthrows = function () { | ||
return data; | ||
}; | ||
return data; | ||
} | ||
function _utils2() { | ||
@@ -72,2 +53,4 @@ const data = require("../utils"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } | ||
@@ -77,6 +60,4 @@ | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
const IMPORT_TEMPLATE = _template().default.statement('var NAME = parcelRequire(ASSET_ID);'); | ||
const IMPORT_TEMPLATE = _template().default.expression('parcelRequire(ASSET_ID)'); | ||
const EXPORT_TEMPLATE = _template().default.statement('parcelRequire.register(ASSET_ID, IDENTIFIER)'); | ||
@@ -88,6 +69,8 @@ | ||
function generateBundleImports(from, { | ||
const DEFAULT_INTEROP_TEMPLATE = _template().default.statement('var NAME = $parcel$interopDefault(MODULE);'); | ||
function generateBundleImports(bundleGraph, from, { | ||
bundle, | ||
assets | ||
}, path, bundleGraph) { | ||
}, scope) { | ||
let statements = []; | ||
@@ -101,13 +84,25 @@ | ||
path.unshiftContainer('body', statements); | ||
for (let asset of assets) { | ||
var _path$scope$getBindin; | ||
// `var ${id};` was inserted already, add RHS | ||
let res = (0, _nullthrows().default)(path.scope.getBinding((0, _utils2().getName)(asset, 'init'))).path.get('init').replaceWith(IMPORT_TEMPLATE({ | ||
statements.push(IMPORT_TEMPLATE({ | ||
NAME: (0, _utils2().getIdentifier)(asset, 'init'), | ||
ASSET_ID: t().stringLiteral(bundleGraph.getAssetPublicId(asset)) | ||
})); | ||
(_path$scope$getBindin = path.scope.getBinding('parcelRequire')) === null || _path$scope$getBindin === void 0 ? void 0 : _path$scope$getBindin.reference(res[0].get('callee')); | ||
scope.add('$parcel$global'); | ||
scope.add('parcelRequire'); | ||
if (asset.meta.isCommonJS) { | ||
let deps = bundleGraph.getIncomingDependencies(asset); | ||
let hasDefaultInterop = deps.some(dep => dep.symbols.hasExportSymbol('default') && from.hasDependency(dep)); | ||
if (hasDefaultInterop) { | ||
statements.push(DEFAULT_INTEROP_TEMPLATE({ | ||
NAME: (0, _utils2().getIdentifier)(asset, '$interop$default'), | ||
MODULE: t().callExpression((0, _utils2().getIdentifier)(asset, 'init'), []) | ||
})); | ||
scope.add('$parcel$interopDefault'); | ||
} | ||
} | ||
} | ||
return statements; | ||
} | ||
@@ -122,4 +117,3 @@ | ||
function generateExports(bundleGraph, bundle, referencedAssets, path) { | ||
let exported = new Set(); | ||
function generateBundleExports(bundleGraph, bundle, referencedAssets, scope) { | ||
let statements = []; | ||
@@ -129,3 +123,2 @@ | ||
let exportsId = (0, _utils2().getName)(asset, 'init'); | ||
exported.add(exportsId); | ||
statements.push(EXPORT_TEMPLATE({ | ||
@@ -135,2 +128,4 @@ ASSET_ID: t().stringLiteral(bundleGraph.getAssetPublicId(asset)), | ||
})); | ||
scope.add('$parcel$global'); | ||
scope.add('parcelRequire'); | ||
} | ||
@@ -141,4 +136,2 @@ | ||
if (entry && !referencedAssets.has(entry) && (!(0, _utils2().isEntry)(bundle, bundleGraph) || (0, _utils2().isReferenced)(bundle, bundleGraph))) { | ||
let exportsId = (0, _utils2().assertString)(entry.meta.exportsIdentifier); | ||
exported.add(exportsId); | ||
statements.push( // Export a function returning the exports, as other cases of global output | ||
@@ -150,29 +143,11 @@ // register init functions. | ||
})); | ||
scope.add('$parcel$global'); | ||
scope.add('parcelRequire'); | ||
} | ||
let decls = path.pushContainer('body', statements); | ||
return statements; | ||
} | ||
for (let decl of decls) { | ||
var _path$scope$getBindin2, _path$scope$getBindin3; | ||
let callee = decl.get('expression.callee'); | ||
if (callee.isMemberExpression()) { | ||
callee = callee.get('object'); | ||
} | ||
(_path$scope$getBindin2 = path.scope.getBinding(callee.node.name)) === null || _path$scope$getBindin2 === void 0 ? void 0 : _path$scope$getBindin2.reference(callee); | ||
let arg = decl.get('expression.arguments.1'); | ||
if (!arg.isIdentifier()) { | ||
// anonymous init function expression | ||
(0, _assert().default)(arg.isFunctionExpression()); | ||
arg = arg.get('body.body.0.argument'); | ||
} // $FlowFixMe | ||
(_path$scope$getBindin3 = path.scope.getBinding(arg.node.name)) === null || _path$scope$getBindin3 === void 0 ? void 0 : _path$scope$getBindin3.reference(arg); | ||
} | ||
return exported; | ||
function generateMainExport(node) { | ||
return [node]; | ||
} |
@@ -8,6 +8,6 @@ "use strict"; | ||
function _generator() { | ||
const data = _interopRequireDefault(require("@babel/generator")); | ||
function _babelAstUtils() { | ||
const data = require("@parcel/babel-ast-utils"); | ||
_generator = function () { | ||
_babelAstUtils = function () { | ||
return data; | ||
@@ -123,21 +123,13 @@ }; | ||
let { | ||
code, | ||
rawMappings | ||
} = (0, _generator().default)(ast, { | ||
content, | ||
map | ||
} = (0, _babelAstUtils().generateAST)({ | ||
ast, | ||
sourceMaps: !!bundle.env.sourceMap, | ||
minified: bundle.env.minify, | ||
comments: true // retain /*@__PURE__*/ comments for terser | ||
options | ||
}); | ||
let map = null; | ||
if (bundle.env.sourceMap && rawMappings != null) { | ||
map = new (_sourceMap().default)(options.projectRoot); | ||
map.addIndexedMappings(rawMappings); | ||
} | ||
return { | ||
contents: code, | ||
contents: content, | ||
map | ||
}; | ||
} |
392
lib/hoist.js
@@ -143,2 +143,3 @@ "use strict"; | ||
asset.meta.exportsIdentifier = (0, _utils().getName)(asset, 'exports'); | ||
asset.meta.staticExports = true; | ||
@@ -187,3 +188,3 @@ _traverse().default.cache.clearScope(); | ||
if (node.name === 'module' && (!(0, t().isMemberExpression)(parent) || parent.computed) && !((0, t().isUnaryExpression)(parent) && parent.operator === 'typeof') && !path.scope.hasBinding('module') && !path.scope.getData('shouldWrap')) { | ||
if (node.name === 'module' && !isStaticMemberExpression(parent) && !((0, t().isUnaryExpression)(parent) && parent.operator === 'typeof') && !path.scope.hasBinding('module') && !path.scope.getData('shouldWrap')) { | ||
asset.meta.isCommonJS = true; | ||
@@ -202,4 +203,4 @@ shouldWrap = true; | ||
left: node | ||
}) || (0, t().isMemberExpression)(parent) && ((0, t().isIdentifier)(parent.property) && !parent.computed || (0, t().isStringLiteral)(parent.property)) || path.scope.getData('shouldWrap'))) { | ||
asset.meta.resolveExportsBailedOut = true; // The namespace object is used in the asset itself | ||
}) || isStaticMemberExpression(parent) || path.scope.getData('shouldWrap'))) { | ||
asset.meta.staticExports = false; // The namespace object is used in the asset itself | ||
@@ -231,4 +232,4 @@ asset.addDependency({ | ||
left: node | ||
}) || (0, t().isMemberExpression)(parent) && ((0, t().isIdentifier)(parent.property) && !parent.computed || (0, t().isStringLiteral)(parent.property)) || path.scope.getData('shouldWrap'))) { | ||
asset.meta.resolveExportsBailedOut = true; // The namespace object is used in the asset itself | ||
}) || isStaticMemberExpression(parent) || path.scope.getData('shouldWrap'))) { | ||
asset.meta.staticExports = false; // The namespace object is used in the asset itself | ||
@@ -257,2 +258,6 @@ asset.addDependency({ | ||
path.scope.setData('cjsExportsReassigned', false); | ||
if (shouldWrap) { | ||
asset.meta.staticExports = false; | ||
} | ||
}, | ||
@@ -290,3 +295,3 @@ | ||
if (scope.hasGlobal(exportsIdentifier.name) && !scope.hasBinding(exportsIdentifier.name)) { | ||
if (!scope.hasBinding(exportsIdentifier.name)) { | ||
scope.push({ | ||
@@ -299,8 +304,2 @@ id: exportsIdentifier, | ||
if (asset.meta.isCommonJS) { | ||
if (asset.meta.resolveExportsBailedOut) { | ||
for (let s of asset.symbols.exportSymbols()) { | ||
asset.symbols.delete(s); | ||
} | ||
} | ||
asset.symbols.set('*', exportsIdentifier.name); | ||
@@ -329,11 +328,19 @@ } | ||
if (t().matchesPattern(path.node, 'module.exports')) { | ||
let exportsId = getExportsIdentifier(asset, path.scope); | ||
path.replaceWith(exportsId); | ||
asset.symbols.set('*', exportsId.name, (0, _babelAstUtils().convertBabelLoc)(path.node.loc)); | ||
// Replace module.exports.foo with exported identifier if possible, | ||
// and add a self-referencing dependency so we know the symbol is used. | ||
let selfReference = addSelfReference(path, asset); | ||
if (!path.scope.hasBinding(exportsId.name)) { | ||
path.scope.getProgramParent().push({ | ||
id: exportsId, | ||
init: t().objectExpression([]) | ||
}); | ||
if (selfReference) { | ||
path.parentPath.replaceWith(selfReference); | ||
} else { | ||
let exportsId = getExportsIdentifier(asset, path.scope); | ||
asset.symbols.set('*', exportsId.name, (0, _babelAstUtils().convertBabelLoc)(path.node.loc)); | ||
path.replaceWith(exportsId); | ||
if (!path.scope.hasBinding(exportsId.name)) { | ||
path.scope.getProgramParent().push({ | ||
id: t().clone(exportsId), | ||
init: t().objectExpression([]) | ||
}); | ||
} | ||
} | ||
@@ -353,4 +360,17 @@ } else if (t().matchesPattern(path.node, 'module.id')) { | ||
if (path.node.name === 'exports' && !path.scope.hasBinding('exports') && !path.scope.getData('shouldWrap')) { | ||
path.replaceWith(getCJSExportsIdentifier(asset, path.scope)); | ||
asset.meta.isCommonJS = true; | ||
asset.meta.isCommonJS = true; // Mark if exports is accessed non-statically. | ||
if (!isStaticMemberExpression(path.parent)) { | ||
asset.meta.staticExports = false; | ||
} // Replace exports.foo with exported identifier if possible, | ||
// and add a self-referencing dependency so we know the symbol is used. | ||
let selfReference = addSelfReference(path, asset); | ||
if (selfReference) { | ||
path.parentPath.replaceWith(selfReference); | ||
} else { | ||
path.replaceWith(getCJSExportsIdentifier(asset, path.scope)); | ||
} | ||
} | ||
@@ -377,7 +397,20 @@ | ||
asset.meta.isCommonJS = true; // Mark if exports is accessed non-statically. | ||
if (!isStaticMemberExpression(path.parent)) { | ||
asset.meta.staticExports = false; | ||
} | ||
if (asset.meta.isES6Module) { | ||
path.replaceWith(t().identifier('undefined')); | ||
} else { | ||
path.replaceWith(getExportsIdentifier(asset, path.scope)); | ||
asset.meta.isCommonJS = true; | ||
// Replace this.foo with exported identifier if possible, | ||
// and add a self-referencing dependency so we know the symbol is used. | ||
let selfReference = addSelfReference(path, asset); | ||
if (selfReference) { | ||
path.parentPath.replaceWith(selfReference); | ||
} else { | ||
path.replaceWith(getExportsIdentifier(asset, path.scope)); | ||
} | ||
} | ||
@@ -425,2 +458,3 @@ } | ||
asset.meta.isCommonJS = true; | ||
asset.meta.staticExports = false; | ||
} // If we can statically evaluate the name of a CommonJS export, create an ES6-style export for it. | ||
@@ -443,10 +477,4 @@ // This allows us to remove the CommonJS export object completely in many cases. | ||
if (!scope.hasBinding(identifier.name)) { | ||
// These have a special meaning, we'll have to fallback from the '*' symbol. | ||
// '*' will always be registered into the symbols at the end. | ||
if (name !== 'default' && name !== '*') { | ||
asset.symbols.set(name, identifier.name, (0, _babelAstUtils().convertBabelLoc)(path.node.loc)); | ||
} // If in the program scope, create a variable declaration and initialize with the exported value. | ||
// If in the program scope, create a variable declaration and initialize with the exported value. | ||
// Otherwise, declare the variable in the program scope, and assign to it here. | ||
if (path.scope === scope) { | ||
@@ -460,5 +488,20 @@ let [decl] = path.insertBefore(t().variableDeclaration('var', [t().variableDeclarator(t().clone(identifier), right)])); | ||
path.insertBefore(t().expressionStatement(t().assignmentExpression('=', t().clone(identifier), right))); | ||
} // These have a special meaning, we'll have to fallback from the '*' symbol. | ||
// '*' will always be registered into the symbols at the end. | ||
if ((name !== 'default' || asset.symbols.hasExportSymbol('__esModule')) && name !== '*') { | ||
asset.symbols.set(name, identifier.name, (0, _babelAstUtils().convertBabelLoc)(path.node.loc), { | ||
isPure: isPure(scope.getBinding(identifier.name)) | ||
}); | ||
} | ||
} else { | ||
var _asset$symbols$get; | ||
path.insertBefore(t().expressionStatement(t().assignmentExpression('=', t().clone(identifier), right))); | ||
let meta = (_asset$symbols$get = asset.symbols.get(name)) === null || _asset$symbols$get === void 0 ? void 0 : _asset$symbols$get.meta; | ||
if (meta != null) { | ||
meta.isPure = false; | ||
} | ||
} | ||
@@ -497,7 +540,2 @@ | ||
return; | ||
} | ||
if (!dep.isAsync) { | ||
// the dependencies visitor replaces import() with require() | ||
asset.meta.isCommonJS = true; | ||
} // If this require call does not occur in the top-level, e.g. in a function | ||
@@ -510,14 +548,22 @@ // or inside an if statement, or if it might potentially happen conditionally, | ||
dep.meta.shouldWrap = true; | ||
} // Try to statically analyze a dynamic import() call | ||
} // Generate a variable name based on the current asset id and the module name to require. | ||
// This will be replaced by the final variable name of the resolved asset in the packager. | ||
let replacement = REQUIRE_CALL_TEMPLATE({ | ||
ID: t().stringLiteral(asset.id), | ||
SOURCE: t().stringLiteral(arg.value) | ||
}); | ||
replacement.loc = path.node.loc; | ||
let memberAccesses; | ||
let properties; | ||
let propertyScope; | ||
let replacePath; | ||
let binding; | ||
let { | ||
parent | ||
} = path; // Try to statically analyze a dynamic import() call | ||
if (dep.isAsync) { | ||
let properties; | ||
let binding; | ||
let { | ||
parent | ||
} = path; | ||
let { | ||
parent: grandparent | ||
@@ -563,36 +609,103 @@ } = path.parentPath; | ||
} | ||
} else if (isStaticMemberExpression(parent, { | ||
object: path.node | ||
})) { | ||
var _parent$property$name; | ||
if (properties != null && properties.every(p => (0, t().isObjectProperty)(p) && (0, t().isIdentifier)(p.key))) { | ||
// take symbols listed when destructuring | ||
memberAccesses = properties.map(p => { | ||
(0, _assert().default)((0, t().isObjectProperty)(p)); | ||
(0, _assert().default)((0, t().isIdentifier)(p.key)); | ||
return { | ||
name: p.key.name, | ||
loc: (0, _babelAstUtils().convertBabelLoc)(p.loc) | ||
}; | ||
}); | ||
} else if (!path.scope.getData('shouldWrap') && // eval is evil | ||
binding != null && binding.constant && binding.referencePaths.every(({ | ||
parent, | ||
node | ||
}) => (0, t().isMemberExpression)(parent, { | ||
object: node | ||
}) && (!parent.computed && (0, t().isIdentifier)(parent.property) || (0, t().isStringLiteral)(parent.property)))) { | ||
// properties of member expressions if all of them are static | ||
memberAccesses = binding.referencePaths.map(({ | ||
parent | ||
}) => { | ||
var _parent$property$name; | ||
// e.g. require('foo').bar | ||
// $FlowFixMe | ||
let name = (_parent$property$name = parent.property.name) !== null && _parent$property$name !== void 0 ? _parent$property$name : parent.property.value; | ||
memberAccesses = [{ | ||
name, | ||
loc: (0, _babelAstUtils().convertBabelLoc)(parent.loc) | ||
}]; // If in an assignment expression, replace with a sequence expression | ||
// so that the $parcel$require is still in the correct position. | ||
// Otherwise, add a third argument to the $parcel$require call set to | ||
// the identifier to replace it with. This will be replaced in the linker. | ||
(0, _assert().default)((0, t().isMemberExpression)(parent)); | ||
return { | ||
// $FlowFixMe[prop-missing] | ||
name: (_parent$property$name = parent.property.name) !== null && _parent$property$name !== void 0 ? _parent$property$name : parent.property.value, | ||
loc: (0, _babelAstUtils().convertBabelLoc)(parent.loc) | ||
}; | ||
}); | ||
if ((0, t().isAssignmentExpression)(path.parentPath.parent, { | ||
left: parent | ||
})) { | ||
let assignment = t().cloneNode(path.parentPath.parent); | ||
assignment.left = t().identifier((0, _utils().getName)(asset, 'importAsync', dep.id, name)); | ||
path.parentPath.parentPath.replaceWith(t().sequenceExpression([replacement, assignment])); | ||
replacement = null; | ||
} else { | ||
replacement.arguments.push(t().identifier((0, _utils().getName)(asset, 'importAsync', dep.id, name))); | ||
replacePath = path.parentPath; | ||
} | ||
} else if ((0, t().isVariableDeclarator)(parent, { | ||
init: path.node | ||
})) { | ||
if ((0, t().isObjectPattern)(parent.id)) { | ||
// let { x: y } = require("./b.js"); | ||
properties = parent.id.properties; | ||
propertyScope = path.parentPath.parentPath.scope; | ||
} else if ((0, t().isIdentifier)(parent.id)) { | ||
// let ns = require("./b.js"); | ||
binding = path.parentPath.parentPath.scope.getBinding(parent.id.name); | ||
} | ||
replacePath = path.parentPath; | ||
} else if ( // ({ x: y } = require("./b.js")); | ||
(0, t().isAssignmentExpression)(parent, { | ||
right: path.node | ||
}) && (0, t().isObjectPattern)(parent.left) && isUnusedValue(path.parentPath)) { | ||
properties = parent.left.properties; | ||
propertyScope = path.parentPath.scope; | ||
replacePath = path.parentPath; | ||
} | ||
if (properties != null && properties.length > 0 && properties.every(p => (0, t().isObjectProperty)(p) && (0, t().isIdentifier)(p.key))) { | ||
// take symbols listed when destructuring | ||
memberAccesses = properties.map(p => { | ||
(0, _assert().default)((0, t().isObjectProperty)(p)); | ||
(0, _assert().default)((0, t().isIdentifier)(p.key)); | ||
if (!dep.isAsync) { | ||
let name = p.key.name; | ||
let binding = propertyScope.getBinding(name); | ||
if (binding) { | ||
for (let ref of binding.referencePaths) { | ||
ref.replaceWith(t().identifier((0, _utils().getName)(asset, 'importAsync', dep.id, name))); | ||
} | ||
} | ||
} | ||
return { | ||
name: p.key.name, | ||
loc: (0, _babelAstUtils().convertBabelLoc)(p.loc) | ||
}; | ||
}); | ||
} else if (!path.scope.getData('shouldWrap') && // eval is evil | ||
binding != null && binding.constant && binding.referencePaths.length > 0 && binding.referencePaths.every(({ | ||
parent, | ||
node | ||
}) => isStaticMemberExpression(parent, { | ||
object: node | ||
}))) { | ||
// properties of member expressions if all of them are static | ||
memberAccesses = binding.referencePaths.map(({ | ||
parentPath, | ||
parent | ||
}) => { | ||
var _parent$property$name2; | ||
(0, _assert().default)((0, t().isMemberExpression)(parent)); // $FlowFixMe | ||
let name = (_parent$property$name2 = parent.property.name) !== null && _parent$property$name2 !== void 0 ? _parent$property$name2 : parent.property.value; | ||
if (!dep.isAsync) { | ||
parentPath.replaceWith(t().identifier((0, _utils().getName)(asset, 'importAsync', dep.id, name))); | ||
} | ||
return { | ||
// $FlowFixMe[prop-missing] | ||
name, | ||
loc: (0, _babelAstUtils().convertBabelLoc)(parent.loc) | ||
}; | ||
}); | ||
} | ||
dep.symbols.ensure(); | ||
@@ -608,16 +721,35 @@ | ||
} | ||
} else { | ||
} else if (!isUnusedValue(path)) { | ||
// non-async and async fallback: everything | ||
dep.symbols.set('*', (0, _utils().getName)(asset, 'require', source), (0, _babelAstUtils().convertBabelLoc)(path.node.loc)); // Mark the dependency as CJS so that we keep the $id$exports var in the linker. | ||
dep.meta.isCommonJS = true; | ||
dep.symbols.set('*', (0, _utils().getName)(asset, 'require', source), (0, _babelAstUtils().convertBabelLoc)(path.node.loc)); | ||
} // Generate a variable name based on the current asset id and the module name to require. | ||
// This will be replaced by the final variable name of the resolved asset in the packager. | ||
} | ||
if (memberAccesses != null && replacePath && replacement) { | ||
// Can't replace a variable declarator with a function call. | ||
// Need to replace the whole declaration. | ||
if ((0, t().isVariableDeclarator)(replacePath.node)) { | ||
let declaration = replacePath.parent; | ||
(0, _assert().default)((0, t().isVariableDeclaration)(declaration)); // If there is only one declarator, it's safe to replace the whole declaration. | ||
// Otherwise, split into multiple declarations so we can replace just one | ||
// with an expression statement containing the $parcel$require call. | ||
let replacement = REQUIRE_CALL_TEMPLATE({ | ||
ID: t().stringLiteral(asset.id), | ||
SOURCE: t().stringLiteral(arg.value) | ||
}); | ||
replacement.loc = path.node.loc; | ||
path.replaceWith(replacement); | ||
if (declaration.declarations.length === 1) { | ||
replacePath.parentPath.replaceWith(replacement); | ||
} else { | ||
let declIndex = declaration.declarations.indexOf(replacePath.node); | ||
replacePath.parentPath.insertBefore(t().variableDeclaration(declaration.kind, declaration.declarations.slice(0, declIndex))); | ||
replacePath.parentPath.insertBefore(t().expressionStatement(replacement)); | ||
for (let i = declIndex; i >= 0; i--) { | ||
replacePath.parentPath.get(`declarations.${i}`).remove(); | ||
} | ||
} | ||
} else { | ||
replacePath.replaceWith(replacement); | ||
} | ||
} else if (replacement) { | ||
path.replaceWith(replacement); | ||
} | ||
} else if (t().matchesPattern(callee, 'require.resolve')) { | ||
@@ -635,5 +767,10 @@ let replacement = REQUIRE_RESOLVE_CALL_TEMPLATE({ | ||
let dep = asset.getDependencies().find(dep => dep.moduleSpecifier === path.node.source.value); | ||
if (dep) dep.symbols.ensure(); // For each specifier, rename the local variables to point to the imported name. | ||
if (dep) { | ||
dep.meta.isES6Module = true; | ||
dep.symbols.ensure(); | ||
} // For each specifier, rename the local variables to point to the imported name. | ||
// This will be replaced by the final variable name of the resolved asset in the packager. | ||
for (let specifier of path.node.specifiers) { | ||
@@ -660,9 +797,9 @@ let binding = (0, _nullthrows().default)(path.scope.getBinding(specifier.local.name)); // Ignore unused specifiers in node-modules, especially for when TS was poorly transpiled. | ||
if ((0, t().isIdentifier)(node) && (0, t().isMemberExpression)(parent, { | ||
if ((0, t().isIdentifier)(node) && isStaticMemberExpression(parent, { | ||
object: node | ||
}) && (!parent.computed && (0, t().isIdentifier)(parent.property) || (0, t().isStringLiteral)(parent.property))) { | ||
var _parent$property$name2, _dep$symbols$get; | ||
})) { | ||
var _parent$property$name3, _dep$symbols$get; | ||
let imported = // $FlowFixMe | ||
(_parent$property$name2 = parent.property.name) !== null && _parent$property$name2 !== void 0 ? _parent$property$name2 : parent.property.value; | ||
(_parent$property$name3 = parent.property.name) !== null && _parent$property$name3 !== void 0 ? _parent$property$name3 : parent.property.value; | ||
let id = (0, _utils().getIdentifier)(asset, 'import', specifier.local.name, imported); | ||
@@ -775,3 +912,6 @@ let existing = (_dep$symbols$get = dep.symbols.get(imported)) === null || _dep$symbols$get === void 0 ? void 0 : _dep$symbols$get.local; | ||
if (!asset.symbols.hasExportSymbol('default')) { | ||
asset.symbols.set('default', identifier.name, (0, _babelAstUtils().convertBabelLoc)(loc)); | ||
let binding = path.scope.getBinding(identifier.name); | ||
asset.symbols.set('default', identifier.name, (0, _babelAstUtils().convertBabelLoc)(loc), { | ||
isPure: isPure(binding) | ||
}); | ||
} | ||
@@ -789,4 +929,8 @@ }, | ||
let dep = asset.getDependencies().find(dep => dep.moduleSpecifier === source.value); | ||
if (dep) dep.symbols.ensure(); | ||
if (dep) { | ||
dep.meta.isES6Module = true; | ||
dep.symbols.ensure(); | ||
} | ||
for (let specifier of (0, _nullthrows().default)(specifiers)) { | ||
@@ -832,4 +976,2 @@ let exported = specifier.exported; | ||
} else if (declaration) { | ||
path.replaceWith(declaration); | ||
if ((0, t().isIdentifier)(declaration.id)) { | ||
@@ -844,2 +986,4 @@ addExport(asset, path, declaration.id, declaration.id); | ||
} | ||
path.replaceWith(declaration); | ||
} else { | ||
@@ -860,2 +1004,3 @@ for (let specifier of specifiers) { | ||
if (dep) { | ||
dep.meta.isES6Module = true; | ||
dep.symbols.ensure(); | ||
@@ -889,2 +1034,23 @@ dep.symbols.set('*', '*', (0, _babelAstUtils().convertBabelLoc)(path.node.loc), true); | ||
function isPure(binding) { | ||
if (!binding || !binding.constant) { | ||
return false; | ||
} | ||
let references = binding.referencePaths.filter(reference => !reference.isExportDeclaration()); | ||
if (references.length > 0) { | ||
return false; | ||
} | ||
let path = binding.path; | ||
if ((0, t().isVariableDeclarator)(path.node) && (0, t().isIdentifier)(path.node.id)) { | ||
let init = path.get('init'); | ||
return init.isPure() || init.isIdentifier() || init.isThisExpression(); | ||
} | ||
return path.isPure(); | ||
} | ||
function addImport(asset, path) { | ||
@@ -929,3 +1095,6 @@ // Replace with a $parcel$require call so we know where to insert side effects. | ||
if (!asset.symbols.hasExportSymbol(exported.name)) { | ||
asset.symbols.set(exported.name, identifier.name, (0, _babelAstUtils().convertBabelLoc)(exported.loc)); | ||
let binding = scope.getBinding(local.name); | ||
asset.symbols.set(exported.name, identifier.name, (0, _babelAstUtils().convertBabelLoc)(exported.loc), { | ||
isPure: isPure(binding) | ||
}); | ||
} | ||
@@ -999,2 +1168,43 @@ | ||
} | ||
} | ||
function isUnusedValue(path) { | ||
let { | ||
parent | ||
} = path; | ||
return (0, t().isExpressionStatement)(parent) || (0, t().isSequenceExpression)(parent) && (Array.isArray(path.container) && path.key !== path.container.length - 1 || isUnusedValue(path.parentPath)); | ||
} | ||
function addSelfReference(path, asset) { | ||
// If referencing a property on this/exports/module.exports, create a self-referencing dependency | ||
// to track that the symbol is used, and replace the member expression with. | ||
if (isStaticMemberExpression(path.parent, { | ||
object: path.node | ||
}) && !(0, t().isAssignmentExpression)(path.parentPath.parent, { | ||
left: path.parent | ||
})) { | ||
var _path$parent$property; | ||
// $FlowFixMe | ||
let name = (_path$parent$property = path.parent.property.name) !== null && _path$parent$property !== void 0 ? _path$parent$property : path.parent.property.value; // Do not create a self-reference for the `default` symbol unless we have seen an __esModule flag. | ||
if (name === 'default' && !asset.symbols.hasExportSymbol('__esModule')) { | ||
return; | ||
} | ||
let local = (0, _utils().getExportIdentifier)(asset, name); | ||
asset.addDependency({ | ||
moduleSpecifier: `./${(0, _path().basename)(asset.filePath)}`, | ||
symbols: new Map([[name, { | ||
local: local.name, | ||
isWeak: false, | ||
loc: (0, _babelAstUtils().convertBabelLoc)(path.node.loc) | ||
}]]) | ||
}); | ||
return local; | ||
} | ||
} | ||
function isStaticMemberExpression(node, opts) { | ||
return (0, t().isMemberExpression)(node, opts) && ((0, t().isIdentifier)(node.property) && !node.computed || (0, t().isStringLiteral)(node.property)); | ||
} |
@@ -24,8 +24,2 @@ "use strict"; | ||
}); | ||
Object.defineProperty(exports, "shake", { | ||
enumerable: true, | ||
get: function () { | ||
return _shake().default; | ||
} | ||
}); | ||
Object.defineProperty(exports, "generate", { | ||
@@ -68,12 +62,2 @@ enumerable: true, | ||
function _shake() { | ||
const data = _interopRequireDefault(require("./shake")); | ||
_shake = function () { | ||
return data; | ||
}; | ||
return data; | ||
} | ||
function _generate() { | ||
@@ -87,4 +71,2 @@ const data = require("./generate"); | ||
return data; | ||
} | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
} |
798
lib/link.js
@@ -58,6 +58,6 @@ "use strict"; | ||
function _babelAstUtils() { | ||
const data = require("@parcel/babel-ast-utils"); | ||
function _babylonWalk() { | ||
const data = require("@parcel/babylon-walk"); | ||
_babelAstUtils = function () { | ||
_babylonWalk = function () { | ||
return data; | ||
@@ -69,6 +69,6 @@ }; | ||
function _traverse() { | ||
const data = _interopRequireDefault(require("@babel/traverse")); | ||
function _babelAstUtils() { | ||
const data = require("@parcel/babel-ast-utils"); | ||
_traverse = function () { | ||
_babelAstUtils = function () { | ||
return data; | ||
@@ -80,6 +80,6 @@ }; | ||
function _shake() { | ||
const data = _interopRequireDefault(require("./shake")); | ||
function _globals() { | ||
const data = _interopRequireDefault(require("globals")); | ||
_shake = function () { | ||
_globals = function () { | ||
return data; | ||
@@ -117,6 +117,2 @@ }; | ||
const ESMODULE_TEMPLATE = _template().default.statement(`$parcel$defineInteropFlag(EXPORTS);`); | ||
const DEFAULT_INTEROP_TEMPLATE = _template().default.statement('var NAME = $parcel$interopDefault(MODULE);'); | ||
const THROW_TEMPLATE = _template().default.statement('$parcel$missingModule(MODULE);'); | ||
@@ -130,2 +126,14 @@ | ||
const PARCEL_REQUIRE_TEMPLATE = _template().default.statement(`var parcelRequire = $parcel$global.PARCEL_REQUIRE_NAME`); | ||
const BUILTINS = Object.keys(_globals().default.builtin); | ||
const GLOBALS_BY_CONTEXT = { | ||
browser: new Set([...BUILTINS, ...Object.keys(_globals().default.browser)]), | ||
'web-worker': new Set([...BUILTINS, ...Object.keys(_globals().default.worker)]), | ||
'service-worker': new Set([...BUILTINS, ...Object.keys(_globals().default.serviceworker)]), | ||
node: new Set([...BUILTINS, ...Object.keys(_globals().default.node)]), | ||
'electron-main': new Set([...BUILTINS, ...Object.keys(_globals().default.node)]), | ||
'electron-renderer': new Set([...BUILTINS, ...Object.keys(_globals().default.node), ...Object.keys(_globals().default.browser)]) | ||
}; | ||
function link({ | ||
@@ -136,3 +144,4 @@ bundle, | ||
options, | ||
wrappedAssets | ||
wrappedAssets, | ||
parcelRequireName | ||
}) { | ||
@@ -143,7 +152,11 @@ let format = _index().default[bundle.env.outputFormat]; | ||
let imports = new Map(); | ||
let exports = new Map(); | ||
let assets = new Map(); | ||
let exportsMap = new Map(); | ||
let scope = new (_babylonWalk().Scope)('program'); | ||
let globalNames = GLOBALS_BY_CONTEXT[bundle.env.context]; | ||
let helpers = (0, _utils().getHelpers)(); | ||
let importedFiles = new Map(); | ||
let referencedAssets = new Set(); // return {ast, referencedAssets}; | ||
// If building a library, the target is actually another bundler rather | ||
let referencedAssets = new Set(); | ||
let reexports = new Set(); // If building a library, the target is actually another bundler rather | ||
// than the final output that could be loaded in a browser. So, loader | ||
@@ -185,2 +198,8 @@ // runtimes are excluded, and instead we add imports into the entry bundle | ||
for (let [symbol, { | ||
local | ||
}] of asset.symbols) { | ||
exports.set(local, [asset, symbol]); | ||
} | ||
if (bundleGraph.isAssetReferencedByDependant(bundle, asset)) { | ||
@@ -190,4 +209,66 @@ referencedAssets.add(asset); | ||
}); | ||
let entry = bundle.getMainEntry(); | ||
let exportedSymbols = new Map(); | ||
if (entry) { | ||
if (entry.meta.isCommonJS) { | ||
if (bundle.env.outputFormat === 'commonjs') { | ||
exportedSymbols.set((0, _utils().assertString)(entry.meta.exportsIdentifier), [{ | ||
exportAs: '*', | ||
local: 'exports' | ||
}]); | ||
} | ||
} else { | ||
for (let { | ||
exportAs, | ||
exportSymbol, | ||
symbol, | ||
asset, | ||
loc | ||
} of bundleGraph.getExportedSymbols(entry)) { | ||
if (typeof symbol === 'string') { | ||
let symbols = exportedSymbols.get(symbol === '*' ? (0, _utils().assertString)(entry.meta.exportsIdentifier) : symbol); | ||
let local = exportAs; | ||
if (symbols) { | ||
local = symbols[0].local; | ||
} else { | ||
symbols = []; | ||
exportedSymbols.set(symbol, symbols); | ||
if (local === '*') { | ||
local = 'exports'; | ||
} else if (!t().isValidIdentifier(local) || globalNames.has(local)) { | ||
local = scope.generateUid(local); | ||
} else { | ||
scope.add(local); | ||
} | ||
} | ||
symbols.push({ | ||
exportAs, | ||
local | ||
}); | ||
} else if (symbol === null) { | ||
// TODO `meta.exportsIdentifier[exportSymbol]` should be exported | ||
let relativePath = (0, _path().relative)(options.projectRoot, asset.filePath); | ||
throw (0, _utils().getThrowableDiagnosticForNode)(`${relativePath} couldn't be statically analyzed when importing '${exportSymbol}'`, entry.filePath, loc); | ||
} else if (symbol !== false) { | ||
let relativePath = (0, _path().relative)(options.projectRoot, asset.filePath); | ||
throw (0, _utils().getThrowableDiagnosticForNode)(`${relativePath} does not export '${exportSymbol}'`, entry.filePath, loc); | ||
} | ||
} | ||
} | ||
} | ||
let resolveSymbolCache = new Map(); | ||
function resolveSymbol(inputAsset, inputSymbol, bundle) { | ||
let k = inputAsset.id + ':' + inputSymbol + ':' + bundle.id; | ||
let cached = resolveSymbolCache.get(k); | ||
if (cached) { | ||
return cached; | ||
} | ||
let { | ||
@@ -200,4 +281,4 @@ asset, | ||
if (asset.meta.resolveExportsBailedOut) { | ||
return { | ||
if (asset.meta.staticExports === false) { | ||
let res = { | ||
asset: asset, | ||
@@ -208,2 +289,4 @@ symbol: exportSymbol, | ||
}; | ||
resolveSymbolCache.set(k, res); | ||
return res; | ||
} | ||
@@ -215,3 +298,3 @@ | ||
// a deferred import | ||
return { | ||
let res = { | ||
asset: asset, | ||
@@ -222,2 +305,4 @@ symbol: exportSymbol, | ||
}; | ||
resolveSymbolCache.set(k, res); | ||
return res; | ||
} // If this is a wildcard import, resolve to the exports object. | ||
@@ -230,7 +315,7 @@ | ||
if (replacements && identifier && replacements.has(identifier)) { | ||
if (identifier && replacements.has(identifier)) { | ||
identifier = replacements.get(identifier); | ||
} | ||
return { | ||
let res = { | ||
asset: asset, | ||
@@ -241,8 +326,94 @@ symbol: exportSymbol, | ||
}; | ||
resolveSymbolCache.set(k, res); | ||
return res; | ||
} | ||
function maybeReplaceIdentifier(path) { | ||
let needsExportsIdentifierCache = new Map(); | ||
let bundleNeedsMainExportsIdentifier = bundle.env.outputFormat === 'global' && (!(0, _utils().isEntry)(bundle, bundleGraph) || (0, _utils().isReferenced)(bundle, bundleGraph)) || bundle.env.outputFormat === 'esmodule' && (entry === null || entry === void 0 ? void 0 : entry.meta.isCommonJS); | ||
function needsExportsIdentifier(name) { | ||
let asset = exportsMap.get(name); | ||
if (asset) { | ||
return needsExportsIdentifierForAsset(asset); | ||
} | ||
return true; | ||
} | ||
function needsExportsIdentifierForAsset(asset) { | ||
if (needsExportsIdentifierCache.has(asset)) { | ||
return needsExportsIdentifierCache.get(asset); | ||
} | ||
if (asset.meta.staticExports === false || wrappedAssets.has(asset.id) || referencedAssets.has(asset)) { | ||
needsExportsIdentifierCache.set(asset, true); | ||
return true; | ||
} | ||
let isEntry = asset === bundle.getMainEntry(); | ||
if (isEntry && bundleNeedsMainExportsIdentifier) { | ||
needsExportsIdentifierCache.set(asset, true); | ||
return true; | ||
} | ||
let deps = bundleGraph.getIncomingDependencies(asset); | ||
let usedSymbols = bundleGraph.getUsedSymbols(asset); | ||
if (usedSymbols.has('*') && (!isEntry || asset.meta.isCommonJS)) { | ||
needsExportsIdentifierCache.set(asset, true); | ||
return true; | ||
} | ||
let res = deps.some(dep => // Internalized async dependencies need the exports object for Promise.resolve($id$exports) | ||
dep.isAsync && bundle.hasDependency(dep) || // If there's a dependency on the namespace, and the parent asset's exports object is used, | ||
// we need to keep the exports object for $parcel$exportWildcard. | ||
!isEntry && dep.symbols.hasExportSymbol('*') && needsExportsIdentifierForAsset((0, _nullthrows().default)(bundleGraph.getAssetWithDependency(dep))) || // If the asset is CommonJS and there's an ES6 dependency on `default`, we need the | ||
// exports identifier to call $parcel$interopDefault. | ||
asset.meta.isCommonJS && dep.meta.isES6Module && dep.symbols.hasExportSymbol('default') || // If the asset is an ES6 module with a default export, and there's a CommonJS dependency | ||
// on it, we need the exports identifier to call $parcel$defineInteropFlag. | ||
asset.meta.isES6Module && asset.symbols.hasExportSymbol('default') && dep.meta.isCommonJS && !dep.isAsync && dep.symbols.hasExportSymbol('*') || // If one of the symbols imported by the dependency doesn't resolve, then we need the | ||
// exports identifier to fall back to. | ||
[...dep.symbols].some(([symbol]) => !resolveSymbol(asset, symbol, bundle).identifier)); | ||
needsExportsIdentifierCache.set(asset, res); | ||
return res; | ||
} | ||
function needsDeclaration(name) { | ||
let exp = exports.get(name); | ||
if (exp) { | ||
var _asset$symbols$get, _asset$symbols$get$me; | ||
let [asset, local] = exp; | ||
if (asset === bundle.getMainEntry() && bundle.env.isLibrary) { | ||
return true; | ||
} | ||
if (asset.meta.staticExports === false) { | ||
return true; | ||
} | ||
let usedSymbols = bundleGraph.getUsedSymbols(asset); // If the asset is CommonJS, and "default" was used but not defined, this | ||
// will resolve to the $id$exports object, so we need to retain all symbols. | ||
if (asset.meta.isCommonJS && usedSymbols.has('default') && !asset.symbols.hasExportSymbol('default')) { | ||
return true; | ||
} // Otherwise, if the symbol is pure and unused, it is safe to remove. | ||
if ((_asset$symbols$get = asset.symbols.get(local)) === null || _asset$symbols$get === void 0 ? void 0 : (_asset$symbols$get$me = _asset$symbols$get.meta) === null || _asset$symbols$get$me === void 0 ? void 0 : _asset$symbols$get$me.isPure) { | ||
return usedSymbols.has(local) || usedSymbols.has('*'); | ||
} | ||
} | ||
return true; | ||
} | ||
function maybeReplaceIdentifier(node, ancestors) { | ||
let { | ||
name | ||
} = path.node; | ||
} = node; | ||
@@ -256,7 +427,7 @@ if (typeof name !== 'string') { | ||
if (replacement) { | ||
path.node.name = replacement; | ||
node.name = replacement; | ||
} | ||
if (imports.has(name)) { | ||
let node; | ||
let res; | ||
let imported = imports.get(name); | ||
@@ -266,48 +437,18 @@ | ||
// import was deferred | ||
node = t().objectExpression([]); | ||
res = t().objectExpression([]); | ||
} else { | ||
let [asset, symbol] = imported; | ||
node = replaceImportNode(asset, symbol, path); // If the export does not exist, replace with an empty object. | ||
res = replaceImportNode(asset, symbol, node, ancestors); // If the export does not exist, replace with an empty object. | ||
if (!node) { | ||
node = t().objectExpression([]); | ||
if (!res) { | ||
res = t().objectExpression([]); | ||
} | ||
} | ||
path.replaceWith(node); | ||
if ((0, t().isObjectExpression)(node)) { | ||
(0, _assert().default)(node.properties.length === 0); | ||
} else if ((0, t().isIdentifier)(node)) { | ||
(0, _nullthrows().default)(path.scope.getBinding(node.name)).reference(path); | ||
} else { | ||
if ((0, t().isCallExpression)(node)) { | ||
// $id$init() | ||
(0, _assert().default)((0, t().isIdentifier)(node.callee)); | ||
(0, _nullthrows().default)(path.scope.getBinding(node.callee.name)).reference(path.get('callee')); | ||
} else { | ||
(0, _assert().default)((0, t().isMemberExpression)(node)); | ||
if ((0, t().isIdentifier)(node.object)) { | ||
(0, _nullthrows().default)(path.scope.getBinding(node.object.name)).reference(path.get('object')); | ||
} else { | ||
// $id$init().prop | ||
(0, _assert().default)((0, t().isCallExpression)(node.object)); | ||
let { | ||
callee | ||
} = node.object; | ||
(0, _assert().default)((0, t().isIdentifier)(callee)); | ||
(0, _nullthrows().default)(path.scope.getBinding(callee.name)).reference(path.get('object.callee')); | ||
} | ||
} | ||
} | ||
} else if (exportsMap.has(name) && !path.scope.hasBinding(name)) { | ||
// If it's an undefined $id$exports identifier. | ||
(0, _utils().dereferenceIdentifier)(path.node, path.scope); | ||
path.replaceWith(t().objectExpression([])); | ||
return res; | ||
} | ||
} // path is an Identifier like $id$import$foo that directly imports originalName from originalModule | ||
} // node is an Identifier like $id$import$foo that directly imports originalName from originalModule | ||
function replaceImportNode(originalModule, originalName, path) { | ||
function replaceImportNode(originalModule, originalName, node, ancestors) { | ||
let { | ||
@@ -317,7 +458,20 @@ asset: mod, | ||
identifier | ||
} = resolveSymbol(originalModule, originalName, bundle); | ||
let node = identifier ? findSymbol(path, identifier) : identifier; // If the module is not in this bundle, create a `require` call for it. | ||
} = resolveSymbol(originalModule, originalName, bundle); // If the symbol resolves to the original module where the export is defined, | ||
// do not perform any replacements. | ||
if (!node && (!mod.meta.id || !assets.has((0, _utils().assertString)(mod.meta.id)))) { | ||
if (node === false) { | ||
let exp = exports.get(node.name); | ||
if (exp && exp[0] === mod) { | ||
return node; | ||
} | ||
let res = identifier != null ? findSymbol(node, identifier) : identifier; | ||
if (mod.meta.staticExports === false || wrappedAssets.has(mod.id)) { | ||
res = null; | ||
} // If the module is not in this bundle, create a `require` call for it. | ||
if (!mod.meta.id || !assets.has((0, _utils().assertString)(mod.meta.id))) { | ||
if (res === false) { | ||
// Asset was skipped | ||
@@ -327,4 +481,4 @@ return null; | ||
node = addBundleImport(mod, path); | ||
return node ? interop(mod, symbol, path, node) : null; | ||
res = addBundleImport(mod, node, ancestors); | ||
return res ? interop(mod, symbol, node, res) : null; | ||
} // The ESM 'does not export' case was already handled by core's symbol proapgation. | ||
@@ -335,7 +489,7 @@ // Look for an exports object if we bailed out. | ||
if (node === undefined && mod.meta.isCommonJS || node === null) { | ||
if (res === undefined && mod.meta.isCommonJS || res === null) { | ||
if (wrappedAssets.has(mod.id)) { | ||
node = t().callExpression((0, _utils().getIdentifier)(mod, 'init'), []); | ||
res = t().callExpression((0, _utils().getIdentifier)(mod, 'init'), []); | ||
} else { | ||
node = findSymbol(path, (0, _utils().assertString)(mod.meta.exportsIdentifier)); | ||
res = findSymbol(node, (0, _utils().assertString)(mod.meta.exportsIdentifier)); | ||
@@ -347,16 +501,22 @@ if (!node) { | ||
node = interop(mod, symbol, path, node); | ||
return node; | ||
res = interop(mod, symbol, res, res); | ||
return res; | ||
} | ||
return node; | ||
return res; | ||
} | ||
function findSymbol(path, symbol) { | ||
function findSymbol(node, symbol) { | ||
if (symbol && replacements.has(symbol)) { | ||
symbol = replacements.get(symbol); | ||
} // if the symbol is in the scope there is no need to remap it | ||
} | ||
let exp = symbol && exportedSymbols.get(symbol); | ||
if (symbol && path.scope.getProgramParent().hasBinding(symbol)) { | ||
if (exp) { | ||
symbol = exp[0].local; | ||
} // if the symbol exists there is no need to remap it | ||
if (symbol) { | ||
return t().identifier(symbol); | ||
@@ -368,22 +528,6 @@ } | ||
function interop(mod, originalName, path, node) { | ||
function interop(mod, originalName, originalNode, node) { | ||
// Handle interop for default imports of CommonJS modules. | ||
if (mod.meta.isCommonJS && originalName === 'default') { | ||
if (mod.meta.isCommonJS && originalName === 'default' && (0, _utils().needsDefaultInterop)(bundleGraph, bundle, mod)) { | ||
let name = (0, _utils().getName)(mod, '$interop$default'); | ||
if (!path.scope.getBinding(name)) { | ||
let binding = (0, _nullthrows().default)(path.scope.getBinding(bundle.hasAsset(mod) && !wrappedAssets.has(mod.id) ? (0, _utils().assertString)(mod.meta.exportsIdentifier) : // If this bundle doesn't have the asset, use the binding for | ||
// the `parcelRequire`d init function. | ||
(0, _utils().getName)(mod, 'init'))); | ||
(0, _assert().default)(binding.path.getStatementParent().parentPath.isProgram(), "Expected binding declaration's parent to be the program"); // Hoist to the nearest path with the same scope as the exports is declared in. | ||
let parent = (0, _nullthrows().default)(path.findParent(p => t().isProgram(p.parent))); | ||
let [decl] = parent.insertBefore(DEFAULT_INTEROP_TEMPLATE({ | ||
NAME: t().identifier(name), | ||
MODULE: node | ||
})); | ||
binding.reference(decl.get('declarations.0.init')); | ||
getScopeBefore(parent).registerDeclaration(decl); | ||
} | ||
return t().identifier(name); | ||
@@ -393,4 +537,8 @@ } // if there is a CommonJS export return $id$exports.name | ||
if (originalName !== '*') { | ||
return t().memberExpression(node, t().identifier(originalName)); | ||
if (originalName !== '*' && node != null) { | ||
if (t().isValidIdentifier(originalName, false)) { | ||
return t().memberExpression(node, t().identifier(originalName)); | ||
} else { | ||
return t().memberExpression(node, t().stringLiteral(originalName), true); | ||
} | ||
} | ||
@@ -401,7 +549,3 @@ | ||
function getScopeBefore(path) { | ||
return path.isScope() ? path.parentPath.scope : path.scope; | ||
} | ||
function addExternalModule(path, dep) { | ||
function addExternalModule(node, ancestors, dep) { | ||
// Find an existing import for this specifier, or create a new one. | ||
@@ -411,7 +555,9 @@ let importedFile = importedFiles.get(dep.moduleSpecifier); | ||
if (!importedFile) { | ||
var _dep$meta; | ||
importedFile = { | ||
source: dep.moduleSpecifier, | ||
specifiers: new Map(), | ||
isCommonJS: !!dep.meta.isCommonJS, | ||
loc: (0, _babelAstUtils().convertBabelLoc)(path.node.loc) | ||
isCommonJS: !!((_dep$meta = dep.meta) === null || _dep$meta === void 0 ? void 0 : _dep$meta.isCommonJS), | ||
loc: (0, _babelAstUtils().convertBabelLoc)(node.loc) | ||
}; | ||
@@ -421,3 +567,2 @@ importedFiles.set(dep.moduleSpecifier, importedFile); | ||
let programScope = path.scope.getProgramParent(); | ||
(0, _assert().default)(importedFile.specifiers != null); | ||
@@ -437,4 +582,14 @@ let specifiers = importedFile.specifiers; // For each of the imported symbols, add to the list of imported specifiers. | ||
renamed = replacements.get(local); | ||
renamed = replacements.get(local); // If this symbol is re-exported, add it to the reexport list. | ||
let exp = exportedSymbols.get(local); | ||
if (exp) { | ||
renamed = exp[0].local; | ||
for (let e of exp) { | ||
reexports.add(e); | ||
} | ||
} | ||
if (!renamed) { | ||
@@ -447,8 +602,9 @@ // Rename the specifier to something nicer. Try to use the imported | ||
if (imported === 'default' || imported === '*') { | ||
renamed = programScope.generateUid(dep.moduleSpecifier); | ||
} else if (programScope.hasBinding(imported) || programScope.hasReference(imported)) { | ||
renamed = programScope.generateUid(imported); | ||
renamed = scope.generateUid(dep.moduleSpecifier); | ||
} else if (scope.has(imported)) { | ||
renamed = scope.generateUid(imported); | ||
} else { | ||
scope.add(imported); | ||
} | ||
programScope.references[renamed] = true; | ||
replacements.set(local, renamed); | ||
@@ -458,8 +614,2 @@ } | ||
specifiers.set(imported, renamed); | ||
if (!programScope.hasOwnBinding(renamed)) { | ||
// add binding so we can track the scope | ||
let [decl] = programScope.path.unshiftContainer('body', t().variableDeclaration('var', [t().variableDeclarator(t().identifier(renamed))])); | ||
programScope.registerDeclaration(decl); | ||
} | ||
} | ||
@@ -470,3 +620,3 @@ | ||
function addBundleImport(mod, path) { | ||
function addBundleImport(mod, node, ancestors) { | ||
// Find a bundle that's reachable from the current bundle (sibling or ancestor) | ||
@@ -487,3 +637,3 @@ // containing this asset, and create an import for it if needed. | ||
assets: new Set(), | ||
loc: (0, _babelAstUtils().convertBabelLoc)(path.node.loc) | ||
loc: (0, _babelAstUtils().convertBabelLoc)(node.loc) | ||
}; | ||
@@ -494,25 +644,6 @@ importedFiles.set(filePath, imported); | ||
if (!isUnusedValue(path) && mod.meta.exportsIdentifier) { | ||
if (!isUnusedValue(ancestors) && mod.meta.exportsIdentifier) { | ||
(0, _assert().default)(imported.assets != null); | ||
imported.assets.add(mod); | ||
let initIdentifier = (0, _utils().getIdentifier)(mod, 'init'); | ||
let program = path.scope.getProgramParent().path; | ||
if (!program.scope.hasOwnBinding(initIdentifier.name)) { | ||
// add binding so we can track the scope | ||
// If parcelRequire exists in scope, be sure to insert after that so the global outputFormat | ||
// can add the rhs later and reference it properly. | ||
let declNode = t().variableDeclaration('var', [t().variableDeclarator(initIdentifier)]); | ||
let parcelRequire = program.scope.getBinding('parcelRequire'); | ||
let decl; | ||
if (parcelRequire) { | ||
[decl] = parcelRequire.path.getStatementParent().insertAfter(declNode); | ||
} else { | ||
[decl] = program.unshiftContainer('body', [declNode]); | ||
} | ||
program.scope.registerDeclaration(decl); | ||
} | ||
return t().callExpression(initIdentifier, []); | ||
@@ -522,8 +653,8 @@ } | ||
(0, _traverse().default)(ast, { | ||
CallExpression(path) { | ||
(0, _babylonWalk().traverse2)(ast, { | ||
CallExpression(node, state, ancestors) { | ||
let { | ||
arguments: args, | ||
callee | ||
} = path.node; | ||
} = node; | ||
@@ -538,3 +669,3 @@ if (!(0, t().isIdentifier)(callee)) { | ||
if (args.length !== 2 || !(0, t().isStringLiteral)(id) || !(0, t().isStringLiteral)(source)) { | ||
if (args.length < 2 || !(0, t().isStringLiteral)(id) || !(0, t().isStringLiteral)(source)) { | ||
throw new Error('invariant: invalid signature, expected : $parcel$require(number, string)'); | ||
@@ -549,3 +680,3 @@ } | ||
asyncResolution.value : bundleGraph.getDependencyResolution(dep, bundle); | ||
let node; | ||
let newNode; | ||
@@ -555,32 +686,33 @@ if (!bundleGraph.isDependencySkipped(dep)) { | ||
if (dep.isOptional) { | ||
node = THROW_TEMPLATE({ | ||
newNode = THROW_TEMPLATE({ | ||
MODULE: t().stringLiteral(source.value) | ||
}); | ||
scope.add('$parcel$missingModule'); | ||
} else { | ||
let name = addExternalModule(path, dep); | ||
let name = addExternalModule(node, ancestors, dep); | ||
if (!isUnusedValue(path) && name) { | ||
node = t().identifier(name); | ||
if (!isUnusedValue(ancestors) && name) { | ||
newNode = t().identifier(name); | ||
} | ||
} | ||
} else { | ||
if (mod.meta.id && assets.has((0, _utils().assertString)(mod.meta.id))) { | ||
let name = (0, _utils().assertString)(mod.meta.exportsIdentifier); | ||
let isValueUsed = !isUnusedValue(path); | ||
// If there is a third arg, it is an identifier to replace the require with. | ||
// This happens when `require('foo').bar` is detected in the hoister. | ||
if (args.length > 2 && (0, t().isIdentifier)(args[2])) { | ||
newNode = maybeReplaceIdentifier(args[2], ancestors); | ||
} else { | ||
if (mod.meta.id && assets.has((0, _utils().assertString)(mod.meta.id))) { | ||
let isValueUsed = !isUnusedValue(ancestors); // We need to wrap the module in a function when a require | ||
// call happens inside a non top-level scope, e.g. in a | ||
// function, if statement, or conditional expression. | ||
if (asset.meta.isCommonJS && isValueUsed) { | ||
maybeAddEsModuleFlag(path.scope, mod); | ||
} // We need to wrap the module in a function when a require | ||
// call happens inside a non top-level scope, e.g. in a | ||
// function, if statement, or conditional expression. | ||
if (wrappedAssets.has(mod.id)) { | ||
node = t().callExpression((0, _utils().getIdentifier)(mod, 'init'), []); | ||
} // Replace with nothing if the require call's result is not used. | ||
else if (isValueUsed) { | ||
node = t().identifier(replacements.get(name) || name); | ||
} | ||
} else if (mod.type === 'js') { | ||
node = addBundleImport(mod, path); | ||
if (wrappedAssets.has(mod.id)) { | ||
newNode = t().callExpression((0, _utils().getIdentifier)(mod, 'init'), []); | ||
} // Replace with nothing if the require call's result is not used. | ||
else if (isValueUsed) { | ||
newNode = t().identifier((0, _utils().assertString)(mod.meta.exportsIdentifier)); | ||
} | ||
} else if (mod.type === 'js') { | ||
newNode = addBundleImport(mod, node, ancestors); | ||
} | ||
} // async dependency that was internalized | ||
@@ -590,4 +722,4 @@ | ||
if ((asyncResolution === null || asyncResolution === void 0 ? void 0 : asyncResolution.type) === 'asset') { | ||
node = t().callExpression(t().memberExpression(t().identifier('Promise'), t().identifier('resolve')), // $FlowFixMe[incompatible-call] | ||
[node]); | ||
newNode = t().callExpression(t().memberExpression(t().identifier('Promise'), t().identifier('resolve')), // $FlowFixMe[incompatible-call] | ||
[newNode]); | ||
} | ||
@@ -597,10 +729,10 @@ } | ||
if (node) { | ||
path.replaceWith(node); | ||
if (newNode) { | ||
return newNode; | ||
} else { | ||
if (path.parentPath.isExpressionStatement()) { | ||
path.parentPath.remove(); | ||
if (isUnusedValue(ancestors)) { | ||
return _babylonWalk().REMOVE; | ||
} else { | ||
// e.g. $parcel$exportWildcard; | ||
path.replaceWith(t().objectExpression([])); | ||
return t().objectExpression([]); | ||
} | ||
@@ -621,11 +753,19 @@ } | ||
if (bundle.env.outputFormat !== 'commonjs') { | ||
throw (0, _utils().getThrowableDiagnosticForNode)("`require.resolve` calls for excluded assets are only supported with outputFormat: 'commonjs'", mapped.filePath, (0, _babelAstUtils().convertBabelLoc)(path.node.loc)); | ||
throw (0, _utils().getThrowableDiagnosticForNode)("`require.resolve` calls for excluded assets are only supported with outputFormat: 'commonjs'", mapped.filePath, (0, _babelAstUtils().convertBabelLoc)(node.loc)); | ||
} | ||
path.replaceWith(REQUIRE_RESOLVE_CALL_TEMPLATE({ | ||
return REQUIRE_RESOLVE_CALL_TEMPLATE({ | ||
ID: t().stringLiteral(source.value) | ||
})); | ||
}); | ||
} else { | ||
throw (0, _utils().getThrowableDiagnosticForNode)("`require.resolve` calls for bundled modules or bundled assets aren't supported with scope hoisting", mapped.filePath, (0, _babelAstUtils().convertBabelLoc)(path.node.loc)); | ||
throw (0, _utils().getThrowableDiagnosticForNode)("`require.resolve` calls for bundled modules or bundled assets aren't supported with scope hoisting", mapped.filePath, (0, _babelAstUtils().convertBabelLoc)(node.loc)); | ||
} | ||
} else if (callee.name === '$parcel$exportWildcard') { | ||
if (args.length !== 2 || !(0, t().isIdentifier)(args[0])) { | ||
throw new Error('Invalid call to $parcel$exportWildcard'); | ||
} | ||
if (!needsExportsIdentifier(args[0].name)) { | ||
return _babylonWalk().REMOVE; | ||
} | ||
} else if (callee.name === '$parcel$export') { | ||
@@ -636,4 +776,8 @@ let [obj, symbol] = args; | ||
let objName = obj.name; | ||
let symbolName = symbol.value; | ||
let symbolName = symbol.value; // Remove if the $id$exports object is unused. | ||
if (!needsExportsIdentifier(objName)) { | ||
return _babylonWalk().REMOVE; | ||
} | ||
if (objName === 'exports') { | ||
@@ -652,3 +796,3 @@ // Assignment inside a wrapped asset | ||
if (unused) { | ||
(0, _utils().pathRemove)(path); | ||
return _babylonWalk().REMOVE; | ||
} | ||
@@ -659,132 +803,152 @@ } | ||
VariableDeclarator: { | ||
exit(path) { | ||
// Replace references to declarations like `var x = require('x')` | ||
// with the final export identifier instead. | ||
// This allows us to potentially replace accesses to e.g. `x.foo` with | ||
// a variable like `$id$export$foo` later, avoiding the exports object altogether. | ||
exit(node) { | ||
let { | ||
id, | ||
init | ||
} = path.node; | ||
id | ||
} = node; | ||
if (!(0, t().isIdentifier)(init)) { | ||
return; | ||
} | ||
if ((0, t().isIdentifier)(id)) { | ||
if (!needsExportsIdentifier(id.name)) { | ||
return _babylonWalk().REMOVE; | ||
} | ||
let module = exportsMap.get(init.name); | ||
if (!module) { | ||
return; | ||
if (!needsDeclaration(id.name)) { | ||
return _babylonWalk().REMOVE; | ||
} | ||
} | ||
} | ||
let isGlobal = path.scope == path.scope.getProgramParent(); // Replace patterns like `var {x} = require('y')` with e.g. `$id$export$x`. | ||
}, | ||
VariableDeclaration: { | ||
exit(node) { | ||
if (node.declarations.length === 0) { | ||
return _babylonWalk().REMOVE; | ||
} // Handle exported declarations using output format specific logic. | ||
if ((0, t().isObjectPattern)(id)) { | ||
for (let p of path.get('id.properties')) { | ||
let { | ||
computed, | ||
key, | ||
value | ||
} = p.node; | ||
if (computed || !(0, t().isIdentifier)(key) || !(0, t().isIdentifier)(value)) { | ||
continue; | ||
} | ||
let exported = []; | ||
let { | ||
identifier | ||
} = resolveSymbol(module, key.name); | ||
for (let decl of node.declarations) { | ||
let bindingIdentifiers = t().getBindingIdentifiers(decl.id); | ||
if (identifier) { | ||
replace(value.name, identifier, p); | ||
for (let name in bindingIdentifiers) { | ||
let exp = exportedSymbols.get(name); | ||
if (isGlobal) { | ||
replacements.set(value.name, identifier); | ||
} | ||
if (exp) { | ||
bindingIdentifiers[name].name = exp[0].local; | ||
exported.push(...exp); | ||
} | ||
} | ||
} | ||
if (id.properties.length === 0) { | ||
path.remove(); | ||
} | ||
} else if ((0, t().isIdentifier)(id)) { | ||
replace(id.name, init.name, path); | ||
if (exported.length > 0) { | ||
return format.generateMainExport(node, exported); | ||
} | ||
} | ||
if (isGlobal) { | ||
replacements.set(id.name, init.name); | ||
} | ||
}, | ||
Declaration: { | ||
exit(node) { | ||
if (t().isVariableDeclaration(node)) { | ||
return; | ||
} | ||
function replace(id, init, path) { | ||
let binding = (0, _nullthrows().default)(path.scope.getBinding(id)); | ||
if (node.id != null && (0, t().isIdentifier)(node.id)) { | ||
let id = node.id; | ||
if (!binding.constant) { | ||
return; | ||
} | ||
if (!needsDeclaration(id.name)) { | ||
return _babylonWalk().REMOVE; | ||
} // Handle exported declarations using output format specific logic. | ||
for (let ref of binding.referencePaths) { | ||
ref.replaceWith(t().identifier(init)); | ||
let exp = exportedSymbols.get(id.name); | ||
if (exp) { | ||
id.name = exp[0].local; | ||
return format.generateMainExport(node, exp); | ||
} | ||
} | ||
} | ||
path.remove(); | ||
}, | ||
AssignmentExpression(node, state, ancestors) { | ||
if ((0, t().isIdentifier)(node.left)) { | ||
let res = maybeReplaceIdentifier(node.left, ancestors); | ||
if ((0, t().isIdentifier)(res) || (0, t().isMemberExpression)(res)) { | ||
node.left = res; | ||
} | ||
return; | ||
} | ||
}, | ||
MemberExpression: { | ||
exit(path) { | ||
let { | ||
if (!(0, t().isMemberExpression)(node.left)) { | ||
return; | ||
} | ||
let { | ||
left: { | ||
object, | ||
property, | ||
computed | ||
} = path.node; | ||
}, | ||
right | ||
} = node; | ||
if (!((0, t().isIdentifier)(object) && ((0, t().isIdentifier)(property) && !computed || (0, t().isStringLiteral)(property)))) { | ||
return; | ||
} | ||
if (!((0, t().isIdentifier)(object) && ((0, t().isIdentifier)(property) && !computed || (0, t().isStringLiteral)(property)))) { | ||
return; | ||
} // Rename references to exported symbols to the exported name. | ||
let asset = exportsMap.get(object.name); | ||
if (!asset) { | ||
return; | ||
} // If it's a $id$exports.name expression. | ||
let exp = exportedSymbols.get(object.name); | ||
if (exp) { | ||
object.name = exp[0].local; | ||
} | ||
let name = (0, t().isIdentifier)(property) ? property.name : property.value; | ||
let { | ||
identifier | ||
} = resolveSymbol(asset, name, bundle); | ||
let asset = exportsMap.get(object.name); | ||
if (identifier == null || identifier === false || !path.scope.hasBinding(identifier)) { | ||
if (!asset) { | ||
return; | ||
} | ||
if (!needsExportsIdentifier(object.name)) { | ||
return _babylonWalk().REMOVE; | ||
} | ||
if ((0, t().isIdentifier)(right) && !needsDeclaration(right.name)) { | ||
return _babylonWalk().REMOVE; | ||
} | ||
}, | ||
Identifier(node, state, ancestors) { | ||
if (t().isReferenced(node, ancestors[ancestors.length - 2], ancestors[ancestors.length - 3])) { | ||
// If referencing a helper, add it to the scope. | ||
if (helpers.has(node.name)) { | ||
scope.add(node.name); | ||
return; | ||
} | ||
} // Rename references to exported symbols to the exported name. | ||
let { | ||
parent, | ||
parentPath | ||
} = path; // If inside an expression, update the actual export binding as well | ||
// (This is needed so that `require()`d CJS namespace objects can be mutatated.) | ||
if ((0, t().isAssignmentExpression)(parent, { | ||
left: path.node | ||
})) { | ||
if ((0, t().isIdentifier)(parent.right)) { | ||
maybeReplaceIdentifier(parentPath.get('right')); // do not modify `$id$exports.foo = $id$export$foo` statements | ||
let exp = exportedSymbols.get(node.name); | ||
if ((0, t().isIdentifier)(parent.right, { | ||
name: identifier | ||
})) { | ||
return; | ||
} // If the right side was imported from a different bundle, there is no $id$export$foo binding in this bundle | ||
if (exp) { | ||
node.name = exp[0].local; | ||
} | ||
return maybeReplaceIdentifier(node, ancestors); | ||
} | ||
}, | ||
if (!path.scope.hasBinding(identifier)) { | ||
return; | ||
} | ||
} // turn `$id$exports.foo = ...` into `$id$exports.foo = $id$export$foo = ...` | ||
ExpressionStatement: { | ||
exit(node) { | ||
// Handle exported declarations using output format specific logic. | ||
if ((0, t().isAssignmentExpression)(node.expression) && (0, t().isIdentifier)(node.expression.left)) { | ||
let left = node.expression.left; | ||
let exp = exportedSymbols.get(left.name); | ||
parentPath.get('right').replaceWith(t().assignmentExpression('=', t().identifier(identifier), parent.right)); | ||
} else { | ||
path.replaceWith(t().identifier(identifier)); | ||
if (exp) { | ||
left.name = exp[0].local; | ||
return format.generateMainExport(node, exp); | ||
} | ||
} | ||
@@ -794,17 +958,23 @@ } | ||
}, | ||
SequenceExpression: { | ||
exit(node) { | ||
// This can happen if a $parcel$require result is unused. | ||
if (node.expressions.length === 1) { | ||
return node.expressions[0]; | ||
} | ||
} | ||
ReferencedIdentifier(path) { | ||
maybeReplaceIdentifier(path); | ||
}, | ||
Program: { | ||
exit(path) { | ||
// Recrawl to get all bindings. | ||
path.scope.crawl(); | ||
exit(node) { | ||
// $FlowFixMe | ||
let statements = node.body; | ||
for (let file of importedFiles.values()) { | ||
if (file.bundle) { | ||
format.generateBundleImports(bundle, file, path, bundleGraph); | ||
let res = format.generateBundleImports(bundleGraph, bundle, file, scope); | ||
statements = res.concat(statements); | ||
} else { | ||
format.generateExternalImport(bundle, file, path); | ||
let res = format.generateExternalImport(bundle, file, scope); | ||
statements = res.concat(statements); | ||
} | ||
@@ -817,7 +987,3 @@ } | ||
// not in the current bundle. | ||
for (let asset of referencedAssets) { | ||
maybeAddEsModuleFlag(path.scope, asset); | ||
} | ||
let decls = path.pushContainer('body', [...referencedAssets].filter(a => !wrappedAssets.has(a.id)).map(a => { | ||
statements = statements.concat([...referencedAssets].filter(a => !wrappedAssets.has(a.id)).map(a => { | ||
return FAKE_INIT_TEMPLATE({ | ||
@@ -828,18 +994,29 @@ INIT: (0, _utils().getIdentifier)(a, 'init'), | ||
})); | ||
} // Generate exports | ||
for (let decl of decls) { | ||
var _path$scope$getBindin; | ||
path.scope.registerDeclaration(decl); | ||
let returnId = decl.get('body.body.0.argument'); // TODO Sometimes deferred/excluded assets are referenced, causing this function to | ||
// become `function $id$init() { return {}; }` (because of the ReferencedIdentifier visitor). | ||
// But a asset that isn't here should never be referenced in the first place. | ||
let exported = format.generateBundleExports(bundleGraph, bundle, referencedAssets, scope, reexports); | ||
statements = statements.concat(exported); // If the prelude is needed, ensure parcelRequire is available. | ||
(_path$scope$getBindin = path.scope.getBinding(returnId.node.name)) === null || _path$scope$getBindin === void 0 ? void 0 : _path$scope$getBindin.reference(returnId); | ||
if (!scope.names.has('parcelRequire') && (0, _utils().needsPrelude)(bundle, bundleGraph)) { | ||
scope.add('parcelRequire'); | ||
} | ||
let usedHelpers = []; | ||
for (let name of scope.names) { | ||
let helper = helpers.get(name); | ||
if (helper) { | ||
usedHelpers.push(helper); | ||
} else if (name === 'parcelRequire') { | ||
usedHelpers.push(PARCEL_REQUIRE_TEMPLATE({ | ||
PARCEL_REQUIRE_NAME: t().identifier(parcelRequireName) | ||
})); | ||
} | ||
} | ||
// Generate exports | ||
let exported = format.generateExports(bundleGraph, bundle, referencedAssets, path, replacements, options, maybeReplaceIdentifier); | ||
(0, _shake().default)(path.scope, exported, exportsMap); | ||
statements = usedHelpers.concat(statements); // $FlowFixMe | ||
return t().program(statements); | ||
} | ||
@@ -855,35 +1032,6 @@ | ||
function maybeAddEsModuleFlag(scope, mod) { | ||
// Insert __esModule interop flag if the required module is an ES6 module with a default export. | ||
// This ensures that code generated by Babel and other tools works properly. | ||
if (mod.meta.isES6Module && mod.symbols.hasExportSymbol('default')) { | ||
let name = (0, _utils().assertString)(mod.meta.exportsIdentifier); | ||
let binding = scope.getBinding(name); | ||
if (binding && !binding.path.getData('hasESModuleFlag')) { | ||
let f = (0, _nullthrows().default)(scope.getProgramParent().getBinding('$parcel$defineInteropFlag')); | ||
let paths = [...binding.constantViolations]; | ||
if (binding.path.node.init) { | ||
paths.push(binding.path); | ||
} | ||
for (let path of paths) { | ||
let [stmt] = path.getStatementParent().insertAfter(ESMODULE_TEMPLATE({ | ||
EXPORTS: t().identifier(name) | ||
})); | ||
f.reference(stmt.get('expression.callee')); | ||
binding.reference(stmt.get('expression.arguments.0')); | ||
} | ||
binding.path.setData('hasESModuleFlag', true); | ||
} | ||
} | ||
} | ||
function isUnusedValue(path) { | ||
let { | ||
parent | ||
} = path; | ||
return (0, t().isExpressionStatement)(parent) || (0, t().isSequenceExpression)(parent) && (Array.isArray(path.container) && path.key !== path.container.length - 1 || isUnusedValue(path.parentPath)); | ||
function isUnusedValue(ancestors, i = 1) { | ||
let node = ancestors[ancestors.length - i]; | ||
let parent = ancestors[ancestors.length - i - 1]; | ||
return (0, t().isExpressionStatement)(parent) || (0, t().isSequenceExpression)(parent) && (node !== parent.expressions[parent.expressions.length - 1] || isUnusedValue(ancestors, i + 1)); | ||
} |
@@ -13,2 +13,3 @@ "use strict"; | ||
exports.hasAsyncDescendant = hasAsyncDescendant; | ||
exports.needsDefaultInterop = needsDefaultInterop; | ||
exports.assertString = assertString; | ||
@@ -21,3 +22,15 @@ exports.pathDereference = pathDereference; | ||
exports.getThrowableDiagnosticForNode = getThrowableDiagnosticForNode; | ||
exports.parse = parse; | ||
exports.getHelpers = getHelpers; | ||
function _parser() { | ||
const data = require("@babel/parser"); | ||
_parser = function () { | ||
return data; | ||
}; | ||
return data; | ||
} | ||
function _babylonWalk() { | ||
@@ -83,2 +96,12 @@ const data = require("@parcel/babylon-walk"); | ||
function _fs() { | ||
const data = _interopRequireDefault(require("fs")); | ||
_fs = function () { | ||
return data; | ||
}; | ||
return data; | ||
} | ||
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } | ||
@@ -159,2 +182,12 @@ | ||
function needsDefaultInterop(bundleGraph, bundle, asset) { | ||
let deps = bundleGraph.getIncomingDependencies(asset); | ||
if (asset.meta.isCommonJS && !asset.symbols.hasExportSymbol('default')) { | ||
return deps.some(dep => bundle.hasDependency(dep) && dep.meta.isES6Module && dep.symbols.hasExportSymbol('default')); | ||
} | ||
return false; | ||
} | ||
function assertString(v) { | ||
@@ -296,2 +329,40 @@ (0, _assert().default)(typeof v === 'string'); | ||
}); | ||
} | ||
function parse(code, sourceFilename) { | ||
let ast = (0, _parser().parse)(code, { | ||
sourceFilename, | ||
allowReturnOutsideFunction: true, | ||
plugins: ['dynamicImport'] | ||
}); | ||
return ast.program.body; | ||
} | ||
let helpersCache; | ||
function getHelpers() { | ||
if (helpersCache != null) { | ||
return helpersCache; | ||
} | ||
let helpersPath = _path().default.join(__dirname, 'helpers.js'); | ||
let statements = parse(_fs().default.readFileSync(helpersPath, 'utf8'), helpersPath); | ||
helpersCache = new Map(); | ||
for (let statement of statements) { | ||
if ((0, t().isVariableDeclaration)(statement)) { | ||
if (statement.declarations.length !== 1 || !(0, t().isIdentifier)(statement.declarations[0].id)) { | ||
throw new Error('Unsupported helper'); | ||
} | ||
helpersCache.set(statement.declarations[0].id.name, statement); | ||
} else if ((0, t().isFunctionDeclaration)(statement) && (0, t().isIdentifier)(statement.id)) { | ||
helpersCache.set(statement.id.name, statement); | ||
} else { | ||
throw new Error('Unsupported helper'); | ||
} | ||
} | ||
return helpersCache; | ||
} |
{ | ||
"name": "@parcel/scope-hoisting", | ||
"version": "2.0.0-nightly.522+71264fe1", | ||
"version": "2.0.0-nightly.524+e183e32c", | ||
"description": "Blazing fast, zero configuration web application bundler", | ||
@@ -23,3 +23,2 @@ "license": "MIT", | ||
"dependencies": { | ||
"@babel/generator": "^7.3.3", | ||
"@babel/parser": "^7.0.0", | ||
@@ -29,10 +28,11 @@ "@babel/template": "^7.4.0", | ||
"@babel/types": "^7.12.11", | ||
"@parcel/babel-ast-utils": "2.0.0-nightly.2144+71264fe1", | ||
"@parcel/babylon-walk": "2.0.0-nightly.2144+71264fe1", | ||
"@parcel/diagnostic": "2.0.0-nightly.522+71264fe1", | ||
"@parcel/babel-ast-utils": "2.0.0-nightly.2146+e183e32c", | ||
"@parcel/babylon-walk": "2.0.0-nightly.2146+e183e32c", | ||
"@parcel/diagnostic": "2.0.0-nightly.524+e183e32c", | ||
"@parcel/source-map": "2.0.0-alpha.4.19", | ||
"@parcel/utils": "2.0.0-nightly.522+71264fe1", | ||
"@parcel/utils": "2.0.0-nightly.524+e183e32c", | ||
"globals": "^13.2.0", | ||
"nullthrows": "^1.1.1" | ||
}, | ||
"gitHead": "71264fe1af27c6eec127fe8fa3241dd1a2e6c029" | ||
"gitHead": "e183e32c811f066310fff0610aa856ca73a2fcb6" | ||
} |
@@ -11,4 +11,5 @@ // @flow | ||
CallExpression, | ||
ClassDeclaration, | ||
Expression, | ||
Identifier, | ||
LVal, | ||
Node, | ||
@@ -19,3 +20,2 @@ Statement, | ||
import {parse as babelParse} from '@babel/parser'; | ||
import path from 'path'; | ||
@@ -35,3 +35,8 @@ import * as t from '@babel/types'; | ||
} from '@babel/types'; | ||
import {simple as walkSimple, traverse} from '@parcel/babylon-walk'; | ||
import { | ||
simple as walkSimple, | ||
traverse2, | ||
REMOVE, | ||
SKIP, | ||
} from '@parcel/babylon-walk'; | ||
import {PromiseQueue, relativeUrl, relativePath} from '@parcel/utils'; | ||
@@ -41,10 +46,12 @@ import invariant from 'assert'; | ||
import nullthrows from 'nullthrows'; | ||
import {assertString, getName, getIdentifier, needsPrelude} from './utils'; | ||
import template from '@babel/template'; | ||
import { | ||
assertString, | ||
getName, | ||
getIdentifier, | ||
parse, | ||
needsPrelude, | ||
needsDefaultInterop, | ||
} from './utils'; | ||
const HELPERS_PATH = path.join(__dirname, 'helpers.js'); | ||
const HELPERS = parse( | ||
fs.readFileSync(path.join(__dirname, 'helpers.js'), 'utf8'), | ||
HELPERS_PATH, | ||
); | ||
const PRELUDE_PATH = path.join(__dirname, 'prelude.js'); | ||
@@ -56,2 +63,15 @@ const PRELUDE = parse( | ||
const DEFAULT_INTEROP_TEMPLATE = template.statement< | ||
{| | ||
NAME: LVal, | ||
MODULE: Expression, | ||
|}, | ||
VariableDeclaration, | ||
>('var NAME = $parcel$interopDefault(MODULE);'); | ||
const ESMODULE_TEMPLATE = template.statement< | ||
{|EXPORTS: Expression|}, | ||
Statement, | ||
>(`$parcel$defineInteropFlag(EXPORTS);`); | ||
type AssetASTMap = Map<string, Array<Statement>>; | ||
@@ -95,3 +115,3 @@ type TraversalContext = {| | ||
queue.add(() => | ||
processAsset(options, bundle, node.value, wrappedAssets), | ||
processAsset(options, bundleGraph, bundle, node.value, wrappedAssets), | ||
); | ||
@@ -102,14 +122,4 @@ } | ||
let outputs = new Map<string, Array<Statement>>(await queue.run()); | ||
let result = [...HELPERS]; | ||
let result = []; | ||
// Add a declaration for parcelRequire that points to the unique global name. | ||
if (bundle.env.outputFormat === 'global') { | ||
result.push( | ||
...parse( | ||
`var parcelRequire = $parcel$global.${parcelRequireName};`, | ||
PRELUDE_PATH, | ||
), | ||
); | ||
} | ||
if (needsPrelude(bundle, bundleGraph)) { | ||
@@ -194,2 +204,3 @@ result.push( | ||
options: PluginOptions, | ||
bundleGraph: BundleGraph<NamedBundle>, | ||
bundle: NamedBundle, | ||
@@ -208,2 +219,30 @@ asset: Asset, | ||
// If this is a CommonJS module, add an interop default declaration if there are any ES6 default | ||
// import dependencies in the same bundle for that module. | ||
if (needsDefaultInterop(bundleGraph, bundle, asset)) { | ||
statements.push( | ||
DEFAULT_INTEROP_TEMPLATE({ | ||
NAME: getIdentifier(asset, '$interop$default'), | ||
MODULE: t.identifier(assertString(asset.meta.exportsIdentifier)), | ||
}), | ||
); | ||
} | ||
// If this is an ES6 module with a default export, and it's required by a | ||
// CommonJS module in the same bundle, then add an __esModule flag for interop with babel. | ||
if (asset.meta.isES6Module && asset.symbols.hasExportSymbol('default')) { | ||
let deps = bundleGraph.getIncomingDependencies(asset); | ||
let hasCJSDep = deps.some( | ||
dep => | ||
dep.meta.isCommonJS && !dep.isAsync && dep.symbols.hasExportSymbol('*'), | ||
); | ||
if (hasCJSDep) { | ||
statements.push( | ||
ESMODULE_TEMPLATE({ | ||
EXPORTS: t.identifier(assertString(asset.meta.exportsIdentifier)), | ||
}), | ||
); | ||
} | ||
} | ||
if (wrappedAssets.has(asset.id)) { | ||
@@ -225,12 +264,2 @@ statements = wrapModule(asset, statements); | ||
function parse(code, sourceFilename) { | ||
let ast = babelParse(code, { | ||
sourceFilename, | ||
allowReturnOutsideFunction: true, | ||
plugins: ['dynamicImport'], | ||
}); | ||
return ast.program.body; | ||
} | ||
function shouldSkipAsset( | ||
@@ -302,5 +331,4 @@ bundleGraph: BundleGraph<NamedBundle>, | ||
const WRAP_MODULE_VISITOR = { | ||
VariableDeclaration(path, {decls}) { | ||
// $FlowFixMe | ||
let {node, parent} = (path: {|node: VariableDeclaration, parent: Node|}); | ||
VariableDeclaration(node, {decls}, ancestors) { | ||
let parent = ancestors[ancestors.length - 2]; | ||
let isParentForX = | ||
@@ -311,3 +339,3 @@ isForInStatement(parent, {left: node}) || | ||
if (node.kind === 'var' || isProgram(path.parent)) { | ||
if (node.kind === 'var' || isProgram(parent)) { | ||
let replace: Array<any> = []; | ||
@@ -342,16 +370,14 @@ for (let decl of node.declarations) { | ||
path.replaceWith(n); | ||
return n; | ||
} else { | ||
path.remove(); | ||
return REMOVE; | ||
} | ||
} | ||
path.skip(); | ||
return SKIP; | ||
}, | ||
FunctionDeclaration(path, {fns}) { | ||
fns.push(path.node); | ||
path.remove(); | ||
FunctionDeclaration(node, {fns}) { | ||
fns.push(node); | ||
return REMOVE; | ||
}, | ||
ClassDeclaration(path, {decls}) { | ||
// $FlowFixMe | ||
let {node} = (path: {|node: ClassDeclaration|}); | ||
ClassDeclaration(node, {decls}) { | ||
let {id} = node; | ||
@@ -363,11 +389,8 @@ invariant(isIdentifier(id)); | ||
decls.push(t.variableDeclarator(id)); | ||
path.replaceWith( | ||
t.expressionStatement( | ||
t.assignmentExpression('=', id, t.toExpression(node)), | ||
), | ||
return t.expressionStatement( | ||
t.assignmentExpression('=', id, t.toExpression(node)), | ||
); | ||
path.skip(); | ||
}, | ||
'Function|Class'(path) { | ||
path.skip(); | ||
'Function|Class'() { | ||
return SKIP; | ||
}, | ||
@@ -383,3 +406,3 @@ shouldSkip(node) { | ||
let program = t.program(statements); | ||
traverse(program, WRAP_MODULE_VISITOR, {decls, fns}); | ||
traverse2(program, WRAP_MODULE_VISITOR, {asset, decls, fns}); | ||
@@ -386,0 +409,0 @@ let executed = getName(asset, 'executed'); |
// @flow | ||
import type {Asset, BundleGraph, NamedBundle, Symbol} from '@parcel/types'; | ||
import type { | ||
Asset, | ||
BundleGraph, | ||
PluginOptions, | ||
NamedBundle, | ||
Symbol, | ||
} from '@parcel/types'; | ||
import type { | ||
Expression, | ||
@@ -16,31 +10,19 @@ ExpressionStatement, | ||
ObjectProperty, | ||
Program, | ||
VariableDeclaration, | ||
VariableDeclarator, | ||
} from '@babel/types'; | ||
import type {NodePath} from '@babel/traverse'; | ||
import type {ExternalBundle, ExternalModule} from '../types'; | ||
import type {Scope} from '@parcel/babylon-walk'; | ||
import * as t from '@babel/types'; | ||
import { | ||
isCallExpression, | ||
isAssignmentExpression, | ||
isExpressionStatement, | ||
isIdentifier, | ||
isMemberExpression, | ||
isObjectExpression, | ||
isVariableDeclaration, | ||
isVariableDeclarator, | ||
} from '@babel/types'; | ||
import template from '@babel/template'; | ||
import invariant from 'assert'; | ||
import nullthrows from 'nullthrows'; | ||
import {relative} from 'path'; | ||
import {relativeBundlePath} from '@parcel/utils'; | ||
import rename from '../renamer'; | ||
import { | ||
assertString, | ||
getIdentifier, | ||
getName, | ||
getThrowableDiagnosticForNode, | ||
removeReplaceBinding, | ||
} from '../utils'; | ||
import {getIdentifier, getName} from '../utils'; | ||
@@ -105,3 +87,3 @@ const REQUIRE_TEMPLATE = template.expression< | ||
scope, | ||
): Array<VariableDeclaration> { | ||
): Array<BabelNode> { | ||
// If destructuring is not supported, generate a series of variable declarations | ||
@@ -144,9 +126,7 @@ // with member expressions for each property. | ||
export function generateBundleImports( | ||
bundleGraph: BundleGraph<NamedBundle>, | ||
from: NamedBundle, | ||
{bundle, assets}: ExternalBundle, | ||
path: NodePath<Program>, | ||
// Implement an interface consistent with other formats | ||
// eslint-disable-next-line no-unused-vars | ||
bundleGraph: BundleGraph<NamedBundle>, | ||
) { | ||
scope: Scope, | ||
): Array<BabelNode> { | ||
let specifiers: Array<ObjectProperty> = [...assets].map(asset => { | ||
@@ -162,28 +142,10 @@ let id = getName(asset, 'init'); | ||
if (specifiers.length > 0) { | ||
let decls = path.unshiftContainer( | ||
'body', | ||
generateDestructuringAssignment( | ||
bundle.env, | ||
specifiers, | ||
expression, | ||
path.scope, | ||
), | ||
return generateDestructuringAssignment( | ||
bundle.env, | ||
specifiers, | ||
expression, | ||
scope, | ||
); | ||
for (let decl of decls) { | ||
// every VariableDeclaration emitted by generateDestructuringAssignment has only | ||
// one VariableDeclarator | ||
let next = decl.get<NodePath<VariableDeclarator>>('declarations.0'); | ||
for (let [name] of (Object.entries( | ||
decl.getBindingIdentifierPaths(), | ||
): Array<[string, any]>)) { | ||
if (path.scope.hasOwnBinding(name)) { | ||
removeReplaceBinding(path.scope, name, next); | ||
} else { | ||
path.scope.registerDeclaration(decl); | ||
} | ||
} | ||
} | ||
} else { | ||
path.unshiftContainer('body', [t.expressionStatement(expression)]); | ||
return [t.expressionStatement(expression)]; | ||
} | ||
@@ -195,5 +157,4 @@ } | ||
external: ExternalModule, | ||
path: NodePath<Program>, | ||
) { | ||
let {scope} = path; | ||
scope: Scope, | ||
): Array<BabelNode> { | ||
let {source, specifiers, isCommonJS} = external; | ||
@@ -224,3 +185,3 @@ | ||
let statements: Array<VariableDeclaration | ExpressionStatement> = []; | ||
let statements: Array<BabelNode> = []; | ||
// Attempt to combine require calls as much as possible. Namespace, default, and named specifiers | ||
@@ -248,2 +209,4 @@ // cannot be combined, so in the case where we have more than one type, assign the require() result | ||
}); | ||
scope.add('$parcel$exportWildcard'); | ||
} | ||
@@ -268,2 +231,4 @@ | ||
); | ||
scope.add('$parcel$interopDefault'); | ||
} | ||
@@ -292,2 +257,4 @@ | ||
); | ||
scope.add('$parcel$interopDefault'); | ||
} else if (specifiersWildcard) { | ||
@@ -303,2 +270,4 @@ let require = REQUIRE_TEMPLATE({ | ||
}); | ||
scope.add('$parcel$exportWildcard'); | ||
} | ||
@@ -333,63 +302,14 @@ | ||
let decls: $ReadOnlyArray< | ||
NodePath<ExpressionStatement | VariableDeclaration>, | ||
> = path.unshiftContainer('body', statements); | ||
for (let decl of decls) { | ||
if (isVariableDeclaration(decl.node)) { | ||
let declarator = decl.get<NodePath<VariableDeclarator>>('declarations.0'); | ||
for (let [name] of (Object.entries( | ||
decl.getBindingIdentifierPaths(), | ||
): Array<[string, any]>)) { | ||
if (path.scope.hasOwnBinding(name)) { | ||
removeReplaceBinding(path.scope, name, declarator); | ||
} else { | ||
// $FlowFixMe | ||
path.scope.registerBinding(decl.node.kind, declarator); | ||
} | ||
} | ||
if (isCallExpression(declarator.node.init)) { | ||
if (!isIdentifier(declarator.node.init.callee, {name: 'require'})) { | ||
// $parcel$exportWildcard or $parcel$interopDefault | ||
let id = declarator.get<NodePath<Identifier>>('init.callee'); | ||
let {name} = id.node; | ||
nullthrows(path.scope.getBinding(name)).reference(id); | ||
for (let arg of declarator.get<$ReadOnlyArray<NodePath<Expression>>>( | ||
'init.arguments', | ||
)) { | ||
if (isIdentifier(arg.node)) { | ||
// $FlowFixMe | ||
nullthrows(path.scope.getBinding(arg.node.name)).reference(arg); | ||
} | ||
} | ||
} | ||
} else if (isIdentifier(declarator.node.init)) { | ||
// a temporary variable for the transpiled destructuring assigment | ||
nullthrows(path.scope.getBinding(declarator.node.init.name)).reference( | ||
declarator.get<NodePath<Identifier>>('init'), | ||
); | ||
} else if ( | ||
isMemberExpression(declarator.node.init) && | ||
isIdentifier(declarator.node.init.object) | ||
) { | ||
// (a temporary variable for the transpiled destructuring assigment).symbol | ||
nullthrows( | ||
path.scope.getBinding(declarator.node.init.object.name), | ||
).reference(declarator.get<NodePath<Identifier>>('init.object')); | ||
} | ||
} | ||
} | ||
return statements; | ||
} | ||
export function generateExports( | ||
export function generateBundleExports( | ||
bundleGraph: BundleGraph<NamedBundle>, | ||
bundle: NamedBundle, | ||
referencedAssets: Set<Asset>, | ||
path: NodePath<Program>, | ||
replacements: Map<Symbol, Symbol>, | ||
options: PluginOptions, | ||
maybeReplaceIdentifier: (NodePath<Identifier>) => void, | ||
): Set<Symbol> { | ||
scope: Scope, | ||
reexports: Set<{|exportAs: string, local: string|}>, | ||
): Array<BabelNode> { | ||
let exported = new Set<Symbol>(); | ||
let statements: Array<ExpressionStatement> = []; | ||
let statements: Array<BabelNode> = []; | ||
@@ -407,119 +327,73 @@ for (let asset of referencedAssets) { | ||
let entry = bundle.getMainEntry(); | ||
if (entry) { | ||
if (entry.meta.isCommonJS) { | ||
let exportsId = assertString(entry.meta.exportsIdentifier); | ||
for (let exp of reexports) { | ||
statements.push( | ||
EXPORT_TEMPLATE({ | ||
NAME: t.identifier(exp.exportAs), | ||
IDENTIFIER: t.identifier(exp.local), | ||
}), | ||
); | ||
} | ||
let binding = path.scope.getBinding(exportsId); | ||
if (binding) { | ||
// If the exports object is constant, then we can just remove it and rename the | ||
// references to the builtin CommonJS exports object. Otherwise, assign to module.exports. | ||
invariant(isVariableDeclarator(binding.path.node)); | ||
let init = binding.path.node.init; | ||
let isEmptyObject = | ||
init && isObjectExpression(init) && init.properties.length === 0; | ||
if (binding.constant && isEmptyObject) { | ||
for (let path of binding.referencePaths) { | ||
// This is never a ExportNamedDeclaration | ||
invariant(isIdentifier(path.node)); | ||
path.node.name = 'exports'; | ||
} | ||
return statements; | ||
} | ||
binding.path.remove(); | ||
exported.add('exports'); | ||
} else { | ||
exported.add(exportsId); | ||
statements.push( | ||
MODULE_EXPORTS_TEMPLATE({ | ||
IDENTIFIER: t.identifier(exportsId), | ||
}), | ||
); | ||
} | ||
export function generateMainExport( | ||
node: BabelNode, | ||
exported: Array<{|exportAs: string, local: string|}>, | ||
): Array<BabelNode> { | ||
let statements = [node]; | ||
for (let {exportAs, local} of exported) { | ||
if (exportAs === '*') { | ||
// Replace assignments to the `exports` object with `module.exports` | ||
if (isExpressionStatement(node)) { | ||
let expression = node.expression; | ||
invariant(isAssignmentExpression(expression)); | ||
expression.left = t.memberExpression( | ||
t.identifier('module'), | ||
t.identifier('exports'), | ||
); | ||
continue; | ||
} | ||
} else { | ||
for (let {exportAs, exportSymbol, symbol, asset, loc} of nullthrows( | ||
bundleGraph.getExportedSymbols(entry, bundle), | ||
)) { | ||
if (symbol === false) { | ||
// skipped | ||
} else if (symbol === null) { | ||
// TODO `meta.exportsIdentifier[exportSymbol]` should be exported | ||
let relativePath = relative(options.projectRoot, asset.filePath); | ||
throw getThrowableDiagnosticForNode( | ||
`${relativePath} couldn't be statically analyzed when importing '${exportSymbol}'`, | ||
entry.filePath, | ||
loc, | ||
); | ||
} else if (symbol != null && symbol !== false) { | ||
let hasReplacement = replacements.get(symbol); | ||
symbol = hasReplacement ?? symbol; | ||
// If there is an existing binding with the exported name (e.g. an import), | ||
// rename it so we can use the name for the export instead. | ||
if (path.scope.hasBinding(exportAs, true) && exportAs !== symbol) { | ||
rename(path.scope, exportAs, path.scope.generateUid(exportAs)); | ||
} | ||
// Remove the `exports` declaration if set to an empty object. | ||
// Otherwise, assign to `module.exports`. | ||
let isExports = false; | ||
if (isVariableDeclaration(node)) { | ||
let decl = node.declarations.find( | ||
decl => isIdentifier(decl.id) && decl.id.name === local, | ||
); | ||
isExports = | ||
decl && | ||
decl.init && | ||
isObjectExpression(decl.init) && | ||
decl.init.properties.length === 0; | ||
} | ||
let binding = path.scope.getBinding(symbol); | ||
if (binding) { | ||
if (!hasReplacement) { | ||
let id = | ||
// We cannot use the name if it's already used as global (e.g. `Map`). | ||
!t.isValidIdentifier(exportAs) || path.scope.hasGlobal(exportAs) | ||
? path.scope.generateUid(exportAs) | ||
: exportAs; | ||
// rename only once, avoid having to update `replacements` transitively | ||
rename(path.scope, symbol, id); | ||
replacements.set(symbol, id); | ||
symbol = id; | ||
} | ||
if (!isExports) { | ||
statements.push( | ||
MODULE_EXPORTS_TEMPLATE({ | ||
IDENTIFIER: t.identifier(local), | ||
}), | ||
); | ||
} else { | ||
statements.shift(); | ||
} | ||
} else { | ||
// Exports other than the default export are live bindings. | ||
// Only insert an assignment to module.exports for non-default exports. | ||
if (isExpressionStatement(node) && exportAs === 'default') { | ||
continue; | ||
} | ||
let [stmt] = binding.path.getStatementParent().insertAfter( | ||
EXPORT_TEMPLATE({ | ||
NAME: t.identifier(exportAs), | ||
IDENTIFIER: t.identifier(symbol), | ||
}), | ||
); | ||
binding.reference( | ||
stmt.get<NodePath<Identifier>>('expression.right'), | ||
); | ||
// Exports other than the default export are live bindings. Insert an assignment | ||
// after each constant violation so this remains true. | ||
if (exportAs !== 'default') { | ||
for (let path of binding.constantViolations) { | ||
let [stmt] = path.insertAfter( | ||
EXPORT_TEMPLATE({ | ||
NAME: t.identifier(exportAs), | ||
IDENTIFIER: t.identifier(symbol), | ||
}), | ||
); | ||
binding.reference( | ||
stmt.get<NodePath<Identifier>>('expression.right'), | ||
); | ||
} | ||
} | ||
} else { | ||
// `getExportedSymbols` can return `$id$import$foo` symbols so that cross-bundle imports | ||
// are resolved correctly. There is no binding in that case. | ||
let [decl] = path.pushContainer('body', [ | ||
EXPORT_TEMPLATE({ | ||
NAME: t.identifier(exportAs), | ||
IDENTIFIER: t.identifier(symbol), | ||
}), | ||
]); | ||
maybeReplaceIdentifier(decl.get('expression.right')); | ||
} | ||
} | ||
} | ||
statements.push( | ||
EXPORT_TEMPLATE({ | ||
NAME: t.identifier(exportAs), | ||
IDENTIFIER: t.identifier(local), | ||
}), | ||
); | ||
} | ||
} | ||
let stmts = path.pushContainer('body', statements); | ||
for (let stmt of stmts) { | ||
let id = stmt.get<NodePath<Identifier>>('expression.right'); | ||
nullthrows(path.scope.getBinding(id.node.name)).reference(id); | ||
} | ||
return exported; | ||
return statements; | ||
} |
// @flow strict-local | ||
import type { | ||
Asset, | ||
Bundle, | ||
BundleGraph, | ||
NamedBundle, | ||
PluginOptions, | ||
Symbol, | ||
} from '@parcel/types'; | ||
import type {NodePath} from '@babel/traverse'; | ||
import type { | ||
ClassDeclaration, | ||
FunctionDeclaration, | ||
Identifier, | ||
ExportSpecifier, | ||
ImportDeclaration, | ||
Program, | ||
VariableDeclarator, | ||
} from '@babel/types'; | ||
import type {Asset, Bundle, BundleGraph, NamedBundle} from '@parcel/types'; | ||
import type {Scope} from '@parcel/babylon-walk'; | ||
import type {ExternalBundle, ExternalModule} from '../types'; | ||
import * as t from '@babel/types'; | ||
import { | ||
isClassDeclaration, | ||
isExportNamedDeclaration, | ||
isFunctionDeclaration, | ||
isImportDeclaration, | ||
isVariableDeclaration, | ||
} from '@babel/types'; | ||
import invariant from 'assert'; | ||
import nullthrows from 'nullthrows'; | ||
import {relative} from 'path'; | ||
import {isExpressionStatement, isVariableDeclaration} from '@babel/types'; | ||
import {relativeBundlePath} from '@parcel/utils'; | ||
import rename from '../renamer'; | ||
import { | ||
getName, | ||
removeReplaceBinding, | ||
getThrowableDiagnosticForNode, | ||
verifyScopeState, | ||
} from '../utils'; | ||
import {assertString, getName} from '../utils'; | ||
export function generateBundleImports( | ||
bundleGraph: BundleGraph<NamedBundle>, | ||
from: NamedBundle, | ||
{bundle, assets}: ExternalBundle, | ||
path: NodePath<Program>, | ||
// Implement an interface consistent with other formats | ||
// eslint-disable-next-line no-unused-vars | ||
bundleGraph: BundleGraph<NamedBundle>, | ||
) { | ||
scope: Scope, | ||
): Array<BabelNode> { | ||
let specifiers = [...assets].map(asset => { | ||
@@ -56,3 +24,3 @@ let id = getName(asset, 'init'); | ||
let [decl] = path.unshiftContainer('body', [ | ||
return [ | ||
t.importDeclaration( | ||
@@ -62,8 +30,3 @@ specifiers, | ||
), | ||
]); | ||
for (let spec of decl.get<Array<NodePath<BabelNodeImportSpecifier>>>( | ||
'specifiers', | ||
)) { | ||
removeReplaceBinding(path.scope, spec.node.local.name, spec, 'module'); | ||
} | ||
]; | ||
} | ||
@@ -74,4 +37,5 @@ | ||
external: ExternalModule, | ||
path: NodePath<Program>, | ||
) { | ||
// eslint-disable-next-line no-unused-vars | ||
scope: Scope, | ||
): Array<BabelNode> { | ||
let {source, specifiers, isCommonJS} = external; | ||
@@ -93,3 +57,3 @@ let defaultSpecifier = null; | ||
let statements: Array<ImportDeclaration> = []; | ||
let statements: Array<BabelNode> = []; | ||
@@ -114,340 +78,104 @@ // ESModule syntax allows combining default and namespace specifiers, or default and named, but not all three. | ||
let decls = path.unshiftContainer('body', statements); | ||
for (let decl of decls) { | ||
let specifiers = decl.get< | ||
Array< | ||
NodePath< | ||
| BabelNodeImportSpecifier | ||
| BabelNodeImportDefaultSpecifier | ||
| BabelNodeImportNamespaceSpecifier, | ||
>, | ||
>, | ||
>('specifiers'); | ||
for (let specifier of specifiers) { | ||
for (let name of Object.keys(specifier.getBindingIdentifiers())) { | ||
removeReplaceBinding(path.scope, name, specifier, 'module'); | ||
} | ||
} | ||
} | ||
return statements; | ||
} | ||
export function generateExports( | ||
export function generateBundleExports( | ||
bundleGraph: BundleGraph<NamedBundle>, | ||
bundle: Bundle, | ||
bundle: NamedBundle, | ||
referencedAssets: Set<Asset>, | ||
programPath: NodePath<Program>, | ||
replacements: Map<Symbol, Symbol>, | ||
options: PluginOptions, | ||
maybeReplaceIdentifier: (NodePath<Identifier>) => void, | ||
): Set<Symbol> { | ||
// maps the bundles's export symbols to the bindings | ||
let exportedIdentifiers = new Map<Symbol, Symbol>(); | ||
// let exportedIdentifiersBailout = new Map<Symbol, [Asset, Symbol]>(); | ||
let entry = bundle.getMainEntry(); | ||
if (entry) { | ||
// Get all used symbols for this bundle (= entry + subgraph) | ||
let usedSymbols = new Set<Symbol>(); | ||
for (let d of bundleGraph.getIncomingDependencies(entry)) { | ||
let used = bundleGraph.getUsedSymbols(d); | ||
if (d.symbols.isCleared || used.has('*')) { | ||
usedSymbols = null; | ||
break; | ||
} | ||
used.forEach(s => nullthrows(usedSymbols).add(s)); | ||
} | ||
scope: Scope, | ||
reexports: Set<{|exportAs: string, local: string|}>, | ||
): Array<BabelNode> { | ||
let statements = []; | ||
for (let {exportAs, exportSymbol, symbol, asset, loc} of nullthrows( | ||
bundleGraph.getExportedSymbols(entry, bundle), | ||
)) { | ||
if (usedSymbols && !usedSymbols.has(exportAs)) { | ||
// an unused symbol | ||
continue; | ||
} | ||
if (referencedAssets.size > 0 || reexports.size > 0) { | ||
statements.push( | ||
t.exportNamedDeclaration( | ||
null, | ||
[...referencedAssets] | ||
.map(asset => { | ||
let name = getName(asset, 'init'); | ||
return t.exportSpecifier(t.identifier(name), t.identifier(name)); | ||
}) | ||
.concat( | ||
[...reexports].map(exp => | ||
t.exportSpecifier( | ||
t.identifier(exp.local), | ||
t.identifier(exp.exportAs), | ||
), | ||
), | ||
), | ||
), | ||
); | ||
} | ||
if (symbol === false) { | ||
// skipped | ||
} else if (symbol === null) { | ||
// TODO `asset.meta.exportsIdentifier[exportSymbol]` should be exported | ||
let relativePath = relative(options.projectRoot, asset.filePath); | ||
throw getThrowableDiagnosticForNode( | ||
`${relativePath} couldn't be statically analyzed when importing '${exportSymbol}'`, | ||
entry.filePath, | ||
loc, | ||
); | ||
// exportedIdentifiersBailout.set(exportAs, [asset, exportSymbol]); | ||
} else { | ||
invariant(symbol != null); | ||
symbol = replacements.get(symbol) || symbol; | ||
// If the main entry is a CommonJS asset, export its `module.exports` property as the `default` export | ||
let entry = bundle.getMainEntry(); | ||
if (entry?.meta.isCommonJS === true) { | ||
statements.push( | ||
t.exportDefaultDeclaration( | ||
t.identifier(assertString(entry.meta.exportsIdentifier)), | ||
), | ||
); | ||
} | ||
// Map CommonJS module.exports assignments to default ESM exports for interop | ||
if (exportAs === '*') { | ||
exportAs = 'default'; | ||
} | ||
return statements; | ||
} | ||
// If there is an existing binding with the exported name (e.g. an import), | ||
// rename it so we can use the name for the export instead. | ||
if ( | ||
programPath.scope.hasBinding(exportAs, true) && | ||
exportAs !== symbol | ||
) { | ||
rename( | ||
programPath.scope, | ||
exportAs, | ||
programPath.scope.generateUid(exportAs), | ||
); | ||
} | ||
exportedIdentifiers.set(exportAs, symbol); | ||
} | ||
} | ||
export function generateMainExport( | ||
node: BabelNode, | ||
exported: Array<{|exportAs: string, local: string|}>, | ||
): Array<BabelNode> { | ||
if (isExpressionStatement(node)) { | ||
return [node]; | ||
} | ||
for (let asset of referencedAssets) { | ||
let exportsId = getName(asset, 'init'); | ||
exportedIdentifiers.set(exportsId, exportsId); | ||
} | ||
let statements = []; | ||
let exported = new Set<Symbol>(); | ||
let bindingIdentifiers = t.getBindingIdentifiers(node); | ||
let ids: Array<string> = Object.keys(bindingIdentifiers); | ||
programPath.traverse({ | ||
Declaration(path) { | ||
if (path.isExportDeclaration() || path.parentPath.isExportDeclaration()) { | ||
return; | ||
} | ||
// Export '*' (re-exported CJS exports object) as default | ||
let defaultExport = exported.find( | ||
e => e.exportAs === 'default' || e.exportAs === '*', | ||
); | ||
let namedExports = exported.filter( | ||
e => e.exportAs !== 'default' && e.exportAs !== '*', | ||
); | ||
let {node} = path; | ||
if (exported.length === 1 && defaultExport && !isVariableDeclaration(node)) { | ||
// If there's only a default export, then export the declaration directly. | ||
// $FlowFixMe - we don't need to worry about type declarations here. | ||
statements.push(t.exportDefaultDeclaration(node)); | ||
} else if ( | ||
namedExports.length === exported.length && | ||
namedExports.length === ids.length && | ||
namedExports.every(({exportAs, local}) => exportAs === local) | ||
) { | ||
// If there's only named exports, all of the ids are exported, | ||
// and none of them are renamed, export the declaration directly. | ||
statements.push(t.exportNamedDeclaration(node, [])); | ||
} else { | ||
// Otherwise, add a default export and named export for the identifiers after the original declaration. | ||
statements.push(node); | ||
let bindingIdentifiers = path.getBindingIdentifierPaths(false, true); | ||
let ids: Array<string> = Object.keys(bindingIdentifiers); | ||
if (ids.length === 0) { | ||
return; | ||
} | ||
ids.sort(); | ||
let exportedIdentifiersFiltered = ([ | ||
...exportedIdentifiers.entries(), | ||
]: Array<[Symbol, Symbol]>) | ||
.filter( | ||
([exportSymbol, symbol]) => | ||
exportSymbol !== 'default' && ids.includes(symbol), | ||
) | ||
.sort(([, a], [, b]) => (a > b ? -1 : a < b ? 1 : 0)); | ||
let exportedSymbolsBindings = exportedIdentifiersFiltered.map( | ||
([, symbol]) => symbol, | ||
if (defaultExport) { | ||
statements.push( | ||
t.exportDefaultDeclaration(t.identifier(defaultExport.local)), | ||
); | ||
let exportedSymbols = exportedIdentifiersFiltered.map( | ||
([exportSymbol]) => exportSymbol, | ||
); | ||
} | ||
let defaultExport = exportedIdentifiers.get('default'); | ||
if (!ids.includes(defaultExport)) { | ||
defaultExport = null; | ||
} else { | ||
exportedIdentifiers.delete('default'); | ||
} | ||
// If all exports in the binding are named exports, export the entire declaration. | ||
// Also rename all of the identifiers to their exported name. | ||
if ( | ||
exportedSymbols.every(s => !path.scope.hasGlobal(s)) && | ||
areArraysStrictlyEqual(ids, exportedSymbolsBindings) && | ||
!path.isImportDeclaration() | ||
) { | ||
// We don't update the references in `node` itself (e.g. init), because this statement | ||
// will never be removed and therefore the shaking doesn't need correct | ||
// information. All existing references in `node` are "dead" but will also never be removed. | ||
if (process.env.PARCEL_BUILD_ENV !== 'production') { | ||
verifyScopeState(programPath.scope); | ||
} | ||
let [decl] = path.replaceWith(t.exportNamedDeclaration(node, [])); | ||
if (process.env.PARCEL_BUILD_ENV !== 'production') { | ||
programPath.scope.crawl(); | ||
} | ||
for (let sym of exportedSymbols) { | ||
let id = nullthrows(exportedIdentifiers.get(sym)); | ||
id = replacements.get(id) || id; | ||
nullthrows(path.scope.getBinding(id)).reference(decl); | ||
rename(path.scope, id, sym); | ||
replacements.set(id, sym); | ||
exported.add(sym); | ||
} | ||
// If the default export is part of the declaration, add it as well | ||
if (defaultExport != null) { | ||
defaultExport = replacements.get(defaultExport) || defaultExport; | ||
let binding = path.scope.getBinding(defaultExport); | ||
let insertPath = path; | ||
if (binding && !binding.constant) { | ||
insertPath = | ||
binding.constantViolations[binding.constantViolations.length - 1]; | ||
} | ||
let [decl] = insertPath.insertAfter( | ||
t.exportDefaultDeclaration(t.identifier(defaultExport)), | ||
); | ||
binding?.reference(decl); | ||
} | ||
// If there is only a default export, export the entire declaration. | ||
} else if ( | ||
ids.length === 1 && | ||
defaultExport != null && | ||
!isVariableDeclaration(node) && | ||
!isImportDeclaration(node) | ||
) { | ||
invariant(isFunctionDeclaration(node) || isClassDeclaration(node)); | ||
let binding = nullthrows( | ||
path.scope.getBinding(nullthrows(node.id).name), | ||
); | ||
// We don't update the references in `node` itself (e.g. function body), because this statement | ||
// will never be removed and therefore the shaking doesn't need correct | ||
// information. All existing references in `node` are "dead" but will also never be removed. | ||
if (process.env.PARCEL_BUILD_ENV !== 'production') { | ||
verifyScopeState(programPath.scope); | ||
} | ||
let [decl] = path.replaceWith(t.exportDefaultDeclaration(node)); | ||
if (process.env.PARCEL_BUILD_ENV !== 'production') { | ||
programPath.scope.crawl(); | ||
} | ||
binding.path = decl.get< | ||
NodePath<FunctionDeclaration | ClassDeclaration>, | ||
>('declaration'); | ||
binding.reference(decl); | ||
// Otherwise, add export statements after for each identifier. | ||
} else { | ||
if (defaultExport != null) { | ||
defaultExport = replacements.get(defaultExport) || defaultExport; | ||
let binding = path.scope.getBinding(defaultExport); | ||
let insertPath = path; | ||
if (binding && !binding.constant) { | ||
insertPath = | ||
binding.constantViolations[binding.constantViolations.length - 1]; | ||
} | ||
let node = t.exportDefaultDeclaration(t.identifier(defaultExport)); | ||
let decl; | ||
if (insertPath.parentPath.isProgram()) { | ||
[decl] = insertPath.insertAfter(node); | ||
} else { | ||
[decl] = programPath.pushContainer('body', node); | ||
} | ||
binding?.reference(decl.get<NodePath<Identifier>>('declaration')); | ||
} | ||
if (exportedSymbols.length > 0) { | ||
let [decl] = path.insertAfter(t.exportNamedDeclaration(null, [])); | ||
for (let sym of exportedSymbols) { | ||
let id = nullthrows(exportedIdentifiers.get(sym)); | ||
id = replacements.get(id) || id; | ||
let symLocal = path.scope.hasGlobal(sym) | ||
? path.scope.generateUid(sym) | ||
: sym; | ||
rename(path.scope, id, symLocal); | ||
replacements.set(id, symLocal); | ||
exported.add(symLocal); | ||
let [spec] = decl.unshiftContainer('specifiers', [ | ||
t.exportSpecifier(t.identifier(symLocal), t.identifier(sym)), | ||
]); | ||
path.scope | ||
.getBinding(symLocal) | ||
?.reference(spec.get<NodePath<Identifier>>('local')); | ||
} | ||
} | ||
} | ||
exportedSymbols.forEach(s => exportedIdentifiers.delete(s)); | ||
}, | ||
}); | ||
if (exportedIdentifiers.size > 0) { | ||
let declarations = []; | ||
let exportedIdentifiersSpecifiers = []; | ||
// `export { $id$init().foo as foo};` is not valid, so instead do: | ||
// ``` | ||
// let syntheticExport$foo = $id$init().foo; | ||
// export { syntheticExport$foo as foo}; | ||
// ``` | ||
for (let [exportAs, symbol] of exportedIdentifiers) { | ||
declarations.push( | ||
t.variableDeclarator( | ||
t.identifier('syntheticExport$' + exportAs), | ||
t.identifier(symbol), | ||
if (namedExports.length > 0) { | ||
statements.push( | ||
t.exportNamedDeclaration( | ||
null, | ||
namedExports.map(e => | ||
t.exportSpecifier(t.identifier(e.local), t.identifier(e.exportAs)), | ||
), | ||
), | ||
); | ||
exportedIdentifiersSpecifiers.push( | ||
t.exportSpecifier( | ||
t.identifier('syntheticExport$' + exportAs), | ||
t.identifier(exportAs), | ||
), | ||
); | ||
} | ||
let [decl, exports] = programPath.pushContainer('body', [ | ||
t.variableDeclaration('var', declarations), | ||
t.exportNamedDeclaration(null, exportedIdentifiersSpecifiers), | ||
]); | ||
invariant(isVariableDeclaration(decl.node)); | ||
programPath.scope.registerDeclaration(decl); | ||
for (let d of decl.get<Array<NodePath<VariableDeclarator>>>( | ||
'declarations', | ||
)) { | ||
maybeReplaceIdentifier(d.get<NodePath<Identifier>>('init')); | ||
} | ||
invariant(isExportNamedDeclaration(exports.node)); | ||
programPath.scope.registerDeclaration(exports); | ||
for (let e of exports.get<Array<NodePath<ExportSpecifier>>>('specifiers')) { | ||
nullthrows(programPath.scope.getBinding(e.node.local.name)).reference( | ||
e.get<NodePath<Identifier>>('local'), | ||
); | ||
} | ||
} | ||
// This would be needed if we want to export symbols from different bundles, | ||
// but it's currently not possible to actually trigger this. | ||
// | ||
// if (exportedIdentifiersBailout.size > 0) { | ||
// let declarations = []; | ||
// let exportedIdentifiersBailoutSpecifiers = []; | ||
// for (let [exportAs, [asset, exportSymbol]] of exportedIdentifiersBailout) { | ||
// invariant( | ||
// !programPath.scope.hasBinding( | ||
// getExportIdentifier(asset, exportSymbol).name, | ||
// ), | ||
// ); | ||
// invariant(programPath.scope.hasBinding(getName(asset, 'init'))); | ||
// declarations.push( | ||
// t.variableDeclarator( | ||
// getExportIdentifier(asset, exportSymbol), | ||
// t.memberExpression( | ||
// t.callExpression(t.identifier(getName(asset, 'init')), []), // it isn't in this bundle, TODO import if not already there | ||
// t.identifier(exportSymbol), | ||
// ), | ||
// ), | ||
// ); | ||
// exportedIdentifiersBailoutSpecifiers.push( | ||
// t.exportSpecifier( | ||
// getExportIdentifier(asset, exportSymbol), | ||
// t.identifier(exportAs), | ||
// ), | ||
// ); | ||
// } | ||
// programPath.pushContainer('body', [ | ||
// t.variableDeclaration('var', declarations), | ||
// t.exportNamedDeclaration(null, exportedIdentifiersBailoutSpecifiers), | ||
// ]); | ||
// programPath.scope.crawl(); | ||
// } | ||
return exported; | ||
return statements; | ||
} | ||
function areArraysStrictlyEqual<T>(a: Array<T>, b: Array<T>) { | ||
return ( | ||
a.length === b.length && | ||
a.every(function(a_v, i) { | ||
return a_v === b[i]; | ||
}) | ||
); | ||
} |
// @flow | ||
import type {Asset, BundleGraph, NamedBundle} from '@parcel/types'; | ||
import type { | ||
Asset, | ||
Bundle, | ||
BundleGraph, | ||
NamedBundle, | ||
PluginOptions, | ||
Symbol, | ||
} from '@parcel/types'; | ||
import type {NodePath} from '@babel/traverse'; | ||
import type { | ||
ExpressionStatement, | ||
Identifier, | ||
Node, | ||
Program, | ||
LVal, | ||
Statement, | ||
StringLiteral, | ||
CallExpression, | ||
VariableDeclaration, | ||
Expression, | ||
} from '@babel/types'; | ||
import type {ExternalBundle, ExternalModule} from '../types'; | ||
import type {Scope} from '@parcel/babylon-walk'; | ||
import invariant from 'assert'; | ||
import * as t from '@babel/types'; | ||
import template from '@babel/template'; | ||
import {relativeBundlePath} from '@parcel/utils'; | ||
import nullthrows from 'nullthrows'; | ||
import { | ||
assertString, | ||
getName, | ||
getIdentifier, | ||
getThrowableDiagnosticForNode, | ||
@@ -36,6 +28,6 @@ isEntry, | ||
const IMPORT_TEMPLATE = template.expression< | ||
{|ASSET_ID: StringLiteral|}, | ||
CallExpression, | ||
>('parcelRequire(ASSET_ID)'); | ||
const IMPORT_TEMPLATE = template.statement< | ||
{|NAME: Identifier, ASSET_ID: StringLiteral|}, | ||
ExpressionStatement, | ||
>('var NAME = parcelRequire(ASSET_ID);'); | ||
const EXPORT_TEMPLATE = template.statement< | ||
@@ -53,9 +45,16 @@ {|IDENTIFIER: Identifier, ASSET_ID: StringLiteral|}, | ||
>('importScripts(BUNDLE);'); | ||
const DEFAULT_INTEROP_TEMPLATE = template.statement< | ||
{| | ||
NAME: LVal, | ||
MODULE: Expression, | ||
|}, | ||
VariableDeclaration, | ||
>('var NAME = $parcel$interopDefault(MODULE);'); | ||
export function generateBundleImports( | ||
bundleGraph: BundleGraph<NamedBundle>, | ||
from: NamedBundle, | ||
{bundle, assets}: ExternalBundle, | ||
path: NodePath<Program>, | ||
bundleGraph: BundleGraph<NamedBundle>, | ||
) { | ||
scope: Scope, | ||
): Array<BabelNode> { | ||
let statements = []; | ||
@@ -69,18 +68,33 @@ if (from.env.isWorker()) { | ||
} | ||
path.unshiftContainer('body', statements); | ||
for (let asset of assets) { | ||
// `var ${id};` was inserted already, add RHS | ||
let res: NodePath<CallExpression>[] = nullthrows( | ||
path.scope.getBinding(getName(asset, 'init')), | ||
) | ||
.path.get('init') | ||
.replaceWith( | ||
IMPORT_TEMPLATE({ | ||
ASSET_ID: t.stringLiteral(bundleGraph.getAssetPublicId(asset)), | ||
}), | ||
statements.push( | ||
IMPORT_TEMPLATE({ | ||
NAME: getIdentifier(asset, 'init'), | ||
ASSET_ID: t.stringLiteral(bundleGraph.getAssetPublicId(asset)), | ||
}), | ||
); | ||
scope.add('$parcel$global'); | ||
scope.add('parcelRequire'); | ||
if (asset.meta.isCommonJS) { | ||
let deps = bundleGraph.getIncomingDependencies(asset); | ||
let hasDefaultInterop = deps.some( | ||
dep => | ||
dep.symbols.hasExportSymbol('default') && from.hasDependency(dep), | ||
); | ||
if (hasDefaultInterop) { | ||
statements.push( | ||
DEFAULT_INTEROP_TEMPLATE({ | ||
NAME: getIdentifier(asset, '$interop$default'), | ||
MODULE: t.callExpression(getIdentifier(asset, 'init'), []), | ||
}), | ||
); | ||
path.scope.getBinding('parcelRequire')?.reference(res[0].get('callee')); | ||
scope.add('$parcel$interopDefault'); | ||
} | ||
} | ||
} | ||
return statements; | ||
} | ||
@@ -90,7 +104,7 @@ | ||
// eslint-disable-next-line no-unused-vars | ||
bundle: Bundle, | ||
bundle: NamedBundle, | ||
{loc}: ExternalModule, | ||
// eslint-disable-next-line no-unused-vars | ||
path: NodePath<Program>, | ||
) { | ||
scope: Scope, | ||
): Array<BabelNode> { | ||
throw getThrowableDiagnosticForNode( | ||
@@ -103,20 +117,14 @@ 'External modules are not supported when building for browser', | ||
export function generateExports( | ||
export function generateBundleExports( | ||
bundleGraph: BundleGraph<NamedBundle>, | ||
bundle: NamedBundle, | ||
referencedAssets: Set<Asset>, | ||
path: NodePath<Program>, | ||
scope: Scope, | ||
// eslint-disable-next-line no-unused-vars | ||
replacements: Map<Symbol, Symbol>, | ||
// eslint-disable-next-line no-unused-vars | ||
options: PluginOptions, | ||
// eslint-disable-next-line no-unused-vars | ||
maybeReplaceIdentifier: (NodePath<Identifier>) => void, | ||
): Set<Symbol> { | ||
let exported = new Set<Symbol>(); | ||
let statements: Array<ExpressionStatement> = []; | ||
reexports: Set<{|exportAs: string, local: string|}>, | ||
): Array<BabelNode> { | ||
let statements: Array<BabelNode> = []; | ||
for (let asset of referencedAssets) { | ||
let exportsId = getName(asset, 'init'); | ||
exported.add(exportsId); | ||
@@ -129,2 +137,5 @@ statements.push( | ||
); | ||
scope.add('$parcel$global'); | ||
scope.add('parcelRequire'); | ||
} | ||
@@ -138,5 +149,2 @@ | ||
) { | ||
let exportsId = assertString(entry.meta.exportsIdentifier); | ||
exported.add(exportsId); | ||
statements.push( | ||
@@ -150,24 +158,16 @@ // Export a function returning the exports, as other cases of global output | ||
); | ||
} | ||
let decls = path.pushContainer('body', statements); | ||
for (let decl of decls) { | ||
let callee = decl.get('expression.callee'); | ||
if (callee.isMemberExpression()) { | ||
callee = callee.get('object'); | ||
} | ||
path.scope.getBinding(callee.node.name)?.reference(callee); | ||
let arg = decl.get<NodePath<Node>>('expression.arguments.1'); | ||
if (!arg.isIdentifier()) { | ||
// anonymous init function expression | ||
invariant(arg.isFunctionExpression()); | ||
arg = arg.get<NodePath<Identifier>>('body.body.0.argument'); | ||
} | ||
// $FlowFixMe | ||
path.scope.getBinding(arg.node.name)?.reference(arg); | ||
scope.add('$parcel$global'); | ||
scope.add('parcelRequire'); | ||
} | ||
return exported; | ||
return statements; | ||
} | ||
export function generateMainExport( | ||
node: BabelNode, | ||
// eslint-disable-next-line no-unused-vars | ||
exported: Array<{|exportAs: string, local: string|}>, | ||
): Array<BabelNode> { | ||
return [node]; | ||
} |
@@ -17,3 +17,3 @@ // @flow | ||
import babelGenerate from '@babel/generator'; | ||
import {generateAST} from '@parcel/babel-ast-utils'; | ||
import invariant from 'assert'; | ||
@@ -103,18 +103,12 @@ import {isEntry} from './utils'; | ||
let {code, rawMappings} = babelGenerate(ast, { | ||
let {content, map} = generateAST({ | ||
ast, | ||
sourceMaps: !!bundle.env.sourceMap, | ||
minified: bundle.env.minify, | ||
comments: true, // retain /*@__PURE__*/ comments for terser | ||
options, | ||
}); | ||
let map = null; | ||
if (bundle.env.sourceMap && rawMappings != null) { | ||
map = new SourceMap(options.projectRoot); | ||
map.addIndexedMappings(rawMappings); | ||
} | ||
return { | ||
contents: code, | ||
contents: content, | ||
map, | ||
}; | ||
} |
423
src/hoist.js
@@ -41,3 +41,6 @@ // @flow | ||
isUnaryExpression, | ||
isVariableDeclaration, | ||
isVariableDeclarator, | ||
isExpressionStatement, | ||
isSequenceExpression, | ||
} from '@babel/types'; | ||
@@ -110,2 +113,3 @@ import traverse from '@babel/traverse'; | ||
asset.meta.exportsIdentifier = getName(asset, 'exports'); | ||
asset.meta.staticExports = true; | ||
@@ -161,3 +165,3 @@ traverse.cache.clearScope(); | ||
node.name === 'module' && | ||
(!isMemberExpression(parent) || parent.computed) && | ||
!isStaticMemberExpression(parent) && | ||
!(isUnaryExpression(parent) && parent.operator === 'typeof') && | ||
@@ -180,9 +184,7 @@ !path.scope.hasBinding('module') && | ||
isAssignmentExpression(parent, {left: node}) || | ||
(isMemberExpression(parent) && | ||
((isIdentifier(parent.property) && !parent.computed) || | ||
isStringLiteral(parent.property))) || | ||
isStaticMemberExpression(parent) || | ||
path.scope.getData('shouldWrap') | ||
) | ||
) { | ||
asset.meta.resolveExportsBailedOut = true; | ||
asset.meta.staticExports = false; | ||
// The namespace object is used in the asset itself | ||
@@ -220,9 +222,7 @@ asset.addDependency({ | ||
isAssignmentExpression(parent, {left: node}) || | ||
(isMemberExpression(parent) && | ||
((isIdentifier(parent.property) && !parent.computed) || | ||
isStringLiteral(parent.property))) || | ||
isStaticMemberExpression(parent) || | ||
path.scope.getData('shouldWrap') | ||
) | ||
) { | ||
asset.meta.resolveExportsBailedOut = true; | ||
asset.meta.staticExports = false; | ||
// The namespace object is used in the asset itself | ||
@@ -255,2 +255,6 @@ asset.addDependency({ | ||
path.scope.setData('cjsExportsReassigned', false); | ||
if (shouldWrap) { | ||
asset.meta.staticExports = false; | ||
} | ||
}, | ||
@@ -293,6 +297,3 @@ | ||
// Add variable that represents module.exports if it is referenced and not declared. | ||
if ( | ||
scope.hasGlobal(exportsIdentifier.name) && | ||
!scope.hasBinding(exportsIdentifier.name) | ||
) { | ||
if (!scope.hasBinding(exportsIdentifier.name)) { | ||
scope.push({id: exportsIdentifier, init: t.objectExpression([])}); | ||
@@ -302,8 +303,2 @@ } | ||
if (asset.meta.isCommonJS) { | ||
if (asset.meta.resolveExportsBailedOut) { | ||
for (let s of asset.symbols.exportSymbols()) { | ||
asset.symbols.delete(s); | ||
} | ||
} | ||
asset.symbols.set('*', exportsIdentifier.name); | ||
@@ -331,10 +326,17 @@ } | ||
if (t.matchesPattern(path.node, 'module.exports')) { | ||
let exportsId = getExportsIdentifier(asset, path.scope); | ||
path.replaceWith(exportsId); | ||
asset.symbols.set('*', exportsId.name, convertBabelLoc(path.node.loc)); | ||
// Replace module.exports.foo with exported identifier if possible, | ||
// and add a self-referencing dependency so we know the symbol is used. | ||
let selfReference = addSelfReference(path, asset); | ||
if (selfReference) { | ||
path.parentPath.replaceWith(selfReference); | ||
} else { | ||
let exportsId = getExportsIdentifier(asset, path.scope); | ||
asset.symbols.set('*', exportsId.name, convertBabelLoc(path.node.loc)); | ||
path.replaceWith(exportsId); | ||
if (!path.scope.hasBinding(exportsId.name)) { | ||
path.scope | ||
.getProgramParent() | ||
.push({id: exportsId, init: t.objectExpression([])}); | ||
if (!path.scope.hasBinding(exportsId.name)) { | ||
path.scope | ||
.getProgramParent() | ||
.push({id: t.clone(exportsId), init: t.objectExpression([])}); | ||
} | ||
} | ||
@@ -364,4 +366,17 @@ } else if (t.matchesPattern(path.node, 'module.id')) { | ||
) { | ||
path.replaceWith(getCJSExportsIdentifier(asset, path.scope)); | ||
asset.meta.isCommonJS = true; | ||
// Mark if exports is accessed non-statically. | ||
if (!isStaticMemberExpression(path.parent)) { | ||
asset.meta.staticExports = false; | ||
} | ||
// Replace exports.foo with exported identifier if possible, | ||
// and add a self-referencing dependency so we know the symbol is used. | ||
let selfReference = addSelfReference(path, asset); | ||
if (selfReference) { | ||
path.parentPath.replaceWith(selfReference); | ||
} else { | ||
path.replaceWith(getCJSExportsIdentifier(asset, path.scope)); | ||
} | ||
} | ||
@@ -388,7 +403,20 @@ | ||
asset.meta.isCommonJS = true; | ||
// Mark if exports is accessed non-statically. | ||
if (!isStaticMemberExpression(path.parent)) { | ||
asset.meta.staticExports = false; | ||
} | ||
if (asset.meta.isES6Module) { | ||
path.replaceWith(t.identifier('undefined')); | ||
} else { | ||
path.replaceWith(getExportsIdentifier(asset, path.scope)); | ||
asset.meta.isCommonJS = true; | ||
// Replace this.foo with exported identifier if possible, | ||
// and add a self-referencing dependency so we know the symbol is used. | ||
let selfReference = addSelfReference(path, asset); | ||
if (selfReference) { | ||
path.parentPath.replaceWith(selfReference); | ||
} else { | ||
path.replaceWith(getExportsIdentifier(asset, path.scope)); | ||
} | ||
} | ||
@@ -443,2 +471,3 @@ } | ||
asset.meta.isCommonJS = true; | ||
asset.meta.staticExports = false; | ||
} | ||
@@ -472,12 +501,2 @@ | ||
if (!scope.hasBinding(identifier.name)) { | ||
// These have a special meaning, we'll have to fallback from the '*' symbol. | ||
// '*' will always be registered into the symbols at the end. | ||
if (name !== 'default' && name !== '*') { | ||
asset.symbols.set( | ||
name, | ||
identifier.name, | ||
convertBabelLoc(path.node.loc), | ||
); | ||
} | ||
// If in the program scope, create a variable declaration and initialize with the exported value. | ||
@@ -501,2 +520,16 @@ // Otherwise, declare the variable in the program scope, and assign to it here. | ||
} | ||
// These have a special meaning, we'll have to fallback from the '*' symbol. | ||
// '*' will always be registered into the symbols at the end. | ||
if ( | ||
(name !== 'default' || asset.symbols.hasExportSymbol('__esModule')) && | ||
name !== '*' | ||
) { | ||
asset.symbols.set( | ||
name, | ||
identifier.name, | ||
convertBabelLoc(path.node.loc), | ||
{isPure: isPure(scope.getBinding(identifier.name))}, | ||
); | ||
} | ||
} else { | ||
@@ -508,2 +541,7 @@ path.insertBefore( | ||
); | ||
let meta = asset.symbols.get(name)?.meta; | ||
if (meta != null) { | ||
meta.isPure = false; | ||
} | ||
} | ||
@@ -549,7 +587,2 @@ | ||
if (!dep.isAsync) { | ||
// the dependencies visitor replaces import() with require() | ||
asset.meta.isCommonJS = true; | ||
} | ||
// If this require call does not occur in the top-level, e.g. in a function | ||
@@ -570,9 +603,19 @@ // or inside an if statement, or if it might potentially happen conditionally, | ||
// Generate a variable name based on the current asset id and the module name to require. | ||
// This will be replaced by the final variable name of the resolved asset in the packager. | ||
let replacement = REQUIRE_CALL_TEMPLATE({ | ||
ID: t.stringLiteral(asset.id), | ||
SOURCE: t.stringLiteral(arg.value), | ||
}); | ||
replacement.loc = path.node.loc; | ||
let memberAccesses: ?Array<{|name: string, loc: ?SourceLocation|}>; | ||
let properties: ?Array<RestElement | ObjectProperty>; | ||
let propertyScope; | ||
let replacePath; | ||
let binding; | ||
let {parent} = path; | ||
// Try to statically analyze a dynamic import() call | ||
let memberAccesses: ?Array<{|name: string, loc: ?SourceLocation|}>; | ||
if (dep.isAsync) { | ||
let properties: ?Array<RestElement | ObjectProperty>; | ||
let binding; | ||
let {parent} = path; | ||
let {parent: grandparent} = path.parentPath; | ||
@@ -619,36 +662,107 @@ if ( | ||
} | ||
} else if (isStaticMemberExpression(parent, {object: path.node})) { | ||
// e.g. require('foo').bar | ||
// $FlowFixMe | ||
let name = parent.property.name ?? parent.property.value; | ||
memberAccesses = [ | ||
{ | ||
name, | ||
loc: convertBabelLoc(parent.loc), | ||
}, | ||
]; | ||
if ( | ||
properties != null && | ||
properties.every(p => isObjectProperty(p) && isIdentifier(p.key)) | ||
) { | ||
// take symbols listed when destructuring | ||
memberAccesses = properties.map(p => { | ||
invariant(isObjectProperty(p)); | ||
invariant(isIdentifier(p.key)); | ||
return {name: p.key.name, loc: convertBabelLoc(p.loc)}; | ||
}); | ||
} else if ( | ||
!path.scope.getData('shouldWrap') && // eval is evil | ||
binding != null && | ||
binding.constant && | ||
binding.referencePaths.every( | ||
({parent, node}) => | ||
isMemberExpression(parent, {object: node}) && | ||
((!parent.computed && isIdentifier(parent.property)) || | ||
isStringLiteral(parent.property)), | ||
) | ||
) { | ||
// properties of member expressions if all of them are static | ||
memberAccesses = binding.referencePaths.map(({parent}) => { | ||
invariant(isMemberExpression(parent)); | ||
return { | ||
// $FlowFixMe[prop-missing] | ||
name: parent.property.name ?? parent.property.value, | ||
loc: convertBabelLoc(parent.loc), | ||
}; | ||
}); | ||
// If in an assignment expression, replace with a sequence expression | ||
// so that the $parcel$require is still in the correct position. | ||
// Otherwise, add a third argument to the $parcel$require call set to | ||
// the identifier to replace it with. This will be replaced in the linker. | ||
if (isAssignmentExpression(path.parentPath.parent, {left: parent})) { | ||
let assignment = t.cloneNode(path.parentPath.parent); | ||
assignment.left = t.identifier( | ||
getName(asset, 'importAsync', dep.id, name), | ||
); | ||
path.parentPath.parentPath.replaceWith( | ||
t.sequenceExpression([replacement, assignment]), | ||
); | ||
replacement = null; | ||
} else { | ||
replacement.arguments.push( | ||
t.identifier(getName(asset, 'importAsync', dep.id, name)), | ||
); | ||
replacePath = path.parentPath; | ||
} | ||
} else if (isVariableDeclarator(parent, {init: path.node})) { | ||
if (isObjectPattern(parent.id)) { | ||
// let { x: y } = require("./b.js"); | ||
properties = parent.id.properties; | ||
propertyScope = path.parentPath.parentPath.scope; | ||
} else if (isIdentifier(parent.id)) { | ||
// let ns = require("./b.js"); | ||
binding = path.parentPath.parentPath.scope.getBinding(parent.id.name); | ||
} | ||
replacePath = path.parentPath; | ||
} else if ( | ||
// ({ x: y } = require("./b.js")); | ||
isAssignmentExpression(parent, {right: path.node}) && | ||
isObjectPattern(parent.left) && | ||
isUnusedValue(path.parentPath) | ||
) { | ||
properties = parent.left.properties; | ||
propertyScope = path.parentPath.scope; | ||
replacePath = path.parentPath; | ||
} | ||
if ( | ||
properties != null && | ||
properties.length > 0 && | ||
properties.every(p => isObjectProperty(p) && isIdentifier(p.key)) | ||
) { | ||
// take symbols listed when destructuring | ||
memberAccesses = properties.map(p => { | ||
invariant(isObjectProperty(p)); | ||
invariant(isIdentifier(p.key)); | ||
if (!dep.isAsync) { | ||
let name = p.key.name; | ||
let binding = propertyScope.getBinding(name); | ||
if (binding) { | ||
for (let ref of binding.referencePaths) { | ||
ref.replaceWith( | ||
t.identifier(getName(asset, 'importAsync', dep.id, name)), | ||
); | ||
} | ||
} | ||
} | ||
return {name: p.key.name, loc: convertBabelLoc(p.loc)}; | ||
}); | ||
} else if ( | ||
!path.scope.getData('shouldWrap') && // eval is evil | ||
binding != null && | ||
binding.constant && | ||
binding.referencePaths.length > 0 && | ||
binding.referencePaths.every(({parent, node}) => | ||
isStaticMemberExpression(parent, {object: node}), | ||
) | ||
) { | ||
// properties of member expressions if all of them are static | ||
memberAccesses = binding.referencePaths.map(({parentPath, parent}) => { | ||
invariant(isMemberExpression(parent)); | ||
// $FlowFixMe | ||
let name = parent.property.name ?? parent.property.value; | ||
if (!dep.isAsync) { | ||
parentPath.replaceWith( | ||
t.identifier(getName(asset, 'importAsync', dep.id, name)), | ||
); | ||
} | ||
return { | ||
// $FlowFixMe[prop-missing] | ||
name, | ||
loc: convertBabelLoc(parent.loc), | ||
}; | ||
}); | ||
} | ||
dep.symbols.ensure(); | ||
@@ -664,5 +778,4 @@ if (memberAccesses != null) { | ||
} | ||
} else { | ||
} else if (!isUnusedValue(path)) { | ||
// non-async and async fallback: everything | ||
dep.meta.isCommonJS = true; | ||
dep.symbols.set( | ||
@@ -673,12 +786,41 @@ '*', | ||
); | ||
// Mark the dependency as CJS so that we keep the $id$exports var in the linker. | ||
dep.meta.isCommonJS = true; | ||
} | ||
// Generate a variable name based on the current asset id and the module name to require. | ||
// This will be replaced by the final variable name of the resolved asset in the packager. | ||
let replacement = REQUIRE_CALL_TEMPLATE({ | ||
ID: t.stringLiteral(asset.id), | ||
SOURCE: t.stringLiteral(arg.value), | ||
}); | ||
replacement.loc = path.node.loc; | ||
path.replaceWith(replacement); | ||
if (memberAccesses != null && replacePath && replacement) { | ||
// Can't replace a variable declarator with a function call. | ||
// Need to replace the whole declaration. | ||
if (isVariableDeclarator(replacePath.node)) { | ||
let declaration = replacePath.parent; | ||
invariant(isVariableDeclaration(declaration)); | ||
// If there is only one declarator, it's safe to replace the whole declaration. | ||
// Otherwise, split into multiple declarations so we can replace just one | ||
// with an expression statement containing the $parcel$require call. | ||
if (declaration.declarations.length === 1) { | ||
replacePath.parentPath.replaceWith(replacement); | ||
} else { | ||
let declIndex = declaration.declarations.indexOf(replacePath.node); | ||
replacePath.parentPath.insertBefore( | ||
t.variableDeclaration( | ||
declaration.kind, | ||
declaration.declarations.slice(0, declIndex), | ||
), | ||
); | ||
replacePath.parentPath.insertBefore( | ||
t.expressionStatement(replacement), | ||
); | ||
for (let i = declIndex; i >= 0; i--) { | ||
replacePath.parentPath.get(`declarations.${i}`).remove(); | ||
} | ||
} | ||
} else { | ||
replacePath.replaceWith(replacement); | ||
} | ||
} else if (replacement) { | ||
path.replaceWith(replacement); | ||
} | ||
} else if (t.matchesPattern(callee, 'require.resolve')) { | ||
@@ -699,3 +841,6 @@ let replacement = REQUIRE_RESOLVE_CALL_TEMPLATE({ | ||
if (dep) dep.symbols.ensure(); | ||
if (dep) { | ||
dep.meta.isES6Module = true; | ||
dep.symbols.ensure(); | ||
} | ||
@@ -727,5 +872,3 @@ // For each specifier, rename the local variables to point to the imported name. | ||
isIdentifier(node) && | ||
isMemberExpression(parent, {object: node}) && | ||
((!parent.computed && isIdentifier(parent.property)) || | ||
isStringLiteral(parent.property)) | ||
isStaticMemberExpression(parent, {object: node}) | ||
) { | ||
@@ -853,3 +996,6 @@ let imported: string = | ||
if (!asset.symbols.hasExportSymbol('default')) { | ||
asset.symbols.set('default', identifier.name, convertBabelLoc(loc)); | ||
let binding = path.scope.getBinding(identifier.name); | ||
asset.symbols.set('default', identifier.name, convertBabelLoc(loc), { | ||
isPure: isPure(binding), | ||
}); | ||
} | ||
@@ -866,3 +1012,6 @@ }, | ||
if (dep) dep.symbols.ensure(); | ||
if (dep) { | ||
dep.meta.isES6Module = true; | ||
dep.symbols.ensure(); | ||
} | ||
@@ -916,4 +1065,2 @@ for (let specifier of nullthrows(specifiers)) { | ||
} else if (declaration) { | ||
path.replaceWith(declaration); | ||
if (isIdentifier(declaration.id)) { | ||
@@ -927,2 +1074,4 @@ addExport(asset, path, declaration.id, declaration.id); | ||
} | ||
path.replaceWith(declaration); | ||
} else { | ||
@@ -942,2 +1091,3 @@ for (let specifier of specifiers) { | ||
if (dep) { | ||
dep.meta.isES6Module = true; | ||
dep.symbols.ensure(); | ||
@@ -967,2 +1117,23 @@ dep.symbols.set('*', '*', convertBabelLoc(path.node.loc), true); | ||
function isPure(binding) { | ||
if (!binding || !binding.constant) { | ||
return false; | ||
} | ||
let references = binding.referencePaths.filter( | ||
reference => !reference.isExportDeclaration(), | ||
); | ||
if (references.length > 0) { | ||
return false; | ||
} | ||
let path = binding.path; | ||
if (isVariableDeclarator(path.node) && isIdentifier(path.node.id)) { | ||
let init = path.get<NodePath<Expression>>('init'); | ||
return init.isPure() || init.isIdentifier() || init.isThisExpression(); | ||
} | ||
return path.isPure(); | ||
} | ||
function addImport( | ||
@@ -1010,2 +1181,3 @@ asset: MutableAsset, | ||
if (!asset.symbols.hasExportSymbol(exported.name)) { | ||
let binding = scope.getBinding(local.name); | ||
asset.symbols.set( | ||
@@ -1015,2 +1187,3 @@ exported.name, | ||
convertBabelLoc(exported.loc), | ||
{isPure: isPure(binding)}, | ||
); | ||
@@ -1087,1 +1260,57 @@ } | ||
} | ||
function isUnusedValue(path: NodePath<Node>): boolean { | ||
let {parent} = path; | ||
return ( | ||
isExpressionStatement(parent) || | ||
(isSequenceExpression(parent) && | ||
((Array.isArray(path.container) && | ||
path.key !== path.container.length - 1) || | ||
isUnusedValue(path.parentPath))) | ||
); | ||
} | ||
function addSelfReference( | ||
path: NodePath<Node>, | ||
asset: MutableAsset, | ||
): ?Identifier { | ||
// If referencing a property on this/exports/module.exports, create a self-referencing dependency | ||
// to track that the symbol is used, and replace the member expression with. | ||
if ( | ||
isStaticMemberExpression(path.parent, {object: path.node}) && | ||
!isAssignmentExpression(path.parentPath.parent, {left: path.parent}) | ||
) { | ||
// $FlowFixMe | ||
let name = path.parent.property.name ?? path.parent.property.value; | ||
// Do not create a self-reference for the `default` symbol unless we have seen an __esModule flag. | ||
if (name === 'default' && !asset.symbols.hasExportSymbol('__esModule')) { | ||
return; | ||
} | ||
let local = getExportIdentifier(asset, name); | ||
asset.addDependency({ | ||
moduleSpecifier: `./${basename(asset.filePath)}`, | ||
symbols: new Map([ | ||
[ | ||
name, | ||
{ | ||
local: local.name, | ||
isWeak: false, | ||
loc: convertBabelLoc(path.node.loc), | ||
}, | ||
], | ||
]), | ||
}); | ||
return local; | ||
} | ||
} | ||
function isStaticMemberExpression(node: Node, opts: any): boolean { | ||
return ( | ||
isMemberExpression(node, opts) && | ||
((isIdentifier(node.property) && !node.computed) || | ||
isStringLiteral(node.property)) | ||
); | ||
} |
@@ -5,3 +5,2 @@ // @flow | ||
export {link} from './link'; | ||
export {default as shake} from './shake'; | ||
export {generate} from './generate'; |
925
src/link.js
@@ -17,5 +17,2 @@ // @flow | ||
Identifier, | ||
LVal, | ||
Node, | ||
ObjectProperty, | ||
Statement, | ||
@@ -25,3 +22,2 @@ StringLiteral, | ||
} from '@babel/types'; | ||
import type {NodePath} from '@babel/traverse'; | ||
@@ -35,36 +31,24 @@ import nullthrows from 'nullthrows'; | ||
isAssignmentExpression, | ||
isCallExpression, | ||
isExpressionStatement, | ||
isIdentifier, | ||
isMemberExpression, | ||
isObjectExpression, | ||
isObjectPattern, | ||
isSequenceExpression, | ||
isStringLiteral, | ||
} from '@babel/types'; | ||
import {traverse2, REMOVE, Scope} from '@parcel/babylon-walk'; | ||
import {convertBabelLoc} from '@parcel/babel-ast-utils'; | ||
import traverse from '@babel/traverse'; | ||
import treeShake from './shake'; | ||
import globals from 'globals'; | ||
import { | ||
assertString, | ||
getName, | ||
getHelpers, | ||
getIdentifier, | ||
dereferenceIdentifier, | ||
pathRemove, | ||
getThrowableDiagnosticForNode, | ||
verifyScopeState, | ||
isEntry, | ||
isReferenced, | ||
needsPrelude, | ||
needsDefaultInterop, | ||
} from './utils'; | ||
import OutputFormats from './formats/index.js'; | ||
const ESMODULE_TEMPLATE = template.statement< | ||
{|EXPORTS: Expression|}, | ||
Statement, | ||
>(`$parcel$defineInteropFlag(EXPORTS);`); | ||
const DEFAULT_INTEROP_TEMPLATE = template.statement< | ||
{| | ||
NAME: LVal, | ||
MODULE: Expression, | ||
|}, | ||
VariableDeclaration, | ||
>('var NAME = $parcel$interopDefault(MODULE);'); | ||
const THROW_TEMPLATE = template.statement<{|MODULE: StringLiteral|}, Statement>( | ||
@@ -83,5 +67,26 @@ '$parcel$missingModule(MODULE);', | ||
}`); | ||
const PARCEL_REQUIRE_TEMPLATE = template.statement< | ||
{|PARCEL_REQUIRE_NAME: Identifier|}, | ||
VariableDeclaration, | ||
>(`var parcelRequire = $parcel$global.PARCEL_REQUIRE_NAME`); | ||
type LinkResult = {|ast: File, referencedAssets: Set<Asset>|}; | ||
const BUILTINS = Object.keys(globals.builtin); | ||
const GLOBALS_BY_CONTEXT = { | ||
browser: new Set([...BUILTINS, ...Object.keys(globals.browser)]), | ||
'web-worker': new Set([...BUILTINS, ...Object.keys(globals.worker)]), | ||
'service-worker': new Set([ | ||
...BUILTINS, | ||
...Object.keys(globals.serviceworker), | ||
]), | ||
node: new Set([...BUILTINS, ...Object.keys(globals.node)]), | ||
'electron-main': new Set([...BUILTINS, ...Object.keys(globals.node)]), | ||
'electron-renderer': new Set([ | ||
...BUILTINS, | ||
...Object.keys(globals.node), | ||
...Object.keys(globals.browser), | ||
]), | ||
}; | ||
export function link({ | ||
@@ -93,2 +98,3 @@ bundle, | ||
wrappedAssets, | ||
parcelRequireName, | ||
}: {| | ||
@@ -100,2 +106,3 @@ bundle: NamedBundle, | ||
wrappedAssets: Set<string>, | ||
parcelRequireName: string, | ||
|}): LinkResult { | ||
@@ -105,10 +112,14 @@ let format = OutputFormats[bundle.env.outputFormat]; | ||
let imports: Map<Symbol, null | [Asset, Symbol, ?SourceLocation]> = new Map(); | ||
let exports: Map<Symbol, [Asset, Symbol]> = new Map(); | ||
let assets: Map<string, Asset> = new Map(); | ||
let exportsMap: Map<Symbol, Asset> = new Map(); | ||
let scope = new Scope('program'); | ||
let globalNames = GLOBALS_BY_CONTEXT[bundle.env.context]; | ||
let helpers = getHelpers(); | ||
let importedFiles = new Map<string, ExternalModule | ExternalBundle>(); | ||
let referencedAssets = new Set(); | ||
let reexports = new Set(); | ||
// return {ast, referencedAssets}; | ||
// If building a library, the target is actually another bundler rather | ||
@@ -150,2 +161,6 @@ // than the final output that could be loaded in a browser. So, loader | ||
for (let [symbol, {local}] of asset.symbols) { | ||
exports.set(local, [asset, symbol]); | ||
} | ||
if (bundleGraph.isAssetReferencedByDependant(bundle, asset)) { | ||
@@ -156,3 +171,75 @@ referencedAssets.add(asset); | ||
let entry = bundle.getMainEntry(); | ||
let exportedSymbols: Map< | ||
string, | ||
Array<{|exportAs: string, local: string|}>, | ||
> = new Map(); | ||
if (entry) { | ||
if (entry.meta.isCommonJS) { | ||
if (bundle.env.outputFormat === 'commonjs') { | ||
exportedSymbols.set(assertString(entry.meta.exportsIdentifier), [ | ||
{exportAs: '*', local: 'exports'}, | ||
]); | ||
} | ||
} else { | ||
for (let { | ||
exportAs, | ||
exportSymbol, | ||
symbol, | ||
asset, | ||
loc, | ||
} of bundleGraph.getExportedSymbols(entry)) { | ||
if (typeof symbol === 'string') { | ||
let symbols = exportedSymbols.get( | ||
symbol === '*' | ||
? assertString(entry.meta.exportsIdentifier) | ||
: symbol, | ||
); | ||
let local = exportAs; | ||
if (symbols) { | ||
local = symbols[0].local; | ||
} else { | ||
symbols = []; | ||
exportedSymbols.set(symbol, symbols); | ||
if (local === '*') { | ||
local = 'exports'; | ||
} else if (!t.isValidIdentifier(local) || globalNames.has(local)) { | ||
local = scope.generateUid(local); | ||
} else { | ||
scope.add(local); | ||
} | ||
} | ||
symbols.push({exportAs, local}); | ||
} else if (symbol === null) { | ||
// TODO `meta.exportsIdentifier[exportSymbol]` should be exported | ||
let relativePath = relative(options.projectRoot, asset.filePath); | ||
throw getThrowableDiagnosticForNode( | ||
`${relativePath} couldn't be statically analyzed when importing '${exportSymbol}'`, | ||
entry.filePath, | ||
loc, | ||
); | ||
} else if (symbol !== false) { | ||
let relativePath = relative(options.projectRoot, asset.filePath); | ||
throw getThrowableDiagnosticForNode( | ||
`${relativePath} does not export '${exportSymbol}'`, | ||
entry.filePath, | ||
loc, | ||
); | ||
} | ||
} | ||
} | ||
} | ||
let resolveSymbolCache = new Map(); | ||
function resolveSymbol(inputAsset, inputSymbol: Symbol, bundle) { | ||
let k = inputAsset.id + ':' + inputSymbol + ':' + bundle.id; | ||
let cached = resolveSymbolCache.get(k); | ||
if (cached) { | ||
return cached; | ||
} | ||
let {asset, exportSymbol, symbol, loc} = bundleGraph.resolveSymbol( | ||
@@ -163,4 +250,4 @@ inputAsset, | ||
); | ||
if (asset.meta.resolveExportsBailedOut) { | ||
return { | ||
if (asset.meta.staticExports === false) { | ||
let res = { | ||
asset: asset, | ||
@@ -171,2 +258,5 @@ symbol: exportSymbol, | ||
}; | ||
resolveSymbolCache.set(k, res); | ||
return res; | ||
} | ||
@@ -178,3 +268,3 @@ | ||
// a deferred import | ||
return { | ||
let res = { | ||
asset: asset, | ||
@@ -185,2 +275,5 @@ symbol: exportSymbol, | ||
}; | ||
resolveSymbolCache.set(k, res); | ||
return res; | ||
} | ||
@@ -193,11 +286,122 @@ | ||
if (replacements && identifier && replacements.has(identifier)) { | ||
if (identifier && replacements.has(identifier)) { | ||
identifier = replacements.get(identifier); | ||
} | ||
return {asset: asset, symbol: exportSymbol, identifier, loc}; | ||
let res = {asset: asset, symbol: exportSymbol, identifier, loc}; | ||
resolveSymbolCache.set(k, res); | ||
return res; | ||
} | ||
function maybeReplaceIdentifier(path: NodePath<Identifier>) { | ||
let {name} = path.node; | ||
let needsExportsIdentifierCache = new Map(); | ||
let bundleNeedsMainExportsIdentifier = | ||
(bundle.env.outputFormat === 'global' && | ||
(!isEntry(bundle, bundleGraph) || isReferenced(bundle, bundleGraph))) || | ||
(bundle.env.outputFormat === 'esmodule' && entry?.meta.isCommonJS); | ||
function needsExportsIdentifier(name: string) { | ||
let asset = exportsMap.get(name); | ||
if (asset) { | ||
return needsExportsIdentifierForAsset(asset); | ||
} | ||
return true; | ||
} | ||
function needsExportsIdentifierForAsset(asset: Asset) { | ||
if (needsExportsIdentifierCache.has(asset)) { | ||
return needsExportsIdentifierCache.get(asset); | ||
} | ||
if ( | ||
asset.meta.staticExports === false || | ||
wrappedAssets.has(asset.id) || | ||
referencedAssets.has(asset) | ||
) { | ||
needsExportsIdentifierCache.set(asset, true); | ||
return true; | ||
} | ||
let isEntry = asset === bundle.getMainEntry(); | ||
if (isEntry && bundleNeedsMainExportsIdentifier) { | ||
needsExportsIdentifierCache.set(asset, true); | ||
return true; | ||
} | ||
let deps = bundleGraph.getIncomingDependencies(asset); | ||
let usedSymbols = bundleGraph.getUsedSymbols(asset); | ||
if (usedSymbols.has('*') && (!isEntry || asset.meta.isCommonJS)) { | ||
needsExportsIdentifierCache.set(asset, true); | ||
return true; | ||
} | ||
let res = deps.some( | ||
dep => | ||
// Internalized async dependencies need the exports object for Promise.resolve($id$exports) | ||
(dep.isAsync && bundle.hasDependency(dep)) || | ||
// If there's a dependency on the namespace, and the parent asset's exports object is used, | ||
// we need to keep the exports object for $parcel$exportWildcard. | ||
(!isEntry && | ||
dep.symbols.hasExportSymbol('*') && | ||
needsExportsIdentifierForAsset( | ||
nullthrows(bundleGraph.getAssetWithDependency(dep)), | ||
)) || | ||
// If the asset is CommonJS and there's an ES6 dependency on `default`, we need the | ||
// exports identifier to call $parcel$interopDefault. | ||
(asset.meta.isCommonJS && | ||
dep.meta.isES6Module && | ||
dep.symbols.hasExportSymbol('default')) || | ||
// If the asset is an ES6 module with a default export, and there's a CommonJS dependency | ||
// on it, we need the exports identifier to call $parcel$defineInteropFlag. | ||
(asset.meta.isES6Module && | ||
asset.symbols.hasExportSymbol('default') && | ||
dep.meta.isCommonJS && | ||
!dep.isAsync && | ||
dep.symbols.hasExportSymbol('*')) || | ||
// If one of the symbols imported by the dependency doesn't resolve, then we need the | ||
// exports identifier to fall back to. | ||
[...dep.symbols].some( | ||
([symbol]) => !resolveSymbol(asset, symbol, bundle).identifier, | ||
), | ||
); | ||
needsExportsIdentifierCache.set(asset, res); | ||
return res; | ||
} | ||
function needsDeclaration(name: string) { | ||
let exp = exports.get(name); | ||
if (exp) { | ||
let [asset, local] = exp; | ||
if (asset === bundle.getMainEntry() && bundle.env.isLibrary) { | ||
return true; | ||
} | ||
if (asset.meta.staticExports === false) { | ||
return true; | ||
} | ||
let usedSymbols = bundleGraph.getUsedSymbols(asset); | ||
// If the asset is CommonJS, and "default" was used but not defined, this | ||
// will resolve to the $id$exports object, so we need to retain all symbols. | ||
if ( | ||
asset.meta.isCommonJS && | ||
usedSymbols.has('default') && | ||
!asset.symbols.hasExportSymbol('default') | ||
) { | ||
return true; | ||
} | ||
// Otherwise, if the symbol is pure and unused, it is safe to remove. | ||
if (asset.symbols.get(local)?.meta?.isPure) { | ||
return usedSymbols.has(local) || usedSymbols.has('*'); | ||
} | ||
} | ||
return true; | ||
} | ||
function maybeReplaceIdentifier(node: Identifier, ancestors) { | ||
let {name} = node; | ||
if (typeof name !== 'string') { | ||
@@ -209,58 +413,26 @@ return; | ||
if (replacement) { | ||
path.node.name = replacement; | ||
node.name = replacement; | ||
} | ||
if (imports.has(name)) { | ||
let node; | ||
let res: ?BabelNode; | ||
let imported = imports.get(name); | ||
if (imported == null) { | ||
// import was deferred | ||
node = t.objectExpression([]); | ||
res = t.objectExpression([]); | ||
} else { | ||
let [asset, symbol] = imported; | ||
node = replaceImportNode(asset, symbol, path); | ||
res = replaceImportNode(asset, symbol, node, ancestors); | ||
// If the export does not exist, replace with an empty object. | ||
if (!node) { | ||
node = t.objectExpression([]); | ||
if (!res) { | ||
res = t.objectExpression([]); | ||
} | ||
} | ||
path.replaceWith(node); | ||
if (isObjectExpression(node)) { | ||
invariant(node.properties.length === 0); | ||
} else if (isIdentifier(node)) { | ||
nullthrows(path.scope.getBinding(node.name)).reference(path); | ||
} else { | ||
if (isCallExpression(node)) { | ||
// $id$init() | ||
invariant(isIdentifier(node.callee)); | ||
nullthrows(path.scope.getBinding(node.callee.name)).reference( | ||
path.get<NodePath<Identifier>>('callee'), | ||
); | ||
} else { | ||
invariant(isMemberExpression(node)); | ||
if (isIdentifier(node.object)) { | ||
nullthrows(path.scope.getBinding(node.object.name)).reference( | ||
path.get<NodePath<Identifier>>('object'), | ||
); | ||
} else { | ||
// $id$init().prop | ||
invariant(isCallExpression(node.object)); | ||
let {callee} = node.object; | ||
invariant(isIdentifier(callee)); | ||
nullthrows(path.scope.getBinding(callee.name)).reference( | ||
path.get<NodePath<Identifier>>('object.callee'), | ||
); | ||
} | ||
} | ||
} | ||
} else if (exportsMap.has(name) && !path.scope.hasBinding(name)) { | ||
// If it's an undefined $id$exports identifier. | ||
dereferenceIdentifier(path.node, path.scope); | ||
path.replaceWith(t.objectExpression([])); | ||
return res; | ||
} | ||
} | ||
// path is an Identifier like $id$import$foo that directly imports originalName from originalModule | ||
function replaceImportNode(originalModule, originalName, path) { | ||
// node is an Identifier like $id$import$foo that directly imports originalName from originalModule | ||
function replaceImportNode(originalModule, originalName, node, ancestors) { | ||
let {asset: mod, symbol, identifier} = resolveSymbol( | ||
@@ -271,7 +443,18 @@ originalModule, | ||
); | ||
let node = identifier ? findSymbol(path, identifier) : identifier; | ||
// If the symbol resolves to the original module where the export is defined, | ||
// do not perform any replacements. | ||
let exp = exports.get(node.name); | ||
if (exp && exp[0] === mod) { | ||
return node; | ||
} | ||
let res = identifier != null ? findSymbol(node, identifier) : identifier; | ||
if (mod.meta.staticExports === false || wrappedAssets.has(mod.id)) { | ||
res = null; | ||
} | ||
// If the module is not in this bundle, create a `require` call for it. | ||
if (!node && (!mod.meta.id || !assets.has(assertString(mod.meta.id)))) { | ||
if (node === false) { | ||
if (!mod.meta.id || !assets.has(assertString(mod.meta.id))) { | ||
if (res === false) { | ||
// Asset was skipped | ||
@@ -281,4 +464,4 @@ return null; | ||
node = addBundleImport(mod, path); | ||
return node ? interop(mod, symbol, path, node) : null; | ||
res = addBundleImport(mod, node, ancestors); | ||
return res ? interop(mod, symbol, node, res) : null; | ||
} | ||
@@ -290,7 +473,7 @@ | ||
// TODO remove the first part of the condition once bundleGraph.resolveSymbol().identifier === null covers this | ||
if ((node === undefined && mod.meta.isCommonJS) || node === null) { | ||
if ((res === undefined && mod.meta.isCommonJS) || res === null) { | ||
if (wrappedAssets.has(mod.id)) { | ||
node = t.callExpression(getIdentifier(mod, 'init'), []); | ||
res = t.callExpression(getIdentifier(mod, 'init'), []); | ||
} else { | ||
node = findSymbol(path, assertString(mod.meta.exportsIdentifier)); | ||
res = findSymbol(node, assertString(mod.meta.exportsIdentifier)); | ||
if (!node) { | ||
@@ -301,10 +484,10 @@ return null; | ||
node = interop(mod, symbol, path, node); | ||
return node; | ||
res = interop(mod, symbol, res, res); | ||
return res; | ||
} | ||
return node; | ||
return res; | ||
} | ||
function findSymbol(path, symbol) { | ||
function findSymbol(node, symbol) { | ||
if (symbol && replacements.has(symbol)) { | ||
@@ -314,4 +497,9 @@ symbol = replacements.get(symbol); | ||
// if the symbol is in the scope there is no need to remap it | ||
if (symbol && path.scope.getProgramParent().hasBinding(symbol)) { | ||
let exp = symbol && exportedSymbols.get(symbol); | ||
if (exp) { | ||
symbol = exp[0].local; | ||
} | ||
// if the symbol exists there is no need to remap it | ||
if (symbol) { | ||
return t.identifier(symbol); | ||
@@ -323,38 +511,10 @@ } | ||
function interop(mod, originalName, path, node) { | ||
function interop(mod, originalName, originalNode, node) { | ||
// Handle interop for default imports of CommonJS modules. | ||
if (mod.meta.isCommonJS && originalName === 'default') { | ||
if ( | ||
mod.meta.isCommonJS && | ||
originalName === 'default' && | ||
needsDefaultInterop(bundleGraph, bundle, mod) | ||
) { | ||
let name = getName(mod, '$interop$default'); | ||
if (!path.scope.getBinding(name)) { | ||
let binding = nullthrows( | ||
path.scope.getBinding( | ||
bundle.hasAsset(mod) && !wrappedAssets.has(mod.id) | ||
? assertString(mod.meta.exportsIdentifier) | ||
: // If this bundle doesn't have the asset, use the binding for | ||
// the `parcelRequire`d init function. | ||
getName(mod, 'init'), | ||
), | ||
); | ||
invariant( | ||
binding.path.getStatementParent().parentPath.isProgram(), | ||
"Expected binding declaration's parent to be the program", | ||
); | ||
// Hoist to the nearest path with the same scope as the exports is declared in. | ||
let parent = nullthrows(path.findParent(p => t.isProgram(p.parent))); | ||
let [decl] = parent.insertBefore( | ||
DEFAULT_INTEROP_TEMPLATE({ | ||
NAME: t.identifier(name), | ||
MODULE: node, | ||
}), | ||
); | ||
binding.reference( | ||
decl.get<NodePath<Identifier>>('declarations.0.init'), | ||
); | ||
getScopeBefore(parent).registerDeclaration(decl); | ||
} | ||
return t.identifier(name); | ||
@@ -364,4 +524,8 @@ } | ||
// if there is a CommonJS export return $id$exports.name | ||
if (originalName !== '*') { | ||
return t.memberExpression(node, t.identifier(originalName)); | ||
if (originalName !== '*' && node != null) { | ||
if (t.isValidIdentifier(originalName, false)) { | ||
return t.memberExpression(node, t.identifier(originalName)); | ||
} else { | ||
return t.memberExpression(node, t.stringLiteral(originalName), true); | ||
} | ||
} | ||
@@ -372,7 +536,3 @@ | ||
function getScopeBefore(path) { | ||
return path.isScope() ? path.parentPath.scope : path.scope; | ||
} | ||
function addExternalModule(path, dep) { | ||
function addExternalModule(node, ancestors, dep) { | ||
// Find an existing import for this specifier, or create a new one. | ||
@@ -384,4 +544,4 @@ let importedFile = importedFiles.get(dep.moduleSpecifier); | ||
specifiers: new Map(), | ||
isCommonJS: !!dep.meta.isCommonJS, | ||
loc: convertBabelLoc(path.node.loc), | ||
isCommonJS: !!dep.meta?.isCommonJS, | ||
loc: convertBabelLoc(node.loc), | ||
}; | ||
@@ -392,4 +552,2 @@ | ||
let programScope = path.scope.getProgramParent(); | ||
invariant(importedFile.specifiers != null); | ||
@@ -408,2 +566,12 @@ let specifiers = importedFile.specifiers; | ||
renamed = replacements.get(local); | ||
// If this symbol is re-exported, add it to the reexport list. | ||
let exp = exportedSymbols.get(local); | ||
if (exp) { | ||
renamed = exp[0].local; | ||
for (let e of exp) { | ||
reexports.add(e); | ||
} | ||
} | ||
if (!renamed) { | ||
@@ -415,11 +583,9 @@ // Rename the specifier to something nicer. Try to use the imported | ||
if (imported === 'default' || imported === '*') { | ||
renamed = programScope.generateUid(dep.moduleSpecifier); | ||
} else if ( | ||
programScope.hasBinding(imported) || | ||
programScope.hasReference(imported) | ||
) { | ||
renamed = programScope.generateUid(imported); | ||
renamed = scope.generateUid(dep.moduleSpecifier); | ||
} else if (scope.has(imported)) { | ||
renamed = scope.generateUid(imported); | ||
} else { | ||
scope.add(imported); | ||
} | ||
programScope.references[renamed] = true; | ||
replacements.set(local, renamed); | ||
@@ -429,13 +595,2 @@ } | ||
specifiers.set(imported, renamed); | ||
if (!programScope.hasOwnBinding(renamed)) { | ||
// add binding so we can track the scope | ||
let [decl] = programScope.path.unshiftContainer( | ||
'body', | ||
t.variableDeclaration('var', [ | ||
t.variableDeclarator(t.identifier(renamed)), | ||
]), | ||
); | ||
programScope.registerDeclaration(decl); | ||
} | ||
} | ||
@@ -446,3 +601,3 @@ | ||
function addBundleImport(mod, path) { | ||
function addBundleImport(mod, node, ancestors) { | ||
// Find a bundle that's reachable from the current bundle (sibling or ancestor) | ||
@@ -466,3 +621,3 @@ // containing this asset, and create an import for it if needed. | ||
assets: new Set(), | ||
loc: convertBabelLoc(path.node.loc), | ||
loc: convertBabelLoc(node.loc), | ||
}; | ||
@@ -473,3 +628,3 @@ importedFiles.set(filePath, imported); | ||
// If not unused, add the asset to the list of specifiers to import. | ||
if (!isUnusedValue(path) && mod.meta.exportsIdentifier) { | ||
if (!isUnusedValue(ancestors) && mod.meta.exportsIdentifier) { | ||
invariant(imported.assets != null); | ||
@@ -479,23 +634,2 @@ imported.assets.add(mod); | ||
let initIdentifier = getIdentifier(mod, 'init'); | ||
let program = path.scope.getProgramParent().path; | ||
if (!program.scope.hasOwnBinding(initIdentifier.name)) { | ||
// add binding so we can track the scope | ||
// If parcelRequire exists in scope, be sure to insert after that so the global outputFormat | ||
// can add the rhs later and reference it properly. | ||
let declNode = t.variableDeclaration('var', [ | ||
t.variableDeclarator(initIdentifier), | ||
]); | ||
let parcelRequire = program.scope.getBinding('parcelRequire'); | ||
let decl; | ||
if (parcelRequire) { | ||
[decl] = parcelRequire.path | ||
.getStatementParent() | ||
.insertAfter(declNode); | ||
} else { | ||
[decl] = program.unshiftContainer('body', [declNode]); | ||
} | ||
program.scope.registerDeclaration(decl); | ||
} | ||
return t.callExpression(initIdentifier, []); | ||
@@ -505,5 +639,5 @@ } | ||
traverse(ast, { | ||
CallExpression(path) { | ||
let {arguments: args, callee} = path.node; | ||
traverse2(ast, { | ||
CallExpression(node, state, ancestors) { | ||
let {arguments: args, callee} = node; | ||
if (!isIdentifier(callee)) { | ||
@@ -517,3 +651,3 @@ return; | ||
if ( | ||
args.length !== 2 || | ||
args.length < 2 || | ||
!isStringLiteral(id) || | ||
@@ -541,3 +675,3 @@ !isStringLiteral(source) | ||
: bundleGraph.getDependencyResolution(dep, bundle); | ||
let node; | ||
let newNode; | ||
@@ -547,29 +681,34 @@ if (!bundleGraph.isDependencySkipped(dep)) { | ||
if (dep.isOptional) { | ||
node = THROW_TEMPLATE({MODULE: t.stringLiteral(source.value)}); | ||
newNode = THROW_TEMPLATE({MODULE: t.stringLiteral(source.value)}); | ||
scope.add('$parcel$missingModule'); | ||
} else { | ||
let name = addExternalModule(path, dep); | ||
if (!isUnusedValue(path) && name) { | ||
node = t.identifier(name); | ||
let name = addExternalModule(node, ancestors, dep); | ||
if (!isUnusedValue(ancestors) && name) { | ||
newNode = t.identifier(name); | ||
} | ||
} | ||
} else { | ||
if (mod.meta.id && assets.has(assertString(mod.meta.id))) { | ||
let name = assertString(mod.meta.exportsIdentifier); | ||
// If there is a third arg, it is an identifier to replace the require with. | ||
// This happens when `require('foo').bar` is detected in the hoister. | ||
if (args.length > 2 && isIdentifier(args[2])) { | ||
newNode = maybeReplaceIdentifier(args[2], ancestors); | ||
} else { | ||
if (mod.meta.id && assets.has(assertString(mod.meta.id))) { | ||
let isValueUsed = !isUnusedValue(ancestors); | ||
let isValueUsed = !isUnusedValue(path); | ||
if (asset.meta.isCommonJS && isValueUsed) { | ||
maybeAddEsModuleFlag(path.scope, mod); | ||
// We need to wrap the module in a function when a require | ||
// call happens inside a non top-level scope, e.g. in a | ||
// function, if statement, or conditional expression. | ||
if (wrappedAssets.has(mod.id)) { | ||
newNode = t.callExpression(getIdentifier(mod, 'init'), []); | ||
} | ||
// Replace with nothing if the require call's result is not used. | ||
else if (isValueUsed) { | ||
newNode = t.identifier( | ||
assertString(mod.meta.exportsIdentifier), | ||
); | ||
} | ||
} else if (mod.type === 'js') { | ||
newNode = addBundleImport(mod, node, ancestors); | ||
} | ||
// We need to wrap the module in a function when a require | ||
// call happens inside a non top-level scope, e.g. in a | ||
// function, if statement, or conditional expression. | ||
if (wrappedAssets.has(mod.id)) { | ||
node = t.callExpression(getIdentifier(mod, 'init'), []); | ||
} | ||
// Replace with nothing if the require call's result is not used. | ||
else if (isValueUsed) { | ||
node = t.identifier(replacements.get(name) || name); | ||
} | ||
} else if (mod.type === 'js') { | ||
node = addBundleImport(mod, path); | ||
} | ||
@@ -579,3 +718,3 @@ | ||
if (asyncResolution?.type === 'asset') { | ||
node = t.callExpression( | ||
newNode = t.callExpression( | ||
t.memberExpression( | ||
@@ -586,3 +725,3 @@ t.identifier('Promise'), | ||
// $FlowFixMe[incompatible-call] | ||
[node], | ||
[newNode], | ||
); | ||
@@ -593,10 +732,10 @@ } | ||
if (node) { | ||
path.replaceWith(node); | ||
if (newNode) { | ||
return newNode; | ||
} else { | ||
if (path.parentPath.isExpressionStatement()) { | ||
path.parentPath.remove(); | ||
if (isUnusedValue(ancestors)) { | ||
return REMOVE; | ||
} else { | ||
// e.g. $parcel$exportWildcard; | ||
path.replaceWith(t.objectExpression([])); | ||
return t.objectExpression([]); | ||
} | ||
@@ -628,9 +767,9 @@ } | ||
mapped.filePath, | ||
convertBabelLoc(path.node.loc), | ||
convertBabelLoc(node.loc), | ||
); | ||
} | ||
path.replaceWith( | ||
REQUIRE_RESOLVE_CALL_TEMPLATE({ID: t.stringLiteral(source.value)}), | ||
); | ||
return REQUIRE_RESOLVE_CALL_TEMPLATE({ | ||
ID: t.stringLiteral(source.value), | ||
}); | ||
} else { | ||
@@ -640,5 +779,13 @@ throw getThrowableDiagnosticForNode( | ||
mapped.filePath, | ||
convertBabelLoc(path.node.loc), | ||
convertBabelLoc(node.loc), | ||
); | ||
} | ||
} else if (callee.name === '$parcel$exportWildcard') { | ||
if (args.length !== 2 || !isIdentifier(args[0])) { | ||
throw new Error('Invalid call to $parcel$exportWildcard'); | ||
} | ||
if (!needsExportsIdentifier(args[0].name)) { | ||
return REMOVE; | ||
} | ||
} else if (callee.name === '$parcel$export') { | ||
@@ -651,2 +798,7 @@ let [obj, symbol] = args; | ||
// Remove if the $id$exports object is unused. | ||
if (!needsExportsIdentifier(objName)) { | ||
return REMOVE; | ||
} | ||
if (objName === 'exports') { | ||
@@ -664,3 +816,3 @@ // Assignment inside a wrapped asset | ||
if (unused) { | ||
pathRemove(path); | ||
return REMOVE; | ||
} | ||
@@ -670,146 +822,174 @@ } | ||
VariableDeclarator: { | ||
exit(path) { | ||
// Replace references to declarations like `var x = require('x')` | ||
// with the final export identifier instead. | ||
// This allows us to potentially replace accesses to e.g. `x.foo` with | ||
// a variable like `$id$export$foo` later, avoiding the exports object altogether. | ||
let {id, init} = path.node; | ||
if (!isIdentifier(init)) { | ||
return; | ||
} | ||
exit(node) { | ||
let {id} = node; | ||
let module = exportsMap.get(init.name); | ||
if (!module) { | ||
return; | ||
if (isIdentifier(id)) { | ||
if (!needsExportsIdentifier(id.name)) { | ||
return REMOVE; | ||
} | ||
if (!needsDeclaration(id.name)) { | ||
return REMOVE; | ||
} | ||
} | ||
}, | ||
}, | ||
VariableDeclaration: { | ||
exit(node) { | ||
if (node.declarations.length === 0) { | ||
return REMOVE; | ||
} | ||
let isGlobal = path.scope == path.scope.getProgramParent(); | ||
// Replace patterns like `var {x} = require('y')` with e.g. `$id$export$x`. | ||
if (isObjectPattern(id)) { | ||
for (let p of path.get<Array<NodePath<ObjectProperty>>>( | ||
'id.properties', | ||
)) { | ||
let {computed, key, value} = p.node; | ||
if (computed || !isIdentifier(key) || !isIdentifier(value)) { | ||
continue; | ||
// Handle exported declarations using output format specific logic. | ||
let exported = []; | ||
for (let decl of node.declarations) { | ||
let bindingIdentifiers = t.getBindingIdentifiers(decl.id); | ||
for (let name in bindingIdentifiers) { | ||
let exp = exportedSymbols.get(name); | ||
if (exp) { | ||
bindingIdentifiers[name].name = exp[0].local; | ||
exported.push(...exp); | ||
} | ||
let {identifier} = resolveSymbol(module, key.name); | ||
if (identifier) { | ||
replace(value.name, identifier, p); | ||
if (isGlobal) { | ||
replacements.set(value.name, identifier); | ||
} | ||
} | ||
} | ||
} | ||
if (id.properties.length === 0) { | ||
path.remove(); | ||
} | ||
} else if (isIdentifier(id)) { | ||
replace(id.name, init.name, path); | ||
if (isGlobal) { | ||
replacements.set(id.name, init.name); | ||
} | ||
if (exported.length > 0) { | ||
return format.generateMainExport(node, exported); | ||
} | ||
}, | ||
}, | ||
Declaration: { | ||
exit(node) { | ||
if (t.isVariableDeclaration(node)) { | ||
return; | ||
} | ||
function replace(id, init, path) { | ||
let binding = nullthrows(path.scope.getBinding(id)); | ||
if (!binding.constant) { | ||
return; | ||
if (node.id != null && isIdentifier(node.id)) { | ||
let id = node.id; | ||
if (!needsDeclaration(id.name)) { | ||
return REMOVE; | ||
} | ||
for (let ref of binding.referencePaths) { | ||
ref.replaceWith(t.identifier(init)); | ||
// Handle exported declarations using output format specific logic. | ||
let exp = exportedSymbols.get(id.name); | ||
if (exp) { | ||
id.name = exp[0].local; | ||
return format.generateMainExport(node, exp); | ||
} | ||
path.remove(); | ||
} | ||
}, | ||
}, | ||
MemberExpression: { | ||
exit(path) { | ||
let {object, property, computed} = path.node; | ||
if ( | ||
!( | ||
isIdentifier(object) && | ||
((isIdentifier(property) && !computed) || isStringLiteral(property)) | ||
) | ||
) { | ||
return; | ||
AssignmentExpression(node, state, ancestors) { | ||
if (isIdentifier(node.left)) { | ||
let res = maybeReplaceIdentifier(node.left, ancestors); | ||
if (isIdentifier(res) || isMemberExpression(res)) { | ||
node.left = res; | ||
} | ||
let asset = exportsMap.get(object.name); | ||
if (!asset) { | ||
return; | ||
} | ||
if (!isMemberExpression(node.left)) { | ||
return; | ||
} | ||
let { | ||
left: {object, property, computed}, | ||
right, | ||
} = node; | ||
if ( | ||
!( | ||
isIdentifier(object) && | ||
((isIdentifier(property) && !computed) || isStringLiteral(property)) | ||
) | ||
) { | ||
return; | ||
} | ||
// Rename references to exported symbols to the exported name. | ||
let exp = exportedSymbols.get(object.name); | ||
if (exp) { | ||
object.name = exp[0].local; | ||
} | ||
let asset = exportsMap.get(object.name); | ||
if (!asset) { | ||
return; | ||
} | ||
if (!needsExportsIdentifier(object.name)) { | ||
return REMOVE; | ||
} | ||
if (isIdentifier(right) && !needsDeclaration(right.name)) { | ||
return REMOVE; | ||
} | ||
}, | ||
Identifier(node, state, ancestors) { | ||
if ( | ||
t.isReferenced( | ||
node, | ||
ancestors[ancestors.length - 2], | ||
ancestors[ancestors.length - 3], | ||
) | ||
) { | ||
// If referencing a helper, add it to the scope. | ||
if (helpers.has(node.name)) { | ||
scope.add(node.name); | ||
return; | ||
} | ||
// If it's a $id$exports.name expression. | ||
let name = isIdentifier(property) ? property.name : property.value; | ||
let {identifier} = resolveSymbol(asset, name, bundle); | ||
// Rename references to exported symbols to the exported name. | ||
let exp = exportedSymbols.get(node.name); | ||
if (exp) { | ||
node.name = exp[0].local; | ||
} | ||
return maybeReplaceIdentifier(node, ancestors); | ||
} | ||
}, | ||
ExpressionStatement: { | ||
exit(node) { | ||
// Handle exported declarations using output format specific logic. | ||
if ( | ||
identifier == null || | ||
identifier === false || | ||
!path.scope.hasBinding(identifier) | ||
isAssignmentExpression(node.expression) && | ||
isIdentifier(node.expression.left) | ||
) { | ||
return; | ||
} | ||
let {parent, parentPath} = path; | ||
// If inside an expression, update the actual export binding as well | ||
// (This is needed so that `require()`d CJS namespace objects can be mutatated.) | ||
if (isAssignmentExpression(parent, {left: path.node})) { | ||
if (isIdentifier(parent.right)) { | ||
maybeReplaceIdentifier( | ||
parentPath.get<NodePath<Identifier>>('right'), | ||
); | ||
// do not modify `$id$exports.foo = $id$export$foo` statements | ||
if (isIdentifier(parent.right, {name: identifier})) { | ||
return; | ||
} | ||
// If the right side was imported from a different bundle, there is no $id$export$foo binding in this bundle | ||
if (!path.scope.hasBinding(identifier)) { | ||
return; | ||
} | ||
let left = node.expression.left; | ||
let exp = exportedSymbols.get(left.name); | ||
if (exp) { | ||
left.name = exp[0].local; | ||
return format.generateMainExport(node, exp); | ||
} | ||
// turn `$id$exports.foo = ...` into `$id$exports.foo = $id$export$foo = ...` | ||
parentPath | ||
.get<NodePath<Node>>('right') | ||
.replaceWith( | ||
t.assignmentExpression( | ||
'=', | ||
t.identifier(identifier), | ||
parent.right, | ||
), | ||
); | ||
} else { | ||
path.replaceWith(t.identifier(identifier)); | ||
} | ||
}, | ||
}, | ||
ReferencedIdentifier(path) { | ||
maybeReplaceIdentifier(path); | ||
SequenceExpression: { | ||
exit(node) { | ||
// This can happen if a $parcel$require result is unused. | ||
if (node.expressions.length === 1) { | ||
return node.expressions[0]; | ||
} | ||
}, | ||
}, | ||
Program: { | ||
exit(path) { | ||
// Recrawl to get all bindings. | ||
path.scope.crawl(); | ||
exit(node) { | ||
// $FlowFixMe | ||
let statements: Array<BabelNode> = node.body; | ||
for (let file of importedFiles.values()) { | ||
if (file.bundle) { | ||
format.generateBundleImports(bundle, file, path, bundleGraph); | ||
let res = format.generateBundleImports( | ||
bundleGraph, | ||
bundle, | ||
file, | ||
scope, | ||
); | ||
statements = res.concat(statements); | ||
} else { | ||
format.generateExternalImport(bundle, file, path); | ||
let res = format.generateExternalImport(bundle, file, scope); | ||
statements = res.concat(statements); | ||
} | ||
} | ||
if (process.env.PARCEL_BUILD_ENV !== 'production') { | ||
verifyScopeState(path.scope); | ||
} | ||
if (referencedAssets.size > 0) { | ||
@@ -819,8 +999,3 @@ // Insert fake init functions that will be imported in other bundles, | ||
// not in the current bundle. | ||
for (let asset of referencedAssets) { | ||
maybeAddEsModuleFlag(path.scope, asset); | ||
} | ||
let decls = path.pushContainer( | ||
'body', | ||
statements = statements.concat( | ||
([...referencedAssets]: Array<Asset>) | ||
@@ -835,35 +1010,41 @@ .filter(a => !wrappedAssets.has(a.id)) | ||
); | ||
for (let decl of decls) { | ||
path.scope.registerDeclaration(decl); | ||
let returnId = decl.get<NodePath<Identifier>>( | ||
'body.body.0.argument', | ||
); | ||
// TODO Sometimes deferred/excluded assets are referenced, causing this function to | ||
// become `function $id$init() { return {}; }` (because of the ReferencedIdentifier visitor). | ||
// But a asset that isn't here should never be referenced in the first place. | ||
path.scope.getBinding(returnId.node.name)?.reference(returnId); | ||
} | ||
} | ||
if (process.env.PARCEL_BUILD_ENV !== 'production') { | ||
verifyScopeState(path.scope); | ||
} | ||
// Generate exports | ||
let exported = format.generateExports( | ||
let exported = format.generateBundleExports( | ||
bundleGraph, | ||
bundle, | ||
referencedAssets, | ||
path, | ||
replacements, | ||
options, | ||
maybeReplaceIdentifier, | ||
scope, | ||
reexports, | ||
); | ||
if (process.env.PARCEL_BUILD_ENV !== 'production') { | ||
verifyScopeState(path.scope); | ||
statements = statements.concat(exported); | ||
// If the prelude is needed, ensure parcelRequire is available. | ||
if ( | ||
!scope.names.has('parcelRequire') && | ||
needsPrelude(bundle, bundleGraph) | ||
) { | ||
scope.add('parcelRequire'); | ||
} | ||
treeShake(path.scope, exported, exportsMap); | ||
let usedHelpers: Array<BabelNode> = []; | ||
for (let name of scope.names) { | ||
let helper = helpers.get(name); | ||
if (helper) { | ||
usedHelpers.push(helper); | ||
} else if (name === 'parcelRequire') { | ||
usedHelpers.push( | ||
PARCEL_REQUIRE_TEMPLATE({ | ||
PARCEL_REQUIRE_NAME: t.identifier(parcelRequireName), | ||
}), | ||
); | ||
} | ||
} | ||
statements = usedHelpers.concat(statements); | ||
// $FlowFixMe | ||
return t.program(statements); | ||
}, | ||
@@ -876,43 +1057,11 @@ }, | ||
function maybeAddEsModuleFlag(scope, mod) { | ||
// Insert __esModule interop flag if the required module is an ES6 module with a default export. | ||
// This ensures that code generated by Babel and other tools works properly. | ||
if (mod.meta.isES6Module && mod.symbols.hasExportSymbol('default')) { | ||
let name = assertString(mod.meta.exportsIdentifier); | ||
let binding = scope.getBinding(name); | ||
if (binding && !binding.path.getData('hasESModuleFlag')) { | ||
let f = nullthrows( | ||
scope.getProgramParent().getBinding('$parcel$defineInteropFlag'), | ||
); | ||
let paths = [...binding.constantViolations]; | ||
if (binding.path.node.init) { | ||
paths.push(binding.path); | ||
} | ||
for (let path of paths) { | ||
let [stmt] = path | ||
.getStatementParent() | ||
.insertAfter(ESMODULE_TEMPLATE({EXPORTS: t.identifier(name)})); | ||
f.reference(stmt.get<NodePath<Identifier>>('expression.callee')); | ||
binding.reference( | ||
stmt.get<NodePath<Identifier>>('expression.arguments.0'), | ||
); | ||
} | ||
binding.path.setData('hasESModuleFlag', true); | ||
} | ||
} | ||
} | ||
function isUnusedValue(path: NodePath<Node>): boolean { | ||
let {parent} = path; | ||
function isUnusedValue(ancestors, i = 1) { | ||
let node = ancestors[ancestors.length - i]; | ||
let parent = ancestors[ancestors.length - i - 1]; | ||
return ( | ||
isExpressionStatement(parent) || | ||
(isSequenceExpression(parent) && | ||
((Array.isArray(path.container) && | ||
path.key !== path.container.length - 1) || | ||
isUnusedValue(path.parentPath))) | ||
(node !== parent.expressions[parent.expressions.length - 1] || | ||
isUnusedValue(ancestors, i + 1))) | ||
); | ||
} |
@@ -7,8 +7,7 @@ // @flow strict-local | ||
NamedBundle, | ||
PluginOptions, | ||
Symbol, | ||
SourceLocation, | ||
} from '@parcel/types'; | ||
import type {NodePath} from '@babel/traverse'; | ||
import type {Identifier, Program} from '@babel/types'; | ||
import type {Node} from '@babel/types'; | ||
import type {Scope} from '@parcel/babylon-walk'; | ||
@@ -30,21 +29,23 @@ export type ExternalModule = {| | ||
generateBundleImports( | ||
bundleGraph: BundleGraph<NamedBundle>, | ||
from: NamedBundle, | ||
external: ExternalBundle, | ||
path: NodePath<Program>, | ||
bundleGraph: BundleGraph<NamedBundle>, | ||
): void, | ||
scope: Scope, | ||
): Array<Node>, | ||
generateExternalImport( | ||
bundle: NamedBundle, | ||
external: ExternalModule, | ||
path: NodePath<Program>, | ||
): void, | ||
generateExports( | ||
scope: Scope, | ||
): Array<Node>, | ||
generateBundleExports( | ||
bundleGraph: BundleGraph<NamedBundle>, | ||
bundle: NamedBundle, | ||
referencedAssets: Set<Asset>, | ||
path: NodePath<Program>, | ||
replacements: Map<Symbol, Symbol>, | ||
options: PluginOptions, | ||
maybeReplaceIdentifier: (NodePath<Identifier>) => void, | ||
): Set<Symbol>, | ||
scope: Scope, | ||
reexports: Set<{|exportAs: string, local: string|}>, | ||
): Array<Node>, | ||
generateMainExport( | ||
node: Node, | ||
exported: Array<{|exportAs: string, local: string|}>, | ||
): Array<Node>, | ||
|}; |
@@ -18,3 +18,5 @@ // @flow | ||
VariableDeclarator, | ||
Statement, | ||
} from '@babel/types'; | ||
import {parse as babelParse} from '@babel/parser'; | ||
import type {Diagnostic} from '@parcel/diagnostic'; | ||
@@ -25,6 +27,12 @@ | ||
import * as t from '@babel/types'; | ||
import {isVariableDeclarator, isVariableDeclaration} from '@babel/types'; | ||
import { | ||
isIdentifier, | ||
isFunctionDeclaration, | ||
isVariableDeclarator, | ||
isVariableDeclaration, | ||
} from '@babel/types'; | ||
import invariant from 'assert'; | ||
import nullthrows from 'nullthrows'; | ||
import path from 'path'; | ||
import fs from 'fs'; | ||
@@ -145,2 +153,20 @@ export function getName( | ||
export function needsDefaultInterop( | ||
bundleGraph: BundleGraph<NamedBundle>, | ||
bundle: NamedBundle, | ||
asset: Asset, | ||
): boolean { | ||
let deps = bundleGraph.getIncomingDependencies(asset); | ||
if (asset.meta.isCommonJS && !asset.symbols.hasExportSymbol('default')) { | ||
return deps.some( | ||
dep => | ||
bundle.hasDependency(dep) && | ||
dep.meta.isES6Module && | ||
dep.symbols.hasExportSymbol('default'), | ||
); | ||
} | ||
return false; | ||
} | ||
export function assertString(v: mixed): string { | ||
@@ -303,1 +329,41 @@ invariant(typeof v === 'string'); | ||
} | ||
export function parse(code: string, sourceFilename: string): Array<Statement> { | ||
let ast = babelParse(code, { | ||
sourceFilename, | ||
allowReturnOutsideFunction: true, | ||
plugins: ['dynamicImport'], | ||
}); | ||
return ast.program.body; | ||
} | ||
let helpersCache; | ||
export function getHelpers(): Map<string, BabelNode> { | ||
if (helpersCache != null) { | ||
return helpersCache; | ||
} | ||
let helpersPath = path.join(__dirname, 'helpers.js'); | ||
let statements = parse(fs.readFileSync(helpersPath, 'utf8'), helpersPath); | ||
helpersCache = new Map(); | ||
for (let statement of statements) { | ||
if (isVariableDeclaration(statement)) { | ||
if ( | ||
statement.declarations.length !== 1 || | ||
!isIdentifier(statement.declarations[0].id) | ||
) { | ||
throw new Error('Unsupported helper'); | ||
} | ||
helpersCache.set(statement.declarations[0].id.name, statement); | ||
} else if (isFunctionDeclaration(statement) && isIdentifier(statement.id)) { | ||
helpersCache.set(statement.id.name, statement); | ||
} else { | ||
throw new Error('Unsupported helper'); | ||
} | ||
} | ||
return helpersCache; | ||
} |
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
8
250886
31
6863
+ Addedglobals@^13.2.0
+ Addedglobals@13.24.0(transitive)
+ Addedtype-fest@0.20.2(transitive)
- Removed@babel/generator@^7.3.3