001/* 002 * Minify Maven Plugin 003 * https://github.com/samaxes/minify-maven-plugin 004 * 005 * Copyright (c) 2009 samaxes.com 006 * 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019package com.samaxes.maven.minify.plugin; 020 021import com.google.common.collect.Lists; 022import com.google.javascript.jscomp.*; 023import com.google.javascript.jscomp.Compiler; 024import com.samaxes.maven.minify.common.ClosureConfig; 025import com.samaxes.maven.minify.common.JavaScriptErrorReporter; 026import com.samaxes.maven.minify.common.YuiConfig; 027import com.samaxes.maven.minify.plugin.MinifyMojo.Engine; 028import com.yahoo.platform.yui.compressor.JavaScriptCompressor; 029import org.apache.maven.plugin.logging.Log; 030 031import java.io.*; 032import java.nio.charset.Charset; 033import java.util.ArrayList; 034import java.util.List; 035import java.util.Map; 036 037/** 038 * Task for merging and compressing JavaScript files. 039 */ 040public class ProcessJSFilesTask extends ProcessFilesTask { 041 042 private final ClosureConfig closureConfig; 043 044 /** 045 * Task constructor. 046 * 047 * @param log Maven plugin log 048 * @param verbose display additional info 049 * @param bufferSize size of the buffer used to read source files 050 * @param charset if a character set is specified, a byte-to-char variant allows the encoding to be selected. 051 * Otherwise, only byte-to-byte operations are used 052 * @param suffix final file name suffix 053 * @param nosuffix whether to use a suffix for the minified file name or not 054 * @param skipMerge whether to skip the merge step or not 055 * @param skipMinify whether to skip the minify step or not 056 * @param webappSourceDir web resources source directory 057 * @param webappTargetDir web resources target directory 058 * @param inputDir directory containing source files 059 * @param sourceFiles list of source files to include 060 * @param sourceIncludes list of source files to include 061 * @param sourceExcludes list of source files to exclude 062 * @param outputDir directory to write the final file 063 * @param outputFilename the output file name 064 * @param engine minify processor engine selected 065 * @param yuiConfig YUI Compressor configuration 066 * @param closureConfig Google Closure Compiler configuration 067 * @throws FileNotFoundException when the given source file does not exist 068 */ 069 public ProcessJSFilesTask(Log log, boolean verbose, Integer bufferSize, Charset charset, String suffix, 070 boolean nosuffix, boolean skipMerge, boolean skipMinify, String webappSourceDir, 071 String webappTargetDir, String inputDir, List<String> sourceFiles, 072 List<String> sourceIncludes, List<String> sourceExcludes, String outputDir, 073 String outputFilename, Engine engine, YuiConfig yuiConfig, ClosureConfig closureConfig) 074 throws FileNotFoundException { 075 super(log, verbose, bufferSize, charset, suffix, nosuffix, skipMerge, skipMinify, webappSourceDir, 076 webappTargetDir, inputDir, sourceFiles, sourceIncludes, sourceExcludes, outputDir, outputFilename, 077 engine, yuiConfig); 078 079 this.closureConfig = closureConfig; 080 } 081 082 /** 083 * Minifies a JavaScript file. Create missing parent directories if needed. 084 * 085 * @param mergedFile input file resulting from the merged step 086 * @param minifiedFile output file resulting from the minify step 087 * @throws IOException when the minify step fails 088 */ 089 @Override 090 protected void minify(File mergedFile, File minifiedFile) throws IOException { 091 if (!minifiedFile.getParentFile().exists() && !minifiedFile.getParentFile().mkdirs()) { 092 throw new RuntimeException("Unable to create target directory for: " + minifiedFile.getParentFile()); 093 } 094 095 try (InputStream in = new FileInputStream(mergedFile); 096 OutputStream out = new FileOutputStream(minifiedFile); 097 InputStreamReader reader = new InputStreamReader(in, charset); 098 OutputStreamWriter writer = new OutputStreamWriter(out, charset)) { 099 log.info("Creating the minified file [" + (verbose ? minifiedFile.getPath() : minifiedFile.getName()) + "]."); 100 101 switch (engine) { 102 case CLOSURE: 103 log.debug("Using Google Closure Compiler engine."); 104 105 CompilerOptions options = new CompilerOptions(); 106 closureConfig.getCompilationLevel().setOptionsForCompilationLevel(options); 107 options.setOutputCharset(charset); 108 options.setLanguageIn(closureConfig.getLanguageIn()); 109 options.setLanguageOut(closureConfig.getLanguageOut()); 110 options.setDependencyOptions(closureConfig.getDependencyOptions()); 111 options.setColorizeErrorOutput(closureConfig.getColorizeErrorOutput()); 112 options.setAngularPass(closureConfig.getAngularPass()); 113 options.setExtraAnnotationNames(closureConfig.getExtraAnnotations()); 114 options.setDefineReplacements(closureConfig.getDefineReplacements()); 115 // options.setRewritePolyfills(closureConfig.getLanguageIn().isEs6OrHigher()); 116 117 File sourceMapResult = new File(minifiedFile.getPath() + ".map"); 118 if (closureConfig.getSourceMapFormat() != null) { 119 options.setSourceMapFormat(closureConfig.getSourceMapFormat()); 120 options.setSourceMapOutputPath(sourceMapResult.getPath()); 121 // options.setSourceMapLocationMappings(Lists.newArrayList(new 122 // SourceMap.LocationMapping(sourceDir.getPath() + File.separator, ""))); 123 } 124 125 if (closureConfig.getWarningLevels() != null) { 126 for (Map.Entry<DiagnosticGroup, CheckLevel> warningLevel : closureConfig.getWarningLevels().entrySet()) { 127 options.setWarningLevel(warningLevel.getKey(), warningLevel.getValue()); 128 } 129 } 130 131 SourceFile input = SourceFile.fromInputStream(mergedFile.getName(), in, charset); 132 List<SourceFile> externs = new ArrayList<>(); 133 externs.addAll(CommandLineRunner.getBuiltinExterns(closureConfig.getEnvironment())); 134 externs.addAll(closureConfig.getExterns()); 135 136 Compiler compiler = new Compiler(); 137 compiler.compile(externs, Lists.newArrayList(input), options); 138 139 // Check for errors. 140 JSError[] errors = compiler.getErrors(); 141 if (errors.length > 0) { 142 StringBuilder msg = new StringBuilder("JSCompiler errors\n"); 143 MessageFormatter formatter = new LightweightMessageFormatter(compiler); 144 for (JSError e : errors) { 145 msg.append(formatter.formatError(e)); 146 } 147 throw new RuntimeException(msg.toString()); 148 } 149 150 writer.append(compiler.toSource()); 151 152 if (closureConfig.getSourceMapFormat() != null) { 153 log.info("Creating the minified file map [" 154 + (verbose ? sourceMapResult.getPath() : sourceMapResult.getName()) + "]."); 155 156 if (sourceMapResult.createNewFile()) { 157 flushSourceMap(sourceMapResult, minifiedFile.getName(), compiler.getSourceMap()); 158 159 writer.append(System.getProperty("line.separator")); 160 writer.append("//# sourceMappingURL=").append(sourceMapResult.getName()); 161 } 162 } 163 164 break; 165 case YUI: 166 log.debug("Using YUI Compressor engine."); 167 168 JavaScriptCompressor compressor = new JavaScriptCompressor(reader, new JavaScriptErrorReporter(log, 169 mergedFile.getName())); 170 compressor.compress(writer, yuiConfig.getLineBreak(), yuiConfig.isMunge(), verbose, 171 yuiConfig.isPreserveSemicolons(), yuiConfig.isDisableOptimizations()); 172 break; 173 default: 174 log.warn("JavaScript engine not supported."); 175 break; 176 } 177 } catch (IOException e) { 178 log.error( 179 "Failed to compress the JavaScript file [" 180 + (verbose ? mergedFile.getPath() : mergedFile.getName()) + "].", e); 181 throw e; 182 } 183 184 logCompressionGains(mergedFile, minifiedFile); 185 } 186 187 private void flushSourceMap(File sourceMapOutputFile, String minifyFileName, SourceMap sourceMap) { 188 try (FileWriter out = new FileWriter(sourceMapOutputFile)) { 189 sourceMap.appendTo(out, minifyFileName); 190 } catch (IOException e) { 191 log.error("Failed to write the JavaScript Source Map file [" 192 + (verbose ? sourceMapOutputFile.getPath() : sourceMapOutputFile.getName()) + "].", e); 193 } 194 } 195}