diff --git a/packages/babel-plugin-transform-es2015-modules-commonjs/package.json b/packages/babel-plugin-transform-es2015-modules-commonjs/package.json index cfad0ef5ef..148504224f 100644 --- a/packages/babel-plugin-transform-es2015-modules-commonjs/package.json +++ b/packages/babel-plugin-transform-es2015-modules-commonjs/package.json @@ -9,14 +9,12 @@ "babel-types": "^6.23.0", "babel-runtime": "^6.22.0", "babel-template": "^6.23.0", - "babel-plugin-transform-strict-mode": "^6.22.0", - "babel-plugin-syntax-object-rest-spread": "^6.13.0" + "babel-plugin-transform-strict-mode": "^6.22.0" }, "keywords": [ "babel-plugin" ], "devDependencies": { - "babel-plugin-transform-object-assign": "6.22.0", "babel-helper-plugin-test-runner": "^6.24.0" } } diff --git a/packages/babel-plugin-transform-es2015-modules-commonjs/src/index.js b/packages/babel-plugin-transform-es2015-modules-commonjs/src/index.js index 0f2c19e52e..5011bc10ed 100644 --- a/packages/babel-plugin-transform-es2015-modules-commonjs/src/index.js +++ b/packages/babel-plugin-transform-es2015-modules-commonjs/src/index.js @@ -1,6 +1,5 @@ import { basename, extname } from "path"; import template from "babel-template"; -import babelPluginSyntaxObjectRestSpread from "babel-plugin-syntax-object-rest-spread"; import babelPluginTransformStrictMode from "babel-plugin-transform-strict-mode"; import * as t from "babel-types"; @@ -129,287 +128,316 @@ export default function () { } }; - return Object.assign( - { - inherits: babelPluginTransformStrictMode, + return { + inherits: babelPluginTransformStrictMode, - visitor: { - ThisExpression(path, state) { - // If other plugins run after this plugin's Program#exit handler, we allow them to - // insert top-level `this` values. This allows the AMD and UMD plugins to - // function properly. - if (this.ranCommonJS) return; + visitor: { + ThisExpression(path, state) { + // If other plugins run after this plugin's Program#exit handler, we allow them to + // insert top-level `this` values. This allows the AMD and UMD plugins to + // function properly. + if (this.ranCommonJS) return; - if ( - state.opts.allowTopLevelThis !== true && - !path.findParent((path) => !path.is("shadow") && - THIS_BREAK_KEYS.indexOf(path.type) >= 0) - ) { - path.replaceWith(t.identifier("undefined")); + if ( + state.opts.allowTopLevelThis !== true && + !path.findParent((path) => !path.is("shadow") && + THIS_BREAK_KEYS.indexOf(path.type) >= 0) + ) { + path.replaceWith(t.identifier("undefined")); + } + }, + + Program: { + exit(path) { + this.ranCommonJS = true; + + const strict = !!this.opts.strict; + const noInterop = !!this.opts.noInterop; + + const { scope } = path; + + // rename these commonjs variables if they're declared in the file + scope.rename("module"); + scope.rename("exports"); + scope.rename("require"); + + let hasExports = false; + let hasImports = false; + + const body: Array = path.get("body"); + const imports = Object.create(null); + const exports = Object.create(null); + + const nonHoistedExportNames = Object.create(null); + + const topNodes = []; + const remaps = Object.create(null); + + const requires = Object.create(null); + + function addRequire(source, blockHoist) { + const cached = requires[source]; + if (cached) return cached; + + const ref = path.scope.generateUidIdentifier(basename(source, extname(source))); + + const varDecl = t.variableDeclaration("var", [ + t.variableDeclarator(ref, buildRequire( + t.stringLiteral(source) + ).expression) + ]); + + // Copy location from the original import statement for sourcemap + // generation. + if (imports[source]) { + varDecl.loc = imports[source].loc; + } + + if (typeof blockHoist === "number" && blockHoist > 0) { + varDecl._blockHoist = blockHoist; + } + + topNodes.push(varDecl); + + return requires[source] = ref; } - }, - Program: { - exit(path) { - this.ranCommonJS = true; + function addTo(obj, key, arr) { + const existing = obj[key] || []; + obj[key] = existing.concat(arr); + } - const strict = !!this.opts.strict; - const noInterop = !!this.opts.noInterop; + for (const path of body) { + if (path.isExportDeclaration()) { + hasExports = true; - const { scope } = path; - - // rename these commonjs variables if they're declared in the file - scope.rename("module"); - scope.rename("exports"); - scope.rename("require"); - - let hasExports = false; - let hasImports = false; - - const body: Array = path.get("body"); - const imports = Object.create(null); - const exports = Object.create(null); - - const nonHoistedExportNames = Object.create(null); - - const topNodes = []; - const remaps = Object.create(null); - - const requires = Object.create(null); - - function addRequire(source, blockHoist) { - const cached = requires[source]; - if (cached) return cached; - - const ref = path.scope.generateUidIdentifier(basename(source, extname(source))); - - const varDecl = t.variableDeclaration("var", [ - t.variableDeclarator(ref, buildRequire( - t.stringLiteral(source) - ).expression) - ]); - - // Copy location from the original import statement for sourcemap - // generation. - if (imports[source]) { - varDecl.loc = imports[source].loc; - } - - if (typeof blockHoist === "number" && blockHoist > 0) { - varDecl._blockHoist = blockHoist; - } - - topNodes.push(varDecl); - - return requires[source] = ref; - } - - function addTo(obj, key, arr) { - const existing = obj[key] || []; - obj[key] = existing.concat(arr); - } - - for (const path of body) { - if (path.isExportDeclaration()) { - hasExports = true; - - const specifiers = [].concat(path.get("declaration"), path.get("specifiers")); - for (const specifier of specifiers) { - const ids = specifier.getBindingIdentifiers(); - if (ids.__esModule) { - throw specifier.buildCodeFrameError("Illegal export \"__esModule\""); - } + const specifiers = [].concat(path.get("declaration"), path.get("specifiers")); + for (const specifier of specifiers) { + const ids = specifier.getBindingIdentifiers(); + if (ids.__esModule) { + throw specifier.buildCodeFrameError("Illegal export \"__esModule\""); } } + } - if (path.isImportDeclaration()) { - hasImports = true; + if (path.isImportDeclaration()) { + hasImports = true; - const key = path.node.source.value; - const importsEntry = imports[key] || { - specifiers: [], - maxBlockHoist: 0, - loc: path.node.loc, - }; + const key = path.node.source.value; + const importsEntry = imports[key] || { + specifiers: [], + maxBlockHoist: 0, + loc: path.node.loc, + }; - importsEntry.specifiers.push(...path.node.specifiers); + importsEntry.specifiers.push(...path.node.specifiers); - if (typeof path.node._blockHoist === "number") { - importsEntry.maxBlockHoist = Math.max( - path.node._blockHoist, - importsEntry.maxBlockHoist - ); - } + if (typeof path.node._blockHoist === "number") { + importsEntry.maxBlockHoist = Math.max( + path.node._blockHoist, + importsEntry.maxBlockHoist + ); + } - imports[key] = importsEntry; + imports[key] = importsEntry; - path.remove(); - } else if (path.isExportDefaultDeclaration()) { - const declaration = path.get("declaration"); - if (declaration.isFunctionDeclaration()) { - const id = declaration.node.id; - const defNode = t.identifier("default"); - if (id) { - addTo(exports, id.name, defNode); - topNodes.push(buildExportsAssignment(defNode, id)); - path.replaceWith(declaration.node); - } else { - topNodes.push(buildExportsAssignment(defNode, t.toExpression(declaration.node))); - path.remove(); - } - } else if (declaration.isClassDeclaration()) { - const id = declaration.node.id; - const defNode = t.identifier("default"); - if (id) { - addTo(exports, id.name, defNode); - path.replaceWithMultiple([ - declaration.node, - buildExportsAssignment(defNode, id) - ]); - } else { - path.replaceWith(buildExportsAssignment(defNode, t.toExpression(declaration.node))); - - // Manualy re-queue `export default class {}` expressions so that the ES3 transform - // has an opportunity to convert them. Ideally this would happen automatically from the - // replaceWith above. See #4140 for more info. - path.parentPath.requeue(path.get("expression.left")); - } + path.remove(); + } else if (path.isExportDefaultDeclaration()) { + const declaration = path.get("declaration"); + if (declaration.isFunctionDeclaration()) { + const id = declaration.node.id; + const defNode = t.identifier("default"); + if (id) { + addTo(exports, id.name, defNode); + topNodes.push(buildExportsAssignment(defNode, id)); + path.replaceWith(declaration.node); } else { - path.replaceWith(buildExportsAssignment(t.identifier("default"), declaration.node)); + topNodes.push(buildExportsAssignment(defNode, t.toExpression(declaration.node))); + path.remove(); + } + } else if (declaration.isClassDeclaration()) { + const id = declaration.node.id; + const defNode = t.identifier("default"); + if (id) { + addTo(exports, id.name, defNode); + path.replaceWithMultiple([ + declaration.node, + buildExportsAssignment(defNode, id) + ]); + } else { + path.replaceWith(buildExportsAssignment(defNode, t.toExpression(declaration.node))); - // Manualy re-queue `export default foo;` expressions so that the ES3 transform + // Manualy re-queue `export default class {}` expressions so that the ES3 transform // has an opportunity to convert them. Ideally this would happen automatically from the // replaceWith above. See #4140 for more info. path.parentPath.requeue(path.get("expression.left")); } - } else if (path.isExportNamedDeclaration()) { - const declaration = path.get("declaration"); - if (declaration.node) { - if (declaration.isFunctionDeclaration()) { - const id = declaration.node.id; - addTo(exports, id.name, id); - topNodes.push(buildExportsAssignment(id, id)); - path.replaceWith(declaration.node); - } else if (declaration.isClassDeclaration()) { - const id = declaration.node.id; - addTo(exports, id.name, id); - path.replaceWithMultiple([ - declaration.node, - buildExportsAssignment(id, id) - ]); - nonHoistedExportNames[id.name] = true; - } else if (declaration.isVariableDeclaration()) { - const declarators = declaration.get("declarations"); - for (const decl of declarators) { - const id = decl.get("id"); + } else { + path.replaceWith(buildExportsAssignment(t.identifier("default"), declaration.node)); - const init = decl.get("init"); - const exportsToInsert = []; - if (!init.node) init.replaceWith(t.identifier("undefined")); - - if (id.isIdentifier()) { - addTo(exports, id.node.name, id.node); - init.replaceWith(buildExportsAssignment(id.node, init.node).expression); - nonHoistedExportNames[id.node.name] = true; - } else if (id.isObjectPattern()) { - for (let i = 0; i < id.node.properties.length; i++) { - const prop = id.node.properties[i]; - let propValue = prop.value; - if (t.isAssignmentPattern(propValue)) { - propValue = propValue.left; - } else if (t.isRestProperty(prop)) { - propValue = prop.argument; - } - addTo(exports, propValue.name, propValue); - exportsToInsert.push(buildExportsAssignment(propValue, propValue)); - nonHoistedExportNames[propValue.name] = true; - } - } else if (id.isArrayPattern() && id.node.elements) { - for (let i = 0; i < id.node.elements.length; i++) { - let elem = id.node.elements[i]; - if (!elem) continue; - if (t.isAssignmentPattern(elem)) { - elem = elem.left; - } else if (t.isRestElement(elem)) { - elem = elem.argument; - } - const name = elem.name; - addTo(exports, name, elem); - exportsToInsert.push(buildExportsAssignment(elem, elem)); - nonHoistedExportNames[name] = true; - } - } - path.insertAfter(exportsToInsert); - } - path.replaceWith(declaration.node); - } - continue; - } - - const specifiers = path.get("specifiers"); - const nodes = []; - const source = path.node.source; - if (source) { - const ref = addRequire(source.value, path.node._blockHoist); - - for (const specifier of specifiers) { - if (specifier.isExportNamespaceSpecifier()) { - // todo - } else if (specifier.isExportDefaultSpecifier()) { - // todo - } else if (specifier.isExportSpecifier()) { - if (!noInterop && specifier.node.local.name === "default") { - topNodes.push(buildExportsFrom(t.stringLiteral(specifier.node.exported.name), - t.memberExpression( - t.callExpression(this.addHelper("interopRequireDefault"), [ref]), - specifier.node.local - ) - )); - } else { - topNodes.push(buildExportsFrom(t.stringLiteral(specifier.node.exported.name), - t.memberExpression(ref, specifier.node.local))); - } - nonHoistedExportNames[specifier.node.exported.name] = true; - } - } - } else { - for (const specifier of specifiers) { - if (specifier.isExportSpecifier()) { - addTo(exports, specifier.node.local.name, specifier.node.exported); - nonHoistedExportNames[specifier.node.exported.name] = true; - nodes.push(buildExportsAssignment(specifier.node.exported, specifier.node.local)); - } - } - } - path.replaceWithMultiple(nodes); - } else if (path.isExportAllDeclaration()) { - const exportNode = buildExportAll({ - OBJECT: addRequire(path.node.source.value, path.node._blockHoist) - }); - exportNode.loc = path.node.loc; - topNodes.push(exportNode); - path.remove(); + // Manualy re-queue `export default foo;` expressions so that the ES3 transform + // has an opportunity to convert them. Ideally this would happen automatically from the + // replaceWith above. See #4140 for more info. + path.parentPath.requeue(path.get("expression.left")); } - } + } else if (path.isExportNamedDeclaration()) { + const declaration = path.get("declaration"); + if (declaration.node) { + if (declaration.isFunctionDeclaration()) { + const id = declaration.node.id; + addTo(exports, id.name, id); + topNodes.push(buildExportsAssignment(id, id)); + path.replaceWith(declaration.node); + } else if (declaration.isClassDeclaration()) { + const id = declaration.node.id; + addTo(exports, id.name, id); + path.replaceWithMultiple([ + declaration.node, + buildExportsAssignment(id, id) + ]); + nonHoistedExportNames[id.name] = true; + } else if (declaration.isVariableDeclaration()) { + const declarators = declaration.get("declarations"); + for (const decl of declarators) { + const id = decl.get("id"); - for (const source in imports) { - const { specifiers, maxBlockHoist } = imports[source]; - if (specifiers.length) { - const uid = addRequire(source, maxBlockHoist); + const init = decl.get("init"); + const exportsToInsert = []; + if (!init.node) init.replaceWith(t.identifier("undefined")); - let wildcard; + if (id.isIdentifier()) { + addTo(exports, id.node.name, id.node); + init.replaceWith(buildExportsAssignment(id.node, init.node).expression); + nonHoistedExportNames[id.node.name] = true; + } else if (id.isObjectPattern()) { + for (let i = 0; i < id.node.properties.length; i++) { + const prop = id.node.properties[i]; + let propValue = prop.value; + if (t.isAssignmentPattern(propValue)) { + propValue = propValue.left; + } else if (t.isRestProperty(prop)) { + propValue = prop.argument; + } + addTo(exports, propValue.name, propValue); + exportsToInsert.push(buildExportsAssignment(propValue, propValue)); + nonHoistedExportNames[propValue.name] = true; + } + } else if (id.isArrayPattern() && id.node.elements) { + for (let i = 0; i < id.node.elements.length; i++) { + let elem = id.node.elements[i]; + if (!elem) continue; + if (t.isAssignmentPattern(elem)) { + elem = elem.left; + } else if (t.isRestElement(elem)) { + elem = elem.argument; + } + const name = elem.name; + addTo(exports, name, elem); + exportsToInsert.push(buildExportsAssignment(elem, elem)); + nonHoistedExportNames[name] = true; + } + } + path.insertAfter(exportsToInsert); + } + path.replaceWith(declaration.node); + } + continue; + } - for (let i = 0; i < specifiers.length; i++) { - const specifier = specifiers[i]; - if (t.isImportNamespaceSpecifier(specifier)) { - if (strict || noInterop) { - remaps[specifier.local.name] = uid; + const specifiers = path.get("specifiers"); + const nodes = []; + const source = path.node.source; + if (source) { + const ref = addRequire(source.value, path.node._blockHoist); + + for (const specifier of specifiers) { + if (specifier.isExportNamespaceSpecifier()) { + // todo + } else if (specifier.isExportDefaultSpecifier()) { + // todo + } else if (specifier.isExportSpecifier()) { + if (!noInterop && specifier.node.local.name === "default") { + topNodes.push(buildExportsFrom(t.stringLiteral(specifier.node.exported.name), + t.memberExpression( + t.callExpression(this.addHelper("interopRequireDefault"), [ref]), + specifier.node.local + ) + )); } else { + topNodes.push(buildExportsFrom(t.stringLiteral(specifier.node.exported.name), + t.memberExpression(ref, specifier.node.local))); + } + nonHoistedExportNames[specifier.node.exported.name] = true; + } + } + } else { + for (const specifier of specifiers) { + if (specifier.isExportSpecifier()) { + addTo(exports, specifier.node.local.name, specifier.node.exported); + nonHoistedExportNames[specifier.node.exported.name] = true; + nodes.push(buildExportsAssignment(specifier.node.exported, specifier.node.local)); + } + } + } + path.replaceWithMultiple(nodes); + } else if (path.isExportAllDeclaration()) { + const exportNode = buildExportAll({ + OBJECT: addRequire(path.node.source.value, path.node._blockHoist) + }); + exportNode.loc = path.node.loc; + topNodes.push(exportNode); + path.remove(); + } + } + + for (const source in imports) { + const { specifiers, maxBlockHoist } = imports[source]; + if (specifiers.length) { + const uid = addRequire(source, maxBlockHoist); + + let wildcard; + + for (let i = 0; i < specifiers.length; i++) { + const specifier = specifiers[i]; + if (t.isImportNamespaceSpecifier(specifier)) { + if (strict || noInterop) { + remaps[specifier.local.name] = uid; + } else { + const varDecl = t.variableDeclaration("var", [ + t.variableDeclarator( + specifier.local, + t.callExpression( + this.addHelper("interopRequireWildcard"), + [uid] + ) + ) + ]); + + if (maxBlockHoist > 0) { + varDecl._blockHoist = maxBlockHoist; + } + + topNodes.push(varDecl); + } + wildcard = specifier.local; + } else if (t.isImportDefaultSpecifier(specifier)) { + specifiers[i] = t.importSpecifier(specifier.local, t.identifier("default")); + } + } + + for (const specifier of specifiers) { + if (t.isImportSpecifier(specifier)) { + let target = uid; + if (specifier.imported.name === "default") { + if (wildcard) { + target = wildcard; + } else if (!noInterop) { + target = wildcard = path.scope.generateUidIdentifier(uid.name); const varDecl = t.variableDeclaration("var", [ t.variableDeclarator( - specifier.local, + target, t.callExpression( - this.addHelper("interopRequireWildcard"), + this.addHelper("interopRequireDefault"), [uid] ) ) @@ -421,100 +449,68 @@ export default function () { topNodes.push(varDecl); } - wildcard = specifier.local; - } else if (t.isImportDefaultSpecifier(specifier)) { - specifiers[i] = t.importSpecifier(specifier.local, t.identifier("default")); } + remaps[specifier.local.name] = t.memberExpression(target, + t.cloneWithoutLoc(specifier.imported)); } - - for (const specifier of specifiers) { - if (t.isImportSpecifier(specifier)) { - let target = uid; - if (specifier.imported.name === "default") { - if (wildcard) { - target = wildcard; - } else if (!noInterop) { - target = wildcard = path.scope.generateUidIdentifier(uid.name); - const varDecl = t.variableDeclaration("var", [ - t.variableDeclarator( - target, - t.callExpression( - this.addHelper("interopRequireDefault"), - [uid] - ) - ) - ]); - - if (maxBlockHoist > 0) { - varDecl._blockHoist = maxBlockHoist; - } - - topNodes.push(varDecl); - } - } - remaps[specifier.local.name] = t.memberExpression(target, - t.cloneWithoutLoc(specifier.imported)); - } - } - } else { - // bare import - const requireNode = buildRequire(t.stringLiteral(source)); - requireNode.loc = imports[source].loc; - topNodes.push(requireNode); } + } else { + // bare import + const requireNode = buildRequire(t.stringLiteral(source)); + requireNode.loc = imports[source].loc; + topNodes.push(requireNode); } - - if (hasImports && Object.keys(nonHoistedExportNames).length) { - - // avoid creating too long of export assignment to prevent stack overflow - const maxHoistedExportsNodeAssignmentLength = 100; - const nonHoistedExportNamesArr = Object.keys(nonHoistedExportNames); - - for ( - let currentExportsNodeAssignmentLength = 0; - currentExportsNodeAssignmentLength < nonHoistedExportNamesArr.length; - currentExportsNodeAssignmentLength += maxHoistedExportsNodeAssignmentLength - ) { - const nonHoistedExportNamesChunk = nonHoistedExportNamesArr.slice( - currentExportsNodeAssignmentLength, - currentExportsNodeAssignmentLength + maxHoistedExportsNodeAssignmentLength); - - let hoistedExportsNode = t.identifier("undefined"); - - nonHoistedExportNamesChunk.forEach(function (name) { - hoistedExportsNode = buildExportsAssignment(t.identifier(name), hoistedExportsNode) - .expression; - }); - - const node = t.expressionStatement(hoistedExportsNode); - node._blockHoist = 3; - - topNodes.unshift(node); - } - } - - // add __esModule declaration if this file has any exports - if (hasExports && !strict) { - let buildTemplate = buildExportsModuleDeclaration; - if (this.opts.loose) buildTemplate = buildLooseExportsModuleDeclaration; - - const declar = buildTemplate(); - declar._blockHoist = 3; - - topNodes.unshift(declar); - } - - path.unshiftContainer("body", topNodes); - path.traverse(reassignmentVisitor, { - remaps, - scope, - exports, - requeueInParent: (newPath) => path.requeue(newPath), - }); } + + if (hasImports && Object.keys(nonHoistedExportNames).length) { + + // avoid creating too long of export assignment to prevent stack overflow + const maxHoistedExportsNodeAssignmentLength = 100; + const nonHoistedExportNamesArr = Object.keys(nonHoistedExportNames); + + for ( + let currentExportsNodeAssignmentLength = 0; + currentExportsNodeAssignmentLength < nonHoistedExportNamesArr.length; + currentExportsNodeAssignmentLength += maxHoistedExportsNodeAssignmentLength + ) { + const nonHoistedExportNamesChunk = nonHoistedExportNamesArr.slice( + currentExportsNodeAssignmentLength, + currentExportsNodeAssignmentLength + maxHoistedExportsNodeAssignmentLength); + + let hoistedExportsNode = t.identifier("undefined"); + + nonHoistedExportNamesChunk.forEach(function (name) { + hoistedExportsNode = buildExportsAssignment(t.identifier(name), hoistedExportsNode) + .expression; + }); + + const node = t.expressionStatement(hoistedExportsNode); + node._blockHoist = 3; + + topNodes.unshift(node); + } + } + + // add __esModule declaration if this file has any exports + if (hasExports && !strict) { + let buildTemplate = buildExportsModuleDeclaration; + if (this.opts.loose) buildTemplate = buildLooseExportsModuleDeclaration; + + const declar = buildTemplate(); + declar._blockHoist = 3; + + topNodes.unshift(declar); + } + + path.unshiftContainer("body", topNodes); + path.traverse(reassignmentVisitor, { + remaps, + scope, + exports, + requeueInParent: (newPath) => path.requeue(newPath), + }); } } - }, - babelPluginSyntaxObjectRestSpread() - ); + } + }; } diff --git a/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/strict/export-const-destructuring-object-rest/actual.js b/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/strict/export-const-destructuring-object-rest/actual.js deleted file mode 100644 index d4d52568ce..0000000000 --- a/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/strict/export-const-destructuring-object-rest/actual.js +++ /dev/null @@ -1 +0,0 @@ -export const { foo, ...bar } = {}; diff --git a/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/strict/export-const-destructuring-object-rest/expected.js b/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/strict/export-const-destructuring-object-rest/expected.js deleted file mode 100644 index 4e27e34014..0000000000 --- a/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/strict/export-const-destructuring-object-rest/expected.js +++ /dev/null @@ -1,5 +0,0 @@ -"use strict"; - -const { foo, ...bar } = {}; -exports.foo = foo; -exports.bar = bar;