☰
Closure Compiler 完全指南:从基础用法到 ADVANCED 深度优化
2026/10/14 8:07:31 网站建设 项目流程
  • 编译器
  • 开发工具
  • 前端

【免费下载链接】closure-compiler

A JavaScript checker and optimizer.

项目地址:https://gitcode.com/gh_mirrors/clo/closure-compiler
点击查看免费下载

本文以仓库根目录的 README.md 为核心骨架,结合 src/com/google/javascript/jscomp 下的编译器源码、test/com/google/javascript/jscomp/CommandLineRunnerTest.java 测试用例,以及 externs 目录,系统讲解 Closure Compiler 的定位、核心使用方式、命令行参数、模块约束、构建方法与源码级工作原理。阅读完本文,你将能够独立完成从安装、单文件压缩、多脚本合并到 ADVANCED 全量优化的完整实践,并理解编译器在背后做了哪些事情、为什么会对输入代码提出严格的约束。

一、Closure Compiler 是什么

Closure Compiler 是 Google 开源的一款 JavaScript 检查与优化工具。它不是一个传统意义上"从源语言编译到机器码"的编译器,而是从 JavaScript 编译成更好的 JavaScript:解析你的 JS 代码、进行分析、移除死代码、重写并压缩剩下的部分,同时还会检查语法、变量引用和类型,并对常见的 JavaScript 坑发出警告。项目的仓库自述(README.md)将其定位为"A JavaScript checker and optimizer",其核心价值在于让 JavaScript下载更快、运行更快。

在 Google 内部,Closure Compiler 被大规模用于以下场景:

  • 大幅压缩大型 JavaScript 应用的代码体积(尤其是使用ADVANCED优化模式时);
  • 检查 JS 代码中的错误,并校验代码是否符合通用或项目特定的最佳实践(conformance);
  • 定义面向用户的消息,使其可以被替换为翻译后的版本,从而生成应用的本地化版本;
  • 将较新的 JS 特性转译(transpile)为旧浏览器可运行的形式;
  • 将输出应用拆分为多个 chunk(代码块),按需单独加载。需要注意的是,这些 chunk 是普通 JavaScript 脚本,并不使用 ECMAScript 的import/export语法。

在源码层面,编译器的核心入口位于 src/com/google/javascript/jscomp/Compiler.java,命令行入口位于 src/com/google/javascript/jscomp/CommandLineRunner.java,其中CommandLineRunner内部定义了Flags类负责解析全部命令行参数。命令行参数的解析依赖 args4j 库(见 THIRD_PARTY_NOTICES),而底层 JavaScript 解析器则源自 Mozilla Rhino 的改造版本,代码位于 src/com/google/javascript/rhino,README 的 "Dependency Licenses" 一节对此有详细说明:Rhino 的解析树数据结构被提取并大幅修改,同时新增了 JSDoc 解析器和静态类型系统。

二、重要的使用前提(Important Caveats)

README 用了大篇幅列出七条"重要注意事项",这些是理解 Closure Compiler 行为边界的关键,任何实际项目接入前都应通读。

1. 非 ADVANCED 模式已被弃用

除ADVANCED之外的编译模式从一开始就是"事后补充"(afterthought),Google 已将其标记为弃用。对于非ADVANCED场景,社区认为其他工具(如 terser 等)表现相当且与更广泛的 JS 生态集成更好。这意味着:如果你只需要简单的压缩,可以选择其他工具;Closure Compiler 的核心价值集中在ADVANCED全量优化上。

2. 不适合任意 JavaScript

要让ADVANCED模式产出可正常运行的 JS,输入代码必须以 Closure Compiler 的思维方式编写("written with closure-compiler in mind")。这是使用该工具最大的心智成本。

3. 它是"全世界"优化器

Closure Compiler 是一个"whole world"(全量世界)优化器:它期望直接看到、或至少获得关于每一个全局变量、每一个导出变量、每一个属性名的每一种可能使用方式的信息。它会激进地移除和重命名变量与属性以让输出尽可能小;如果全局变量或属性的使用对它隐藏,就会产生损坏的输出 JS。虽然可以编写自定义 externs 文件告诉编译器保留某些名称不变,以便被编译单元之外的代码安全访问,但这种维护往往繁琐(详见下文"externs 机制")。

