GeekFactory

int128.hatenablog.com

Webアプリの実行時にJavaScriptを圧縮する

Webアプリの公開にあたっては、データ転送量や実行効率の点からJavaScriptを圧縮(minify)して配信することが推奨されています。ここでは実行時に圧縮する方法を説明します。

JavaScriptの圧縮にはGoogle Closure Compilerを利用します。Maven pom.xmlに以下を追記するか、zipアーカイブをダウンロードして配置します。

	<dependencies>
		<dependency>
			<groupId>com.google.javascript</groupId>
			<artifactId>closure-compiler</artifactId>
			<version>r1592</version>
			<exclusions>
				<!-- 実行時はAntなくても動く -->
				<exclusion>
					<artifactId>ant</artifactId>
					<groupId>org.apache.ant</groupId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

圧縮されたJavaScriptを返すサーブレットは以下のようになります。

public class MinifiedController extends Controller {
  private static final String JS_SRC = "js/src";

  protected void run() throws Exception {
    List<File> sources = findJavaScriptFiles(JS_SRC);
    String compiled = compile(sources);
    response.setHeader(/* キャッシュ制御ヘッダ */);
    response.setCharacterEncoding("UTF-8");
    response.setContentType("text/javascript");
    response.getWriter().append(compiled);
    response.flushBuffer();
  }

  /**
   * Finds JavaScript files under directories.
   *
   * @param directories
   * @return list of files
   */
  private static List<File> findJavaScriptFiles(String... directories) {
    FilenameFilter filter = new FilenameFilter() {
      @Override
      public boolean accept(File dir, String name) {
        return name.endsWith(".js");
      }
    };
    List<File> result = new ArrayList<File>();
    for (String directory : directories) {
      File[] files = new File(directory).listFiles(filter);
      if (files != null) {
        result.addAll(Arrays.asList(files));
      }
    }
    return result;
  }

  /**
   * Compiles JavaScript sources.
   *
   * @param scripts
   * @return
   */
  private static String compile(List<File> scripts) {
    List<JSSourceFile> externs = new ArrayList<JSSourceFile>();
    List<JSSourceFile> sources = new ArrayList<JSSourceFile>();
    for (File file : scripts) {
      sources.add(JSSourceFile.fromFile(file));
    }

    Compiler compiler = new Compiler(System.err);
    compiler.disableThreads();
    CompilerOptions options = new CompilerOptions();
    Result result = compiler.compile(externs, sources, options);
    if (!result.success) {
      throw new RuntimeException("Closure Compiler returned error");
    }
    return compiler.toSource();
  }
}

ここでは最低限(Whitespace)の圧縮しか行っていませんが、Google Closure Compilerは多彩な最適化オプションを持っています。Compilerクラスの詳しい使い方は CompileTask#execute() が参考になります。

APサーバのフロントにいるWebサーバでリソースがキャッシュされるように設定してください。App Engineの場合は Cache-Control: public, max-age=86400 とかしてフロントエンドでキャッシュされるようにします。

ですが、実はフロントエンドキャッシュは使ってません。キャッシュをexpireさせる契機を制御できないので使いづらいと思いました。上記と同じ仕組みを使って、開発環境の実行時にJavaScriptを圧縮してローカルに保存するようにしています。