====== Google Javascript Closure Compiler ======
* [[https://github.com/google/closure-compiler|Google Closure Compiler]]
* [[https://github.com/google/closure-compiler/wiki|Closure Compiler Wiki]]
===== 역할 =====
* 불필요한 코드를 삭제하고, 공백등을 제거하여 용량을 줄여준다.
* 압축시 에러를 일으킬만한 문법(세미콜론 안 쓴 것등)을 자동 보정해준다.
* 잘못된 코드에 경고와 에러를 내 주어서 브라우저까지 확인하지 않아도 오류를 알 수 있다. 특히 많이 실수하는 (IE 8 이하에서 안되는) json의 마지막 쉼표 등을 찾아서 오류를 내준다.
===== 문제점 =====
* ''float''을 키워드로 인식하는 버그가 있다.
* 이는 ClosureCompiler가 사용하는 Rhino 엔진이 ''float''을 키워드로 지정했기 때문인 것 같다.
* 웹 브라우저에서는 ''float''을 키워드로 보지 않는다.
* ''style.float = xx'' 갈은 구문이 있다면 ''style['float'] = xx''로 변경해야 한다.
* ''outputEncoding'' 을 명시하지 않으면 한글을 Unicode 기호로 바꾼다(\ucXXX 형태). 이 때문에 한글로 된 문자열이 많으면 파일 크기가 오히려 늘어난다. Ant로 할 경우 ''outputEncoding'' 옵션이 지정 안 되는 듯하다. [[http://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/javascript/jscomp/CompilerOptions.java|CompilerOptions]]
===== 설치 사용 =====
* [[http://closure-compiler.googlecode.com/files/compiler-latest.zip|Closure Compiler 다운로드]]
* [[http://mvnrepository.com/artifact/com.google.javascript/closure-compiler|Maven Central Repository]], [[http://code.google.com/p/closure-compiler/wiki/Maven|Maven 설정 참조]]
* 실행 자바 클래스 : ''com.google.javascript.jscomp.CommandLineRunner''
* 기본 사용법
java -jar compiler.jar --js hello.js --js_output_file hello-compiled.js
* 도움말
java -jar compiler.jar --help
* [[https://developers.google.com/closure/compiler/docs/compilation_levels|컴파일 레벨]]
* ''WHITESPACE_ONLY'' : 공백과 주석 제거등만 실행
* ''SIMPLE_OPTIMIZATIONS'' : 기본값. 공백제거, 세미콜론 보정등. 가끔 오보정이 일아났다.
* ''ADVANCED_OPTIMIZATIONS'' : 더 강력한 압축. 불필요한 코드 삭제 등.
* 적용
java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js hello.js
* Options
* ''%%--%%js VAL'' : 컴파일 대상 파일
* ''%%--%%js_output_file VAL'' : 컴파일 결과 파일. 저장하지 않으면 표준출력.
* ''%%--%%charset VAL'' : 입출력 캐릭터셋
* ''%%--%%compilation-level [WHITESPACE_ONLY | SIMPLE_OPTIMIZATIONS | ADVANCED_OPTIMZATIONS]'' : 컴파일 레벨 지정
===== Ant 연동 =====
* [[http://code.google.com/p/closure-compiler/wiki/BuildingWithAnt|Closure Compiler Building With Ant]]
* [[http://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/javascript/jscomp/ant/CompileTask.java|CompileTask.java]]
* ''outputEncoding'' 적용 되는지 확인해볼 것.
===== Closure Compiler API =====
* [[https://github.com/eriwen/gradle-js-plugin/blob/master/src/main/groovy/com/eriwen/gradle/js/JsMinifier.groovy|JsMinifier.groovy]]에서 [[https://developers.google.com/closure/compiler/|ClosureCompiler]]의 API 사용법 예제를 볼 수 있다.
* 혹은 ''com.google.javascript.jscomp.CommandLineRunner'' 참조
* 일괄 Minify 예
import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import com.google.javascript.jscomp.*;
import com.google.javascript.jscomp.Compiler;
public class BatchJsMinify {
private static final String[] EXCLUDE_PATTERNS = { ".min.js", "-min.js" };
private static final CompilationLevel DEFAULT_COMPILATION_LEVEL = CompilationLevel.SIMPLE_OPTIMIZATIONS;
private String srcDirName;
private final File srcDir;
private String destDirName;
private final File destDir;
private com.google.javascript.jscomp.Compiler compiler = new Compiler();;
private CompilerOptions options = new CompilerOptions();
private WarningLevel warningLevel = WarningLevel.QUIET;
public BatchJsMinify(String srcDirName, String destDirName) {
this.srcDirName = srcDirName;
this.destDirName = destDirName;
srcDir = new File(srcDirName);
destDir = new File(destDirName);
if (!srcDir.exists() || !srcDir.isDirectory()) {
throw new IllegalArgumentException(srcDirName + " must exist and be a directory.");
}
destDir.mkdirs();
}
public void compile() throws IOException {
options.setCodingConvention(CodingConventions.getDefault());
options.setOutputCharset("UTF-8");
warningLevel.setOptionsForWarningLevel(options);
DEFAULT_COMPILATION_LEVEL.setOptionsForCompilationLevel(options);
final List jsSourceFiles = getSourceFiles();
if (jsSourceFiles == null || jsSourceFiles.size() == 0) {
System.out.println("Nothing to compile.");
return;
}
final List defaultExterns = CommandLineRunner.getDefaultExterns();
compiler.disableThreads(); // thread가 활성화 돼 있으면 오히려 종료가 늦게 되었다.
final Result result = compiler.compile(defaultExterns, jsSourceFiles, options);
if (result.success) {
writeToFile(jsSourceFiles);
} else {
printErrors(result);
}
}
private void printErrors(Result result) {
for (JSError error : result.errors) {
System.err.println("Error : " + error.sourceName + ":" + error.lineNumber + " - " + error.description);
}
}
private List getSourceFiles() throws IOException {
final List jsSourceFiles = new ArrayList<>();
Files.walkFileTree(Paths.get(srcDirName), new SimpleFileVisitor() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
String filename = file.toString();
if (filename.endsWith(".js") && !filename.endsWith(".min.js") && !filename.endsWith("-min.js")) {
final SourceFile sourceFile = SourceFile.fromFile(file.toFile(), Charset.forName("UTF-8"));
jsSourceFiles.add(sourceFile);
}
return FileVisitResult.CONTINUE;
}
});
return jsSourceFiles;
}
private void writeToFile(List jsSourceFiles) {
final String[] minified = compiler.toSourceArray();
for (int i = 0; i < jsSourceFiles.size(); i++) {
final SourceFile sourceFile = jsSourceFiles.get(i);
String fileRestPath = sourceFile.getOriginalPath().replace(srcDirName, "");
final File destFile = new File(destDir, fileRestPath);
destFile.getParentFile().mkdirs();
System.out.println("Writing minified js file : " + destFile.getAbsolutePath());
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(destFile), "UTF-8"))) {
writer.write(minified[i]);
} catch (IOException e) {
throw new IllegalStateException("minified js file save error.", e);
}
}
}
}