/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.jooby.assets; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; import java.util.logging.Level; import java.util.stream.Collectors; import org.jooby.MediaType; import org.slf4j.bridge.SLF4JBridgeHandler; import com.google.common.collect.ImmutableList; import com.google.javascript.jscomp.CheckLevel; import com.google.javascript.jscomp.ClosureCodingConvention; import com.google.javascript.jscomp.CommandLineRunner; import com.google.javascript.jscomp.CompilationLevel; import com.google.javascript.jscomp.Compiler; import com.google.javascript.jscomp.CompilerOptions; import com.google.javascript.jscomp.DiagnosticGroups; import com.google.javascript.jscomp.Result; import com.google.javascript.jscomp.SourceFile; import com.typesafe.config.Config; /** * <h1>closure-compiler</h1> * <p> * <a href="https://developers.google.com/closure/compiler">Closure Compiler</a> is a tool * for making JavaScript download and run faster. * </p> * * <h2>usage</h2> * * <pre> * assets { * fileset { * home: ... * } * * pipeline { * ... * dist: [closure-compiler] * } * } * </pre> * * <h2>options</h2> * <pre> * assets { * ... * closure-compiler { * level: advanced * } * } * </pre> * * @author edgar * @since 0.11.0 */ public class ClosureCompiler extends AssetProcessor { static { if (!SLF4JBridgeHandler.isInstalled()) { SLF4JBridgeHandler.removeHandlersForRootLogger(); SLF4JBridgeHandler.install(); } } { set("level", "simple"); } @Override public boolean matches(final MediaType type) { return MediaType.js.matches(type); } @Override public String process(final String filename, final String source, final Config conf) throws Exception { final CompilerOptions copts = new CompilerOptions(); copts.setCodingConvention(new ClosureCodingConvention()); copts.setOutputCharset("UTF-8"); copts.setWarningLevel(DiagnosticGroups.CHECK_VARIABLES, CheckLevel.WARNING); CompilationLevel level = level(get("level")); level.setOptionsForCompilationLevel(copts); Compiler.setLoggingLevel(Level.SEVERE); Compiler compiler = new Compiler(); compiler.disableThreads(); compiler.initOptions(copts); List<SourceFile> externs = externs(copts); Result result = compiler.compile(externs, ImmutableList.of(SourceFile.fromCode(filename, source)), copts); if (result.success) { return compiler.toSource(); } List<AssetProblem> errors = Arrays.stream(result.errors) .map(error -> new AssetProblem(error.sourceName, error.lineNumber, error.getCharno(), error.description, null)) .collect(Collectors.toList()); throw new AssetException(name(), errors); } private CompilationLevel level(final String level) { switch (level.toLowerCase()) { case "simple": return CompilationLevel.SIMPLE_OPTIMIZATIONS; case "advanced": return CompilationLevel.ADVANCED_OPTIMIZATIONS; case "ws": return CompilationLevel.WHITESPACE_ONLY; } throw new IllegalArgumentException("Unknown compilation level: " + level); } private List<SourceFile> externs(final CompilerOptions coptions) throws IOException { List<SourceFile> externs = CommandLineRunner.getBuiltinExterns(coptions); List<String> local = get("externs"); if (local != null) { Class<?> loader = getClass(); for (String js : local) { String path = js.startsWith("/") ? js : "/" + js; try (InputStream stream = loader.getResourceAsStream(path)) { if (stream == null) { throw new FileNotFoundException(path); } externs.add(SourceFile.fromInputStream(path, stream, StandardCharsets.UTF_8)); } } } return externs; } }