All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.transpile.BaseTranspiler Maven / Gradle / Ivy

There is a newer version: 9.0.8
Show newest version
/*
 * Copyright 2016 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.transpile;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.Compiler;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.CompilerOptions.Es6ModuleTranspilation;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import com.google.javascript.jscomp.DiagnosticGroups;
import com.google.javascript.jscomp.PropertyRenamingPolicy;
import com.google.javascript.jscomp.Result;
import com.google.javascript.jscomp.SourceFile;
import com.google.javascript.jscomp.SourceMap;
import com.google.javascript.jscomp.VariableRenamingPolicy;
import com.google.javascript.jscomp.bundle.TranspilationException;
import com.google.javascript.jscomp.deps.ModuleLoader;
import com.google.javascript.jscomp.deps.ModuleLoader.PathEscaper;
import com.google.javascript.jscomp.deps.ModuleLoader.ResolutionMode;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

/** Basic Transpiler implementation for outputting ES5 code. */
public final class BaseTranspiler implements Transpiler {

  private final CompilerSupplier compilerSupplier;
  private final String runtimeLibraryName;

  public BaseTranspiler(CompilerSupplier compilerSupplier, String runtimeLibraryName) {
    this.compilerSupplier = checkNotNull(compilerSupplier);
    this.runtimeLibraryName = checkNotNull(runtimeLibraryName);
  }

  @Override
  public TranspileResult transpile(URI path, String code) {
    CompileResult result = compilerSupplier.compile(path, code);
    if (!result.transpiled) {
      return new TranspileResult(path, code, code, "");
    }
    return new TranspileResult(path, code, result.source, result.sourceMap);
  }

  @Override
  public String runtime() {
    StringBuilder sb = new StringBuilder();
    if (!Strings.isNullOrEmpty(runtimeLibraryName)) {
      sb.append(compilerSupplier.runtime(runtimeLibraryName));
    }
    sb.append(compilerSupplier.runtime("modules"));
    return sb.toString();
  }

  public static final BaseTranspiler LATEST_TRANSPILER = to(FeatureSet.latest(), "");

  public static final BaseTranspiler ES5_TRANSPILER = to(LanguageMode.ECMASCRIPT5.toFeatureSet());

  public static final BaseTranspiler to(FeatureSet featureSet, String runtime) {
    return new BaseTranspiler(new CompilerSupplier(featureSet), runtime);
  }

  public static final BaseTranspiler to(FeatureSet featureSet) {
    return to(featureSet, "es6_runtime");
  }

  /**
   * Wraps the Compiler into a more relevant interface, making it easy to test the Transpiler
   * without depending on implementation details of the Compiler itself. Also works around the fact
   * that the Compiler is not thread-safe (since we may do multiple transpiles concurrently), so we
   * supply a fresh instance each time when we're in single-file mode.
   */
  public static class CompilerSupplier {
    protected final ResolutionMode moduleResolution;
    protected final ImmutableList moduleRoots;
    protected final ImmutableMap prefixReplacements;
    protected final FeatureSet outputFeatureSet;

    public CompilerSupplier() {
      this(LanguageMode.ECMASCRIPT5.toFeatureSet());
    }

    public CompilerSupplier(FeatureSet outputFeatureSet) {
      // Use the default resolution mode
      this(
          outputFeatureSet,
          new CompilerOptions().getModuleResolutionMode(),
          ImmutableList.of(),
          ImmutableMap.of());
    }

    /**
     * Accepts commonly overridden options for ES6 modules to avoid needed to subclass.
     *
     * @param moduleResolution module resolution for resolving import paths
     * @param prefixReplacements prefix replacements for when moduleResolution is {@link
     *     ModuleLoader.ResolutionMode#BROWSER_WITH_TRANSFORMED_PREFIXES}
     */
    public CompilerSupplier(
        FeatureSet outputFeatureSet,
        ModuleLoader.ResolutionMode moduleResolution,
        ImmutableList moduleRoots,
        ImmutableMap prefixReplacements) {
      this.outputFeatureSet = outputFeatureSet;
      this.moduleResolution = moduleResolution;
      this.moduleRoots = moduleRoots;
      this.prefixReplacements = prefixReplacements;
    }

