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

org.primefaces.extensions.optimizerplugin.optimizer.ClosureCompilerOptimizer Maven / Gradle / Ivy

There is a newer version: 2.6.5
Show newest version
/*
 * Copyright 2011-2015 PrimeFaces Extensions
 *
 * 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.
 *
 * $Id$
 */

package org.primefaces.extensions.optimizerplugin.optimizer;

import com.google.common.collect.ImmutableList;
import com.google.common.io.Files;
import com.google.javascript.jscomp.CompilationLevel;
import com.google.javascript.jscomp.Compiler;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.JSError;
import com.google.javascript.jscomp.Result;
import com.google.javascript.jscomp.SourceFile;
import com.google.javascript.jscomp.SourceMap;
import com.google.javascript.jscomp.WarningLevel;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
import org.primefaces.extensions.optimizerplugin.util.ResourcesSetAdapter;
import org.primefaces.extensions.optimizerplugin.util.ResourcesSetJsAdapter;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;

/**
 * Class for Google Closure Compiler doing JavaScript optimization.
 *
 * @author Oleg Varaksin ([email protected])
 */
public class ClosureCompilerOptimizer extends AbstractOptimizer {

    // Note: empty externs, probably CommandLineRunner.getDefaultExterns() would be better
    private static final List EXTERNS_EMPTY = new ArrayList();

    private static final String OPTIMIZED_FILE_EXTENSION = ".optjs";

    private static final String SOURCE_MAP_FILE_EXTENSION = ".map";

