Skip to content

Commit

Permalink
Use the new ES6 modules to a CJS-like module rewriter in closure bund…
Browse files Browse the repository at this point in the history
…ler to safely concatenate ES6 modules in a bundle. Also use it in the gwt transpiler so the Closure Library can transpile ES6 modules.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=183726148
  • Loading branch information
johnplaisted authored and lauraharker committed Jan 30, 2018
1 parent 803a80d commit fa7cb57
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 5 deletions.
18 changes: 18 additions & 0 deletions src/com/google/javascript/jscomp/TranspilationPasses.java
Expand Up @@ -36,6 +36,10 @@ public static void addEs6ModulePass(List<PassFactory> passes) {
passes.add(es6RewriteModule);
}

public static void addEs6ModuleToCjsPass(List<PassFactory> passes) {
passes.add(es6RewriteModuleToCjs);
}

public static void addEs2018Passes(List<PassFactory> passes) {
passes.add(rewriteObjRestSpread);
}
Expand Down Expand Up @@ -121,6 +125,20 @@ protected FeatureSet featureSet() {
}
};

/** Rewrites ES6 modules */
private static final PassFactory es6RewriteModuleToCjs =
new PassFactory("es6RewriteModuleToCjs", true) {
@Override
protected CompilerPass create(AbstractCompiler compiler) {
return new Es6RewriteModulesToCommonJsModules(compiler);
}

@Override
protected FeatureSet featureSet() {
return ES8_MODULES;
}
};