4. 属性重命名要求一致的访问方式

属性重命名要求你自始至终用同一种方式访问属性:要么统一用obj[p](方括号动态访问),要么统一用obj.propName(点号访问),不能混用。当使用方括号(如obj[p])或其他间接方式(如let {p} = obj;解构赋值)访问属性时,编译器看不到被引用的属性的字面名称,就无法确定obj.propName与obj[p]是否指向同一属性。某些情况下编译器会发现该问题并中止编译报错;另一些情况下它会把propName重命名为更短的名字而不自知,导致输出 JS 损坏。

5. 全局变量的内联与属性链展平

编译器会激进地内联全局变量,并把全局变量上的属性链展平(flatten),例如myFoo.some.sub.property会被改写为myFoo$some$sub$property,目的是更方便地检测未使用代码。它尽量在会产生损坏输出时"退缩"或报错终止,但在并非为 Closure Compiler 编写的代码中,仍有概率识别不出问题、直接生成损坏 JS 而不告警。

6. 默认目标环境是浏览器窗口

编译器及其默认使用的 externs 假设目标环境是浏览器窗口。虽然也支持 WebWorkers,但如果尝试使用 WebWorker 实际不可用的特性,编译器很可能不会警告你。另有一些 externs 文件与特性是为 NodeJS 环境添加的(见 contrib/nodejs 目录),但它们没有得到积极支持,且从未工作得很好。

7. 模块系统:goog.module 优先

不使用 lib/base.js 中的goog.module()和goog.require()来声明和使用模块的 JavaScript 得不到良好支持。ECMAScript 的import/export语法直到 2015 年才出现,Closure Compiler 与 closure-library 早已发展出自己的模块声明/使用方式,这至今仍是唯一被良好支持的模块定义方式。

编译器虽然实现了一定程度的 ECMAScript 模块理解,但 Google 内部从未从中获益:Google 的 TypeScript 代码使用 ES 模块,但在交给 closure-compiler 之前会被转换为goog.module()语法。因此 ES 模块支持在 Google 内部实际处于闲置状态,其中的 bug 很可能不会被发现或修复。此外,曾经加入过的 CommonJS 模块输入支持在 Google 内部也未使用,README 明确警告其"极有可能在 2024 年某个时间点被完全移除"。在源码中,src/com/google/javascript/jscomp/Es6RewriteModules.java 专门负责将 ES6 模块重写为goog.module/goog.require体系,其中甚至包含对const {assert} = goog.require('goog.asserts');这类解构式 require 的重写逻辑。

为满足上述目标,编译器对输入施加的限制

综合以上注意事项,README 归纳出使用 Closure Compiler 必须遵守的输入约定:

  • 使用goog.module()与goog.require()声明和使用模块(ES6import/export支持不被积极维护);
  • 在注释中使用注解(annotations)声明类型信息,并提供编译器避免破坏某些代码模式所需的信息,例如@nocollapse与@noinline;
  • 对特定对象类型的属性访问,要么只用点访问(object.property),要么只用动态访问(object[propertyName]或Object.keys(object))——混用会向编译器隐藏部分属性使用,导致属性被重命名时输出代码损坏;
  • 通常编译器期望将整个应用作为一次编译看待;接口必须仔细且显式地构造,才能与编译单元之外的代码互操作;
  • 使用 externs 文件告知编译器哪些变量或属性不得删除或重命名。默认 externs 已声明标准 JS 与 DOM 全局 API;若使用不太常见的 API,或期望某些外部 JS 代码访问正在编译的代码中的 API,则需要补充更多 externs。

这些限制在源码中都有对应实现:例如 src/com/google/javascript/jscomp/CheckClosureImports.java 负责校验 Closure 模块导入,src/com/google/javascript/jscomp/ProcessClosureProvidesAndRequires.java 负责替换goog.provide调用、移除goog.require/goog.requireType/goog.forwardDeclare调用,并把a.b = {};这类提供(provided)的命名空间定义注解为IS_NAMESPACE,供后续 pass 识别。