    @Override
    public void optimize(final ResourcesSetAdapter rsAdapter, final Log log) throws MojoExecutionException {
        ResourcesSetJsAdapter rsa = (ResourcesSetJsAdapter) rsAdapter;
        CompilationLevel compLevel = rsa.getCompilationLevel();
        CompilerOptions options = new CompilerOptions();
        compLevel.setOptionsForCompilationLevel(options);

        WarningLevel warnLevel = rsa.getWarningLevel();
        warnLevel.setOptionsForWarningLevel(options);
        Compiler.setLoggingLevel(Level.WARNING);

        try {
            Charset cset = Charset.forName(rsa.getEncoding());

            if (rsa.getAggregation() == null) {
                // no aggregation
                for (File file : rsa.getFiles()) {
                    log.info("Optimize JS file " + file.getName() + " ...");

                    // statistic
                    addToOriginalSize(file);

                    // path of the original file
                    String path = file.getCanonicalPath();

                    String outputFilePath = null;
                    String outputSourceMapDir = null;
                    File sourceMapFile = null;
                    File sourceFile;

                    if (rsa.getSourceMap() != null) {
                        // setup source map
                        outputFilePath = file.getCanonicalPath();
                        outputSourceMapDir = rsa.getSourceMap().getOutputDir();
                        sourceMapFile = setupSourceMapFile(options, rsa.getSourceMap(), outputFilePath);
                        // create an empty file with ...source.js from the original one
                        sourceFile = getFileWithSuffix(path, OUTPUT_FILE_SUFFIX);

                        if (StringUtils.isNotBlank(rsa.getSuffix())) {
                            // rename original file as ...source.js
                            FileUtils.rename(file, sourceFile);
                        } else {
                            // copy content of the original file to the ...source.js
                            FileUtils.copyFile(file, sourceFile);
                        }
                    } else {
                        sourceFile = file;
                    }

                    // compile
                    List interns = new ArrayList();
                    interns.add(SourceFile.fromFile(sourceFile, cset));
                    Compiler compiler = compile(log, interns, options, rsa.isFailOnWarning());

                    if (StringUtils.isNotBlank(rsa.getSuffix())) {
                        // write compiled content into the new file
                        File outputFile = getFileWithSuffix(path, rsa.getSuffix());
                        Files.write(compiler.toSource(), outputFile, cset);

                        if (sourceMapFile != null) {
                            // write sourceMappingURL into the minified file
                            writeSourceMappingURL(outputFile, sourceMapFile, rsa.getSourceMap().getSourceMapRoot(), cset, log);
                        }

                        // statistic
                        addToOptimizedSize(outputFile);
                    } else {
                        // path of temp. file
                        String pathOptimized = FileUtils.removeExtension(path) + OPTIMIZED_FILE_EXTENSION;

                        // create a new temp. file
                        File outputFile = new File(pathOptimized);
                        Files.touch(outputFile);

                        // write compiled content into the new file and rename it (overwrite the original file)
                        Files.write(compiler.toSource(), outputFile, cset);
                        FileUtils.rename(outputFile, file);

                        if (sourceMapFile != null) {
                            // write sourceMappingURL into the minified file
                            writeSourceMappingURL(file, sourceMapFile, rsa.getSourceMap().getSourceMapRoot(), cset, log);
                        }

                        // statistic
                        addToOptimizedSize(file);
                    }

                    if (outputFilePath != null && sourceMapFile != null) {
                        // write the source map
                        Files.touch(sourceMapFile);
                        writeSourceMap(sourceMapFile, outputFilePath, compiler.getSourceMap(), outputSourceMapDir, log);

                        // move the source file to the source map dir
                        moveToSourceMapDir(sourceFile, outputSourceMapDir, log);
                    }
                }
            } else if (rsa.getAggregation().getOutputFile() != null) {
                // aggregation to one output file
                File outputFile = rsa.getAggregation().getOutputFile();
                File aggrOutputFile = aggregateFiles(rsa, cset, true);

                // statistic
                long sizeBefore = addToOriginalSize(aggrOutputFile);

                if (!rsa.getAggregation().isWithoutCompress()) {
                    // compressing
                    for (File file : rsa.getFiles()) {
                        log.info("Optimize JS file " + file.getName() + " ...");
                    }

                    String outputFilePath = null;
                    File sourceMapFile = null;
                    if (rsa.getSourceMap() != null) {
                        // setup source map
                        outputFilePath = outputFile.getCanonicalPath();
                        sourceMapFile = setupSourceMapFile(options, rsa.getSourceMap(), outputFilePath);
                    }

                    // compile
                    List interns = new ArrayList();
                    interns.add(SourceFile.fromFile(aggrOutputFile, cset));
                    Compiler compiler = compile(log, interns, options, rsa.isFailOnWarning());

                    // delete single files if necessary
                    deleteFilesIfNecessary(rsa, log);

                    // write the compiled content into a new file
                    Files.touch(outputFile);
                    Files.write(compiler.toSource(), outputFile, cset);

                    if (outputFilePath != null && sourceMapFile != null) {
                        // write sourceMappingURL into the minified file
                        writeSourceMappingURL(outputFile, sourceMapFile, rsa.getSourceMap().getSourceMapRoot(), cset, log);

                        // write the source map
                        String outputSourceMapDir = rsa.getSourceMap().getOutputDir();
                        Files.touch(sourceMapFile);
                        writeSourceMap(sourceMapFile, outputFilePath, compiler.getSourceMap(), outputSourceMapDir, log);

                        // move the source file
                        moveToSourceMapDir(aggrOutputFile, outputSourceMapDir, log);
                    } else {
                        // delete the temp. aggregated file ...source.js
                        if (aggrOutputFile.exists() && !aggrOutputFile.delete()) {
                            log.warn("Temporary file " + aggrOutputFile.getName() + " could not be deleted.");
                        }
                    }

                    // statistic
                    addToOptimizedSize(sizeBefore - outputFile.length());
                } else {
                    // delete single files if necessary
                    deleteFilesIfNecessary(rsa, log);

                    // rename aggregated file if necessary
                    renameOutputFileIfNecessary(rsa, aggrOutputFile);

                    // statistic
                    addToOptimizedSize(sizeBefore);
                }
            } else {
                // should not happen
                log.error("Wrong plugin's internal state.");
            }
        } catch (Exception e) {
            throw new MojoExecutionException("Resources optimization failure: " + e.getLocalizedMessage(), e);
        }
    }

