====== 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); } } } }