三、快速开始(Getting Started)

1. 通过 NPM / Yarn 安装

注意:NPM 包并非由 Google 构建和分发,而是社区贡献者 ChadKillingsworth 从独立仓库管理并构建的。安装编译器最简单的方式是使用 NPM 或 Yarn:

yarn global add google-closure-compiler # 或者 npm i -g google-closure-compiler

安装完成后,包管理器会自动链接可执行文件,直接运行:

google-closure-compiler

这会以交互模式启动编译器。输入以下代码并按Enter:

var x = 17 + 25;

随后按Ctrl+Z(Windows)或Ctrl+D(Mac/Linux),再按一次Enter,编译器会返回编译结果(默认使用SIMPLE模式):

var x=42;

这个最小示例背后就是完整编译管线的一次执行:解析 → 常量折叠(constant folding,见 src/com/google/javascript/jscomp/PeepholeFoldConstants.java)→ 代码生成(src/com/google/javascript/jscomp/CodePrinter.java)。17 + 25在编译期被折叠为常量42。

2. 从 Maven 仓库下载

预编译的发布版本也可以通过 Maven 获取(com.google.javascript:closure-compiler),适合在 Java 构建管线中直接引入。

3. Web 端工具

社区还提供了基于 Web 的 UI 与 REST API(jscompressor),可以在浏览器中直接体验编译器的能力。

4. 基本用法:压缩一个 JS 文件

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

5. 核心命令行参数一览

以下参数表来自 README,且在 src/com/google/javascript/jscomp/CommandLineRunner.java 的Flags内部类中有逐项对应的@Option声明与默认值,可作为权威参考:

--flag说明
--compilation_level (-O)指定编译级别。可选值:BUNDLE、WHITESPACE_ONLY、SIMPLE(默认)、ADVANCED
--env决定加载哪一组内置 externs。可选值:BROWSER、CUSTOM。默认BROWSER
--externs包含 JavaScript externs 的文件,可多次指定
--jsJavaScript 文件名,可多次指定。该标志名可选,因为参数默认按文件解释。也支持 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

补充源码细节:--language_in的默认值是STABLE(见CommandLineRunner.java中private String languageIn = "STABLE";),--warning_level的默认值是DEFAULT(private WarningLevel warningLevel = WarningLevel.DEFAULT;),--env默认值为BROWSER。--js标志使用专门的JsOptionHandler处理器,支持 glob 展开与多文件收集。

6. NodeJS API 编程式调用

也可以在 JS 程序中导入google-closure-compiler访问编译器:

import closureCompiler from 'google-closure-compiler'; const { compiler } = closureCompiler; new compiler({ js: 'file-one.js', compilation_level: 'ADVANCED' });

该包在大多数情况下提供对原生 Graal 二进制的编程式访问,否则回退到 Java 版本。

四、编译多个脚本(Compiling Multiple Scripts)

如果有多个脚本,应当用一条编译命令把它们全部编译在一起:

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

编译器会按命令行传入的顺序拼接这些文件。

当你使用 glob 或大量文件时,脚本之间的依赖管理可能成为问题。此时应使用随附的 lib/base.js 提供的依赖强制函数(即goog.module与goog.require),Closure Compiler 会自动重新排序输入。这一自动排序能力在源码中由模块依赖收集与排序相关 pass 实现,例如 src/com/google/javascript/jscomp/FindModuleDependencies.java 与 src/com/google/javascript/jscomp/JSChunkGraph.java。在测试侧,test/com/google/javascript/jscomp/CommandLineRunnerTest.java 中有大量针对多输入文件与模块排序的端到端用例。

五、Closure JavaScript Library:lib/base.js

Closure Compiler 随发布附带 lib/base.js,它提供一组 JavaScript 函数与变量,作为启用 Closure Compiler 某些特性的原语。该文件是 Closure Library 中同名base.js的衍生版本(后者即将被弃用,见 lib/base.js 文件头注释中对 closure-library issue 1214 的引用),由 Closure Compiler 继续向前支持,可能接收新特性,并只保留其公认的核心部分。