    public CompileResult compile(URI path, String code) {
      Compiler compiler = compiler();
      Result result =
          compiler.compile(
              createTrivialExterns(), SourceFile.fromCode(path.toString(), code), options());
      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.
        }
      }
      boolean transpiled = !result.transpiledFiles.isEmpty();
      if (!result.errors.isEmpty()) {
        throw new TranspilationException(compiler, result.errors, result.warnings);
      }
      return new CompileResult(source, transpiled, transpiled ? sourceMap.toString() : "");
    }

    public String runtime(String library) {
      Compiler compiler = compiler();
      CompilerOptions options = options();
      options.setForceLibraryInjection(ImmutableList.of(library));
      compiler.compile(createTrivialExterns(), createEmptySource(), options);
      return compiler.toSource();
    }

    protected Compiler compiler() {
      return new Compiler();
    }

    protected CompilerOptions options() {
      CompilerOptions options = new CompilerOptions();
      setOptions(options);
      return options;
    }

    protected void setOptions(CompilerOptions options) {
      options.setLanguageIn(LanguageMode.ECMASCRIPT_NEXT);
      options.setOutputFeatureSet(outputFeatureSet.without(Feature.MODULES));
      options.setEmitUseStrict(false);
      options.setQuoteKeywordProperties(true);
      options.setSkipNonTranspilationPasses(true);
      options.setVariableRenaming(VariableRenamingPolicy.OFF);
      options.setPropertyRenaming(PropertyRenamingPolicy.OFF);
      options.setWrapGoogModulesForWhitespaceOnly(false);
      options.setPrettyPrint(true);
      options.setWarningLevel(DiagnosticGroups.NON_STANDARD_JSDOC, CheckLevel.OFF);
      options.setEs6ModuleTranspilation(Es6ModuleTranspilation.TO_COMMON_JS_LIKE_MODULES);
      options.setModuleResolutionMode(moduleResolution);
      options.setModuleRoots(moduleRoots);
      options.setBrowserResolverPrefixReplacements(prefixReplacements);

      // Transpiler often used in isolation, so references to other files will never exist.
      options.setWarningLevel(DiagnosticGroups.MODULE_LOAD, CheckLevel.OFF);

      // Don't escape module paths when bundling in the event paths are URLs.
      options.setPathEscaper(PathEscaper.CANONICALIZE_ONLY);
      options.setParseInlineSourceMaps(true);
      options.setApplyInputSourceMaps(true);
      options.setSourceMapOutputPath("/dev/null");
      options.setSourceMapIncludeSourcesContent(true);
      // Make sourcemaps use absolute paths, so that the path is not duplicated if a build tool adds
      // a sourceurl. Exception: if the location has a scheme (like http:) then leave the path
      // intact. This makes this usable from web servers.
      options.setSourceMapLocationMappings(
          ImmutableList.of(
              (location) -> {
                try {
                  if (new URI(location).getScheme() != null) {
                    return location;
                  }
                } catch (URISyntaxException e) {
                  // Swallow, return the absolute version below.
                }
                return new SourceMap.PrefixLocationMapping("", "/").map(location);
              }));
    }

    protected SourceFile createTrivialExterns() {
      return SourceFile.fromCode("externs.js", "function Symbol() {}");
    }

    protected SourceFile createEmptySource() {
      return SourceFile.fromCode("empty.js", "");
    }
  }

  /** The source together with the additional compilation results. */
  public static class CompileResult {
    public final String source;
    public final boolean transpiled;
    public final String sourceMap;

    public CompileResult(String source, boolean transpiled, String sourceMap) {
      this.source = checkNotNull(source);
      this.transpiled = transpiled;
      this.sourceMap = checkNotNull(sourceMap);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy