使用線上工具去壓縮有一個缺點就是要將 Javascript 檔案儲存成 2 份,1 份是原始檔案,另 1 份是經壓縮內容的 Javascript,因為不可能更改經壓縮過的內容,每一次更改檔案就需要更改原始檔案,然後利用 Google Closure Compiler 線上工具再壓縮一次,再更新壓縮內容,而且經壓縮的 Javascript 在瀏覽器 debug 亦比較困難。雖然你可以在 HTML 將引入的 Javascript 檔案改為未經壓縮然後 debug,但萬一忘記改回就麻煩了。
幸好 Google Closure Compiler 有提供到 Java 使用的 API 來達到執行期間 (Run Time) 將 Javascript 壓縮。
首先到 Google Code 下載: Google Closure Compiler
可用以下其中一個方法去壓縮:
protected String compress(InputStream inputStream) throws IOException{
Compiler compiler = new Compiler();
CompilerOptions options = new CompilerOptions();
CompilationLevel.SIMPLE_OPTIMIZATIONS
.setOptionsForCompilationLevel(options);
JSSourceFile extern = JSSourceFile.fromCode("externs.js", " ");
JSSourceFile input = JSSourceFile.fromInputStream("origin.js", inputStream);
compiler.compile(extern, input, options);
return compiler.toSource();
}
protected String compress(String str) throws IOException{
Compiler compiler = new Compiler();
CompilerOptions options = new CompilerOptions();
CompilationLevel.SIMPLE_OPTIMIZATIONS
.setOptionsForCompilationLevel(options);
JSSourceFile extern = JSSourceFile.fromCode("externs.js", " ");
JSSourceFile input = JSSourceFile.fromCode("origin.js", str);
compiler.compile(extern, input, options);
return compiler.toSource();
}
其中 CompilationLevel 分別有 3 個選項:
- CompilationLevel.WHITESPACE_ONLY
- CompilationLevel.SIMPLE_OPTIMIZATIONS
- CompilationLevel.ADVANCED_OPTIMIZATIONS
WHITESPACE_ONLY 只會移除 Javascript 的空白。
SIMPLE_OPTIMIZATIONS 是最常用的一種,移除 Javascript 的空白,而且將一些 Local Variable 或 Local Function 名稱改變,並將一些沒有用到的 Variable 移除,大幅提高 Javascript 的壓縮率。
ADVANCED_OPTIMIZATIONS 是最高壓縮率的模式,將所有 Variable 或 Function 的名稱改變,有使用 Javascript Framework 不建議使用這個選項。
在真實環境中我們可以加入一個 Filter 去將 Javascript 壓縮:
package com.ctlok.pro.filter;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.javascript.jscomp.CompilationLevel;
import com.google.javascript.jscomp.Compiler;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.JSSourceFile;
import com.google.javascript.jscomp.WarningLevel;
public class ClosureCompilerFilter implements Filter {
//for cache
private final Map<String, String> compressedJs = new HashMap<String, String>();
private FilterConfig filterConfig;
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
//Turn off the compiler log
Compiler.setLoggingLevel(Level.OFF);
}
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String uri = req.getRequestURI();
//Prevent compress the compressed Javascript, such as jquery.min.js, mootools.min.js, etc.
if (!uri.matches(".*\\.min\\.js$")){
String js = null;
if (compressedJs.containsKey(uri)){
//get from cache
js = compressedJs.get(uri);
}else{
String contextPath = req.getContextPath();
String jsPath = uri.substring(contextPath.length());
//get javascript file as stream
//getResourceAsStream cannot include context path
InputStream inputStream = filterConfig.getServletContext().getResourceAsStream(jsPath);
js = compress(inputStream);
//put to cache
compressedJs.put(uri, js);
}
resp.getWriter().write(js);
return;
}
chain.doFilter(request, response);
}
protected String compress(InputStream inputStream) throws IOException{
Compiler compiler = new Compiler();
CompilerOptions options = new CompilerOptions();
CompilationLevel.SIMPLE_OPTIMIZATIONS
.setOptionsForCompilationLevel(options);
WarningLevel.QUIET.setOptionsForWarningLevel(options);
JSSourceFile extern = JSSourceFile.fromCode("externs.js", "");
JSSourceFile input = JSSourceFile.fromInputStream("origin.js", inputStream);
compiler.compile(extern, input, options);
return compiler.toSource();
}
}
在 web.xml 加上:
<filter>
<display-name>ClosureCompilerFilter</display-name>
<filter-name>ClosureCompilerFilter</filter-name>
<filter-class>com.ctlok.pro.filter.ClosureCompilerFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ClosureCompilerFilter</filter-name>
<url-pattern>*.js</url-pattern>
</filter-mapping>
這樣就可以令到 .js 結尾的 Javascript 檔案經壓縮再傳出去,又不會將 .min.js 已經壓縮過的 Javascript 又再壓縮一次。
經過壓縮後會儲存在 Map 內,畢竟壓縮的時間也不短。
例如我有一個 /js/myjs.js 的檔案:
function ctlok() {
var self = this;
var $ = jQuery;
this.publicFunction = function() {
localFunction();
};
var localFunction = function() {
var aaaaa = 'aaaaa';
$(':input').val(aaaaa);
};
}
經壓縮後變成:
function ctlok(){var a=jQuery;this.publicFunction=function(){a(":input").val("aaaaa")}};
範例下載:: Google-Closure-Compiler.zip