diff --git a/packages/babel-helper-module-transforms/package.json b/packages/babel-helper-module-transforms/package.json index 8d54404418..bf3ab72d14 100644 --- a/packages/babel-helper-module-transforms/package.json +++ b/packages/babel-helper-module-transforms/package.json @@ -10,6 +10,7 @@ "dependencies": { "@babel/helper-module-imports": "7.0.0-beta.40", "@babel/helper-simple-access": "7.0.0-beta.40", + "@babel/helper-split-export-declaration": "7.0.0-beta.40", "@babel/template": "7.0.0-beta.40", "@babel/types": "7.0.0-beta.40", "lodash": "^4.2.0" diff --git a/packages/babel-helper-module-transforms/src/normalize-and-load-metadata.js b/packages/babel-helper-module-transforms/src/normalize-and-load-metadata.js index c937ab5e61..0b649b5593 100644 --- a/packages/babel-helper-module-transforms/src/normalize-and-load-metadata.js +++ b/packages/babel-helper-module-transforms/src/normalize-and-load-metadata.js @@ -1,6 +1,6 @@ import { basename, extname } from "path"; -import * as t from "@babel/types"; +import splitExportDeclaration from "@babel/helper-split-export-declaration"; export type ModuleMetadata = { exportName: string, @@ -399,35 +399,7 @@ function nameAnonymousExports(programPath: NodePath) { // Name anonymous exported locals. programPath.get("body").forEach(child => { if (!child.isExportDefaultDeclaration()) return; - - // export default foo; - const declaration = child.get("declaration"); - if (declaration.isFunctionDeclaration()) { - if (!declaration.node.id) { - declaration.node.id = declaration.scope.generateUidIdentifier( - "default", - ); - } - } else if (declaration.isClassDeclaration()) { - if (!declaration.node.id) { - declaration.node.id = declaration.scope.generateUidIdentifier( - "default", - ); - } - } else { - const id = declaration.scope.generateUidIdentifier("default"); - const namedDecl = t.exportNamedDeclaration(null, [ - t.exportSpecifier(t.identifier(id.name), t.identifier("default")), - ]); - namedDecl._blockHoist = child.node._blockHoist; - - const varDecl = t.variableDeclaration("var", [ - t.variableDeclarator(id, declaration.node), - ]); - varDecl._blockHoist = child.node._blockHoist; - - child.replaceWithMultiple([namedDecl, varDecl]); - } + splitExportDeclaration(child); }); } diff --git a/packages/babel-helper-module-transforms/src/rewrite-live-references.js b/packages/babel-helper-module-transforms/src/rewrite-live-references.js index 484633d75a..2253a1903e 100644 --- a/packages/babel-helper-module-transforms/src/rewrite-live-references.js +++ b/packages/babel-helper-module-transforms/src/rewrite-live-references.js @@ -112,6 +112,7 @@ const rewriteBindingInitVisitor = { Object.keys(path.getOuterBindingIdentifiers()).forEach(localName => { const exportNames = exported.get(localName) || []; + if (exportNames.length > 0) { const statement = t.expressionStatement( buildBindingExportAssignmentExpression( diff --git a/packages/babel-helper-split-export-declaration/.npmignore b/packages/babel-helper-split-export-declaration/.npmignore new file mode 100644 index 0000000000..f980694583 --- /dev/null +++ b/packages/babel-helper-split-export-declaration/.npmignore @@ -0,0 +1,3 @@ +src +test +*.log diff --git a/packages/babel-helper-split-export-declaration/README.md b/packages/babel-helper-split-export-declaration/README.md new file mode 100644 index 0000000000..195f151e92 --- /dev/null +++ b/packages/babel-helper-split-export-declaration/README.md @@ -0,0 +1,23 @@ +# @babel/helper-split-export-declaration + +## API + +```js +declare export default splitExportDeclaration(path: NodePath); +``` + +## Usage + +```js +import traverse from "@babel/traverse"; +import splitExportDeclaration from "@babel/helper-split-export-declaration"; + +// ... + +traverse(file, { + ExportDefaultDeclaration(path) { + if (!path.get("declaration").isClassDeclaration()) return; + splitExportDeclaration(path); + }, +}); +``` diff --git a/packages/babel-helper-split-export-declaration/package.json b/packages/babel-helper-split-export-declaration/package.json new file mode 100644 index 0000000000..eb4dd125cf --- /dev/null +++ b/packages/babel-helper-split-export-declaration/package.json @@ -0,0 +1,11 @@ +{ + "name": "@babel/helper-split-export-declaration", + "version": "7.0.0-beta.40", + "description": "", + "repository": "https://github.com/babel/babel/tree/master/packages/babel-helper-split-export-declaration", + "license": "MIT", + "main": "lib/index.js", + "dependencies": { + "@babel/types": "7.0.0-beta.40" + } +} diff --git a/packages/babel-helper-split-export-declaration/src/index.js b/packages/babel-helper-split-export-declaration/src/index.js new file mode 100644 index 0000000000..ffbf52243a --- /dev/null +++ b/packages/babel-helper-split-export-declaration/src/index.js @@ -0,0 +1,76 @@ +import * as t from "@babel/types"; + +export default function splitExportDeclaration(exportDeclaration) { + if (!exportDeclaration.isExportDeclaration()) { + throw new Error("Only export declarations can be splitted."); + } + + // build specifiers that point back to this export declaration + const isDefault = exportDeclaration.isExportDefaultDeclaration(); + const declaration = exportDeclaration.get("declaration"); + const isClassDeclaration = declaration.isClassDeclaration(); + + if (isDefault) { + const standaloneDeclaration = + declaration.isFunctionDeclaration() || isClassDeclaration; + + const scope = declaration.isScope() + ? declaration.scope.parent + : declaration.scope; + + let id = declaration.node.id; + let needBindingRegistration = false; + + if (!id) { + needBindingRegistration = true; + + id = scope.generateUidIdentifier("default"); + + if ( + standaloneDeclaration || + declaration.isFunctionExpression() || + declaration.isClassExpression() + ) { + declaration.node.id = t.cloneNode(id); + } + } + + const updatedDeclaration = standaloneDeclaration + ? declaration + : t.variableDeclaration("var", [ + t.variableDeclarator(t.cloneNode(id), declaration.node), + ]); + + const updatedExportDeclaration = t.exportNamedDeclaration(null, [ + t.exportSpecifier(t.cloneNode(id), t.identifier("default")), + ]); + + exportDeclaration.insertAfter(updatedExportDeclaration); + exportDeclaration.replaceWith(updatedDeclaration); + + if (needBindingRegistration) { + scope.registerBinding( + isClassDeclaration ? "let" : "var", + exportDeclaration, + ); + } + + return exportDeclaration; + } + + if (exportDeclaration.get("specifiers").length > 0) { + throw new Error("It doesn't make sense to split exported specifiers."); + } + + const bindingIdentifiers = declaration.getOuterBindingIdentifiers(); + + const specifiers = Object.keys(bindingIdentifiers).map(name => { + return t.exportSpecifier(t.identifier(name), t.identifier(name)); + }); + + const aliasDeclar = t.exportNamedDeclaration(null, specifiers); + + exportDeclaration.insertAfter(aliasDeclar); + exportDeclaration.replaceWith(declaration.node); + return exportDeclaration; +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/general/regression-T2983/output.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/general/regression-T2983/output.js index 221316a2f1..8eadb530f3 100644 --- a/packages/babel-plugin-proposal-class-properties/test/fixtures/general/regression-T2983/output.js +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/general/regression-T2983/output.js @@ -9,15 +9,15 @@ call((_temp = _class = function _class() { value: true }), _temp)); -var _class2 = function _class2() { - babelHelpers.classCallCheck(this, _class2); +var _default = function _default() { + babelHelpers.classCallCheck(this, _default); }; -Object.defineProperty(_class2, "test", { +Object.defineProperty(_default, "test", { configurable: true, enumerable: true, writable: true, value: true }); -export { _class2 as default }; +export { _default as default }; ; diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/regression/T2983/output.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/regression/T2983/output.js index 1ab8d181d8..371191a12b 100644 --- a/packages/babel-plugin-proposal-class-properties/test/fixtures/regression/T2983/output.js +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/regression/T2983/output.js @@ -4,10 +4,10 @@ call((_temp = _class = function _class() { babelHelpers.classCallCheck(this, _class); }, _class.test = true, _temp)); -var _class2 = function _class2() { - babelHelpers.classCallCheck(this, _class2); +var _default = function _default() { + babelHelpers.classCallCheck(this, _default); }; -_class2.test = true; -export { _class2 as default }; +_default.test = true; +export { _default as default }; ; diff --git a/packages/babel-plugin-transform-classes/package.json b/packages/babel-plugin-transform-classes/package.json index 53bb0ba44b..ae3883dbae 100644 --- a/packages/babel-plugin-transform-classes/package.json +++ b/packages/babel-plugin-transform-classes/package.json @@ -11,6 +11,7 @@ "@babel/helper-function-name": "7.0.0-beta.40", "@babel/helper-optimise-call-expression": "7.0.0-beta.40", "@babel/helper-replace-supers": "7.0.0-beta.40", + "@babel/helper-split-export-declaration": "7.0.0-beta.40", "globals": "^11.1.0" }, "keywords": [ diff --git a/packages/babel-plugin-transform-classes/src/index.js b/packages/babel-plugin-transform-classes/src/index.js index a93fe739ed..15e12512d4 100644 --- a/packages/babel-plugin-transform-classes/src/index.js +++ b/packages/babel-plugin-transform-classes/src/index.js @@ -2,6 +2,7 @@ import LooseTransformer from "./loose"; import VanillaTransformer from "./vanilla"; import annotateAsPure from "@babel/helper-annotate-as-pure"; import nameFunction from "@babel/helper-function-name"; +import splitExportDeclaration from "@babel/helper-split-export-declaration"; import { types as t } from "@babel/core"; import globals from "globals"; @@ -24,19 +25,7 @@ export default function(api, options) { visitor: { ExportDefaultDeclaration(path) { if (!path.get("declaration").isClassDeclaration()) return; - - const { node } = path; - const ref = - node.declaration.id || path.scope.generateUidIdentifier("class"); - node.declaration.id = ref; - - // Split the class declaration and the export into two separate statements. - path.replaceWith(node.declaration); - path.insertAfter( - t.exportNamedDeclaration(null, [ - t.exportSpecifier(t.cloneNode(ref), t.identifier("default")), - ]), - ); + splitExportDeclaration(path); }, ClassDeclaration(path) { diff --git a/packages/babel-plugin-transform-classes/test/fixtures/regression/2941/output.js b/packages/babel-plugin-transform-classes/test/fixtures/regression/2941/output.js index 3e311d72a6..9c148ffe0c 100644 --- a/packages/babel-plugin-transform-classes/test/fixtures/regression/2941/output.js +++ b/packages/babel-plugin-transform-classes/test/fixtures/regression/2941/output.js @@ -5,8 +5,8 @@ Object.defineProperty(exports, "__esModule", { }); exports.default = void 0; -var _class = function _class() { - babelHelpers.classCallCheck(this, _class); +var _default = function _default() { + babelHelpers.classCallCheck(this, _default); }; -exports.default = _class; +exports.default = _default; diff --git a/packages/babel-plugin-transform-classes/test/fixtures/spec/export-super-class/output.js b/packages/babel-plugin-transform-classes/test/fixtures/spec/export-super-class/output.js index 7360483607..6aafe37c5a 100644 --- a/packages/babel-plugin-transform-classes/test/fixtures/spec/export-super-class/output.js +++ b/packages/babel-plugin-transform-classes/test/fixtures/spec/export-super-class/output.js @@ -1,14 +1,14 @@ -var _class = +var _default = /*#__PURE__*/ function (_A) { - babelHelpers.inherits(_class, _A); + babelHelpers.inherits(_default, _A); - function _class() { - babelHelpers.classCallCheck(this, _class); - return babelHelpers.possibleConstructorReturn(this, (_class.__proto__ || Object.getPrototypeOf(_class)).apply(this, arguments)); + function _default() { + babelHelpers.classCallCheck(this, _default); + return babelHelpers.possibleConstructorReturn(this, (_default.__proto__ || Object.getPrototypeOf(_default)).apply(this, arguments)); } - return _class; + return _default; }(A); -export { _class as default }; +export { _default as default }; diff --git a/packages/babel-plugin-transform-function-name/test/fixtures/function-name/export/output.js b/packages/babel-plugin-transform-function-name/test/fixtures/function-name/export/output.js index 1fff013ff6..c686b4d026 100644 --- a/packages/babel-plugin-transform-function-name/test/fixtures/function-name/export/output.js +++ b/packages/babel-plugin-transform-function-name/test/fixtures/function-name/export/output.js @@ -1,13 +1,14 @@ -export { _whatever as whatever }; -export { _wowzers as default }; var _foo = "yes", foob = "no"; export { _foo as foo, foob }; function _whatever() {} +export { _whatever as whatever }; + function _wowzers() {} +export { _wowzers as default }; var bar = { foo: function foo() { _foo; diff --git a/packages/babel-plugin-transform-function-name/test/fixtures/function-name/modules-4/output.js b/packages/babel-plugin-transform-function-name/test/fixtures/function-name/modules-4/output.js index 9f010a2519..8ce7e6c3c4 100644 --- a/packages/babel-plugin-transform-function-name/test/fixtures/function-name/modules-4/output.js +++ b/packages/babel-plugin-transform-function-name/test/fixtures/function-name/modules-4/output.js @@ -1,7 +1,6 @@ -export { _foo as foo }; - function _foo(bar) {} +export { _foo as foo }; var bar = { foo: function foo() { _foo; diff --git a/packages/babel-traverse/package.json b/packages/babel-traverse/package.json index c171a47fe1..05159aa2a6 100644 --- a/packages/babel-traverse/package.json +++ b/packages/babel-traverse/package.json @@ -11,6 +11,7 @@ "@babel/code-frame": "7.0.0-beta.40", "@babel/generator": "7.0.0-beta.40", "@babel/helper-function-name": "7.0.0-beta.40", + "@babel/helper-split-export-declaration": "7.0.0-beta.40", "@babel/types": "7.0.0-beta.40", "babylon": "7.0.0-beta.40", "debug": "^3.0.1", diff --git a/packages/babel-traverse/src/scope/index.js b/packages/babel-traverse/src/scope/index.js index 8af703fdc0..f123d1ac24 100644 --- a/packages/babel-traverse/src/scope/index.js +++ b/packages/babel-traverse/src/scope/index.js @@ -210,7 +210,7 @@ export default class Scope { * Generate a unique identifier and add it to the current scope. */ - generateDeclaredUidIdentifier(name: string = "temp") { + generateDeclaredUidIdentifier(name?: string) { const id = this.generateUidIdentifier(name); this.push({ id }); return t.cloneNode(id); @@ -220,7 +220,7 @@ export default class Scope { * Generate a unique identifier. */ - generateUidIdentifier(name: string = "temp") { + generateUidIdentifier(name?: string) { return t.identifier(this.generateUid(name)); } diff --git a/packages/babel-traverse/src/scope/lib/renamer.js b/packages/babel-traverse/src/scope/lib/renamer.js index c18c20b1a1..05b9e807f4 100644 --- a/packages/babel-traverse/src/scope/lib/renamer.js +++ b/packages/babel-traverse/src/scope/lib/renamer.js @@ -1,4 +1,5 @@ import Binding from "../binding"; +import splitExportDeclaration from "@babel/helper-split-export-declaration"; import * as t from "@babel/types"; const renameVisitor = { @@ -40,48 +41,20 @@ export default class Renamer { binding: Binding; maybeConvertFromExportDeclaration(parentDeclar) { - const exportDeclar = - parentDeclar.parentPath.isExportDeclaration() && parentDeclar.parentPath; - if (!exportDeclar) return; + const maybeExportDeclar = parentDeclar.parentPath; - // build specifiers that point back to this export declaration - const isDefault = exportDeclar.isExportDefaultDeclaration(); + if (!maybeExportDeclar.isExportDeclaration()) { + return; + } if ( - isDefault && - (parentDeclar.isFunctionDeclaration() || - parentDeclar.isClassDeclaration()) && - !parentDeclar.node.id + maybeExportDeclar.isExportDefaultDeclaration() && + !maybeExportDeclar.get("declaration").node.id ) { - // Ensure that default class and function exports have a name so they have a identifier to - // reference from the export specifier list. - parentDeclar.node.id = parentDeclar.scope.generateUidIdentifier( - "default", - ); + return; } - const bindingIdentifiers = parentDeclar.getOuterBindingIdentifiers(); - const specifiers = []; - - for (const name in bindingIdentifiers) { - const localName = name === this.oldName ? this.newName : name; - const exportedName = isDefault ? "default" : name; - specifiers.push( - t.exportSpecifier(t.identifier(localName), t.identifier(exportedName)), - ); - } - - if (specifiers.length) { - const aliasDeclar = t.exportNamedDeclaration(null, specifiers); - - // hoist to the top if it's a function - if (parentDeclar.isFunctionDeclaration()) { - aliasDeclar._blockHoist = 3; - } - - exportDeclar.insertAfter(aliasDeclar); - exportDeclar.replaceWith(parentDeclar.node); - } + splitExportDeclaration(maybeExportDeclar); } maybeConvertFromClassFunctionDeclaration(path) { @@ -129,7 +102,10 @@ export default class Renamer { const { scope, path } = binding; const parentDeclar = path.find( - path => path.isDeclaration() || path.isFunctionExpression(), + path => + path.isDeclaration() || + path.isFunctionExpression() || + path.isClassExpression(), ); if (parentDeclar) { this.maybeConvertFromExportDeclaration(parentDeclar);