com.google.javascript.jscomp.deps.TranspilingClosureBundler.java Source code

Java tutorial

Introduction

Here is the source code for com.google.javascript.jscomp.deps.TranspilingClosureBundler.java

Source

/*
 * 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.deps;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableList;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.common.io.CharSource;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.google.javascript.jscomp.Compiler;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
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 java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import javax.annotation.concurrent.NotThreadSafe;

/**
 * {@link ClosureBundler} that transpiles its sources.
 */
@NotThreadSafe
public final class TranspilingClosureBundler extends ClosureBundler {
    private static final HashFunction HASH_FUNCTION = Hashing.goodFastHash(64);
    private static final int DEFAULT_CACHE_SIZE = 100;
    /**
     * Cache recent transpilations, keyed by the hash code of the input
     * to avoid storing the whole input.
     */
    @VisibleForTesting
    final Cache<Long, String> cachedTranspilations;

    // TODO(sdh): Not all transpilation requires the runtime, only inject if actually needed.
    private final String es6Runtime;
    private boolean needToBundleEs6Runtime = true;

    public TranspilingClosureBundler() {
        this(getEs6Runtime());
    }

    /**
     * Creates a new bundler that transpile the sources from ES6 to ES5.
     *
     * @param transpilationCache The cache to use to store already transpiled files
     */
    public TranspilingClosureBundler(Cache<Long, String> transpilationCache) {
        this(getEs6Runtime(), transpilationCache);
    }

    @VisibleForTesting
    TranspilingClosureBundler(String es6Runtime) {
        this(es6Runtime, CacheBuilder.newBuilder().maximumSize(DEFAULT_CACHE_SIZE).<Long, String>build());
    }

    @VisibleForTesting
    TranspilingClosureBundler(String es6Runtime, Cache<Long, String> transpilationCache) {
        this.es6Runtime = es6Runtime;
        this.cachedTranspilations = transpilationCache;
    }

    @Override
    public void appendTo(Appendable out, DependencyInfo info, CharSource content) throws IOException {
        if (needToBundleEs6Runtime) {
            // Piggyback on the first call to transformInput to include the ES6 runtime as well.
            super.appendTo(out, SimpleDependencyInfo.EMPTY, CharSource.wrap(es6Runtime));
            needToBundleEs6Runtime = false;
        }
        super.appendTo(out, info, content);
    }

    private static CompilerOptions getOptions() {
        CompilerOptions options = new CompilerOptions();
        options.setLanguageIn(LanguageMode.ECMASCRIPT6_STRICT);
        options.setLanguageOut(LanguageMode.ECMASCRIPT5);
        // Quoting keyword properties is only needed in ES3, so basically only in IE8.
        // But we set it explicitly here because the way the test bundler works, it invokes
        // the compiler without giving information about the browser, so we have to quote
        // every time to be safe :-/
        options.setQuoteKeywordProperties(true);
        options.setSkipNonTranspilationPasses(true);
        options.setVariableRenaming(VariableRenamingPolicy.OFF);
        options.setPropertyRenaming(PropertyRenamingPolicy.OFF);
        options.setWrapGoogModulesForWhitespaceOnly(false);
        options.setPrettyPrint(true);
        options.setSourceMapOutputPath("/dev/null");
        options.setSourceMapIncludeSourcesContent(true);
        return options;
    }

    @Override
    protected String transformInput(final String js, final String path) {
        try {
            // Don't use built-in hashCode to decrease the likelihood of a collision.
            long hashCode = HASH_FUNCTION.hashString(js, StandardCharsets.UTF_8).asLong();
            return cachedTranspilations.get(hashCode, new Callable<String>() {
                @Override
                public String call() throws IOException, UnsupportedEncodingException {
                    // Neither the compiler nor the options is thread safe, so they can't be
                    // saved as instance state.
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    Compiler compiler = new Compiler(new PrintStream(baos));
                    SourceFile sourceFile = SourceFile.fromCode(path, js);
                    Result result = compiler.<SourceFile, SourceFile>compile(ImmutableList.<SourceFile>of(),
                            ImmutableList.<SourceFile>of(sourceFile), getOptions());
                    if (compiler.getErrorManager().getErrorCount() > 0) {
                        String message;
                        try {
                            message = baos.toString(StandardCharsets.UTF_8.name());
                        } catch (UnsupportedEncodingException e) {
                            throw new RuntimeException(e);
                        }
                        throw new IllegalStateException(message);
                    }
                    if (!result.transpiledFiles.contains(sourceFile)) {
                        return js;
                    }
                    StringBuilder source = new StringBuilder().append(compiler.toSource());
                    StringBuilder sourceMap = new StringBuilder();
                    compiler.getSourceMap().appendTo(sourceMap, path);
                    return source.append("\n//# sourceMappingURL=data:,")
                            .append(URLEncoder.encode(sourceMap.toString(), "UTF-8").replace("+", "%20"))
                            .append("\n").toString();
                }
            });
        } catch (ExecutionException | UncheckedExecutionException e) {
            // IllegalStateExceptions thrown from the callable above will end up here as
            // UncheckedExecutionExceptions, per the contract of Cache#get. Throw the underlying
            // IllegalStateException so that the compiler error message is at the top of the stack trace.
            if (e.getCause() instanceof IllegalStateException) {
                throw (IllegalStateException) e.getCause();
            } else {
                throw Throwables.propagate(e);
            }
        }
    }

    /** Generates the runtime by requesting the "es6_runtime" library from the compiler. */
    private static String getEs6Runtime() {
        CompilerOptions options = getOptions();
        options.setLanguageOut(LanguageMode.ECMASCRIPT3); // change .delete to ['delete']
        options.setForceLibraryInjection(ImmutableList.of("es6_runtime"));
        Compiler compiler = new Compiler();
        SourceFile sourceFile = SourceFile.fromCode("source", "");
        compiler.<SourceFile, SourceFile>compile(ImmutableList.<SourceFile>of(),
                ImmutableList.<SourceFile>of(sourceFile), options);
        return compiler.toSource();
    }
}