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

com.github.jknack.amd4j.ClosureMinifier Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (c) 2013 Edgar Espina
 *
 * This file is part of amd4j (https://github.com/jknack/amd4j)
 *
 * 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.github.jknack.amd4j;

import static org.apache.commons.io.FilenameUtils.removeExtension;
import static org.apache.commons.lang3.Validate.notNull;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.ByteStreams;
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.JSError;
import com.google.javascript.jscomp.Result;
import com.google.javascript.jscomp.SourceFile;

/**
 * An optimizer built on top of Google Closure Compiler.
 *
 * @author edgar.espina
 * @since 0.2.0
 */
public class ClosureMinifier extends Minifier {

  /**
   * The logging system.
   */
  private final Logger logger = LoggerFactory.getLogger(getClass());

  /**
   * The compilation level.
   */
  private CompilationLevel compilationLevel;

  /**
   * The externs expected in externs.zip, in sorted order.
   * Taken from: com.google.javascript.jscomp.CommandLineRunner
   */
  private static final List DEFAULT_EXTERNS_NAMES = ImmutableList.of(
      // JS externs
      "es3.js",
      "es5.js",

      // Event APIs
      "w3c_event.js",
      "w3c_event3.js",
      "gecko_event.js",
      "ie_event.js",
      "webkit_event.js",
      "w3c_device_sensor_event.js",

      // DOM apis
      "w3c_dom1.js",
      "w3c_dom2.js",
      "w3c_dom3.js",
      "gecko_dom.js",
      "ie_dom.js",
      "webkit_dom.js",

      // CSS apis
      "w3c_css.js",
      "gecko_css.js",
      "ie_css.js",
      "webkit_css.js",

      // Top-level namespaces
      "google.js",

      "chrome.js",

      "deprecated.js",
      "fileapi.js",
      "flash.js",
      "gears_symbols.js",
      "gears_types.js",
      "gecko_xml.js",
      "html5.js",
      "ie_vml.js",
      "iphone.js",
      "webstorage.js",
      "w3c_anim_timing.js",
      "w3c_css3d.js",
      "w3c_elementtraversal.js",
      "w3c_geolocation.js",
      "w3c_indexeddb.js",
      "w3c_navigation_timing.js",
      "w3c_range.js",
      "w3c_selectors.js",
      "w3c_xml.js",
      "window.js",
      "webkit_notifications.js",
      "webgl.js");

  /**
   * A cached copy of default externs source files.
   */
  List defaultExterns = null;

  /**
   * Creates a new {@link ClosureMinifier}.
   *
   * @param compilationLevel The compilation level. Required.
   */
  public ClosureMinifier(final CompilationLevel compilationLevel) {
    this.compilationLevel = notNull(compilationLevel, "The compilationLevel is required.");
    try {
      this.defaultExterns = getDefaultExterns();
    } catch (IOException e) {
      logger.warn("Could not load externs.zip. No closure externs will be used.", e);
      this.defaultExterns = Collections. emptyList();
    }
  }

  @Override
  public CharSequence minify(final Config config, final CharSequence source) {
    final CompilerOptions options = new CompilerOptions();
    options.setCodingConvention(new ClosureCodingConvention());
    options.setOutputCharset("UTF-8");
    options.setWarningLevel(DiagnosticGroups.CHECK_VARIABLES, CheckLevel.WARNING);
    compilationLevel.setOptionsForCompilationLevel(options);

    Compiler.setLoggingLevel(Level.SEVERE);
    Compiler compiler = new Compiler();
    compiler.disableThreads();
    compiler.initOptions(options);

    String fname = removeExtension(config.getName()) + ".js";
    Result result = compiler.compile(defaultExterns,
        Arrays.asList(SourceFile.fromCode(fname, source.toString())), options);
    if (result.success) {
      return compiler.toSource();
    }
    JSError[] errors = result.errors;
    throw new IllegalStateException(errors[0].toString());
  }

  /**
   * Build the default list of google closure external variable files.
   * Taken from: com.google.javascript.jscomp.CommandLineRunner
   *
   * @return a mutable list of source files.
   * @throws IOException On error when working with externs.zip
   */
  protected List getDefaultExterns() throws IOException {
    ZipInputStream zip = null;
    try {
      InputStream input = CommandLineRunner.class.getResourceAsStream("/externs.zip");
      notNull(input, "The externs.zip file was not found within the closure classpath");

      zip = new ZipInputStream(input);
      Map externsMap = Maps.newHashMap();
      ZipEntry entry = zip.getNextEntry();
      while (entry != null) {
        BufferedInputStream entryStream = new BufferedInputStream(
            ByteStreams.limit(zip, entry.getSize()));
        externsMap.put(entry.getName(),
            SourceFile.fromInputStream(
                // Give the files an odd prefix, so that they do not conflict
                // with the user's files.
                "externs.zip//" + entry.getName(),
                entryStream));
        entry = zip.getNextEntry();
      }

      Preconditions.checkState(
          externsMap.keySet().equals(Sets.newHashSet(DEFAULT_EXTERNS_NAMES)),
          "Externs zip must match our hard-coded list of externs.");

      // Order matters, so the resources must be added to the result list
      // in the expected order.
      List externs = Lists.newArrayList();
      for (String key : DEFAULT_EXTERNS_NAMES) {
        externs.add(externsMap.get(key));
      }

      return externs;
    } finally {
      IOUtils.closeQuietly(zip);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy