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

Implement import defer parsing support #15845

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -0,0 +1 @@
import defer * as x from "y";
@@ -0,0 +1 @@
import defer * as x from "y";
@@ -1,4 +1,4 @@
{
"plugins": ["sourcePhaseImports"],
"plugins": ["sourcePhaseImports", "deferredImportEvaluation"],
"parserOpts": { "createImportExpressions": true }
}
1 change: 1 addition & 0 deletions packages/babel-parser/data/schema.json
Expand Up @@ -163,6 +163,7 @@
"decorators",
"decorators-legacy",
"decoratorAutoAccessors",
"deferredImportEvaluation",
"destructuringPrivate",
"doExpressions",
"dynamicImport",
Expand Down
6 changes: 4 additions & 2 deletions packages/babel-parser/src/parse-error/standard-errors.ts
Expand Up @@ -65,6 +65,8 @@ export default {
"Decorators must be placed *after* the 'export' keyword. Remove the 'decoratorsBeforeExport: false' option to use the '@decorator export class {}' syntax.",
DecoratorSemicolon: "Decorators must not be followed by a semicolon.",
DecoratorStaticBlock: "Decorators can't be used with a static block.",
DeferImportRequiresNamespace:
'Only `import defer * as x from "./module"` is valid.',
DeletePrivateField: "Deleting a private field is not allowed.",
DestructureNamedImport:
"ES2015 named imports do not destructure. Use another statement for destructuring after the import.",
Expand All @@ -74,6 +76,8 @@ export default {
`\`${exportName}\` has already been exported. Exported identifiers must be unique.`,
DuplicateProto: "Redefinition of __proto__ property.",
DuplicateRegExpFlags: "Duplicate regular expression flag.",
DynamicImportPhaseRequiresImportExpressions: ({ phase }: { phase: string }) =>
`'import.${phase}(...)' can only be parsed when using the 'createImportExpressions' option.`,
ElementAfterRest: "Rest element must be last element.",
EscapedCharNotAnIdentifier: "Invalid Unicode escape.",
ExportBindingIsString: ({
Expand Down Expand Up @@ -235,8 +239,6 @@ export default {
"In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement.",
SourcePhaseImportRequiresDefault:
'Only `import source x from "./module"` is valid.',
SourcePhaseDynamicImportRequiresImportExpressions:
"'import.source(...)' can only be parsed when using the 'createImportExpressions' option.",
StaticPrototype: "Classes may not have static property named prototype.",
SuperNotAllowed:
"`super()` is only valid inside a class constructor of a subclass. Maybe a typo in the method name ('constructor') or not extending another class?",
Expand Down
25 changes: 18 additions & 7 deletions packages/babel-parser/src/parser/expression.ts
Expand Up @@ -1667,16 +1667,27 @@ export default abstract class ExpressionParser extends LValParser {
this.raise(Errors.ImportMetaOutsideModule, { at: id });
}
this.sawUnambiguousESM = true;
} else if (this.isContextual(tt._source)) {
this.expectPlugin("sourcePhaseImports");
} else if (this.isContextual(tt._source) || this.isContextual(tt._defer)) {
const isSource = this.isContextual(tt._source);

// TODO: The proposal doesn't mention import.defer yet because it was
// pending on a decision for import.source. Wait to enable it until it's
// included in the proposal.
if (!isSource) this.unexpected();

this.expectPlugin(
isSource ? "sourcePhaseImports" : "deferredImportEvaluation",
);
if (!this.options.createImportExpressions) {
throw this.raise(
Errors.SourcePhaseDynamicImportRequiresImportExpressions,
{ at: this.state.startLoc },
);
throw this.raise(Errors.DynamicImportPhaseRequiresImportExpressions, {
at: this.state.startLoc,
phase: this.state.value,
});
}
this.next();
(node as Undone<N.ImportExpression>).phase = "source";
(node as Undone<N.ImportExpression>).phase = isSource
? "source"
: "defer";
return this.parseImportCall(node as Undone<N.ImportExpression>);
}

Expand Down
24 changes: 18 additions & 6 deletions packages/babel-parser/src/parser/statement.ts
Expand Up @@ -2871,18 +2871,23 @@ export default abstract class StatementParser extends ExpressionParser {

checkImportReflection(node: Undone<N.ImportDeclaration>) {
const { specifiers } = node;
const isSingleDefaultBinding =
specifiers.length === 1 &&
specifiers[0].type === "ImportDefaultSpecifier";
const singleBindingType =
specifiers.length === 1 ? specifiers[0].type : null;

if (node.phase === "source") {
if (!isSingleDefaultBinding) {
if (singleBindingType !== "ImportDefaultSpecifier") {
this.raise(Errors.SourcePhaseImportRequiresDefault, {
at: specifiers[0].loc.start,
});
}
} else if (node.phase === "defer") {
if (singleBindingType !== "ImportNamespaceSpecifier") {
this.raise(Errors.DeferImportRequiresNamespace, {
at: specifiers[0].loc.start,
});
}
} else if (node.module) {
if (!isSingleDefaultBinding) {
if (singleBindingType !== "ImportDefaultSpecifier") {
this.raise(Errors.ImportReflectionNotBinding, {
at: specifiers[0].loc.start,
});
Expand Down Expand Up @@ -2930,7 +2935,11 @@ export default abstract class StatementParser extends ExpressionParser {

isPotentialImportPhase(isExport: boolean): boolean {
if (isExport) return false;
return this.isContextual(tt._source) || this.isContextual(tt._module);
return (
this.isContextual(tt._source) ||
this.isContextual(tt._defer) ||
this.isContextual(tt._module)
);
}

applyImportPhase(
Expand Down Expand Up @@ -2960,6 +2969,9 @@ export default abstract class StatementParser extends ExpressionParser {
if (phase === "source") {
this.expectPlugin("sourcePhaseImports", loc);
(node as N.ImportDeclaration).phase = "source";
} else if (phase === "defer") {
this.expectPlugin("deferredImportEvaluation", loc);
(node as N.ImportDeclaration).phase = "defer";
} else if (this.hasPlugin("sourcePhaseImports")) {
(node as N.ImportDeclaration).phase = null;
}
Expand Down
1 change: 1 addition & 0 deletions packages/babel-parser/src/tokenizer/types.ts
Expand Up @@ -284,6 +284,7 @@ export const tt = {
_assert: createKeywordLike("assert", { startsExpr }),
_async: createKeywordLike("async", { startsExpr }),
_await: createKeywordLike("await", { startsExpr }),
_defer: createKeywordLike("defer", { startsExpr }),
_from: createKeywordLike("from", { startsExpr }),
_get: createKeywordLike("get", { startsExpr }),
_let: createKeywordLike("let", { startsExpr }),
Expand Down
4 changes: 2 additions & 2 deletions packages/babel-parser/src/types.d.ts
Expand Up @@ -627,7 +627,7 @@ export interface NewExpression extends CallOrNewBase {
export interface ImportExpression extends NodeBase {
type: "ImportExpression";
source: Expression;
phase?: null | "source";
phase?: null | "source" | "defer";
options: Expression | null;
}

Expand Down Expand Up @@ -928,7 +928,7 @@ export interface ImportDeclaration extends NodeBase {
>;
source: Literal;
importKind?: "type" | "typeof" | "value"; // TODO: Not in spec,
phase?: null | "source";
phase?: null | "source" | "defer";
attributes?: ImportAttribute[];
// @deprecated
assertions?: ImportAttribute[];
Expand Down
1 change: 1 addition & 0 deletions packages/babel-parser/src/typings.d.ts
Expand Up @@ -8,6 +8,7 @@ export type Plugin =
| "classStaticBlock" // Enabled by default
| "decimal"
| "decorators-legacy"
| "deferredImportEvaluation"
| "decoratorAutoAccessors"
| "destructuringPrivate"
| "doExpressions"
Expand Down
@@ -0,0 +1 @@
import.defer("x", { with: { attr: "val" } });
@@ -0,0 +1,4 @@
{
"plugins": ["deferredImportEvaluation", "importAttributes"],
"createImportExpressions": true
}
@@ -0,0 +1 @@
import.defer("foo");
@@ -0,0 +1,4 @@
{
"plugins": ["deferredImportEvaluation"],
"throws": "TODO (disabled test)"
}
@@ -0,0 +1 @@
import.defer("foo");
@@ -0,0 +1,4 @@
{
"plugins": ["deferredImportEvaluation"],
"createImportExpressions": true
}
@@ -0,0 +1 @@
import defer * as ns from "x" with { attr: "val" };
@@ -0,0 +1,3 @@
{
"throws": "This experimental syntax requires enabling the parser plugin: \"importAttributes\". (1:35)"
}
@@ -0,0 +1 @@
import defer * as ns from "x";
@@ -0,0 +1,38 @@
{
"type": "File",
"start":0,"end":30,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":30,"index":30}},
"program": {
"type": "Program",
"start":0,"end":30,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":30,"index":30}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ImportDeclaration",
"start":0,"end":30,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":30,"index":30}},
"phase": "defer",
"specifiers": [
{
"type": "ImportNamespaceSpecifier",
"start":13,"end":20,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":20,"index":20}},
"local": {
"type": "Identifier",
"start":18,"end":20,"loc":{"start":{"line":1,"column":18,"index":18},"end":{"line":1,"column":20,"index":20},"identifierName":"ns"},
"name": "ns"
}
}
],
"source": {
"type": "StringLiteral",
"start":26,"end":29,"loc":{"start":{"line":1,"column":26,"index":26},"end":{"line":1,"column":29,"index":29}},
"extra": {
"rawValue": "x",
"raw": "\"x\""
},
"value": "x"
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
import defer x from "x";
@@ -0,0 +1,41 @@
{
"type": "File",
"start":0,"end":24,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":24,"index":24}},
"errors": [
"SyntaxError: Only `import defer * as x from \"./module\"` is valid. (1:13)"
],
"program": {
"type": "Program",
"start":0,"end":24,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":24,"index":24}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ImportDeclaration",
"start":0,"end":24,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":24,"index":24}},
"phase": "defer",
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"start":13,"end":14,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":14,"index":14}},
"local": {
"type": "Identifier",
"start":13,"end":14,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":14,"index":14},"identifierName":"x"},
"name": "x"
}
}
],
"source": {
"type": "StringLiteral",
"start":20,"end":23,"loc":{"start":{"line":1,"column":20,"index":20},"end":{"line":1,"column":23,"index":23}},
"extra": {
"rawValue": "x",
"raw": "\"x\""
},
"value": "x"
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
import defer { x } from "x";
@@ -0,0 +1,46 @@
{
"type": "File",
"start":0,"end":28,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":28,"index":28}},
"errors": [
"SyntaxError: Only `import defer * as x from \"./module\"` is valid. (1:15)"
],
"program": {
"type": "Program",
"start":0,"end":28,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":28,"index":28}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ImportDeclaration",
"start":0,"end":28,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":28,"index":28}},
"phase": "defer",
"specifiers": [
{
"type": "ImportSpecifier",
"start":15,"end":16,"loc":{"start":{"line":1,"column":15,"index":15},"end":{"line":1,"column":16,"index":16}},
"imported": {
"type": "Identifier",
"start":15,"end":16,"loc":{"start":{"line":1,"column":15,"index":15},"end":{"line":1,"column":16,"index":16},"identifierName":"x"},
"name": "x"
},
"local": {
"type": "Identifier",
"start":15,"end":16,"loc":{"start":{"line":1,"column":15,"index":15},"end":{"line":1,"column":16,"index":16},"identifierName":"x"},
"name": "x"
}
}
],
"source": {
"type": "StringLiteral",
"start":24,"end":27,"loc":{"start":{"line":1,"column":24,"index":24},"end":{"line":1,"column":27,"index":27}},
"extra": {
"rawValue": "x",
"raw": "\"x\""
},
"value": "x"
}
}
],
"directives": []
}
}
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["deferredImportEvaluation"]
}
1 change: 1 addition & 0 deletions packages/babel-parser/typings/babel-parser.d.ts
Expand Up @@ -12,6 +12,7 @@ type Plugin =
| "classStaticBlock" // Enabled by default
| "decimal"
| "decorators-legacy"
| "deferredImportEvaluation"
| "decoratorAutoAccessors"
| "destructuringPrivate"
| "doExpressions"
Expand Down
4 changes: 2 additions & 2 deletions packages/babel-types/src/ast-types/generated/index.ts
Expand Up @@ -864,7 +864,7 @@ export interface ImportDeclaration extends BaseNode {
attributes?: Array<ImportAttribute> | null;
importKind?: "type" | "typeof" | "value" | null;
module?: boolean | null;
phase?: "source" | null;
phase?: "source" | "defer" | null;
}

export interface ImportDefaultSpecifier extends BaseNode {
Expand All @@ -888,7 +888,7 @@ export interface ImportExpression extends BaseNode {
type: "ImportExpression";
source: Expression;
options?: Expression | null;
phase?: "source" | null;
phase?: "source" | "defer" | null;
}

export interface MetaProperty extends BaseNode {
Expand Down
4 changes: 2 additions & 2 deletions packages/babel-types/src/definitions/core.ts
Expand Up @@ -1745,7 +1745,7 @@ defineType("ImportDeclaration", {
},
phase: {
default: null,
validate: assertOneOf("source"),
validate: assertOneOf("source", "defer"),
},
specifiers: {
validate: chain(
Expand Down Expand Up @@ -1816,7 +1816,7 @@ defineType("ImportExpression", {
fields: {
phase: {
default: null,
validate: assertOneOf("source"),
validate: assertOneOf("source", "defer"),
},
source: {
validate: assertNodeType("Expression"),
Expand Down