diff --git a/packages/babel-plugin-transform-typescript/src/namespace.js b/packages/babel-plugin-transform-typescript/src/namespace.js index be3b51c949cd..664326f4626c 100644 --- a/packages/babel-plugin-transform-typescript/src/namespace.js +++ b/packages/babel-plugin-transform-typescript/src/namespace.js @@ -1,4 +1,4 @@ -import { template } from "@babel/core"; +import { template, types as t } from "@babel/core"; export default function transpileNamespace(path, t, allowNamespaces) { if (path.node.declare || path.node.id.type === "StringLiteral") { @@ -46,6 +46,57 @@ function getMemberExpression(t, name, itemName) { return t.memberExpression(t.identifier(name), t.identifier(itemName)); } +/** + * Convert export const foo = 1 to Namepsace.foo = 1; + * + * @param {t.VariableDeclaration} node given variable declaration, e.g. `const foo = 1` + * @param {string} name the generated unique namespace member name + * @param {*} hub An instance implements HubInterface defined in `@babel/traverse` that can throw a code frame error + */ +function handleVariableDeclaration( + node: t.VariableDeclaration, + name: string, + hub: any, +): t.Statement[] { + if (node.kind !== "const") { + throw hub.file.buildCodeFrameError( + node, + "Namespaces exporting non-const are not supported by Babel." + + " Change to const or see:" + + " https://babeljs.io/docs/en/babel-plugin-transform-typescript", + ); + } + const { declarations } = node; + if (declarations.every(declarator => t.isIdentifier(declarator.id))) { + // `export const a = 1` transforms to `const a = N.a = 1`, the output + // is smaller than `const a = 1; N.a = a`; + for (const declarator of node.declarations) { + declarator.init = t.assignmentExpression( + "=", + getMemberExpression(t, name, declarator.id.name), + declarator.init, + ); + } + return [node]; + } + // Now we have pattern in declarators + // `export const [a] = 1` transforms to `const [a] = 1; N.a = a` + const bindingIdentifiers = t.getBindingIdentifiers(node); + const assignments = []; + // getBindingIdentifiers returns an object without prototype. + // eslint-disable-next-line guard-for-in + for (const idName in bindingIdentifiers) { + assignments.push( + t.assignmentExpression( + "=", + getMemberExpression(t, name, idName), + t.cloneNode(bindingIdentifiers[idName]), + ), + ); + } + return [node, t.expressionStatement(t.sequenceExpression(assignments))]; +} + function handleNested(path, t, node, parentExport) { const names = new Set(); const realName = node.id; @@ -78,11 +129,14 @@ function handleNested(path, t, node, parentExport) { case "ClassDeclaration": names.add(subNode.id.name); continue; - case "VariableDeclaration": - for (const variable of subNode.declarations) { - names.add(variable.id.name); + case "VariableDeclaration": { + // getBindingIdentifiers returns an object without prototype. + // eslint-disable-next-line guard-for-in + for (const name in t.getBindingIdentifiers(subNode)) { + names.add(name); } continue; + } default: // Neither named declaration nor export, continue to next item. continue; @@ -111,24 +165,16 @@ function handleNested(path, t, node, parentExport) { ); break; } - case "VariableDeclaration": - if (subNode.declaration.kind !== "const") { - throw path.hub.file.buildCodeFrameError( - subNode.declaration, - "Namespaces exporting non-const are not supported by Babel." + - " Change to const or see:" + - " https://babeljs.io/docs/en/babel-plugin-transform-typescript", - ); - } - for (const variable of subNode.declaration.declarations) { - variable.init = t.assignmentExpression( - "=", - getMemberExpression(t, name, variable.id.name), - variable.init, - ); - } - namespaceTopLevel[i] = subNode.declaration; + case "VariableDeclaration": { + const nodes = handleVariableDeclaration( + subNode.declaration, + name, + path.hub, + ); + namespaceTopLevel.splice(i, nodes.length, ...nodes); + i += nodes.length - 1; break; + } case "TSModuleDeclaration": { const transformed = handleNested( path, diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-nested-module/input.ts b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-nested-module/input.ts new file mode 100644 index 000000000000..c5db9c8d7ecf --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-nested-module/input.ts @@ -0,0 +1,6 @@ +namespace N { + const M1 = {} + export module M1 {} + const { M2 } = {} + export module M2 {} +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-nested-module/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-nested-module/output.mjs new file mode 100644 index 000000000000..7d9a95580e17 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-nested-module/output.mjs @@ -0,0 +1,13 @@ +let N; + +(function (_N) { + const M1 = {}; + + (function (_M) {})(M1 || (M1 = _N.M1 || (_N.M1 = {}))); + + const { + M2 + } = {}; + + (function (_M2) {})(M2 || (M2 = _N.M2 || (_N.M2 = {}))); +})(N || (N = {})); diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-destructuring/input.ts b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-destructuring/input.ts new file mode 100644 index 000000000000..3eaf06b7a860 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-destructuring/input.ts @@ -0,0 +1,6 @@ +class C { } +namespace N { + export const { a } = C; + export const [ b ] = C; + export const [ { a: [{ b: c }] }] = A, { a: { b: { d = 1 } } = {}, ...e } = C; +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-destructuring/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-destructuring/output.mjs new file mode 100644 index 000000000000..7ba530d74ec9 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-destructuring/output.mjs @@ -0,0 +1,24 @@ +class C {} + +let N; + +(function (_N) { + const { + a + } = C; + _N.a = a; + const [{ + a: [{ + b: c + }] + }] = A, + { + a: { + b: { + d = 1 + } + } = {}, + ...e + } = C; + _N.e = e, _N.c = c, _N.d = d; +})(N || (N = {}));