Skip to content

Commit d040afa

Browse files
When --dependency_mode is set to STRICT or LOOSE, order dependencies in a deterministic depth-first order from entry points.
Avoids a full parse on every input when CommonJS module processing is enabled. ES6 and CommonJS modules no longer generate synthetic goog.require and goog.provide calls. ES6 Module Transpilation no longer depends on Closure-Library primitives.
1 parent 83d52f6 commit d040afa

File tree

9 files changed

+532
-362
lines changed

9 files changed

+532
-362
lines changed

src/com/google/javascript/jscomp/Compiler.java

Lines changed: 152 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@
6464
import java.io.Serializable;
6565
import java.util.AbstractSet;
6666
import java.util.ArrayList;
67-
import java.util.Collection;
6867
import java.util.Collections;
6968
import java.util.HashMap;
7069
import java.util.HashSet;
@@ -1749,7 +1748,19 @@ Node parseInputs() {
17491748
options.moduleResolutionMode,
17501749
processJsonInputs(inputs));
17511750
}
1751+
} else {
1752+
// Use an empty module loader if we're not actually dealing with modules.
1753+
this.moduleLoader = ModuleLoader.EMPTY;
1754+
}
17521755

1756+
if (options.getDependencyOptions().needsManagement()) {
1757+
findDependenciesFromEntryPoints(
1758+
options.getLanguageIn().toFeatureSet().has(Feature.MODULES),
1759+
options.processCommonJSModules,
1760+
options.transformAMDToCJSModules);
1761+
} else if (options.needsTranspilationFrom(FeatureSet.ES6_MODULES)
1762+
|| options.transformAMDToCJSModules
1763+
|| options.processCommonJSModules) {
17531764
if (options.getLanguageIn().toFeatureSet().has(Feature.MODULES)) {
17541765
parsePotentialModules(inputs);
17551766
}
@@ -1782,12 +1793,12 @@ Node parseInputs() {
17821793
}
17831794
}
17841795

1785-
if (!inputsToRewrite.isEmpty()) {
1786-
forceToEs6Modules(inputsToRewrite.values());
1796+
for (CompilerInput input : inputsToRewrite.values()) {
1797+
forceInputToPathBasedModule(
1798+
input,
1799+
options.getLanguageIn().toFeatureSet().has(Feature.MODULES),
1800+
options.processCommonJSModules);
17871801
}
1788-
} else {
1789-
// Use an empty module loader if we're not actually dealing with modules.
1790-
this.moduleLoader = ModuleLoader.EMPTY;
17911802
}
17921803

17931804
orderInputs();
@@ -1894,6 +1905,141 @@ void orderInputs() {
18941905
}
18951906
}
18961907

