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

com.samaxes.maven.minify.plugin.MinifyMojo Maven / Gradle / Ivy

/*
 * Minify Maven Plugin
 * https://github.com/samaxes/minify-maven-plugin
 *
 * Copyright (c) 2009 samaxes.com
 *
 * 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.samaxes.maven.minify.plugin;

import java.io.File;
import java.io.FileNotFoundException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.maven.plugin.AbstractMojo;
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 com.google.common.base.Strings;
import com.google.javascript.jscomp.CompilationLevel;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import com.google.javascript.jscomp.DependencyOptions;
import com.google.javascript.jscomp.SourceFile;
import com.samaxes.maven.minify.common.ClosureConfig;
import com.samaxes.maven.minify.common.YuiConfig;

/**
 * Goal for combining and minifying CSS and JavaScript files.
 */
@Mojo(name = "minify", defaultPhase = LifecyclePhase.PROCESS_RESOURCES, threadSafe = true)
public class MinifyMojo extends AbstractMojo {

    /**
     * Engine used for minification
     */
    public static enum Engine {
        /** YUI Compressor */
        YUI,
        /** Google Closure Compiler */
        CLOSURE;
    }

    /* ************** */
    /* Global Options */
    /* ************** */

    /**
     * Show source file paths in log output.
     *
     * @since 1.5.2
     * @deprecated Use {@link #verbose} instead.
     */
    @Deprecated
    @Parameter(property = "debug")
    private Boolean debug;

    /**
     * Display additional informational messages and warnings.
     */
    @Parameter(property = "verbose", defaultValue = "false")
    private boolean verbose;

    /**
     * Size of the buffer used to read source files.
     */
    @Parameter(property = "bufferSize", defaultValue = "4096")
    private int bufferSize;