private static final PassFactory rewriteAsyncFunctions =
new HotSwapPassFactory("rewriteAsyncFunctions") {
@Override
Expand Down
22 changes: 18 additions & 4 deletions src/com/google/javascript/jscomp/deps/ClosureBundler.java
Expand Up @@ -18,6 +18,7 @@
import com.google.common.base.Strings;
import com.google.common.io.CharSource;
import com.google.common.io.Files;
import com.google.javascript.jscomp.transpile.BaseTranspiler;
import com.google.javascript.jscomp.transpile.TranspileResult;
import com.google.javascript.jscomp.transpile.Transpiler;
import java.io.File;
Expand All @@ -33,6 +34,7 @@
public final class ClosureBundler {

private final Transpiler transpiler;
private final Transpiler es6ModuleTranspiler;

private final EvalMode mode;
private final String sourceUrl;
Expand All @@ -48,8 +50,7 @@ public ClosureBundler() {
}

public ClosureBundler(Transpiler transpiler) {
this(transpiler, EvalMode.NORMAL, null, "unknown_source",
new ConcurrentHashMap<String, String>());
this(transpiler, EvalMode.NORMAL, null, "unknown_source", new ConcurrentHashMap<>());
}

private ClosureBundler(Transpiler transpiler, EvalMode mode, String sourceUrl, String path,
Expand All @@ -59,6 +60,7 @@ private ClosureBundler(Transpiler transpiler, EvalMode mode, String sourceUrl, S
this.sourceUrl = sourceUrl;
this.path = path;
this.sourceMapCache = sourceMapCache;
this.es6ModuleTranspiler = BaseTranspiler.ES_MODULE_TO_CJS_TRANSPILER;
}

public final ClosureBundler useEval(boolean useEval) {
Expand Down Expand Up @@ -105,6 +107,8 @@ public void appendTo(
CharSource content) throws IOException {
if (info.isModule()) {
mode.appendGoogModule(transpile(content.read()), out, sourceUrl);
} else if ("es6".equals(info.getLoadFlags().get("module"))) {
mode.appendTraditional(transpileEs6Module(content.read()), out, sourceUrl);
} else {
mode.appendTraditional(transpile(content.read()), out, sourceUrl);
}
Expand All @@ -115,6 +119,7 @@ public void appendRuntimeTo(Appendable out) throws IOException {
if (!runtime.isEmpty()) {
mode.appendTraditional(runtime, out, null);
}
mode.appendTraditional(es6ModuleTranspiler.runtime(), out, null);
}

/**
Expand All @@ -125,12 +130,20 @@ public String getSourceMap(String path) {
return Strings.nullToEmpty(sourceMapCache.get(path));
}

private String transpile(String s) {
TranspileResult result = transpiler.transpile(Paths.get(path), s);
private String transpile(String s, Transpiler t) {
TranspileResult result = t.transpile(Paths.get(path), s);
sourceMapCache.put(path, result.sourceMap());
return result.transpiled();
}

private String transpile(String s) {
return transpile(s, transpiler);
}

private String transpileEs6Module(String s) {
return transpile(transpile(s, es6ModuleTranspiler));
}

private enum EvalMode {
EVAL {
@Override
Expand Down Expand Up @@ -172,6 +185,7 @@ void appendGoogModule(String s, Appendable out, String sourceUrl) throws IOExcep
};

abstract void appendTraditional(String s, Appendable out, String sourceUrl) throws IOException;

abstract void appendGoogModule(String s, Appendable out, String sourceUrl) throws IOException;
}

Expand Down
52 changes: 52 additions & 0 deletions src/com/google/javascript/jscomp/transpile/BaseTranspiler.java
Expand Up @@ -25,11 +25,13 @@
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import com.google.javascript.jscomp.DiagnosticGroup;
import com.google.javascript.jscomp.DiagnosticType;
import com.google.javascript.jscomp.Es6RewriteModulesToCommonJsModules;
import com.google.javascript.jscomp.PropertyRenamingPolicy;
import com.google.javascript.jscomp.Result;
import com.google.javascript.jscomp.SourceFile;
import com.google.javascript.jscomp.VariableRenamingPolicy;
import com.google.javascript.jscomp.bundle.TranspilationException;
import com.google.javascript.rhino.Node;
import java.io.IOException;
import java.nio.file.Path;

Expand Down Expand Up @@ -63,6 +65,9 @@ public String runtime() {
public static final BaseTranspiler ES5_TRANSPILER = new BaseTranspiler(
new CompilerSupplier(), "es6_runtime");

public static final BaseTranspiler ES_MODULE_TO_CJS_TRANSPILER =
new BaseTranspiler(new EsmToCjsCompilerSupplier(), "modules");

/**
* Wraps the Compiler into a more relevant interface, making it
* easy to test the Transpiler without depending on implementation
Expand Down Expand Up @@ -143,6 +148,53 @@ protected void setOptions(CompilerOptions options) {
DiagnosticType.error("JSC_CANNOT_CONVERT", ""));
}

/**
* CompilerSupplier that only transforms EcmaScript Modules into a form that can be saftely
* transformed on a file by file basis and concatenated.
*/
public static class EsmToCjsCompilerSupplier extends CompilerSupplier {
@Override
public CompileResult compile(Path path, String code) {
CompilerOptions options = new CompilerOptions();
options.setSourceMapOutputPath("/dev/null");
options.setSourceMapIncludeSourcesContent(true);
options.setPrettyPrint(true);

// Create a compiler and run specifically this one pass on it.
Compiler compiler = compiler();
compiler.init(
ImmutableList.of(),
ImmutableList.of(SourceFile.fromCode(path.toString(), code)),
options);
compiler.parseForCompilation();

boolean transpiled = false;

if (!compiler.hasErrors()
&& compiler.getRoot().getSecondChild().getFirstFirstChild().isModuleBody()) {
new Es6RewriteModulesToCommonJsModules(compiler)
.process(null, compiler.getRoot().getSecondChild());
compiler.getRoot().getSecondChild().getFirstChild().putBooleanProp(Node.TRANSPILED, true);
transpiled = true;
}

Result result = compiler.getResult();
String source = compiler.toSource();
StringBuilder sourceMap = new StringBuilder();
if (result.sourceMap != null) {
try {
result.sourceMap.appendTo(sourceMap, path.toString());
} catch (IOException e) {
// impossible, and not a big deal even if it did happen.
}
}
if (result.errors.length > 0) {
throw new TranspilationException(compiler, result.errors, result.warnings);
}
return new CompileResult(source, transpiled, transpiled ? sourceMap.toString() : "");
}
}

/**
* The source together with the additional compilation results.
*/
Expand Down
37 changes: 36 additions & 1 deletion test/com/google/javascript/jscomp/deps/ClosureBundlerTest.java
Expand Up @@ -19,6 +19,7 @@
import static org.mockito.Mockito.RETURNS_SMART_NULLS;
import static org.mockito.Mockito.when;

import com.google.common.collect.ImmutableMap;
import com.google.javascript.jscomp.transpile.TranspileResult;
import com.google.javascript.jscomp.transpile.Transpiler;
import java.io.IOException;
Expand Down Expand Up @@ -120,8 +121,10 @@ public void testTranspilation() throws IOException {
StringBuilder sb = new StringBuilder();
bundler.appendRuntimeTo(sb);
bundler.appendTo(sb, MODULE, input);
assertThat(sb.toString()).startsWith("RUNTIME;");
// Call endsWith because the ES6 module runtime is also injected.
assertThat(sb.toString())
.isEqualTo("RUNTIME;goog.loadModule(function(exports) {'use strict';TRANSPILED;\n"
.endsWith("goog.loadModule(function(exports) {'use strict';TRANSPILED;\n"
+ ";return exports;});\n");

// Without calling appendRuntimeTo(), the runtime is not included anymore.
Expand All @@ -131,4 +134,36 @@ public void testTranspilation() throws IOException {
.isEqualTo("goog.loadModule(function(exports) {'use strict';TRANSPILED;\n"
+ ";return exports;});\n");
}

public void testEs6Module() throws IOException {
String input =
"import {x} from './other.js';\n"
+ "export {x as y};"
+ "var local;\n"
+ "export function foo() { return local; }\n";
ClosureBundler bundler = new ClosureBundler().withPath("foo.js");
StringBuilder sb = new StringBuilder();
bundler.appendRuntimeTo(sb);
bundler.appendTo(
sb,
SimpleDependencyInfo.builder("", "").setLoadFlags(ImmutableMap.of("module", "es6")).build(),
input);
String result = sb.toString();
// ES6 module runtime should be injected.
assertThat(result).contains("$jscomp.require = createRequire();");
assertThat(sb.toString())
.endsWith(
"$jscomp.registerAndLoadModule(function($$require, $$exports, $$module) {\n"
+ " Object.defineProperties($$exports, {foo:{enumerable:true, get:function() {\n"
+ " return foo;\n"
+ " }}, y:{enumerable:true, get:function() {\n"
+ " return module$other.x;\n"
+ " }}});\n"
+ " var module$other = $$require(\"./other.js\");\n"
+ " var local;\n"
+ " function foo() {\n"
+ " return local;\n"
+ " }\n"
+ "}, \"foo.js\", [\"./other.js\"]);\n");
}
}

0 comments on commit fa7cb57

Please sign in to comment.