1908+
/**
1909+
* Find dependencies by recursively traversing each dependency of an input starting with the entry
1910+
* points. Causes a full parse of each file, but since the file is reachable by walking the graph,
1911+
* this would be required in later compilation passes regardless.
1912+
*
1913+
* <p>Inputs which are not reachable during graph traversal will be dropped.
1914+
*
1915+
* <p>If the dependency mode is set to LOOSE, inputs for which the deps package did not find a
1916+
* provide statement or detect as a module will be treated as entry points.
1917+
*/
1918+
void findDependenciesFromEntryPoints(
1919+
boolean supportEs6Modules, boolean supportCommonJSModules, boolean supportAmdModules) {
1920+
hoistUnorderedExterns();
1921+
List<CompilerInput> entryPoints = new ArrayList<>();
1922+
Map<String, CompilerInput> inputsByProvide = new HashMap<>();
1923+
Map<String, CompilerInput> inputsByIdentifier = new HashMap<>();
1924+
for (CompilerInput input : inputs) {
1925+
if (!options.getDependencyOptions().shouldDropMoochers() && input.getProvides().isEmpty()) {
1926+
entryPoints.add(input);
1927+
}
1928+
inputsByIdentifier.put(
1929+
ModuleIdentifier.forFile(input.getPath().toString()).toString(), input);
1930+
for (String provide : input.getProvides()) {
1931+
if (!provide.startsWith("module$")) {
1932+
inputsByProvide.put(provide, input);
1933+
}
1934+
}
1935+
}
1936+
for (ModuleIdentifier moduleIdentifier : options.getDependencyOptions().getEntryPoints()) {
1937+
CompilerInput input = inputsByIdentifier.get(moduleIdentifier.toString());
1938+
if (input != null) {
1939+
entryPoints.add(input);
1940+
}
1941+
}
1942+
1943+
Set<CompilerInput> workingInputSet = new HashSet<>(inputs);
1944+
List<CompilerInput> orderedInputs = new ArrayList<>();
1945+
for (CompilerInput entryPoint : entryPoints) {
1946+
orderedInputs.addAll(
1947+
depthFirstDependenciesFromInput(
1948+
entryPoint,
1949+
false,
1950+
workingInputSet,
1951+
inputsByIdentifier,
1952+
inputsByProvide,
1953+
supportEs6Modules,
1954+
supportCommonJSModules,
1955+
supportAmdModules));
1956+
}
1957+
1958+
// TODO(ChadKillingsworth) Move this into the standard compilation passes
1959+
if (supportCommonJSModules) {
1960+
for (CompilerInput input : orderedInputs) {
1961+
new ProcessCommonJSModules(this).process(null, input.getAstRoot(this), false);
1962+
}
1963+
}
1964+
}
1965+
1966+
/** For a given input, order it's dependencies in a depth first traversal */
1967+
List<CompilerInput> depthFirstDependenciesFromInput(
1968+
CompilerInput input,
1969+
boolean wasImportedByModule,
1970+
Set<CompilerInput> inputs,
1971+
Map<String, CompilerInput> inputsByIdentifier,
1972+
Map<String, CompilerInput> inputsByProvide,
1973+
boolean supportEs6Modules,
1974+
boolean supportCommonJSModules,
1975+
boolean supportAmdModules) {
1976+
List<CompilerInput> orderedInputs = new ArrayList<>();
1977+
if (!inputs.remove(input)) {
1978+
// It's possible for a module to be included as both a script
1979+
// and a module in the same compilation. In these cases, it should
1980+
// be forced to be a module.
1981+
if (wasImportedByModule && !input.isJsModule()) {
1982+
forceInputToPathBasedModule(input, supportEs6Modules, supportCommonJSModules);
1983+
}
1984+
1985+
return orderedInputs;
1986+
}
1987+
1988+
if (supportAmdModules) {
1989+
new TransformAMDToCJSModule(this).process(null, input.getAstRoot(this));
1990+
}
1991+
1992+
FindModuleDependencies findDeps =
1993+
new FindModuleDependencies(this, supportEs6Modules, supportCommonJSModules);
1994+
findDeps.process(input.getAstRoot(this));
1995+
1996+
// If this input was imported by another module, it is itself a module
1997+
// so we force it to be detected as such.
1998+
if (wasImportedByModule && !input.isJsModule()) {
1999+
forceInputToPathBasedModule(input, supportEs6Modules, supportCommonJSModules);
2000+
}
2001+
2002+
for (String requiredNamespace : input.getRequires()) {
2003+
CompilerInput requiredInput = null;
2004+
boolean requiredByModuleImport = false;
2005+
if (inputsByProvide.containsKey(requiredNamespace)) {
2006+
requiredInput = inputsByProvide.get(requiredNamespace);
2007+
} else if (inputsByIdentifier.containsKey(requiredNamespace)) {
2008+
requiredByModuleImport = true;
2009+
requiredInput = inputsByIdentifier.get(requiredNamespace);
2010+
}
2011+
2012+
if (requiredInput != null) {
2013+
orderedInputs.addAll(
2014+
depthFirstDependenciesFromInput(
2015+
requiredInput,
2016+
requiredByModuleImport,
2017+
inputs,
2018+
inputsByIdentifier,
2019+
inputsByProvide,
2020+
supportEs6Modules,
2021+
supportCommonJSModules,
2022+
supportAmdModules));
2023+
}
2024+
}
2025+
orderedInputs.add(input);
2026+
return orderedInputs;
2027+
}
2028+
2029+
private void forceInputToPathBasedModule(
2030+
CompilerInput input, boolean supportEs6Modules, boolean supportCommonJSModules) {
2031+
2032+
if (supportEs6Modules) {
2033+
FindModuleDependencies findDeps =
2034+
new FindModuleDependencies(this, supportEs6Modules, supportCommonJSModules);
2035+
findDeps.convertToEs6Module(input.getAstRoot(this));
2036+
input.markAsModule(true);
2037+
} else if (supportCommonJSModules) {
2038+
new ProcessCommonJSModules(this).process(null, input.getAstRoot(this), true);
2039+
input.markAsModule(true);
2040+
}
2041+
}
2042+
18972043
/**
18982044
* Hoists inputs with the @externs annotation and no provides or requires into the externs list.
18992045
*/
@@ -2018,18 +2164,6 @@ Map<String, String> processJsonInputs(List<CompilerInput> inputsToProcess) {
20182164
return rewriteJson.getPackageJsonMainEntries();
20192165
}
20202166

