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

org.wisdom.maven.mojos.JavaScriptCompilerMojo Maven / Gradle / Ivy

There is a newer version: 0.10.0
Show newest version
/*
 * #%L
 * Wisdom-Framework
 * %%
 * Copyright (C) 2013 - 2014 Wisdom Framework
 * %%
 * 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.
 * #L%
 */
package org.wisdom.maven.mojos;

import com.google.javascript.jscomp.*;
import org.apache.commons.io.FileUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.wisdom.maven.Constants;
import org.wisdom.maven.WatchingException;
import org.wisdom.maven.node.LoggedOutputStream;
import org.wisdom.maven.utils.WatcherUtils;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Compiles and minifies JavaScript files.
 * 
    *
      It compiles (checks) JavaScript files from src/main/resources/assets and /src/main/assets.
    *
      It minifies these JavaScript files
    *
      It minifies the JavaScript file generated from CoffeeScript
    *
*

* This mojo makes the assumption that the files are already copied/generated to their destination directory, * when it is executed. */ @Mojo(name = "compile-javascript", threadSafe = false, requiresDependencyResolution = ResolutionScope.COMPILE, requiresProject = true, defaultPhase = LifecyclePhase.PREPARE_PACKAGE) public class JavaScriptCompilerMojo extends AbstractWisdomWatcherMojo implements Constants { /** * Selects the compilation level for Google Closure among SIMPLE_OPTIMIZATIONS, * WHITESPACE_ONLY and ADVANCED_OPTIMIZATIONS. * Be aware that ADVANCED_OPTIMIZATIONS modifies the API of your code. */ @Parameter(defaultValue = "WHITESPACE_ONLY") public CompilationLevel googleClosureCompilationLevel; /** * Whether or not the output JavaScript should be pretty. */ @Parameter(defaultValue = "false") public boolean googleClosurePrettyPrint; /** * Whether or not the Google Closure processing is skipped. */ @Parameter(defaultValue = "${skipGoogleClosure}") public boolean skipGoogleClosure; /** * Minified file extension parameter, lets the user define their own extensions to use with * minification. Must not contain the {@literal .js} extension. */ @Parameter(defaultValue = "-min") public String googleClosureMinifierSuffix; /** * Aggregated file delimiter. The string used as header of each aggregated files. * Works only with pretty print. {@literal %name%} and {@literal %num%} are substitute by the file full name and it's * input number respectively. */ @Parameter(defaultValue = "// -- Input %num% -- //") public String googleClosureInputDelimiter; /** * The JavaScript configuration. */ @Parameter protected JavaScript javascript; private File destinationForInternals; private File destinationForExternals; /** * The Error message prefix. */ public static final String COMPILE_TITLE = "Compiling JavaScript files from "; @Override public void execute() throws MojoExecutionException, MojoFailureException { if (skipGoogleClosure) { getLog().debug("Skipping Google Closure Compilation"); removeFromWatching(); return; } this.destinationForInternals = new File(buildDirectory, "classes/assets"); this.destinationForExternals = new File(getWisdomRootDirectory(), ASSETS_DIR); // Check whether or not we have a custom configuration if (javascript == null) { getLog().info("No 'javascript' processing configuration, minifying all '.js' files individually"); try { if (destinationForInternals.isDirectory()) { getLog().info(COMPILE_TITLE + destinationForInternals.getAbsolutePath()); compile(destinationForInternals); } if (destinationForExternals.isDirectory()) { getLog().info(COMPILE_TITLE + destinationForExternals.getAbsolutePath()); compile(destinationForExternals); } } catch (WatchingException e) { throw new MojoExecutionException(e.getMessage(), e); } } else { try { compile(javascript); } catch (WatchingException e) { throw new MojoExecutionException(e.getMessage(), e); } } } private void compile(JavaScript javaScript) throws WatchingException { if (javaScript.getAggregations() == null || javaScript.getAggregations().isEmpty()) { getLog().warn("No 'aggregation' configured in the 'javascript' processing configuration - skip " + "processing"); return; } if (javaScript.getExtern() != null && !javaScript.getExtern().isFile()) { throw new WatchingException("The 'extern' file " + javaScript.getExtern().getAbsolutePath() + " does not " + "exist"); } for (Aggregation aggregation : javaScript.getAggregations()) { compile(aggregation); } // Cleanup if needed for (Aggregation aggregation : javaScript.getAggregations()) { if (aggregation.isRemoveIncludedFiles()) { for (File file : getFiles(aggregation)) { File output = getOutputFile(aggregation); // We must not remove output file. if (! output.getAbsolutePath().equals(file.getAbsolutePath())) { FileUtils.deleteQuietly(file); } } } } } private void compile(Aggregation aggregation) throws WatchingException { File output = getOutputFile(aggregation); if (!output.getParentFile().isDirectory()) { getLog().debug("Create directory " + output.getParentFile().getAbsolutePath() + " : " + output.getParentFile().mkdirs()); } getLog().info("Compressing JavaScript files from aggregation " + aggregation.getSelectedFiles(getInternalAssetOutputDirectory()) + " using Google Closure"); PrintStream out = getPrintStreamToDumpLog(); com.google.javascript.jscomp.Compiler compiler = new com.google.javascript.jscomp.Compiler(out); CompilerOptions options = newCompilerOptions(); if (!aggregation.isMinification()) { //Override the pretty print options if minification false getLog().info("Minification if false, Compilation Level is set to " + CompilationLevel.WHITESPACE_ONLY); CompilationLevel.WHITESPACE_ONLY.setOptionsForCompilationLevel(options); options.setPrettyPrint(true); options.setPrintInputDelimiter(true); options.setInputDelimiter(googleClosureInputDelimiter); } else { getLog().info("Compilation Level set to " + googleClosureCompilationLevel); googleClosureCompilationLevel.setOptionsForCompilationLevel(options); options.setPrettyPrint(googleClosurePrettyPrint); options.setPrintInputDelimiter(googleClosurePrettyPrint); } List inputs = new ArrayList<>(); final Collection fileToAggregate = getFiles(aggregation); for (File file : fileToAggregate) { inputs.add(SourceFile.fromFile(file)); } List externs = new ArrayList<>(); if (javascript.getExtern() != null) { externs.add(new SourceFile(javascript.getExtern().getAbsolutePath())); } compiler.initOptions(options); final Result result = compiler.compile(externs, inputs, options); listErrors(result); if (!result.success) { throw new WatchingException("Error while compile JavaScript files, check log for more details"); } FileUtils.deleteQuietly(output); String[] outputs = compiler.toSourceArray(); for (String source : outputs) { try { FileUtils.write(output, source, true); } catch (IOException e) { throw new WatchingException("Cannot write minified JavaScript file '" + output.getAbsolutePath() + "'", e); } } } private File getOutputFile(Aggregation aggregation) { File output; if (aggregation.getOutput() == null) { output = getDefaultOutputFile(aggregation); } else { output = new File(aggregation.getOutput()); output = fixPath(output); } return output; } private Collection getFiles(Aggregation aggregation) throws WatchingException { List list = new ArrayList<>(); if (aggregation.getFiles() != null && ! aggregation.getFiles().isEmpty()) { for (String file : aggregation.getFiles()) { File theFile = new File(file); if (theFile.exists()) { list.add(theFile); } else { File f = new File(getInternalAssetOutputDirectory(), file); if (!f.exists() && !f.getName().endsWith("js")) { // Append the extension f = new File(getInternalAssetOutputDirectory(), file + ".js"); } if (!f.exists()) { throw new WatchingException("Cannot compute aggregated JavaScript - the '" + f.getAbsolutePath() + "' file does not exist"); } list.add(f); } } return list; } // Else we use a file set. return aggregation.getSelectedFiles(getInternalAssetOutputDirectory()); } private File fixPath(File output) { if (output.isAbsolute()) { return output; } else { return new File(getInternalAssetOutputDirectory(), output.getPath()); } } protected File getDefaultOutputFile(Aggregation aggregation) { String classifier = googleClosureMinifierSuffix; if (aggregation.isMinification()) { if (javascript.getMinifierSuffix() != null) { classifier = javascript.getMinifierSuffix(); } } else { classifier = ""; } return new File(getInternalAssetOutputDirectory(), project.getArtifactId() + classifier + ".js"); } @Override public boolean accept(File file) { return (WatcherUtils.isInDirectory(file, WatcherUtils.getExternalAssetsSource(basedir)) || (WatcherUtils.isInDirectory(file, WatcherUtils.getInternalAssetsSource(basedir))) ) && WatcherUtils.hasExtension(file, "js", "coffee") && isNotMinified(file) && isNotInLibs(file); } /** * Checks whether or not the given file is not in the 'libs' directory. * * @param file the file to check * @return {@code true} if the file is **not** in assets/libs, {@code false} otherwise. */ public static boolean isNotInLibs(File file) { return !file.getAbsolutePath().contains("assets/libs/") && // On windows: !file.getAbsolutePath().contains("assets\\libs\\"); } /** * Checks whether or not the file is minified. * * @param file the file to check * @return {@code true} if the file is minified, {@code false} otherwise. This method only check for the file * extension. */ public boolean isNotMinified(File file) { return !file.getName().endsWith("min.js") && !file.getName().endsWith(googleClosureMinifierSuffix + ".js"); } /** * Computes the file object for the minified version of the given file. The given file must be a '.js' file. * * @param file the file * @return the associated minified file */ public File getMinifiedFile(File file) { File output = getOutputFile(file); return new File(output.getParentFile().getAbsoluteFile(), output.getName().replace(".js", googleClosureMinifierSuffix + ".js")); } @Override public boolean fileCreated(File file) throws WatchingException { if (javascript != null && WatcherUtils.isInDirectory(file, WatcherUtils.getResources(basedir))) { compile(javascript); } else if (WatcherUtils.isInDirectory(file, WatcherUtils.getExternalAssetsSource(basedir))) { compile(destinationForExternals); } else if (WatcherUtils.isInDirectory(file, WatcherUtils.getResources(basedir))) { compile(destinationForInternals); } return true; } @Override public boolean fileUpdated(File file) throws WatchingException { return fileCreated(file); } @Override public boolean fileDeleted(File file) { if (isNotMinified(file)) { File minified = getMinifiedFile(file); FileUtils.deleteQuietly(minified); } return true; } private void compile(File base) throws WatchingException { getLog().info("Compressing JavaScript files from " + base.getName() + " using Google Closure"); PrintStream out = getPrintStreamToDumpLog(); com.google.javascript.jscomp.Compiler compiler = new com.google.javascript.jscomp.Compiler(out); CompilerOptions options = newCompilerOptions(); getLog().info("Compilation Level set to " + googleClosureCompilationLevel); googleClosureCompilationLevel.setOptionsForCompilationLevel(options); options.setPrettyPrint(googleClosurePrettyPrint); options.setPrintInputDelimiter(googleClosurePrettyPrint); Collection files = FileUtils.listFiles(base, new String[]{"js"}, true); List store = new ArrayList<>(); List inputs = new ArrayList<>(); List externs = new ArrayList<>(); for (File file : files) { if (file.isFile() && isNotMinified(file) && isNotInLibs(file)) { store.add(file); inputs.add(SourceFile.fromFile(file)); } } compiler.initOptions(options); final Result result = compiler.compile(externs, inputs, options); listErrors(result); if (!result.success) { throw new WatchingException("Error while compile JavaScript files, check log for more details"); } String[] outputs = compiler.toSourceArray(); for (int i = 0; i < store.size(); i++) { try { FileUtils.write(getMinifiedFile(store.get(i)), outputs[i]); } catch (IOException e) { throw new WatchingException("Cannot write minified JavaScript file : " + getMinifiedFile(store.get(i)), e); } } } private PrintStream getPrintStreamToDumpLog() { try { return new PrintStream(new LoggedOutputStream(getLog(), true), true, "UTF-8"); } catch (UnsupportedEncodingException e) { // This should not happen as the UTF-8 encoding is mandatory for the JVM. throw new IllegalArgumentException("UTF-8 not supported"); } } /** * @return default {@link CompilerOptions} object to be used by compressor. */ protected CompilerOptions newCompilerOptions() { final CompilerOptions options = new CompilerOptions(); /** * According to John Lenz from the Closure Compiler project, if you are using the Compiler API directly, you * should specify a CodingConvention. {@link http://code.google.com/p/wro4j/issues/detail?id=155} */ options.setCodingConvention(new ClosureCodingConvention()); //set it to warning, otherwise compiler will fail options.setWarningLevel(DiagnosticGroups.CHECK_VARIABLES, CheckLevel.WARNING); return options; } /** * List the errors that google is providing from the compiler output. * * @param result the results from the compiler */ private void listErrors(final Result result) { for (JSError warning : result.warnings) { getLog().warn(warning.toString()); } for (JSError error : result.errors) { getLog().error(error.toString()); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy