Skip to content

Add initial support for dynamic module import #3204

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/com/google/javascript/jscomp/CodeGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,12 @@ protected void add(Node n, Context context) {
add(n.getString());
break;

case DYNAMIC_IMPORT:
add("import(");
addExpr(first, NodeUtil.precedence(type), context);
add(")");
break;

// CLASS -> NAME,EXPR|EMPTY,BLOCK
case CLASS:
{
Expand Down
1 change: 1 addition & 0 deletions src/com/google/javascript/jscomp/NodeUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -1697,6 +1697,7 @@ public static int precedence(Token type) {
case TRUE:
case TAGGED_TEMPLATELIT:
case TEMPLATELIT:
case DYNAMIC_IMPORT:
// Tokens from the type declaration AST
case UNION_TYPE:
return 17;
Expand Down
9 changes: 9 additions & 0 deletions src/com/google/javascript/jscomp/parsing/IRFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
import com.google.javascript.jscomp.parsing.parser.trees.DefaultClauseTree;
import com.google.javascript.jscomp.parsing.parser.trees.DefaultParameterTree;
import com.google.javascript.jscomp.parsing.parser.trees.DoWhileStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.DynamicImportTree;
import com.google.javascript.jscomp.parsing.parser.trees.EmptyStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.EnumDeclarationTree;
import com.google.javascript.jscomp.parsing.parser.trees.ExportDeclarationTree;
Expand Down Expand Up @@ -2480,6 +2481,12 @@ Node processImportSpec(ImportSpecifierTree tree) {
return importSpec;
}

Node processDynamicImport(DynamicImportTree dynamicImportNode) {
maybeWarnForFeature(dynamicImportNode, Feature.DYNAMIC_IMPORT);
Node argument = transform(dynamicImportNode.argument);
return newNode(Token.DYNAMIC_IMPORT, argument);
}

Node processTypeName(TypeNameTree tree) {
Node typeNode;
if (tree.segments.size() == 1) {
Expand Down Expand Up @@ -2989,6 +2996,8 @@ public Node process(ParseTree node) {
return processImportDecl(node.asImportDeclaration());
case IMPORT_SPECIFIER:
return processImportSpec(node.asImportSpecifier());
case DYNAMIC_IMPORT_EXPRESSION:
return processDynamicImport(node.asDynamicImportExpression());

case ARRAY_PATTERN:
return processArrayPattern(node.asArrayPattern());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ public enum Feature {
// https://github.com/tc39/proposal-regexp-unicode-property-escapes
REGEXP_UNICODE_PROPERTY_ESCAPE("RegExp unicode property escape", LangVersion.ES2018),

// Stage 3 proposal likely to be part of ES2020
DYNAMIC_IMPORT("Dynamic module import", LangVersion.ES_NEXT),

// ES6 typed features that are not at all implemented in browsers
ACCESSIBILITY_MODIFIER("accessibility modifier", LangVersion.TYPESCRIPT),
AMBIENT_DECLARATION("ambient declaration", LangVersion.TYPESCRIPT),
Expand Down
22 changes: 21 additions & 1 deletion src/com/google/javascript/jscomp/parsing/parser/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import com.google.javascript.jscomp.parsing.parser.trees.DefaultClauseTree;
import com.google.javascript.jscomp.parsing.parser.trees.DefaultParameterTree;
import com.google.javascript.jscomp.parsing.parser.trees.DoWhileStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.DynamicImportTree;
import com.google.javascript.jscomp.parsing.parser.trees.EmptyStatementTree;
import com.google.javascript.jscomp.parsing.parser.trees.EnumDeclarationTree;
import com.google.javascript.jscomp.parsing.parser.trees.ExportDeclarationTree;
Expand Down Expand Up @@ -392,7 +393,7 @@ private ParseTree parseAmbientNamespaceElement() {

// https://people.mozilla.org/~jorendorff/es6-draft.html#sec-imports
private boolean peekImportDeclaration() {
return peek(TokenType.IMPORT);
return peek(TokenType.IMPORT) && !peek(1, TokenType.OPEN_PAREN);
}

private ParseTree parseImportDeclaration() {
Expand Down Expand Up @@ -2268,6 +2269,8 @@ private ParseTree parsePrimaryExpression() {
return parseSuperExpression();
case THIS:
return parseThisExpression();
case IMPORT:
return parseDynamicImportExpression();
case IDENTIFIER:
case TYPE:
case DECLARE:
Expand Down Expand Up @@ -2309,6 +2312,17 @@ private ThisExpressionTree parseThisExpression() {
return new ThisExpressionTree(getTreeLocation(start));
}

// https://tc39.github.io/proposal-dynamic-import
private DynamicImportTree parseDynamicImportExpression() {
SourcePosition start = getTreeStartLocation();
eat(TokenType.IMPORT);
eat(TokenType.OPEN_PAREN);
ParseTree argument = parseAssignmentExpression();
eat(TokenType.CLOSE_PAREN);
recordFeatureUsed(Feature.DYNAMIC_IMPORT);
return new DynamicImportTree(getTreeLocation(start), argument);
}

private IdentifierExpressionTree parseIdentifierExpression() {
SourcePosition start = getTreeStartLocation();
IdentifierToken identifier = eatId();
Expand Down Expand Up @@ -2898,6 +2912,8 @@ private boolean peekExpression() {
case VOID:
case YIELD:
return true;
case IMPORT:
return peekImportCall();
default:
return false;
}
Expand Down Expand Up @@ -3475,6 +3491,10 @@ private boolean peekUpdateOperator() {
}
}

private boolean peekImportCall() {
return peek(TokenType.IMPORT) && peek(1, TokenType.OPEN_PAREN);
}

// 11.2 Left hand side expression
//
// Also inlines the call expression productions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2019 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.javascript.jscomp.parsing.parser.trees;

import com.google.javascript.jscomp.parsing.parser.util.SourceRange;

public class DynamicImportTree extends ParseTree {
public final ParseTree argument;

public DynamicImportTree(SourceRange location, ParseTree argument) {
super(ParseTreeType.DYNAMIC_IMPORT_EXPRESSION, location);
this.argument = argument;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public IdentifierExpressionTree asIdentifierExpression() {
public IfStatementTree asIfStatement() { return (IfStatementTree) this; }
public ImportDeclarationTree asImportDeclaration() { return (ImportDeclarationTree) this; }
public ImportSpecifierTree asImportSpecifier() { return (ImportSpecifierTree) this; }
public DynamicImportTree asDynamicImportExpression() { return (DynamicImportTree) this; }
public LabelledStatementTree asLabelledStatement() { return (LabelledStatementTree) this; }
public LiteralExpressionTree asLiteralExpression() { return (LiteralExpressionTree) this; }
public MemberExpressionTree asMemberExpression() { return (MemberExpressionTree) this; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,5 @@ public enum ParseTreeType {
CALL_SIGNATURE,
NEW_TARGET_EXPRESSION,
AWAIT_EXPRESSION,
DYNAMIC_IMPORT_EXPRESSION,
}
1 change: 1 addition & 0 deletions src/com/google/javascript/rhino/Token.java
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ public enum Token {
EXPORT_SPECS,
EXPORT_SPEC,
MODULE_BODY,
DYNAMIC_IMPORT,

REST, // "..." in formal parameters, or an array pattern.
SPREAD, // "..." in a call expression, or an array literal.
Expand Down
45 changes: 45 additions & 0 deletions test/com/google/javascript/jscomp/parsing/ParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4958,6 +4958,51 @@ public void testIncorrectAssignmentDoesntCrash() {
"Semi-colon expected");
}

@Test
public void testDynamicImport() {
List<String> dynamicImportUses =
ImmutableList.of(
"import('foo')",
"import('foo').then(function(a) { return a; })",
"var moduleNamespace = import('foo')",
"Promise.all([import('foo')]).then(function(a) { return a; })");
expectFeatures(Feature.DYNAMIC_IMPORT);

for (LanguageMode m : LanguageMode.values()) {
mode = m;
strictMode = (m == LanguageMode.ECMASCRIPT3) ? SLOPPY : STRICT;
if (m.featureSet.has(Feature.DYNAMIC_IMPORT)) {
for (String importUseSource : dynamicImportUses) {
parse(importUseSource);
}
} else {
for (String importUseSource : dynamicImportUses) {
parseWarning(
importUseSource,
requiresLanguageModeMessage(LanguageMode.ES_NEXT, Feature.DYNAMIC_IMPORT));
}
}
}
}

@Test
public void testAwaitDynamicImport() {
List<String> awaitDynamicImportUses =
ImmutableList.of(
"(async function() { return await import('foo'); })()",
"(async function() { await import('foo').then(function(a) { return a; }); })()",
"(async function() { var moduleNamespace = await import('foo'); })()",
lines(
"(async function() {",
"await Promise.all([import('foo')]).then(function(a) { return a; }); })()"));
expectFeatures(Feature.DYNAMIC_IMPORT, Feature.ASYNC_FUNCTIONS);
mode = LanguageMode.ES_NEXT;

for (String importUseSource : awaitDynamicImportUses) {
parse(importUseSource);
}
}

private void assertNodeHasJSDocInfoWithJSType(Node node, JSType jsType) {
JSDocInfo info = node.getJSDocInfo();
assertWithMessage("Node has no JSDocInfo: %s", node).that(info).isNotNull();
Expand Down