Skip to content

Commit

Permalink
Add a new module resolver that acts like BrowserModuleResolver but re…
Browse files Browse the repository at this point in the history
…places some path prefixes before resolving. This is a bit like a very, very watered down package map. Left more simple / vague on purpose as package map is not a define / accepted proposal.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=196189544
  • Loading branch information
johnplaisted authored and tjgq committed May 11, 2018
1 parent 6d6c1b4 commit 594e652
Show file tree
Hide file tree
Showing 13 changed files with 525 additions and 140 deletions.
10 changes: 10 additions & 0 deletions src/com/google/javascript/jscomp/CommandLineRunner.java
Expand Up @@ -783,6 +783,14 @@ private static class Flags {
) )
private ModuleLoader.ResolutionMode moduleResolutionMode = ModuleLoader.ResolutionMode.BROWSER; private ModuleLoader.ResolutionMode moduleResolutionMode = ModuleLoader.ResolutionMode.BROWSER;


@Option(
name = "--browser_resolver_prefix_replacements",
hidden = false,
usage =
"Prefixes to replace in ES6 import paths before resolving. "
+ "module_resolution must be BROWSER_WITH_TRANSFORMED_PREFIXES to take effect.")
private Map<String, String> browserResolverPrefixReplacements = ImmutableMap.of();