    /**
     * If a supported character set is specified, it will be used to read the input file. Otherwise, it will assume that
     * the platform's default character set is being used. The output file is encoded using the same character set.
* See the IANA Charset Registry for a list of valid * encoding types. * * @since 1.3.2 */ @Parameter(property = "charset", defaultValue = "${project.build.sourceEncoding}") private String charset; /** * The output file name suffix. * * @since 1.3.2 */ @Parameter(property = "suffix", defaultValue = "min") private String suffix; /** * Do not append a suffix to the minified output file name, independently of the value in the {@code suffix} * parameter.
* Warning: when both the options {@code nosuffix} and {@code skipMerge} are set to {@code true}, * the plugin execution phase needs to be set to {@code package}, otherwise the output files will be overridden by * the source files during the packaging. * * @since 1.7 */ @Parameter(property = "nosuffix", defaultValue = "false") private boolean nosuffix; /** * Skip the merge step. Minification will be applied to each source file individually. * * @since 1.5.2 */ @Parameter(property = "skipMerge", defaultValue = "false") private boolean skipMerge; /** * Skip the minify step. Useful when merging files that are already minified. * * @since 1.5.2 */ @Parameter(property = "skipMinify", defaultValue = "false") private boolean skipMinify; /** * Webapp source directory. */ @Parameter(property = "webappSourceDir", defaultValue = "${basedir}/src/main/webapp") private String webappSourceDir; /** * Webapp target directory. */ @Parameter(property = "webappTargetDir", defaultValue = "${project.build.directory}/${project.build.finalName}") private String webappTargetDir; /* *********** */ /* CSS Options */ /* *********** */ /** * CSS source directory. */ @Parameter(property = "cssSourceDir", defaultValue = "css") private String cssSourceDir; /** * CSS source file names list. */ @Parameter(property = "cssSourceFiles", alias = "cssFiles") private ArrayList cssSourceFiles; /** * CSS files to include. Specified as fileset patterns which are relative to the CSS source directory. * * @since 1.2 */ @Parameter(property = "cssSourceIncludes", alias = "cssIncludes") private ArrayList cssSourceIncludes; /** * CSS files to exclude. Specified as fileset patterns which are relative to the CSS source directory. * * @since 1.2 */ @Parameter(property = "cssSourceExcludes", alias = "cssExcludes") private ArrayList cssSourceExcludes; /** * CSS target directory. Takes the same value as {@code cssSourceDir} when empty. * * @since 1.3.2 */ @Parameter(property = "cssTargetDir") private String cssTargetDir; /** * CSS output file name. */ @Parameter(property = "cssFinalFile", defaultValue = "style.css") private String cssFinalFile; /** * Define the CSS compressor engine to use.
* Possible values are: * * * @since 1.7.1 */ @Parameter(property = "cssEngine", defaultValue = "YUI") private Engine cssEngine; /* ****************** */ /* JavaScript Options */ /* ****************** */ /** * JavaScript source directory. */ @Parameter(property = "jsSourceDir", defaultValue = "js") private String jsSourceDir; /** * JavaScript source file names list. */ @Parameter(property = "jsSourceFiles", alias = "jsFiles") private ArrayList jsSourceFiles; /** * JavaScript files to include. Specified as fileset patterns which are relative to the JavaScript source directory. * * @since 1.2 */ @Parameter(property = "jsSourceIncludes", alias = "jsIncludes") private ArrayList jsSourceIncludes; /** * JavaScript files to exclude. Specified as fileset patterns which are relative to the JavaScript source directory. * * @since 1.2 */ @Parameter(property = "jsSourceExcludes", alias = "jsExcludes") private ArrayList jsSourceExcludes; /** * JavaScript target directory. Takes the same value as {@code jsSourceDir} when empty. * * @since 1.3.2 */ @Parameter(property = "jsTargetDir") private String jsTargetDir; /** * JavaScript output file name. */ @Parameter(property = "jsFinalFile", defaultValue = "script.js") private String jsFinalFile; /** * Define the JavaScript compressor engine to use.
* Possible values are: * * * @since 1.6 */ @Parameter(property = "jsEngine", defaultValue = "YUI") private Engine jsEngine; /* *************************** */ /* YUI Compressor Only Options */ /* *************************** */ /** * Some source control tools don't like files containing lines longer than, say 8000 characters. The line-break * option is used in that case to split long lines after a specific column. It can also be used to make the code * more readable and easier to debug. Specify {@code 0} to get a line break after each semi-colon in JavaScript, and * after each rule in CSS. Specify {@code -1} to disallow line breaks. * * @deprecated Use {@link #yuiLineBreak} instead. */ @Deprecated @Parameter(property = "linebreak") private Integer linebreak; /** * Some source control tools don't like files containing lines longer than, say 8000 characters. The line-break * option is used in that case to split long lines after a specific column. It can also be used to make the code * more readable and easier to debug. Specify {@code 0} to get a line break after each semi-colon in JavaScript, and * after each rule in CSS. Specify {@code -1} to disallow line breaks. */ @Parameter(property = "yuiLineBreak", defaultValue = "-1") private int yuiLineBreak; /** * Obfuscate local symbols in addition to minification. * * @deprecated Use {@link #yuiNoMunge} instead. */ @Deprecated @Parameter(property = "munge") private Boolean munge; /** * Minify only. Do not obfuscate local symbols. */ @Parameter(property = "yuiNoMunge", defaultValue = "false") private boolean yuiNoMunge; /** * Preserve unnecessary semicolons (such as right before a '}'). This option is useful when compressed code has to * be run through JSLint. * * @deprecated Use {@link #yuiPreserveSemicolons} instead. */ @Deprecated @Parameter(property = "preserveAllSemiColons") private Boolean preserveAllSemiColons; /** * Preserve unnecessary semicolons (such as right before a '}'). This option is useful when compressed code has to * be run through JSLint. */ @Parameter(property = "yuiPreserveSemicolons", defaultValue = "false") private boolean yuiPreserveSemicolons; /** * Disable all the built-in micro-optimizations. * * @deprecated Use {@link #yuiDisableOptimizations} instead. */ @Deprecated @Parameter(property = "disableOptimizations") private Boolean disableOptimizations; /** * Disable all the built-in micro-optimizations. */ @Parameter(property = "yuiDisableOptimizations", defaultValue = "false") private boolean yuiDisableOptimizations; /* ************************************ */ /* Google Closure Compiler Only Options */ /* ************************************ */ /** * Refers to which version of ECMAScript to assume when checking for errors in your code.
* Possible values are: *
    *
  • {@code ECMASCRIPT3}: Checks code assuming ECMAScript 3 compliance, and gives errors for code using features * only present in ECMAScript 5.
  • *
  • {@code ECMASCRIPT5}: Checks code assuming ECMAScript 5 compliance, allowing new features not present in * ECMAScript 3.
  • *
  • {@code ECMASCRIPT5_STRICT}: Like {@code ECMASCRIPT5} but assumes compliance with strict mode ('use strict';). *
  • *
* * @since 1.7.2 */ @Parameter(property = "closureLanguageIn", defaultValue = "ECMASCRIPT3") private LanguageMode closureLanguage; /** * The degree of compression and optimization to apply to your JavaScript.
* There are three possible compilation levels: *
    *
  • {@code WHITESPACE_ONLY}: Just removes whitespace and comments from your JavaScript.
  • *
  • {@code SIMPLE_OPTIMIZATIONS}: Performs compression and optimization that does not interfere with the * interaction between the compiled JavaScript and other JavaScript. This level renames only local variables.
  • *
  • {@code ADVANCED_OPTIMIZATIONS}: Achieves the highest level of compression by renaming symbols in your * JavaScript. When using {@code ADVANCED_OPTIMIZATIONS} compilation you must perform extra steps to preserve * references to external symbols. See Advanced Compilation and * Externs for more information about {@code ADVANCED_OPTIMIZATIONS}.
  • *
* * @since 1.7.2 */ @Parameter(property = "closureCompilationLevel", defaultValue = "SIMPLE_OPTIMIZATIONS") private CompilationLevel closureCompilationLevel; /** * List of JavaScript files containing code that declares function names or other symbols. Use * {@code closureExterns} to preserve symbols that are defined outside of the code you are compiling. The * {@code closureExterns} parameter only has an effect if you are using a {@code CompilationLevel} of * {@code ADVANCED_OPTIMIZATIONS}.
* These file names are relative to {@link #webappSourceDir} directory. * * @since 1.7.2 */ @Parameter(property = "closureExterns") private ArrayList closureExterns; /** *

* Use default externs provided with Closure Compiler. *

*

* For the complete list of externs please visit:
* https://github.com/google/closure- * compiler/tree/master/externs *

* * @since 1.7.4 */ @Parameter(property = "closureUseDefaultExterns", defaultValue = "false") private boolean closureUseDefaultExterns; /** *

* Collects information mapping the generated (compiled) source back to its original source for debugging purposes. *

*

* Please visit Source Map Revision 3 * Proposal for more information. *

* * @since 1.7.3 */ @Parameter(property = "closureCreateSourceMap", defaultValue = "false") private boolean closureCreateSourceMap; /** *

* Enables or disables sorting mode for Closure Library dependencies. *

*

* If true, automatically sort dependencies so that a file that {@code goog.provides} symbol X will always come * before a file that {@code goog.requires} symbol X. *

* * @since 1.7.4 */ @Parameter(property = "closureSortDependencies", defaultValue = "false") private boolean closureSortDependencies; /** * Generate {@code $inject} properties for AngularJS for functions annotated with {@code @ngInject}. * * @since 1.7.3 */ @Parameter(property = "closureAngularPass", defaultValue = "false") private boolean closureAngularPass; /** * Executed when the goal is invoked, it will first invoke a parallel lifecycle, ending at the given phase. */ @Override public void execute() throws MojoExecutionException, MojoFailureException { checkDeprecatedOptions(); if (skipMerge && skipMinify) { getLog().warn("Both merge and minify steps are configured to be skipped."); return; } fillOptionalValues(); YuiConfig yuiConfig = fillYuiConfig(); ClosureConfig closureConfig = fillClosureConfig(); Collection processFilesTasks = new ArrayList(); try { processFilesTasks.add(new ProcessCSSFilesTask(getLog(), debug, bufferSize, charset, suffix, nosuffix, skipMerge, skipMinify, webappSourceDir, webappTargetDir, cssSourceDir, cssSourceFiles, cssSourceIncludes, cssSourceExcludes, cssTargetDir, cssFinalFile, cssEngine, yuiConfig)); processFilesTasks.add(new ProcessJSFilesTask(getLog(), debug, bufferSize, charset, suffix, nosuffix, skipMerge, skipMinify, webappSourceDir, webappTargetDir, jsSourceDir, jsSourceFiles, jsSourceIncludes, jsSourceExcludes, jsTargetDir, jsFinalFile, jsEngine, yuiConfig, closureConfig)); } catch (FileNotFoundException e) { throw new MojoFailureException(e.getMessage(), e); } ExecutorService executor = Executors.newFixedThreadPool(2); try { List> futures = executor.invokeAll(processFilesTasks); for (Future future : futures) { try { future.get(); } catch (ExecutionException e) { throw new MojoExecutionException(e.getMessage(), e); } } executor.shutdown(); } catch (InterruptedException e) { executor.shutdownNow(); throw new MojoExecutionException(e.getMessage(), e); } } private void checkDeprecatedOptions() { if (debug == null) { debug = verbose; } else { getLog().warn( "The option 'debug' is deprecated and will be removed on the next version. Use 'verbose' instead."); } if (linebreak == null) { linebreak = yuiLineBreak; } else { getLog().warn( "The option 'linebreak' is deprecated and will be removed on the next version. Use 'yuiLineBreak' instead."); } if (munge == null) { munge = !yuiNoMunge; } else { getLog().warn( "The option 'munge' is deprecated and will be removed on the next version. Use 'yuiNoMunge' instead."); } if (preserveAllSemiColons == null) { preserveAllSemiColons = yuiPreserveSemicolons; } else { getLog().warn( "The option 'preserveAllSemiColons' is deprecated and will be removed on the next version. Use 'yuiPreserveSemicolons' instead."); } if (disableOptimizations == null) { disableOptimizations = yuiDisableOptimizations; } else { getLog().warn( "The option 'disableOptimizations' is deprecated and will be removed on the next version. Use 'yuiDisableOptimizations' instead."); } } private void fillOptionalValues() { if (Strings.isNullOrEmpty(cssTargetDir)) { cssTargetDir = cssSourceDir; } if (Strings.isNullOrEmpty(jsTargetDir)) { jsTargetDir = jsSourceDir; } if (Strings.isNullOrEmpty(charset)) { charset = Charset.defaultCharset().name(); } } private YuiConfig fillYuiConfig() { return new YuiConfig(linebreak, munge, preserveAllSemiColons, disableOptimizations); } private ClosureConfig fillClosureConfig() { DependencyOptions dependencyOptions = new DependencyOptions(); dependencyOptions.setDependencySorting(closureSortDependencies); List externs = new ArrayList<>(); for (String extern : closureExterns) { externs.add(SourceFile.fromFile(webappSourceDir + File.separator + extern, Charset.forName(charset))); } return new ClosureConfig(closureLanguage, closureCompilationLevel, dependencyOptions, externs, closureUseDefaultExterns, closureCreateSourceMap, closureAngularPass); } }