跳至主内容

Babili (babel-minify)

· 1 分钟阅读
非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

几天前我们以 MIT 许可协议发布了 Babili 的 beta 版本 (0.0.1)!

欢迎在 Babel REPL 中试用,并报告任何错误或可能的优化建议!我们还开设了 #minify Slack 频道!

关于为何需要新压缩工具存在许多(合理的)疑问,本文将解答这些问题。

TL;DR:Babili 能直接处理 ES2015+ 代码,而现有压缩工具大多仅支持 ES5,要求代码在压缩前先转译。随着开发者开始向客户端直接交付 ES2015 代码,这种转译步骤变得不再必要。Babili 还具有模块化/灵活性(作为 Babel 预设支持用户插件),可作为预设或 CLI 工具使用。未来 Babili 还将实现 ES2015+ 专属优化。

发音说明

Shell
# sounds like "bah billy" or "Babadi" (from DBZ)
say Babili

Babili 是阿卡德语中"巴比伦"的意思。

如果记不住名称,使用 babel-minify 也可以(我们为此创建了讨论)。

为何需要代码压缩?

基础层面的代码压缩会移除程序中不影响功能的冗余字符——例如注释、空格、换行符和多余括号。高级压缩则能将代码转换为更小的等效形式,并消除冗余/不可达代码。

代码压缩主要能减小从服务器传输到客户端的 JavaScript 负载:用户需要下载的代码量将减少。高级压缩还能缩短解析时间(需解析的代码更少),某些情况下甚至能提升运行时性能(例如通过函数内联等高级优化)。

现有压缩工具

Uglify 等工具目前尚不支持最新版 ECMAScript(暂未实现:参见 harmony 分支)。

当前工作流中,我们先用 Babel 等工具将 ES2015 代码编译为 ES5 以兼容旧版浏览器,再用 Uglify 等工具缩减包体积。

随着浏览器支持更多 ES2015 特性且我们逐步放弃旧版浏览器,开发者编写的 ECMAScript 版本与压缩目标版本之间存在动态窗口。但由于 Uglify 无法解析 ES2015+ 代码,您仍需将代码降级到 ES5。

Babili

这正是 Babili 的价值所在。

Babili 基于 Babel 工具链构建,天生支持 ES2015+。它由一系列 Babel 插件实现,可通过 babili 预设调用。

示例

假设我们目标环境是 Chrome、Firefox、Edge 和 Safari 的最新版本——它们均支持 ES2015 类。若将 ES2015 类编译为构造函数和原型方法(ES5),不仅会产生更多代码(还可能丧失浏览器对类的原生优化优势)。

JavaScript
class Mangler {
constructor(program) {
this.program = program;
}
}
// need this since otherwise Mangler isn't used
new Mangler();

以前,我们可能会运行 Babel 将类转换为函数,然后在编译后的代码上运行 Uglify 再发送到浏览器。

JavaScript
// ES2015 code -> Babel -> Uglify/Babili -> Minified ES5 Code
var a=function a(b){_classCallCheck(this,a),this.program=b};a();

使用 Babili 时,你可以直接运行这个支持 ES2015 代码的压缩工具。

JavaScript
// ES2015 code -> Babili -> Minified ES2015 Code
class a{constructor(b){this.program=b}}new a;

另外值得注意的是,这并非仅限于 ES2015。由于 Babel 会随 ECMAScript 更新而演进(包括 ES2015、ES2016 和现在的 ES2017),并遵循实验性功能的提案流程(通过我们的 stage-x 预设),该压缩工具应能输出任何受支持的 ECMAScript 版本。

未来我们可能会利用编译时获得的 ES2015+ 语法信息(例如识别箭头函数或块级作用域绑定等)进行高级优化,并能创造性地运用面向 ES2015+ 环境的知识。

我们才刚刚起步,欢迎随时提出建议!

部分插件

以下是部分转换示例:

babel-plugin-minify-constant-folding

尝试对表达式求值并内联结果,目前仅支持数字和字符串处理。

JavaScript
2 * 3;
"b" + a + "c" + "d" + g + z + "f" + "h" + "z"
JavaScript
6;
"b" + a + "cd" + g + z + "fhz";

babel-plugin-minify-mangle-names

基于上下文和作用域的变量重命名。

JavaScript
var globalVariableName = 42;
function foo() {
var longLocalVariableName = 1;
if (longLocalVariableName) {
console.log(longLocalVariableName);
}
}
JavaScript
var globalVariableName = 42;
function foo() {
var a = 1;
if (a) {
console.log(a);
}
}

用法

Babel 预设

如果已在使用 Babel,只需在配置中添加 babili 预设(babel-preset-babili)。

建议仅在生产环境启用,可通过 env 选项配合 process.env.BABEL_ENVprocess.env.NODE_ENV 实现。

Shell
$ npm install babel-preset-babili --save-dev
JavaScript
// previous .babelrc
{ "presets": ["es2015"] }
// .babelrc
{
"presets": ["es2015"],
"env": {
"production": {
"presets": ["babili"]
}
}
}

将 Babili 作为预设使用时存在一个问题:它会在单个文件而非整个打包文件上运行。通常压缩在打包后执行(如 webpack 的 "UglifyJsPlugin"),但这样会丧失转译/压缩同步骤执行的速度优势(需实测验证)。这需要我们思考如何与打包工具集成或传递更多信息。

Babili CLI

若不使用 Babel,可通过独立 CLI 工具 babili(目前是 babel-cli + 预设的封装)在转译后(或直接)运行以替代 Uglify。

Shell
$ npm install babili --save-dev
Shell
$ babili src -d lib
# equivalent to
# babel src -d lib --presets=babili --no-babelrc

Webpack

通过 babel-loader 直接使用预设即可。

Shell
$ npm install babel-core babel-loader babel-preset-babili
JavaScript
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel',
query: {
presets: ['babili']
}
}
]
}

或使用独立插件 babili-webpack-plugin(由同样参与 Babili 开发的 @boopathi 创建)。

Shell
$ npm install babili-webpack-plugin --save-dev
JavaScript
// webpack.config.js
const BabiliPlugin = require("babili-webpack-plugin");
module.exports = {
entry: //...,
output: //...,
plugins: [
new BabiliPlugin(options)
]
}

我们计划近期优化与 Webpack/打包工具的集成方案!另请关注 #100

优缺点

Uglify 优点

  • 若已有压缩流程则无需改动工具链

  • 久经考验/生产就绪(已存在多年且被广泛采用,我们都在使用它)!

  • 速度极快且已能生成紧凑的代码。

Uglify 缺点

  • 采用自定义解析器/工具链,因此难以输出/压缩 ES2015+ 代码并进行修改。

  • 非模块化架构,目前无法在核心外创建自定义插件或压缩策略。

Babili 优点:

  • 原生支持 ES2015+(借助 Babylon 解析器无需特殊处理),且会随标准/浏览器更新而同步升级。

  • 基于现有 Babel 工具链,可作为 Babel 预设或独立工具使用。

  • 具备为 React/Flow 等开发定制智能转换的潜力。

  • 可利用 Flow/Typescript 类型注解实现高级压缩优化。

  • 每个压缩步骤均可拆分为独立插件,提供高度可定制性。这降低了贡献门槛,便于定位/提交具体问题,也方便开发者先独立创建实验性插件再提交至核心。

    • 示例:此插件仅将 true 转为 !0,实现非常简洁。
  • 若用户已熟悉 Babel 转译流程,迁移使用将非常顺畅。

Babili 缺点:

  • 目前处于早期发布阶段,用户基数较小。早期采用者需面对工具未经大规模实战检验的问题。

  • 当前基准测试中性能/压缩率暂不及 Uglify,但我们将重点优化此方面。

TL;DR:Babili 既能跟进 ECMAScript 标准的新特性,也能适配目标运行环境。虽然初版稳定性不及 Uglify,但随着优化推进和用户增多,其潜力巨大。

如何参与贡献

Amjad 开发此项目多时,我们决定提前发布 beta 版供社区测试,欢迎提交错误报告和补丁。

项目尚处早期阶段,亟需多方助力!下一步重点是提升稳定性/健壮性以实现 1.0.0 正式版。

我们将努力使其在简单模式下达到 Uglify/Closure Compiler 的编译速度和代码体积。

  • 更多代码库测试:这对所有用户都至关重要。因压缩器需处理全量代码,可能遇到单元测试未覆盖的边缘情况。期望建立便捷的问题反馈机制;现已支持 REPL 在线复现问题。未来计划开放插件级调试选项,便于精准定位问题。

  • 项目基建/维护:需建立更健壮的基准测试,在流行开源项目中设置集成测试(运行压缩器后执行项目单元测试)。

  • 检查输出结果:若发现可优化处,欢迎直接提交问题报告并提出转换方案。得益于模块化设计,开发者可独立创建插件,我们会评估是否将其纳入核心预设。

非常感谢 Amjad (@amasad) 启动这个项目,也感谢 Facebook 允许我们在 Babel 组织下以 MIT 许可证发布该项目!Sebastian (@kittens) 显然在其中发挥了关键作用——没有 Babel 就不会有这一切。同时感谢 James (@thejameskyle)Juriy (@kangax) 协助推动项目发布!还要特别提及 Boopathi (@boopathi),在看到他自己开发的 babel-minify 项目后,我们邀请他加入了开发团队!