从源码看,lib/base.js 定义了几个关键全局原语:

  • COMPILED:一个@define {boolean}标记,默认false,由编译器在编译时覆盖为true;
  • goog.global:对全局对象的引用,兼容this与self两种获取方式;
  • goog.exportPath_:按命名空间路径构建对象结构("a.b.c"→a={};a.b={};a.b.c={};),供goog.provide与goog.exportSymbol使用;
  • goog.global.CLOSURE_DEFINES:允许在未编译或编译模式下覆盖 define 值,例如var CLOSURE_DEFINES = {'goog.DEBUG': false};——编译器只识别在变量声明处定义的这个值。

值得强调的是,README 明确指出不使用goog.module()/goog.require()声明和使用模块的 JS 得不到良好支持,这一约定贯穿整个编译器设计:从 src/com/google/javascript/jscomp/ProcessClosureProvidesAndRequires.java 对goog.provide/goog.require的替换与移除,到 src/com/google/javascript/jscomp/Es6RewriteModules.java 将 ES6 模块语法统一重写为goog.module体系,再到 src/com/google/javascript/jscomp/CheckClosureImports.java 的导入合法性校验,全部围绕这套模块原语展开。

六、编译级别(CompilationLevel)的源码级解读

README 的参数表列出了四种编译级别,而 src/com/google/javascript/jscomp/CompilationLevel.java 给出了每种级别精确到每个优化开关的实现,理解这张表有助于预判不同级别的行为差异:

枚举值命令行写法定位
BUNDLEBUNDLE仅对文件排序并拼接输出
WHITESPACE_ONLYWHITESPACE_ONLY(或WHITESPACE)仅移除注释与多余空白
SIMPLE_OPTIMIZATIONSSIMPLE默认级别;执行无需外部代码配合的转换(函数参数可重命名,函数名本身不重命名)
ADVANCED_OPTIMIZATIONSADVANCED激进压缩:重命名函数名与变量、删除永不调用的代码等
TRANSPILE_ONLYTRANSPILE_ONLY只转译到目标--language_out并注入 polyfill,不做任何优化

从CompilationLevel.fromString()的实现看,命令行中的SIMPLE/ADVANCED/WHITESPACE会被分别归一化为SIMPLE_OPTIMIZATIONS/ADVANCED_OPTIMIZATIONS/WHITESPACE_ONLY。

WHITESPACE_ONLY

对应applyBasicCompilationOptions(),直接调用options.skipAllCompilerPasses()跳过全部编译器 pass,只保留去除注释与空白的效果。

SIMPLE(默认)

对应applySafeCompilationOptions(),被设计为"安全"选项:即使没有导出任何符号、没有使用任何编码约定,也不会破坏 JS。其关键开关包括:

  • 重命名策略:VariableRenamingPolicy.LOCAL(仅局部变量重命名),PropertyRenamingPolicy.OFF(属性不重命名);
  • 变量内联Reach.LOCAL_ONLY、函数内联Reach.LOCAL_ONLY(仅限局部);
  • 开启常量折叠、变量声明合并、死赋值消除、对象字面量合并、标签重命名、隐藏副作用保护等;
  • 关闭ReplaceIdGenerators(id 生成器替换在 SIMPLE 模式下不运行)。

ADVANCED

对应applyFullCompilationOptions(),要求用户正确导出所有符号才有效。它在"全部安全优化"基础上叠加了大量激进优化:

  • 开启符号与类型检查(setCheckSymbols(true)、setCheckTypes(true));
  • 重命名策略升级为VariableRenamingPolicy.ALL+PropertyRenamingPolicy.ALL_UNQUOTED——所有变量与不带引号的属性都会重命名,这正是 README 反复警告"属性访问必须一致、全局变量与属性使用不可隐藏"的根本原因;
  • 移除 Closure 断言(setRemoveClosureAsserts(true))、移除抽象方法、移除未使用的原型属性与类属性;
  • 属性折叠至PropertyCollapseLevel.ALL、匿名函数折叠、智能名称移除、常量变量内联;
  • 函数与变量内联升至Reach.ALL(全局级别);
  • 开启跨 chunk 代码移动与方法移动、方法去虚拟化(devirtualize)、调用优化(optimize calls)。

