- 编译器
- 开发工具
【免费下载链接】closure-compiler
A JavaScript checker and optimizer.
Closure Compiler(代码中即closure-compiler,本仓库根目录的 README.md 即其官方说明)是一个把 JavaScript 编译成更高效 JavaScript 的"真正编译器":它解析你的代码、分析它、移除死代码,并重写与压缩剩余部分,同时检查语法、变量引用与类型,并对常见 JS 陷阱给出警告。本文以该 README 为骨架,结合仓库源码(编译级别实现、命令行参数解析、默认 externs 加载、base.js模块体系等)展开,帮助你掌握从安装、命令行/NodeJS API 使用,到多脚本编译、依赖管理,再到用 Bazel 从源码构建的完整实战路径。
一、Closure Compiler 是什么
Closure Compiler 不是把 JavaScript 编译成机器码,而是"从 JavaScript 编译到更好的 JavaScript"。它面向一个核心目标:让 JavaScript 下载和运行得更快。从 README.md 的定位可以看出,它覆盖的能力包括:
- 代码压缩:移除死代码、重写并最小化剩余代码;
- 静态检查:检查语法、变量引用和类型,警告常见 JavaScript 陷阱;
- 消息本地化:以可替换翻译版本的方式定义用户可见消息,为应用做本地化;
- 转译(Transpile):把较新的 JS 特性转译成旧浏览器可运行的形态;
- 代码分块(Chunk):把输出应用拆成可按需单独加载的多个脚本(这些 chunk 是普通 JS 脚本,不使用 ES
import/export语法)。
在源码层面,编译器的主流程入口集中在 Compiler.java,其中compile系列方法(Compiler.java#L915-L930)负责接收 externs、输入源码与 CompilerOptions 并产出编译结果;而命令行入口在 CommandLineRunner.java,Eclipse 中可直接运行该类来启动编译器。
二、使用前的关键认知(Important Caveats)
README 专门用一节列出"重要告诫",这些决定了 Closure Compiler 的适用边界,不读懂这些,ADVANCED 模式几乎必然产出损坏的 JS。
1. 非 ADVANCED 模式已被弃用
编译模式中除
ADVANCED之外的模式从来都是"事后补充",且已被弃用。官方认为其他工具在非ADVANCED模式下表现相当,且与更广的 JS 生态集成更好。
也就是说:ADVANCED才是 Closure Compiler 的核心价值场景,SIMPLE/WHITESPACE_ONLY更多用于快速体验或检查。
2. 不适合任意 JavaScript
ADVANCED模式要产出可运行代码,输入 JS 必须是为 Closure Compiler 量身编写的。它假定自己能"看到整个世界"——每个全局变量、每个导出变量、每个属性名的每一种可能用法,要么直接可见,要么通过 externs 获得信息。它会被激进地删除与重命名变量和属性以追求最小体积,一旦有全局变量或属性的使用对它隐藏,输出 JS 就会损坏。
3. 属性访问方式必须一致
属性重命名要求你自始至终用同一种方式访问属性:要么统一obj.propName点访问,要么统一obj[p]动态访问(包括let {p} = obj;解构这类间接方式),不能混用。混合访问会把属性字面名从编译器眼前藏起来:
- 有些情况下编译器会发现该问题并直接报错终止编译;
- 另一些情况下它会无感知地把
propName重命名成更短的名字,产出一份损坏的输出 JS。
对应地,README 的"输入限制"部分也明确要求:对某个对象类型,要么只用点访问,要么只用object[propertyName]/Object.keys(object)这类动态访问。
4. 全局变量内联与属性链扁平化
编译器会激进地内联全局变量,并把全局变量上的属性链拍平,例如myFoo.some.sub.property→myFoo$some$sub$property,以便更容易地发现未使用代码。它在可能导致输出损坏时会尝试回退或报错,但总存在识别不出问题、直接产出损坏 JS 的情况——这在"不是为 Closure Compiler 而写"的代码里更容易发生。@nocollapse等注解正是用于阻止此类激进行为(详见第五节)。
5. 默认环境假设:浏览器窗口
编译器及其默认 externs默认假设目标环境是浏览器窗口。相关能力边界:
- WebWorker 也受支持,但编译器很可能无法警告你使用了 WebWorker 实际不可用的特性;
- 仓库中确实提供了一批 NodeJS 环境的 externs(见 contrib/nodejs/ 下的
fs.js、http.js、path.js等),但 README 明确指出它们未被积极维护、从未工作得很好; - 从源码看,默认 externs 的加载逻辑集中在 DefaultExterns.java:
BROWSER环境会加载核心语言 externs 加一批按依赖顺序排列的浏览器 externs(intl.js、w3c_event.js、w3c_dom1.js等,见 DefaultExterns.java#L37-L62),而CUSTOM环境只保留es3.js、es5.js、es6.js、es6_collections.js这些核心语言 externs(DefaultExterns.java#L33-L34)。
6. 模块体系:goog.module()是唯一被良好支持的方式
不用base.js里的goog.module()/goog.require()声明和使用模块的 JS,支持程度不佳。原因很实在:
- ES
import/export语法 2015 年才出现,Closure Compiler 与 Closure Library 在此之前就发展出了自己的模块声明/使用机制,这一机制至今仍是唯一被良好支持的模块定义方式; - 编译器确实实现了对 ECMAScript 模块的部分理解,但 Google 内部项目改用新语法的收益从未超过改造成本;Google 的 TypeScript 代码使用 ES 模块,但在交给 closure-compiler 之前已被转换为
goog.module()语法。因此 ES 模块支持在 Google 内部实际未被使用,bug 很可能不被发现或修复; - CommonJS 模块输入支持是过去添加的,Google 内部同样未使用,README 指出该支持很可能在 2024 年被整体移除(当前仓库中仍保留 ProcessCommonJSModules.java 等相关实现,但使用前请务必确认你的版本状态)。
三、支持的用途与输入约束
README 总结了 Closure Compiler 在 Google 项目中承担的职责:
| 用途 | 说明 |
|---|---|
| 大幅减小超大 JS 应用的代码体积 | 核心场景,配合ADVANCED模式 |
| 检查错误与规范 | 通用或项目特定的 best practices 检查(conformance) |
| 用户可见消息的本地化 | 定义消息后可用翻译版本替换,构建多语言应用 |
| 转译新 JS 特性 | 产出能在不支持这些特性的浏览器上运行的代码 |
| 输出分块按需加载 | 拆成多个可按需加载的脚本,但不使用 ESimport/export |
为达成这些目标,编译器对输入施加了明确约束(也是排查"输出损坏"类问题时的检查清单):
- 用
goog.module()与goog.require()声明/使用模块(ES6 的import/export支持未被积极维护); - 用注释注解声明类型信息,并提供编译器避免破坏某些代码模式所需的信息,例如
@nocollapse(阻止属性折叠/删除)与@noinline(阻止函数内联)——CheckJSDoc.java 会对@nocollapse的摆放位置做合法性校验,例如标注在 externs 中或不可折叠节点上会发出"JSDoc 无效果"类警告; - 属性访问方式保持一致(点访问或动态访问二选一,理由见第二节);
- 把整个应用作为一次编译看待:编译器假定能看到所有变量与属性的所有用法,可自由重命名或删除看似未使用的部分;与编译单元外部代码互通时,必须显式、谨慎地构造接口;
- 用 externs 文件告知编译器哪些变量/属性不可删除或重命名:仓库自带声明标准 JS 与 DOM 全局 API 的默认 externs(externs/es3.js、externs/es5.js、externs/es6.js、externs/browser/ 等),若使用较冷门 API,或期望外部 JS 代码访问你编译代码中的 API,就需要补充更多 externs。
四、安装与快速开始
通过 NPM / Yarn 安装
最简单的安装方式是包管理器:
yarn global add google-closure-compiler # 或者 npm i -g google-closure-compiler包管理器会自动链接二进制,之后直接执行:
google-closure-compiler交互模式体验
不带参数启动即进入交互模式。输入:
var x = 17 + 25;按Enter,再按Ctrl+Z(Windows)或Ctrl+D(Mac/Linux),再按Enter。编译器会以SIMPLE模式(默认)返回编译结果:
var x=42;一次交互即可直观感受到常量折叠与空白压缩的效果。
通过 Maven 获取预编译版本
除包管理器外,Maven 中央仓库也发布了预编译版本(坐标com.google.javascript:closure-compiler),适合 Java 工具链集成。
五、命令行基本用法
最简单的压缩示例
google-closure-compiler --js file.js --js_output_file file.out.js编译器有大量选项支持从文件读入、写文件输出、检查代码与运行优化。收益最大化的前提是把全部源码交给它(见"编译多个脚本"一节),从而可以启用ADVANCED优化:
google-closure-compiler -O ADVANCED rollup.js --js_output_file rollup.min.js查看全部选项:
google-closure-compiler --help核心参数表
README 给出了如下核心参数(均为命令行入口 CommandLineRunner.java 中由 args4j 注解声明的真实选项):
--flag | 说明 |
|---|---|
--compilation_level (-O) | 指定编译级别。选项:BUNDLE、WHITESPACE_ONLY、SIMPLE(默认)、ADVANCED |
--env | 决定加载哪一组内置 externs。选项:BROWSER、CUSTOM,默认BROWSER |
--externs | 包含 JS externs 的文件,可多次指定 |
--js | JS 文件名,可多次指定;flag 名可省略(参数默认按文件解释);支持 minimatch 风格 glob,例如--js='**.js' --js='!**_test.js'递归包含所有不以_test.js结尾的 js 文件 |
--js_output_file | 主输出文件名;不指定则输出到 stdout |
--language_in | 输入源码应符合的语言规范。选项:ECMASCRIPT3、ECMASCRIPT5、ECMASCRIPT5_STRICT、ECMASCRIPT_2015、ECMASCRIPT_2016、ECMASCRIPT_2017、ECMASCRIPT_2018、ECMASCRIPT_2019、STABLE、ECMASCRIPT_NEXT |
--language_out | 输出应符合的语言规范。选项:ECMASCRIPT3、ECMASCRIPT5、ECMASCRIPT5_STRICT、ECMASCRIPT_2015、ECMASCRIPT_2016、ECMASCRIPT_2017、ECMASCRIPT_2018、ECMASCRIPT_2019、STABLE |
--warning_level (-W) | 指定警告级别。选项:QUIET、DEFAULT、VERBOSE |
编译级别背后的源码实现
--compilation_level的取值解析在 CompilationLevel.java 中完成:枚举定义了BUNDLE、WHITESPACE_ONLY、SIMPLE_OPTIMIZATIONS、ADVANCED_OPTIMIZATIONS四个级别,fromString方法同时接受简写与全称(SIMPLE/SIMPLE_OPTIMIZATIONS、ADVANCED/ADVANCED_OPTIMIZATIONS、WHITESPACE/WHITESPACE_ONLY)。各级别通过setOptionsForCompilationLevel装配不同的 CompilerOptions(CompilationLevel.java#L76-L90):
WHITESPACE_ONLY:调用applyBasicCompilationOptions,直接skipAllCompilerPasses(),仅去注释与多余空白;SIMPLE_OPTIMIZATIONS:调用applySafeCompilationOptions(CompilationLevel.java#L112-L137),执行不依赖外部代码的变换——例如仅重命名函数参数(外部代码不受影响)、不重命名函数名本身;开启 Closure 相关 pass、LOCAL范围变量重命名、局部变量/函数内联、常量折叠、死赋值消除、对象字面量折叠等;ADVANCED_OPTIMIZATIONS:调用applyFullCompilationOptions(CompilationLevel.java#L144-L197),在安全优化之上叠加全套激进优化:符号检查(setCheckSymbols(true))、类型检查(setCheckTypes(true))、变量与属性全量重命名(VariableRenamingPolicy.ALL+PropertyRenamingPolicy.ALL_UNQUOTED)、全局属性折叠(CollapsePropertyLevel.ALL)、未使用原型/类属性移除、未使用变量全量移除、跨 chunk 代码/方法搬移、方法去虚化(devirtualize)与调用优化等。
这也解释了 README 的核心建议:只有ADVANCED才能获得"最大收益",而它要求你把全部源码放进一次编译,并为外部交互显式构造接口(导出、externs、注解)。
补充:chunk 分块选项
从源码看,命令行还支持--chunk选项(CommandLineRunner.java#L248-L261),格式为<name>:<num-js-files>[:[<dep>,...][:]],用于把输出拆成多个可独立加载的 chunk,且要求按依赖顺序列出——这正是 README"输出应用拆分成 chunk 按需加载"这一用途的命令行载体。
六、编译多个脚本
多脚本应放在同一条编译命令里一起编译,这是使用ADVANCED的前提:
google-closure-compiler in1.js in2.js in3.js --js_output_file out.js也支持 minimatch 风格 glob:
# 递归包含子目录下所有 js 文件 google-closure-compiler 'src/**.js' --js_output_file out.js # 递归包含所有 js 文件,但排除测试文件。 # 务必使用单引号,防止 bash 尝试展开 '!' google-closure-compiler 'src/**.js' '!**_test.js' --js_output_file out.js编译器会按命令行传入顺序拼接这些文件。当脚本变多、依赖关系难以手工管理时,应使用随附的 lib/base.js(提供goog.module/goog.require等依赖强制函数),编译器会自动重排输入顺序。
七、NodeJS API
除命令行外,可在 JS 程序中通过导入google-closure-compiler使用编译器:
import closureCompiler from 'google-closure-compiler'; const { compiler } = closureCompiler; new compiler({ js: 'file-one.js', compilation_level: 'ADVANCED' });该 npm 包在多数情况下会提供对原生 Graal 二进制的编程式访问,否则回退到 Java 版本。适合把编译集成进构建脚本、打包工具或 CI 流水线。
八、Closure JavaScript Library:lib/base.js
Closure Compiler 随发布附带 lib/base.js,它提供支撑编译器若干特性的 JS 函数与变量原语,是 Closure Library 中同名base.js的衍生品(该库即将被弃用),这份base.js今后由 Closure Compiler 继续支持并可能增加新特性,设计上只保留其核心部分。
从源码可确认其关键原语(lib/base.js):
COMPILED标志(lib/base.js#L27):编译时被覆盖为true,未编译时为false;CLOSURE_DEFINES(lib/base.js#L60):可在编译前覆盖@define值,例如var CLOSURE_DEFINES = {'goog.DEBUG': false};;goog.provide(name)(lib/base.js#L231):按命名空间路径构建对象结构,如"a.b.c"→a={};a.b={};a.b.c={},同一命名空间重复 provide 会抛错;goog.module(name)(lib/base.js#L315):声明一个模块文件,必须满足"文件内第一个语句、每文件仅一次、不得与goog.provide同文件使用"三条要求;模块文件会被包进严格函数闭包,内部变量私有、代码遵循严格模式;与goog.provide不同,goog.module本身不声明任何全局符号(如需旧式全局符号可用goog.module.declareLegacyNamespace());goog.require(namespace)(lib/base.js#L510):声明对某命名空间模块的依赖。
goog.module之所以重要,是因为 README 明确:这是当前唯一被良好支持的模块定义方式,构建工具正是通过扫描这些 provide/require/module 语句来推断依赖关系。
九、从源码构建编译器
环境准备
| 前置条件 | 说明 |
|---|---|
| Java 11 或更高版本 | 用于编译编译器自身的 Java 源码 |
| Git | Bazel 下载依赖时使用 |
| Bazelisk | 用于构建各类编译器目标 |
Bazelisk是 Bazel 的包装器,会按仓库要求动态加载对应版本的 Bazel,避免因版本不匹配导致的构建错误,也方便不同项目使用不同 Bazel 版本。可通过任意你熟悉的包管理器安装。
终端构建
$ bazelisk build //:compiler_uberjar_deploy.jar # 或者构建全部目标 $ bazelisk build //:all从 package.json 的compile脚本(java -jar bazel-bin/compiler_uberjar_deploy.jar)可以印证:构建完成后,uberjar 位于bazel-bin/目录。
终端测试
$ bazelisk test //:all仓库有数百个独立测试目标,全量运行需要几分钟;开发时通常只跑关心的测试:
bazelisk test //:$path_to_test_file测试源码集中在 test/com/google/javascript/jscomp/,例如编译器各 pass 与检查器都有对应的*Test.java;对命令行行为感兴趣的读者可关注 CommandLineRunner 相关的测试目标。
IDE 构建与运行
在 IDE 中构建可参考 Bazel IDE 集成方案。运行方面:
- 构建完成后用
java -jar调用bazel-bin/compiler_uberjar_deploy.jar,或直接用 package.json 脚本:yarn compile [...args]; - 使用 Eclipse 时:打开 src/com/google/javascript/jscomp/CommandLineRunner.java(或继承它写自己的扩展类)并运行即可;注意 Eclipse 控制台向 stdin 传递
Ctrl+D(End of Transmission)存在已知缺陷,交互模式体验会受影响。
贡献与支持
- 所有贡献者须遵守 code_of_conduct.md;
- 报告 bug 前请先确认"这真的是 bug 而不是 Closure Compiler 的既定行为(尤其
ADVANCED模式下)":查阅官方文档、FAQ、Stack Overflow 上的google-closure-compiler标签、Closure Compiler Discuss 讨论组以及"编译器假设"清单;再确认无人报告过同样问题;最后提交 issue 时附带尽量小的可复现代码; - 建议新特性前先确认该行为未被 FAQ 明确排除(例如字符串内联),再确认无人提过同样请求,然后以 issue 形式提交;
- 提交补丁需要先签署贡献者许可协议(CLA),可在讨论组确认补丁类型是否会被接受,然后 fork、修改(遵循项目编码规范)、提交 pull request 等待项目开发者 review。
十、许可证与依赖
Closure Compiler 采用Apache License 2.0(版权声明见 LICENSE),README 同时记录了主要第三方依赖的版本与许可:
| 依赖 | 版本 | 许可 | 说明 |
|---|---|---|---|
| Rhino | 1.5R3(大幅修改) | NPL / MPL / GPL 双许可 | 提取并大幅改造了 JavaScript 解析树数据结构(代码路径 src/com/google/javascript/rhino);去掉了与解析树无关的代码,新增了 JsDoc 解析器与静态类型系统 |
| Args4j | 2.33 | MIT | 命令行选项解析 |
| Guava Libraries | 31.0.1 | Apache 2.0 | Google 核心 Java 库 |
| JSR 305 | 3.0.1 | BSD | 缺陷检测注解 |
| JUnit | 4.13 | CPL 1.0 | Java 单元测试框架 |
| Protocol Buffers | 3.0.2 | 新 BSD | 结构化数据编码支持库 |
| RE2/J | 1.3 | 新 BSD | Java 线性时间正则匹配 |
| Truth | 1.1 | Apache 2.0 | Java 单元测试断言框架 |
| Ant | 1.10.11 | Apache 2.0 | Java 构建工具 |
| GSON | 2.9.1 | Apache 2.0 | JSON 与 Java 对象互转 |
| Node.js Closure Compiler Externs | e891b4f | Apache 2.0 | NodeJS API 类型契约(代码路径 contrib/nodejs),为兼容 NpmCommandLineRunner 做了大量修改 |
其中 Rhino 的说明尤其值得注意:Closure Compiler 的解析树数据结构源自 Mozilla Rhino(JVM 上的 JavaScript 实现)的局部拷贝,经大幅改造后成为 Google 编译器的基础——这也是理解该仓库"解析 → 检查 → 优化"流水线的起点。
结语
Closure Compiler 是一套要求苛刻但收益极高的 JavaScript 优化工具:它要求你为它而写代码(goog.module()模块、一致的属性访问、显式 externs 与导出、@nocollapse/@noinline等注解),回报是ADVANCED模式下极致的代码体积压缩、类型与规范检查、转译与分块能力。本文覆盖的安装方式、命令行与 NodeJS API、多脚本编译、Bazel 构建与测试,足以支撑你把它接入自己的构建体系;进一步的细节可查阅 README.md、命令行--help输出,以及仓库中 src/com/google/javascript/jscomp/ 下的源码实现。
- 编译器
- 开发工具
【免费下载链接】closure-compiler
A JavaScript checker and optimizer.
相关推荐
Closure Compiler 完全指南:从基础用法到 ADVANCED 深度优化
Closure Compiler 完全指南:从基础用法到 ADVANCED 深度优化 本文以仓库根目录的 README.md https://link.gitc
编译器开发工具前端CANNAscendC SetFlag/WaitFlag同步控制API文档
SetFlag/WaitFlag\ ISASI\ <a name="ZH CN_TOPIC_0000001837467889" </a 产品支持情况<a nam
人工智能深度学习算子库CANNAscendDNS-Challenge 5th技术亮点: dereverberation与干扰抑制新方案
DNS Challenge 5th技术亮点: dereverberation与干扰抑制新方案 DNS Challenge(Deep Noise Suppress
语音音频音频处理人工智能数据集
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考