String import/export specifier (#12091)

* feat: parse moduleExportName
* feat: add validators
* Support string specifier name in commonjs transform
* Support string specifier name in export-ns-from
* test: add loose testcases
* test: add testcases for amd and umd
* feat: support systemjs
* test: update fixtures fixed in #12110
* add plugin name typings
* test: rename test layout
* feat: implement under moduleStringNames flag
* chore: add plugin syntax module string names
* feat: support ModuleExportName as ModuleExportName
* test: update test fixtures
* fix flow errors
* docs: update AST spec
* feat: support { "some imports" as "some exports" }
* feat: support { "some imports" as "some exports" } in systemjs
* test: add test on `import { "foo" }`
* Address review comments
* add moduleStringNames to missing plugin helpers
* Apply suggestions from code review
* update test fixtures
* Update packages/babel-parser/src/parser/error-message.js
* update test fixtures

Co-Authored-By: Kai Cataldo <kai@kaicataldo.com>
Co-authored-by: Brian Ng <bng412@gmail.com>
This commit is contained in:
Huáng Jùnliàng
2020-09-21 16:50:51 -04:00
committed by Nicolò Ribaudo
parent 1b90d90fcc
commit 21d7ee2610
152 changed files with 1872 additions and 96 deletions

View File

@@ -3,6 +3,7 @@ import hoistVariables from "@babel/helper-hoist-variables";
import { template, types as t } from "@babel/core";
import { getImportSource } from "babel-plugin-dynamic-import-node/utils";
import { rewriteThis, getModuleName } from "@babel/helper-module-transforms";
import { isIdentifierName } from "@babel/helper-validator-identifier";
const buildTemplate = template(`
SYSTEM_REGISTER(MODULE_NAME, SOURCES, function (EXPORT_IDENTIFIER, CONTEXT_IDENTIFIER) {
@@ -29,12 +30,50 @@ WARNING: Dynamic import() transformation must be enabled using the
no longer transform import() without using that plugin.
`;
//todo: use getExportSpecifierName in `helper-module-transforms` when this library is refactored to NodePath usage.
export function getExportSpecifierName(
node: Node,
stringSpecifiers: Set<string>,
): string {
if (node.type === "Identifier") {
return node.name;
} else if (node.type === "StringLiteral") {
const stringValue = node.value;
// add specifier value to `stringSpecifiers` only when it can not be converted to an identifier name
// i.e In `import { "foo" as bar }`
// we do not consider `"foo"` to be a `stringSpecifier` because we can treat it as
// `import { foo as bar }`
// This helps minimize the size of `stringSpecifiers` and reduce overhead of checking valid identifier names
// when building transpiled code from metadata
if (!isIdentifierName(stringValue)) {
stringSpecifiers.add(stringValue);
}
return stringValue;
} else {
throw new Error(
`Expected export specifier to be either Identifier or StringLiteral, got ${node.type}`,
);
}
}
type PluginState = {|
contextIdent: string,
// List of names that should only be printed as string literals.
// i.e. `import { "any unicode" as foo } from "some-module"`
// `stringSpecifiers` is Set(1) ["any unicode"]
// In most cases `stringSpecifiers` is an empty Set
stringSpecifiers: Set<string>,
|};
function constructExportCall(
path,
exportIdent,
exportNames,
exportValues,
exportStarTarget,
stringSpecifiers: Set<string>,
) {
const statements = [];
if (exportNames.length === 1) {
@@ -52,7 +91,12 @@ function constructExportCall(
const exportName = exportNames[i];
const exportValue = exportValues[i];
objectProperties.push(
t.objectProperty(t.identifier(exportName), exportValue),
t.objectProperty(
stringSpecifiers.has(exportName)
? t.stringLiteral(exportName)
: t.identifier(exportName),
exportValue,
),
);
}
statements.push(
@@ -179,7 +223,7 @@ export default declare((api, options) => {
},
visitor: {
CallExpression(path, state) {
CallExpression(path, state: PluginState) {
if (t.isImport(path.node.callee)) {
if (!this.file.has("@babel/plugin-proposal-dynamic-import")) {
console.warn(MISSING_PLUGIN_WARNING);
@@ -197,7 +241,7 @@ export default declare((api, options) => {
}
},
MetaProperty(path, state) {
MetaProperty(path, state: PluginState) {
if (
path.node.meta.name === "import" &&
path.node.property.name === "meta"
@@ -228,14 +272,15 @@ export default declare((api, options) => {
Program: {
enter(path, state) {
state.contextIdent = path.scope.generateUid("context");
state.stringSpecifiers = new Set();
if (!allowTopLevelThis) {
rewriteThis(path);
}
},
exit(path, state) {
exit(path, state: PluginState) {
const scope = path.scope;
const exportIdent = scope.generateUid("export");
const contextIdent = state.contextIdent;
const { contextIdent, stringSpecifiers } = state;
const exportMap = Object.create(null);
const modules = [];
@@ -389,28 +434,25 @@ export default declare((api, options) => {
const nodes = [];
for (const specifier of specifiers) {
const binding = scope.getBinding(specifier.local.name);
const { local, exported } = specifier;
const binding = scope.getBinding(local.name);
const exportedName = getExportSpecifierName(
exported,
stringSpecifiers,
);
// hoisted function export
if (
binding &&
t.isFunctionDeclaration(binding.path.node)
) {
exportNames.push(specifier.exported.name);
exportValues.push(t.cloneNode(specifier.local));
exportNames.push(exportedName);
exportValues.push(t.cloneNode(local));
}
// only globals also exported this way
else if (!binding) {
nodes.push(
buildExportCall(
specifier.exported.name,
specifier.local,
),
);
nodes.push(buildExportCall(exportedName, local));
}
addExportName(
specifier.local.name,
specifier.exported.name,
);
addExportName(local.name, exportedName);
}
path.replaceWithMultiple(nodes);
@@ -445,6 +487,7 @@ export default declare((api, options) => {
}
if (t.isImportSpecifier(specifier)) {
const { imported } = specifier;
setterBody.push(
t.expressionStatement(
t.assignmentExpression(
@@ -453,6 +496,7 @@ export default declare((api, options) => {
t.memberExpression(
t.identifier(target),
specifier.imported,
/* computed */ imported.type === "StringLiteral",
),
),
),
@@ -469,9 +513,17 @@ export default declare((api, options) => {
if (t.isExportAllDeclaration(node)) {
hasExportStar = true;
} else if (t.isExportSpecifier(node)) {
exportNames.push(node.exported.name);
const exportedName = getExportSpecifierName(
node.exported,
stringSpecifiers,
);
exportNames.push(exportedName);
exportValues.push(
t.memberExpression(t.identifier(target), node.local),
t.memberExpression(
t.identifier(target),
node.local,
t.isStringLiteral(node.local),
),
);
} else {
// todo
@@ -485,6 +537,7 @@ export default declare((api, options) => {
exportNames,
exportValues,
hasExportStar ? t.identifier(target) : null,
stringSpecifiers,
),
);
}
@@ -533,6 +586,7 @@ export default declare((api, options) => {
exportNames,
exportValues,
null,
stringSpecifiers,
),
);
}