此外,setTypeBasedOptimizationOptions()仅在ADVANCED_OPTIMIZATIONS下生效,额外启用基于类型信息的属性消歧(disambiguate)、属性混淆(ambiguate)、属性内联与类型驱动的局部优化——这些正是"类型注解"在 ADVANCED 模式下价值巨大的原因。

在测试侧,test/com/google/javascript/jscomp/CommandLineRunnerTest.java 直观展示了级别差异:testAdvancedModeRemovesUnusedParams用--compilation_level=ADVANCED_OPTIMIZATIONS编译window.f = function(a) {};,输出为window.a = function() {};(未使用参数a被重命名);testCheckGlobalThisOnWithAdvancedMode则验证 ADVANCED 模式下function f() { this.a = 3; }会触发GLOBAL_THIS检查——因为applyFullCompilationOptions()将GLOBAL_THIS从 SIMPLE 模式的OFF提升为WARNING。

七、externs 机制与运行环境

README 强调必须"使用 externs 文件告知编译器哪些变量或属性不得删除或重命名",默认 externs 声明了标准 JS 与 DOM 全局 API。这一机制在源码中的实现要点如下:

  • src/com/google/javascript/jscomp/DefaultExterns.java 维护内置 externs 清单:核心语言 externs(es3.js、es5.js、es6.js、es6_typed_array.js、es6_collections.js)在任何环境下都会加载;当--env=BROWSER时,再按依赖顺序加载浏览器 externs(intl.js、w3c_dom1.js、gecko_dom.js、ie_dom.js、webkit_dom.js、w3c_css.js、webstorage.js、whatwg_console.js等)。当--env=CUSTOM时,只包含上述核心语言 externs,不加载任何浏览器 API 声明。
  • 实际的 externs 文件位于 externs 目录(浏览器相关在 externs/browser),NodeJS 相关 externs 在 contrib/nodejs。README 的依赖清单一节说明这些 NodeJS externs 源自 dcodeIO 的 node.js-closure-compiler-externs,并做了大量改动以兼容 NpmCommandLineRunner。
  • 使用--externs标志可传入自定义 externs 文件(可多次指定),典型场景是:编译 Web 应用时补充项目私有全局对象,或让外部代码访问编译产物中的某些 API。

从CommandLineRunner.java可以看到--env标志直接映射到CompilerOptions.Environment.BROWSER/CUSTOM,getBuiltinExterns()负责按环境取回对应的内置 externs 集。适用前提提醒:默认环境是浏览器窗口;WebWorker 仅部分支持;NodeJS 相关 externs 不受积极维护(见 contrib/nodejs)。

八、从源码构建编译器(Building the Compiler)

README 提供了完整的自构建流程,适合需要从源码定制或研究编译器实现的场景。

前置条件

前置依赖说明
Java 21 或更高版本用于编译编译器自身的 Java 源码
NodeJS用于生成 Java 编译所需的资源
Git供 Bazel 下载依赖
Bazelisk用于构建编译器的各个 target

安装 Bazelisk

Bazelisk 是 Bazel 的包装器,会根据仓库动态加载合适版本的 Bazel,从而避免因使用错误 Bazel 版本导致的伪错误,也便于在不同项目间切换 Bazel 版本。可通过各主流包管理器安装。

从终端构建

$ bazelisk build //:compiler_uberjar_deploy.jar # 或构建所有内容 $ bazelisk build //:all

从终端测试

$ bazelisk test //:all

仓库有数百个独立测试 target,全量运行需要几分钟;开发期间通常只跑感兴趣的精确测试:

bazelisk test //:$path_to_test_file

测试代码位于 test/com/google/javascript/jscomp(含 400+ 个 Java 测试文件)与 test/com/google/javascript/rhino,另有大量.jsdata转译测试数据。