@Option( @Option(
name = "--package_json_entry_names", name = "--package_json_entry_names",
usage = usage =
Expand Down Expand Up @@ -1808,6 +1816,8 @@ protected CompilerOptions createOptions() {
} }
options.setSourceMapIncludeSourcesContent(flags.sourceMapIncludeSourcesContent); options.setSourceMapIncludeSourcesContent(flags.sourceMapIncludeSourcesContent);
options.setModuleResolutionMode(flags.moduleResolutionMode); options.setModuleResolutionMode(flags.moduleResolutionMode);
options.setBrowserResolverPrefixReplacements(
ImmutableMap.copyOf(flags.browserResolverPrefixReplacements));


if (flags.packageJsonEntryNames != null) { if (flags.packageJsonEntryNames != null) {
try { try {
Expand Down
44 changes: 28 additions & 16 deletions src/com/google/javascript/jscomp/Compiler.java
Expand Up @@ -32,9 +32,14 @@
import com.google.javascript.jscomp.CompilerOptions.DevMode; import com.google.javascript.jscomp.CompilerOptions.DevMode;
import com.google.javascript.jscomp.CoverageInstrumentationPass.CoverageReach; import com.google.javascript.jscomp.CoverageInstrumentationPass.CoverageReach;
import com.google.javascript.jscomp.CoverageInstrumentationPass.InstrumentOption; import com.google.javascript.jscomp.CoverageInstrumentationPass.InstrumentOption;
import com.google.javascript.jscomp.deps.BrowserModuleResolver;
import com.google.javascript.jscomp.deps.BrowserWithTransformedPrefixesModuleResolver;
import com.google.javascript.jscomp.deps.JsFileParser; import com.google.javascript.jscomp.deps.JsFileParser;
import com.google.javascript.jscomp.deps.ModuleLoader; import com.google.javascript.jscomp.deps.ModuleLoader;
import com.google.javascript.jscomp.deps.ModuleLoader.ModuleResolverFactory;
import com.google.javascript.jscomp.deps.NodeModuleResolver;
import com.google.javascript.jscomp.deps.SortedDependencies.MissingProvideException; import com.google.javascript.jscomp.deps.SortedDependencies.MissingProvideException;
import com.google.javascript.jscomp.deps.WebpackModuleResolver;
import com.google.javascript.jscomp.ijs.CheckTypeSummaryWarningsGuard; import com.google.javascript.jscomp.ijs.CheckTypeSummaryWarningsGuard;
import com.google.javascript.jscomp.parsing.Config; import com.google.javascript.jscomp.parsing.Config;
import com.google.javascript.jscomp.parsing.ParserRunner; import com.google.javascript.jscomp.parsing.ParserRunner;
Expand Down Expand Up @@ -1674,27 +1679,34 @@ Node parseInputs() {
if (options.getLanguageIn().toFeatureSet().has(FeatureSet.Feature.MODULES) if (options.getLanguageIn().toFeatureSet().has(FeatureSet.Feature.MODULES)
|| options.processCommonJSModules) { || options.processCommonJSModules) {


ModuleResolverFactory moduleResolverFactory = null;

switch (options.getModuleResolutionMode()) {
case BROWSER:
moduleResolverFactory = BrowserModuleResolver.FACTORY;
break;
case NODE:
// processJsonInputs requires a module loader to already be defined
// so we redefine it afterwards with the package.json inputs
moduleResolverFactory = new NodeModuleResolver.Factory(processJsonInputs(inputs));
break;
case WEBPACK:
moduleResolverFactory = new WebpackModuleResolver.Factory(inputPathByWebpackId);
break;
case BROWSER_WITH_TRANSFORMED_PREFIXES:
moduleResolverFactory =
new BrowserWithTransformedPrefixesModuleResolver.Factory(
options.getBrowserResolverPrefixReplacements());
break;
}

this.moduleLoader = this.moduleLoader =
new ModuleLoader( new ModuleLoader(
null, null,
options.moduleRoots, options.moduleRoots,
inputs, inputs,
ModuleLoader.PathResolver.RELATIVE, moduleResolverFactory,
options.moduleResolutionMode, ModuleLoader.PathResolver.RELATIVE);
inputPathByWebpackId);

if (options.moduleResolutionMode == ModuleLoader.ResolutionMode.NODE) {
// processJsonInputs requires a module loader to already be defined
// so we redefine it afterwards with the package.json inputs
this.moduleLoader =
new ModuleLoader(
null,
options.moduleRoots,
inputs,
ModuleLoader.PathResolver.RELATIVE,
options.moduleResolutionMode,
processJsonInputs(inputs));
}
} else { } else {
// Use an empty module loader if we're not actually dealing with modules. // Use an empty module loader if we're not actually dealing with modules.
this.moduleLoader = ModuleLoader.EMPTY; this.moduleLoader = ModuleLoader.EMPTY;
Expand Down
25 changes: 21 additions & 4 deletions src/com/google/javascript/jscomp/CompilerOptions.java
Expand Up @@ -31,6 +31,7 @@
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.primitives.Chars; import com.google.common.primitives.Chars;
import com.google.javascript.jscomp.deps.ModuleLoader; import com.google.javascript.jscomp.deps.ModuleLoader;
import com.google.javascript.jscomp.deps.ModuleLoader.ResolutionMode;
import com.google.javascript.jscomp.parsing.Config; import com.google.javascript.jscomp.parsing.Config;
import com.google.javascript.jscomp.parsing.parser.FeatureSet; import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.jscomp.resources.ResourceLoader; import com.google.javascript.jscomp.resources.ResourceLoader;
Expand Down Expand Up @@ -1168,7 +1169,13 @@ public void setWrapGoogModulesForWhitespaceOnly(boolean enable) {
private Optional<Boolean> isStrictModeInput = Optional.absent(); private Optional<Boolean> isStrictModeInput = Optional.absent();


/** Which algorithm to use for locating ES6 and CommonJS modules */ /** Which algorithm to use for locating ES6 and CommonJS modules */
ModuleLoader.ResolutionMode moduleResolutionMode; ResolutionMode moduleResolutionMode;

/**
* Map of prefix replacements for use when moduleResolutionMode is {@link
* ResolutionMode#BROWSER_WITH_TRANSFORMED_PREFIXES}.
*/
private ImmutableMap<String, String> browserResolverPrefixReplacements;


/** Which entries to look for in package.json files when processing modules */ /** Which entries to look for in package.json files when processing modules */
List<String> packageJsonEntryNames; List<String> packageJsonEntryNames;
Expand All @@ -1195,6 +1202,7 @@ public CompilerOptions() {


// Which environment to use // Which environment to use
environment = Environment.BROWSER; environment = Environment.BROWSER;
browserResolverPrefixReplacements = ImmutableMap.of();


// Modules // Modules
moduleResolutionMode = ModuleLoader.ResolutionMode.BROWSER; moduleResolutionMode = ModuleLoader.ResolutionMode.BROWSER;
Expand Down Expand Up @@ -2763,12 +2771,21 @@ public CompilerOptions setEmitUseStrict(boolean emitUseStrict) {
return this; return this;
} }


public ModuleLoader.ResolutionMode getModuleResolutionMode() { public ResolutionMode getModuleResolutionMode() {
return this.moduleResolutionMode; return this.moduleResolutionMode;
} }


public void setModuleResolutionMode(ModuleLoader.ResolutionMode mode) { public void setModuleResolutionMode(ResolutionMode moduleResolutionMode) {
this.moduleResolutionMode = mode; this.moduleResolutionMode = moduleResolutionMode;
}

public ImmutableMap<String, String> getBrowserResolverPrefixReplacements() {
return this.browserResolverPrefixReplacements;
}

public void setBrowserResolverPrefixReplacements(
ImmutableMap<String, String> browserResolverPrefixReplacements) {
this.browserResolverPrefixReplacements = browserResolverPrefixReplacements;
} }


public List<String> getPackageJsonEntryNames() { public List<String> getPackageJsonEntryNames() {
Expand Down
Expand Up @@ -21,6 +21,7 @@
import com.google.javascript.jscomp.CheckLevel; import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.ErrorHandler; import com.google.javascript.jscomp.ErrorHandler;
import com.google.javascript.jscomp.JSError; import com.google.javascript.jscomp.JSError;
import com.google.javascript.jscomp.deps.ModuleLoader.ModuleResolverFactory;
import javax.annotation.Nullable; import javax.annotation.Nullable;


/** /**
Expand All @@ -30,6 +31,8 @@
*/ */
public class BrowserModuleResolver extends ModuleResolver { public class BrowserModuleResolver extends ModuleResolver {


public static final ModuleResolverFactory FACTORY = BrowserModuleResolver::new;

public BrowserModuleResolver( public BrowserModuleResolver(
ImmutableSet<String> modulePaths, ImmutableSet<String> modulePaths,
ImmutableList<String> moduleRootPaths, ImmutableList<String> moduleRootPaths,
Expand All @@ -51,7 +54,7 @@ public String resolveJsModule(
colno, colno,
ModuleLoader.INVALID_MODULE_PATH, ModuleLoader.INVALID_MODULE_PATH,
moduleAddress, moduleAddress,
"BROWSER")); ModuleLoader.ResolutionMode.BROWSER.toString()));
return null; return null;
} }


Expand Down
@@ -0,0 +1,173 @@
/*
* Copyright 2017 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.deps;

import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;

import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.DiagnosticType;
import com.google.javascript.jscomp.ErrorHandler;
import com.google.javascript.jscomp.JSError;
import com.google.javascript.jscomp.deps.ModuleLoader.ModuleResolverFactory;
import java.util.Comparator;
import java.util.Set;
import javax.annotation.Nullable;

/**
* Limited superset of the {@link BrowserModuleResolver} that allows for replacing some path
* prefixes before resolving.
*/
public class BrowserWithTransformedPrefixesModuleResolver extends ModuleResolver {

static final DiagnosticType TRANSFORMED_PATH_IS_AMBIGUOUS =
DiagnosticType.error(
"JSC_TRANSFORMED_PATH_IS_AMBIGUOUS",
"Replacing \"{0}\" with \"{1}\" in the import path \"{2}\" is an ambiguous address "
+ "(\"{3}\").");

/** Factory for {@link BrowserWithTransformedPrefixesModuleResolver}. */
public static final class Factory implements ModuleResolverFactory {
private final ImmutableMap<String, String> prefixReplacements;

public Factory(
ImmutableMap<String, String> prefixReplacements) {
this.prefixReplacements = prefixReplacements;
}

@Override
public ModuleResolver create(
ImmutableSet<String> modulePaths,
ImmutableList<String> moduleRootPaths,
ErrorHandler errorHandler) {
return new BrowserWithTransformedPrefixesModuleResolver(
modulePaths, moduleRootPaths, errorHandler, prefixReplacements);
}
}

/**
* Struct of prefix and replacement. Has a natural ordering from longest prefix to shortest
* prefix.
*/
@AutoValue
abstract static class PrefixReplacement {
abstract String prefix();
abstract String replacement();

public static PrefixReplacement of(String prefix, String replacement) {
return new AutoValue_BrowserWithTransformedPrefixesModuleResolver_PrefixReplacement(
prefix, replacement);
}
}

private final ImmutableSet<PrefixReplacement> prefixReplacements;

public BrowserWithTransformedPrefixesModuleResolver(
ImmutableSet<String> modulePaths,
ImmutableList<String> moduleRootPaths,
ErrorHandler errorHandler,
ImmutableMap<String, String> prefixReplacements) {
super(modulePaths, moduleRootPaths, errorHandler);
Set<PrefixReplacement> p =
prefixReplacements
.entrySet()
.stream()
.map(entry -> PrefixReplacement.of(entry.getKey(), entry.getValue()))
.collect(
toImmutableSortedSet(
// Sort by length in descending order to prefixes are applied most specific to
// least specific.
Comparator.<PrefixReplacement>comparingInt(r -> r.prefix().length())
.reversed()
.thenComparing(r -> r.prefix())));
this.prefixReplacements = ImmutableSet.copyOf(p);
}

@Nullable
@Override
public String resolveJsModule(
String scriptAddress, String moduleAddress, String sourcename, int lineno, int colno) {
String transformedAddress = moduleAddress;
for (PrefixReplacement prefixReplacement : prefixReplacements) {
if (moduleAddress.startsWith(prefixReplacement.prefix())) {
transformedAddress =
prefixReplacement.replacement()
+ moduleAddress.substring(prefixReplacement.prefix().length());

if (ModuleLoader.isAmbiguousIdentifier(transformedAddress)) {
errorHandler.report(
CheckLevel.WARNING,
JSError.make(
sourcename,
lineno,
colno,
TRANSFORMED_PATH_IS_AMBIGUOUS,
prefixReplacement.prefix(),
prefixReplacement.replacement(),
moduleAddress,
transformedAddress));
}
break;
}
}

// If ambiguous after the loop it was not transformed and the original moduleAddress is
// ambiguous.
if (ModuleLoader.isAmbiguousIdentifier(transformedAddress)) {
errorHandler.report(
CheckLevel.WARNING,
JSError.make(
sourcename,
lineno,
colno,
ModuleLoader.INVALID_MODULE_PATH,
transformedAddress,
ModuleLoader.ResolutionMode.BROWSER_WITH_TRANSFORMED_PREFIXES.toString()));
return null;
}

String loadAddress = locate(scriptAddress, transformedAddress);
if (transformedAddress == null) {
errorHandler.report(
CheckLevel.WARNING,
JSError.make(sourcename, lineno, colno, ModuleLoader.LOAD_WARNING, moduleAddress));
}
return loadAddress;
}

@Override
public String resolveModuleAsPath(String scriptAddress, String moduleAddress) {
if (ModuleLoader.isRelativeIdentifier(moduleAddress)) {
return super.resolveModuleAsPath(scriptAddress, moduleAddress);
}

String transformedAddress = moduleAddress;
for (PrefixReplacement prefixReplacement : prefixReplacements) {
if (moduleAddress.startsWith(prefixReplacement.prefix())) {
transformedAddress =
prefixReplacement.replacement()
+ moduleAddress.substring(prefixReplacement.prefix().length());
break;
}
}

return ModuleLoader.normalize(transformedAddress, moduleRootPaths);
}
}
5 changes: 1 addition & 4 deletions src/com/google/javascript/jscomp/deps/JsFileParser.java
Expand Up @@ -315,10 +315,7 @@ protected boolean parseLine(String line) throws ParseException {
// cut off the "goog:" prefix // cut off the "goog:" prefix
requires.add(Require.googRequireSymbol(arg.substring(5))); requires.add(Require.googRequireSymbol(arg.substring(5)));
} else { } else {
ModuleLoader.ModulePath path = file.resolveJsModule(arg); ModuleLoader.ModulePath path = file.resolveModuleAsPath(arg);
if (path == null) {
path = file.resolveModuleAsPath(arg);
}
requires.add(Require.es6Import(path.toModuleName(), arg)); requires.add(Require.es6Import(path.toModuleName(), arg));
} }
} }
Expand Down

0 comments on commit 594e652

Please sign in to comment.