2021-
void forceToEs6Modules(Collection<CompilerInput> inputsToProcess) {
2022-
for (CompilerInput input : inputsToProcess) {
2023-
input.setCompiler(this);
2024-
input.addProvide(input.getPath().toModuleName());
2025-
Node root = input.getAstRoot(this);
2026-
if (root == null) {
2027-
continue;
2028-
}
2029-
Es6RewriteModules moduleRewriter = new Es6RewriteModules(this);
2030-
moduleRewriter.forceToEs6Module(root);
2031-
}
2032-
}
20332167

20342168
private List<CompilerInput> parsePotentialModules(List<CompilerInput> inputsToProcess) {
20352169
List<CompilerInput> filteredInputs = new ArrayList<>();

src/com/google/javascript/jscomp/CompilerInput.java

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ public class CompilerInput implements SourceAst, DependencyInfo {
6161
private DependencyInfo dependencyInfo;
6262
private final List<String> extraRequires = new ArrayList<>();
6363
private final List<String> extraProvides = new ArrayList<>();
64+
private final List<String> orderedRequires = new ArrayList<>();
65+
private boolean hasFullParseDependencyInfo = false;
66+
private boolean jsModule = false;
6467

6568
// An AbstractCompiler for doing parsing.
6669
// We do not want to persist this across serialized state.
@@ -151,6 +154,10 @@ public void setCompiler(AbstractCompiler compiler) {
151154
/** Gets a list of types depended on by this input. */
152155
@Override
153156
public Collection<String> getRequires() {
157+
if (hasFullParseDependencyInfo) {
158+
return orderedRequires;
159+
}
160+
154161
return getDependencyInfo().getRequires();
155162
}
156163

@@ -182,19 +189,36 @@ Collection<String> getKnownProvides() {
182189
extraProvides);
183190
}
184191

185-
// TODO(nicksantos): Remove addProvide/addRequire/removeRequire once
186-
// there is better support for discovering non-closure dependencies.
187-
188192
/**
189-
* Registers a type that this input defines.
193+
* Registers a type that this input defines. Includes both explicitly declared namespaces via
194+
* goog.provide and goog.module calls as well as implicit namespaces provided by module rewriting.
190195
*/
191196
public void addProvide(String provide) {
192197
extraProvides.add(provide);
193198
}
194199

195-
/**
196-
* Registers a type that this input depends on.
197-
*/
200+
/** Registers a type that this input depends on in the order seen in the file. */
201+
public boolean addOrderedRequire(String require) {
202+
if (!orderedRequires.contains(require)) {
203+
orderedRequires.add(require);
204+
return true;
205+
}
206+
return false;
207+
}
208+
209+
public void setHasFullParseDependencyInfo(boolean hasFullParseDependencyInfo) {
210+
this.hasFullParseDependencyInfo = hasFullParseDependencyInfo;
211+
}
212+
213+
public boolean isJsModule() {
214+
return jsModule;
215+
}
216+
217+
public void markAsModule(boolean isModule) {
218+
jsModule = isModule;
219+
}
220+
221+
/** Registers a type that this input depends on. */
198222
public void addRequire(String require) {
199223
extraRequires.add(require);
200224
}

src/com/google/javascript/jscomp/Es6RewriteModules.java

Lines changed: 7 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
import com.google.common.base.Preconditions;
2323
import com.google.common.base.Splitter;
24-
import com.google.common.collect.ImmutableSet;
2524
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
2625
import com.google.javascript.jscomp.deps.ModuleLoader;
2726
import com.google.javascript.rhino.IR;
@@ -80,8 +79,6 @@ public final class Es6RewriteModules extends AbstractPostOrderCallback
8079

8180
private Set<String> alreadyRequired;
8281

83-
private Node googRequireInsertSpot;
84-
8582
/**
8683
* Creates a new Es6RewriteModules instance which can be used to rewrite
8784
* ES6 modules to a concatenable form.
@@ -101,29 +98,6 @@ public static boolean isEs6ModuleRoot(Node scriptNode) {
10198
return scriptNode.hasChildren() && scriptNode.getFirstChild().isModuleBody();
10299
}
103100

104-
/**
105-
* Force rewriting of a file into an ES6 module, such as for imported files that contain no
106-
* "import" or "export" statements. Fails if the file contains a goog.provide or goog.module.
107-
*
108-
* @return True, if the file is now an ES6 module. False, if the file must remain a script.
109-
* TODO(blickly): Move this logic out of this pass, since it is independent of whether or
110-
* not we are actually transpiling modules
111-
*/
112-
public boolean forceToEs6Module(Node root) {
113-
if (isEs6ModuleRoot(root)) {
114-
return true;
115-
}
116-
FindGoogProvideOrGoogModule finder = new FindGoogProvideOrGoogModule();
117-
NodeTraversal.traverseEs6(compiler, root, finder);
118-
if (finder.isFound()) {
119-
return false;
120-
}
121-
Node moduleNode = new Node(Token.MODULE_BODY).srcref(root);
122-
moduleNode.addChildrenToBack(root.removeChildren());
123-
root.addChildToBack(moduleNode);
124-
return true;
125-
}
126-
127101
@Override
128102
public void process(Node externs, Node root) {
129103
for (Node file = root.getFirstChild(); file != null; file = file.getNext()) {
@@ -155,7 +129,6 @@ public void clearState() {
155129
this.classes = new HashSet<>();
156130
this.typedefs = new HashSet<>();
157131
this.alreadyRequired = new HashSet<>();
158-
this.googRequireInsertSpot = null;
159132
}
160133

161134
/**
@@ -270,16 +243,7 @@ private void visitImport(NodeTraversal t, Node importDecl, Node parent) {
270243
}
271244
}
272245

273-
// Emit goog.require call for the module.
274-
if (alreadyRequired.add(moduleName)) {
275-
Node require = IR.exprResult(
276-
IR.call(NodeUtil.newQName(compiler, "goog.require"), IR.string(moduleName)));
277-
require.useSourceInfoIfMissingFromForTree(importDecl);
278-
parent.addChildAfter(require, googRequireInsertSpot);
279-
googRequireInsertSpot = require;
280-
t.getInput().addRequire(moduleName);
281-
}
282-
246+
alreadyRequired.add(moduleName);
283247
parent.removeChild(importDecl);
284248
t.reportCodeChange();
285249
}
@@ -466,22 +430,13 @@ private void visitScript(NodeTraversal t, Node script) {
466430
// Rename vars to not conflict in global scope.
467431
NodeTraversal.traverseEs6(compiler, script, new RenameGlobalVars(moduleName));
468432

469-
// Add goog.provide call.
470-
Node googProvide = IR.exprResult(
471-
IR.call(NodeUtil.newQName(compiler, "goog.provide"),
472-
IR.string(moduleName)));
473-
script.addChildToFront(googProvide.useSourceInfoIfMissingFromForTree(script));
474-
t.getInput().addProvide(moduleName);
475-
476-
JSDocInfoBuilder jsDocInfo = script.getJSDocInfo() == null
477-
? new JSDocInfoBuilder(false)
478-
: JSDocInfoBuilder.copyFrom(script.getJSDocInfo());
479-
if (!jsDocInfo.isPopulatedWithFileOverview()) {
480-
jsDocInfo.recordFileOverview("");
433+
if (!exportMap.isEmpty()) {
434+
Node moduleVar = IR.var(IR.name(moduleName), IR.objectlit());
435+
JSDocInfoBuilder infoBuilder = new JSDocInfoBuilder(false);
436+
infoBuilder.recordConstancy();
437+
moduleVar.setJSDocInfo(infoBuilder.build());
438+
script.addChildToFront(moduleVar.useSourceInfoIfMissingFromForTree(script));
481439
}
482-
// Don't check provides and requires, since most of them are auto-generated.
483-
jsDocInfo.recordSuppressions(ImmutableSet.of("missingProvide", "missingRequire"));
484-
script.setJSDocInfo(jsDocInfo.build());
485440

486441
exportMap.clear();
487442
t.reportCodeChange();

0 commit comments

Comments
 (0)