从 IDE 构建

参考 Bazel 官方 IDE 集成文档。

运行构建产物

构建完成后,JAR 位于bazel-bin/目录,可通过java -jar直接运行,或使用仓库 package.json 提供的脚本(其compile脚本内容为java -jar bazel-bin/compiler_uberjar_deploy.jar):

# java -jar bazel-bin/compiler_uberjar_deploy.jar [...args] yarn compile [...args]

在 Eclipse 中运行

  1. 打开 src/com/google/javascript/jscomp/CommandLineRunner.java,或创建该类的自定义扩展版本;
  2. 在 Eclipse 中运行该类;
  3. 交互模式用法同前文,但需注意 Eclipse 控制台传递Ctrl+D(End of Transmission)的已知问题。

九、获取帮助与参与贡献

获取帮助

官方建议的求助渠道依次为:Closure Compiler Discuss 讨论组、Stack Overflow(google-closure-compiler标签)、官方 FAQ。报告 bug 前务必先确认"这真的是 bug,而不是 Closure Compiler 的工作方式"——尤其对ADVANCED_OPTIMIZATIONS而言——可依次核对官方文档、FAQ、Stack Overflow、讨论组以及"compiler assumptions"(编译器假设)清单,再检索是否已有已知 issue;提交 issue 时附上足够细节与最小复现代码。

贡献规范

  • 所有贡献者必须签署贡献者许可协议(CLA);
  • 提交补丁前先在讨论组确认补丁类型会被接受;
  • Fork 仓库、按编码约定修改、提交 Pull Request,由项目开发者 review 后合入;
  • 无论以何种方式贡献,都请遵守 code_of_conduct.md 中的行为准则。

十、许可证与依赖清单

Closure Compiler 本身基于 Apache License 2.0 发布(见 LICENSE),README 的 "Dependency Licenses" 一节列出了全部关键第三方依赖及其本地改动情况,对合规审计很有参考价值:

  • Rhino(1.5R3,重度修改):JavaScript 解析树数据结构来源,位于 src/com/google/javascript/rhino;包被重新命名空间,删除了与解析树无关的代码,并新增 JSDoc 解析器与静态类型系统;
  • args4j(2.33):命令行参数解析;
  • Guava(31.0.1):Google 核心 Java 库;
  • JSR 305(3.0.1):软件缺陷检测注解;
  • JUnit(4.13):Java 单元测试框架;
  • Protocol Buffers(3.0.2):结构化数据编码;
  • RE2/J(1.3):线性时间正则匹配;
  • Truth(1.1):Java 单元测试断言框架;
  • Ant(1.10.11):Java 构建工具;
  • GSON(2.9.1):JSON 与 Java 对象互转;
  • Node.js Closure Compiler Externs:NodeJS API 类型契约,位于 contrib/nodejs,为兼容 NpmCommandLineRunner 做了大量改动。

结语

综合来看,Closure Compiler 的定位非常明确:它不是又一款"压缩器",而是一个要求你把整个应用、全部使用方式都交给它的全量优化编译器。README 中那些看似劝退的"Important Caveats",恰恰是它在ADVANCED模式下能实现极致压缩的前提——统一属性访问、使用goog.module/goog.require、合理编写 externs、正确导出符号。理解了 CompilationLevel.java 中各模式的开关差异,以及 CommandLineRunner.java 中每个参数的默认值与语义,你就能在自己的项目中做出正确取舍:简单压缩交给其他工具,而需要类型检查、死代码消除、属性重命名与代码拆分的大型应用,则值得按 Closure Compiler 的约束来编写代码,并从ADVANCED模式中拿到最大收益。

  • 编译器
  • 开发工具
  • 前端

【免费下载链接】closure-compiler

A JavaScript checker and optimizer.

项目地址:https://gitcode.com/gh_mirrors/clo/closure-compiler
点击查看免费下载
上一篇:Hydra 0.11 升级到 1.0:为配置文件添加 @package 指令完整指南
下一篇:Kedro 项目模板 README 全解:从安装依赖、运行 Pipeline 到测试、Notebook 与打包

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询