    protected Compiler compile(Log log, List interns, CompilerOptions options, boolean failOnWarning)
    throws MojoExecutionException {
        // compile
        Compiler compiler = new Compiler();
        Result result = compiler.compile(EXTERNS_EMPTY, interns, options);

        // evaluate result
        evalResult(result, log, failOnWarning);

        return compiler;
    }

    protected void evalResult(Result result, Log log, boolean failOnWarning) throws MojoExecutionException {
        if (result.warnings != null) {
            for (JSError warning : result.warnings) {
                log.warn(warning.toString());
            }
        }

        if (result.warnings != null && result.warnings.length > 0 && failOnWarning) {
            throw new MojoExecutionException("Resources optimization failure. Please fix warnings and try again.");
        }

        if (result.errors != null) {
            for (JSError error : result.errors) {
                log.error(error.toString());
            }
        }

        if (result.errors != null && result.errors.length > 0) {
            throw new MojoExecutionException("Resources optimization failure. Please fix errors and try again.");
        }

        if (!result.success) {
            throw new MojoExecutionException("Resources optimization failure. Please fix errors and try again.");
        }
    }

    private File setupSourceMapFile(CompilerOptions options, org.primefaces.extensions.optimizerplugin.model.SourceMap sourceMap,
                                    String outputFilePath) throws IOException {
        // set options for source map
        options.setSourceMapDetailLevel(SourceMap.DetailLevel.valueOf(sourceMap.getDetailLevel()));
        options.setSourceMapFormat(SourceMap.Format.valueOf(sourceMap.getFormat()));

        File sourceMapFile = new File(outputFilePath + SOURCE_MAP_FILE_EXTENSION);
        options.setSourceMapOutputPath(sourceMapFile.getCanonicalPath());

        String prefix = outputFilePath.substring(0, outputFilePath.lastIndexOf(File.separator) + 1);
        // Replace backslashes (the file separator used on Windows systems).
        // This is needed due to the same code in SourceMap.java
        if (File.separatorChar == '\\') {
            prefix = prefix.replace('\\', '/');
        }

        List sourceMapLocationMappings =
                ImmutableList.of(new SourceMap.LocationMapping(prefix, ""));
        options.setSourceMapLocationMappings(sourceMapLocationMappings);

        return sourceMapFile;
    }

    private void writeSourceMap(File sourceMapFile, String sourceFileName, SourceMap sourceMap, String outputDir, Log log) {
        try {
            FileWriter out = new FileWriter(sourceMapFile);
            sourceMap.appendTo(out, sourceFileName);
            out.flush();
            IOUtil.close(out);
        } catch (Exception e) {
            log.error("Failed to write an JavaScript Source Map file for " + sourceFileName, e);
        }

        // move the file
        moveToSourceMapDir(sourceMapFile, outputDir, log);
    }

    private void moveToSourceMapDir(File file, String outputDir, Log log) {
        try {
            String name = file.getName();
            String target = outputDir + name;
            File targetFile = new File(target);
            Files.createParentDirs(targetFile);
            Files.move(file, targetFile);
        } catch (Exception e) {
            log.error("File " + file + " could not be moved to " + outputDir, e);
        }
    }

    private void writeSourceMappingURL(File minifiedFile, File sourceMapFile, String sourceMapRoot, Charset cset, Log log) {
        try {
            // write sourceMappingURL
            String smRoot = (sourceMapRoot != null ? sourceMapRoot : "");
            Files.append(System.getProperty("line.separator"), minifiedFile, cset);
            Files.append("//# sourceMappingURL=" + smRoot + sourceMapFile.getName(), minifiedFile, cset);
        } catch (IOException e) {
            log.error("//# sourceMappingURL for the minified file " + minifiedFile + " could not be written", e);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy