Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

String import/export specifier #12091

Merged
merged 25 commits into from Oct 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
db00d85
feat: parse moduleExportName
JLHwung Sep 21, 2020
356fd9e
feat: add validators
JLHwung Sep 22, 2020
1a6b13f
Support string specifier name in commonjs transform
JLHwung Sep 22, 2020
00276bc
Support string specifier name in export-ns-from
JLHwung Sep 22, 2020
b71d726
test: add loose testcases
JLHwung Sep 22, 2020
7f914d3
test: add testcases for amd and umd
JLHwung Sep 22, 2020
463b3e1
feat: support systemjs
JLHwung Sep 22, 2020
b6c441c
test: update fixtures fixed in #12110
JLHwung Sep 25, 2020
730eca0
add plugin name typings
JLHwung Sep 29, 2020
e2e1d0b
test: rename test layout
JLHwung Sep 29, 2020
e14a03a
feat: implement under moduleStringNames flag
JLHwung Sep 29, 2020
ae9d87a
chore: add plugin syntax module string names
JLHwung Sep 29, 2020
c6d3069
feat: support ModuleExportName as ModuleExportName
JLHwung Sep 29, 2020
41407f4
test: update test fixtures
JLHwung Sep 29, 2020
f5759d0
fix flow errors
JLHwung Sep 30, 2020
ec66634
docs: update AST spec
JLHwung Sep 30, 2020
4f8fce4
feat: support { "some imports" as "some exports" }
JLHwung Sep 30, 2020
823b67b
feat: support { "some imports" as "some exports" } in systemjs
JLHwung Sep 30, 2020
04fa4bf
test: add test on `import { "foo" }`
JLHwung Oct 9, 2020
0b4dc58
Address review comments
JLHwung Oct 12, 2020
9f9597b
add moduleStringNames to missing plugin helpers
JLHwung Oct 12, 2020
5f8e9b7
Apply suggestions from code review
JLHwung Oct 12, 2020
f67fba1
update test fixtures
JLHwung Oct 12, 2020
62e7d55
Update packages/babel-parser/src/parser/error-message.js
JLHwung Oct 12, 2020
107444b
update test fixtures
JLHwung Oct 12, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/babel-core/src/parser/util/missing-plugin-helper.js
Expand Up @@ -135,6 +135,12 @@ const pluginNameMap = {
url: "https://git.io/JfK3k",
},
},
moduleStringNames: {
syntax: {
name: "@babel/plugin-syntax-module-string-names",
url: "https://git.io/JTL8G",
},
},
numericSeparator: {
syntax: {
name: "@babel/plugin-syntax-numeric-separator",
Expand Down
1 change: 1 addition & 0 deletions packages/babel-helper-module-transforms/package.json
Expand Up @@ -19,6 +19,7 @@
"@babel/helper-replace-supers": "workspace:^7.10.4",
"@babel/helper-simple-access": "workspace:^7.10.4",
"@babel/helper-split-export-declaration": "workspace:^7.11.0",
"@babel/helper-validator-identifier": "workspace:^7.10.4",
"@babel/template": "workspace:^7.10.4",
"@babel/types": "workspace:^7.11.0",
"lodash": "^4.17.19"
Expand Down
94 changes: 65 additions & 29 deletions packages/babel-helper-module-transforms/src/index.js
Expand Up @@ -10,6 +10,8 @@ import rewriteLiveReferences from "./rewrite-live-references";
import normalizeAndLoadModuleMetadata, {
hasExports,
isSideEffectImport,
type ModuleMetadata,
type SourceModuleMetadata,
} from "./normalize-and-load-metadata";

export { default as getModuleName } from "./get-module-name";
Expand Down Expand Up @@ -180,33 +182,58 @@ export function buildNamespaceInitStatements(
return statements;
}

const getTemplateForReexport = loose => {
return loose
? template.statement`EXPORTS.EXPORT_NAME = NAMESPACE.IMPORT_NAME;`
: template`
Object.defineProperty(EXPORTS, "EXPORT_NAME", {
enumerable: true,
get: function() {
return NAMESPACE.IMPORT_NAME;
},
});
`;
const ReexportTemplate = {
loose: template.statement`EXPORTS.EXPORT_NAME = NAMESPACE_IMPORT;`,
looseComputed: template.statement`EXPORTS["EXPORT_NAME"] = NAMESPACE_IMPORT;`,
spec: template`
Object.defineProperty(EXPORTS, "EXPORT_NAME", {
enumerable: true,
get: function() {
return NAMESPACE_IMPORT;
},
});
`,
};

const buildReexportsFromMeta = (meta, metadata, loose) => {
const buildReexportsFromMeta = (
meta: ModuleMetadata,
metadata: SourceModuleMetadata,
loose,
) => {
const namespace = metadata.lazy
? t.callExpression(t.identifier(metadata.name), [])
: t.identifier(metadata.name);

const templateForCurrentMode = getTemplateForReexport(loose);
return Array.from(metadata.reexports, ([exportName, importName]) =>
templateForCurrentMode({
const { stringSpecifiers } = meta;
return Array.from(metadata.reexports, ([exportName, importName]) => {
let NAMESPACE_IMPORT;
if (stringSpecifiers.has(importName)) {
NAMESPACE_IMPORT = t.memberExpression(
t.cloneNode(namespace),
t.stringLiteral(importName),
true,
);
} else {
NAMESPACE_IMPORT = NAMESPACE_IMPORT = t.memberExpression(
t.cloneNode(namespace),
t.identifier(importName),
);
}
const astNodes = {
EXPORTS: meta.exportName,
EXPORT_NAME: exportName,
NAMESPACE: t.cloneNode(namespace),
IMPORT_NAME: importName,
}),
);
NAMESPACE_IMPORT,
};
if (loose) {
if (stringSpecifiers.has(exportName)) {
return ReexportTemplate.looseComputed(astNodes);
} else {
return ReexportTemplate.loose(astNodes);
}
} else {
return ReexportTemplate.spec(astNodes);
}
});
};

/**
Expand Down Expand Up @@ -363,16 +390,25 @@ function buildExportInitializationStatements(
* Given a set of export names, create a set of nested assignments to
* initialize them all to a given expression.
*/
function buildInitStatement(metadata, exportNames, initExpr) {
const InitTemplate = {
computed: template.expression`EXPORTS["NAME"] = VALUE`,
default: template.expression`EXPORTS.NAME = VALUE`,
};

function buildInitStatement(metadata: ModuleMetadata, exportNames, initExpr) {
const { stringSpecifiers, exportName: EXPORTS } = metadata;
return t.expressionStatement(
exportNames.reduce(
(acc, exportName) =>
template.expression`EXPORTS.NAME = VALUE`({
EXPORTS: metadata.exportName,
NAME: exportName,
VALUE: acc,
}),
initExpr,
),
exportNames.reduce((acc, exportName) => {
const params = {
EXPORTS,
NAME: exportName,
VALUE: acc,
};
if (stringSpecifiers.has(exportName)) {
return InitTemplate.computed(params);
} else {
return InitTemplate.default(params);
}
}, initExpr),
);
}
@@ -1,5 +1,6 @@
import { basename, extname } from "path";

import { isIdentifierName } from "@babel/helper-validator-identifier";
import splitExportDeclaration from "@babel/helper-split-export-declaration";

export type ModuleMetadata = {
Expand All @@ -15,6 +16,12 @@ export type ModuleMetadata = {

// Lookup of source file to source file metadata.
source: Map<string, SourceModuleMetadata>,

// 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>,
};

export type InteropType = "default" | "namespace" | "none";
Expand Down Expand Up @@ -87,13 +94,18 @@ export default function normalizeModuleAndLoadMetadata(
if (!exportName) {
exportName = programPath.scope.generateUidIdentifier("exports").name;
}
const stringSpecifiers = new Set();

nameAnonymousExports(programPath);

const { local, source, hasExports } = getModuleMetadata(programPath, {
loose,
lazy,
});
const { local, source, hasExports } = getModuleMetadata(
programPath,
{
loose,
lazy,
},
stringSpecifiers,
);

removeModuleDeclarations(programPath);

Expand Down Expand Up @@ -124,17 +136,48 @@ export default function normalizeModuleAndLoadMetadata(
hasExports,
local,
source,
stringSpecifiers,
};
}

function getExportSpecifierName(
path: NodePath,
stringSpecifiers: Set<string>,
): string {
if (path.isIdentifier()) {
return path.node.name;
} else if (path.isStringLiteral()) {
const stringValue = path.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 ${path.node.type}`,
);
}
}

/**
* Get metadata about the imports and exports present in this module.
*/
function getModuleMetadata(
programPath: NodePath,
{ loose, lazy }: { loose: boolean, lazy: boolean },
stringSpecifiers: Set<string>,
) {
const localData = getLocalExportMetadata(programPath, loose);
const localData = getLocalExportMetadata(
programPath,
loose,
stringSpecifiers,
);

const sourceData = new Map();
const getData = sourceNode => {
Expand Down Expand Up @@ -199,7 +242,10 @@ function getModuleMetadata(
});
}
} else if (spec.isImportSpecifier()) {
const importName = spec.get("imported").node.name;
const importName = getExportSpecifierName(
spec.get("imported"),
stringSpecifiers,
);
const localName = spec.get("local").node.name;

data.imports.set(localName, importName);
Expand Down Expand Up @@ -231,8 +277,14 @@ function getModuleMetadata(
if (!spec.isExportSpecifier()) {
throw spec.buildCodeFrameError("Unexpected export specifier type");
}
const importName = spec.get("local").node.name;
const exportName = spec.get("exported").node.name;
const importName = getExportSpecifierName(
spec.get("local"),
stringSpecifiers,
);
const exportName = getExportSpecifierName(
spec.get("exported"),
stringSpecifiers,
);

data.reexports.set(exportName, importName);

Expand Down Expand Up @@ -310,6 +362,7 @@ function getModuleMetadata(
function getLocalExportMetadata(
programPath: NodePath,
loose: boolean,
stringSpecifiers: Set<string>,
): Map<string, LocalExportMetadata> {
const bindingKindLookup = new Map();

Expand Down Expand Up @@ -392,11 +445,13 @@ function getLocalExportMetadata(
child.get("specifiers").forEach(spec => {
const local = spec.get("local");
const exported = spec.get("exported");
const localMetadata = getLocalMetadata(local);
const exportName = getExportSpecifierName(exported, stringSpecifiers);

if (exported.node.name === "__esModule") {
if (exportName === "__esModule") {
throw exported.buildCodeFrameError('Illegal export "__esModule".');
}
getLocalMetadata(local).names.push(exported.node.name);
localMetadata.names.push(exportName);
});
}
} else if (child.isExportDefaultDeclaration()) {
Expand Down
Expand Up @@ -3,7 +3,7 @@ import * as t from "@babel/types";
import template from "@babel/template";
import simplifyAccess from "@babel/helper-simple-access";

import type { ModuleMetadata } from "./";
import type { ModuleMetadata } from "./normalize-and-load-metadata";

export default function rewriteLiveReferences(
programPath: NodePath,
Expand Down Expand Up @@ -71,7 +71,13 @@ export default function rewriteLiveReferences(
let namespace = t.identifier(meta.name);
if (meta.lazy) namespace = t.callExpression(namespace, []);

return t.memberExpression(namespace, t.identifier(importName));
const computed = metadata.stringSpecifiers.has(importName);

return t.memberExpression(
namespace,
computed ? t.stringLiteral(importName) : t.identifier(importName),
computed,
);
},
});
}
Expand Down Expand Up @@ -135,11 +141,14 @@ const buildBindingExportAssignmentExpression = (
// class Foo {} export { Foo, Foo as Bar };
// as
// class Foo {} exports.Foo = exports.Bar = Foo;
const { stringSpecifiers } = metadata;
const computed = stringSpecifiers.has(exportName);
return t.assignmentExpression(
"=",
t.memberExpression(
t.identifier(metadata.exportName),
t.identifier(exportName),
computed ? t.stringLiteral(exportName) : t.identifier(exportName),
/* computed */ computed,
),
expr,
);
Expand Down
17 changes: 10 additions & 7 deletions packages/babel-parser/ast/spec.md
Expand Up @@ -1291,7 +1291,7 @@ interface ImportDeclaration <: ModuleDeclaration {
type: "ImportDeclaration";
importKind: null | "type" | "typeof" | "value";
specifiers: [ ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier ];
source: Literal;
source: StringLiteral;
attributes?: [ ImportAttribute ];
}
```
Expand All @@ -1305,7 +1305,7 @@ An import declaration, e.g., `import foo from "mod";`.
```js
interface ImportSpecifier <: ModuleSpecifier {
type: "ImportSpecifier";
imported: Identifier;
imported: Identifier | StringLiteral;
}
```

Expand Down Expand Up @@ -1352,21 +1352,24 @@ interface ExportNamedDeclaration <: ModuleDeclaration {
type: "ExportNamedDeclaration";
declaration: Declaration | null;
specifiers: [ ExportSpecifier ];
source: Literal | null;
source: StringLiteral | null;
}
```

An export named declaration, e.g., `export {foo, bar};`, `export {foo} from "mod";`, `export var foo = 1;` or `export * as foo from "bar";`.

_Note: Having `declaration` populated with non-empty `specifiers` or non-null `source` results in an invalid state._
Note:

- Having `declaration` populated with non-empty `specifiers` or non-null `source` results in an invalid state.
- If `source` is `null`, for each `specifier` of `specifiers`, `specifier.local` can not be a `StringLiteral`.

### ExportSpecifier

```js
interface ExportSpecifier <: ModuleSpecifier {
type: "ExportSpecifier";
exported: Identifier;
local?: Identifier;
exported: Identifier | StringLiteral;
local?: Identifier | StringLiteral;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When can local be a string?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

export { "😄" as "😂" } from "emojis"

}
```

Expand Down Expand Up @@ -1396,7 +1399,7 @@ An export default declaration, e.g., `export default function () {};` or `export
```js
interface ExportAllDeclaration <: ModuleDeclaration {
type: "ExportAllDeclaration";
source: Literal;
source: StringLiteral;
}
```

Expand Down