Skip to main content

Babili(babel-minify)

· 16 min read

几天前我们以 MIT 许可证发布了 Babili 的测试版(0.0.1)!

🌐 We released Babili as beta (0.0.1) a few days ago under an MIT license!

在 Babel REPL 里试试,发现什么 bug 或潜在的优化记得反馈给我们!还有一个 #minify 的 Slack 聊天室!

关于为什么新的压缩工具是个好主意,有很多(合理的)问题,所以这篇文章应该能帮到你。

🌐 There are a lot of (valid) questions about why a new minifier is a good idea, so this post should help with that.

TL;DR:Babili 可以接受 ES2015+ 的输入,而目前的大多数压缩工具主要只能处理 ES5,需要在压缩前先把代码转译。随着大家开始直接向客户端发送 ES2015,这一步变得没那么必要了。Babili 也很模块化/灵活(作为一个 Babel preset,它支持用户插件),可以作为 preset 或命令行工具使用。Babili 还可以针对 ES2015+ 做特定优化。

🌐 TL;DR: Babili can accept ES2015+ input, while current minifiers are mostly limited to ES5, requiring code to be transpiled before minification. This is becoming unnecessary as people begin to ship ES2015 to clients. Babili is also modular/flexible (being a Babel preset means it supports user plugins) and can be used as a preset or CLI tool. Babili will also be able to make ES2015+ specific optimizations.

发音​

🌐 Pronunciation

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

Babili在阿卡德语中是巴比伦。

🌐 Babili is Babylon in Akkadian.

如果你记不住名字,babel-minify 也行(我为名字提了个 问题)。

为什么要压缩?​

🌐 Why minify?

在基本层面上,[压缩](https://en.wikipedia.org/wiki/Minification_(programming)会在不改变程序功能的情况下移除不必要的字符——比如注释、空格、换行和多余的括号。高级压缩可能会将程序转换成更小的等价版本,并移除冗余或无法到达的代码。

🌐 At a basic level, minification removes unnecessary characters from a program without changing its functionality -- things like comments, whitespace, newlines, and extra parentheses. Advanced minification may transform programs into smaller equivalents and remove redundant/unreachable code.

压缩主要用于减少从服务器发送到客户端的 JavaScript 负载大小:用户下载的代码更少,就能使用你的网站。高级压缩还可以缩短解析时间(因为要解析的代码少了),在某些情况下还能提升运行速度(比如像函数内联这样的高级优化)。

🌐 Minification is primarily useful for decreasing the size of the JavaScript payload sent from the server to the client: users will download less code to use your website. Advanced minification can also result in shorter parse time (less code to parse) and in some cases faster runtime (e.g. advanced optimizations like function inlining).

当前的压缩工具​

🌐 Current minifiers

像 Uglify 这样的工具目前还不支持针对最新版本的 ECMAScript(还不行:harmony 分支)。

🌐 Tools such as Uglify don't currently support targeting the latest version of ECMAScript (not yet: harmony branch).

我们现在使用像 Babel 这样的工具把 ES2015 代码编译成 ES5 代码,以支持老旧浏览器。然后我们会用类似 Uglify 的工具来缩减打包文件的大小。

🌐 We currently use tools like Babel to compile ES2015 code down to ES5 code to support older browsers. Then we use something like Uglify to cut down on the bundle size.

随着浏览器实现更多的 ES2015 特性,并且我们放弃对旧版本浏览器的支持,你编写的 ECMAScript 版本和你压缩到的目标 ECMAScript 版本之间会有一个滑动窗口。不过由于 Uglify 无法解析 ES2015 及以上的语法,你还是得把代码编译成 ES5。

🌐 As browsers implement more ES2015 features and we drop support for older browser versions, there is a sliding window of the version of ECMAScript you write in and the target ECMAScript version you minify to. However since Uglify cannot parse ES2015+, you would still have to compile down to ES5 anyway.

巴比利​

🌐 Babili

这就是Babili派上用场的地方。

🌐 That's where Babili comes in.

Babili 支持 ES2015+,因为它是使用 Babel 工具链构建的。它以一组 Babel 插件的形式编写,可以通过 babili 预设 使用。

🌐 Babili is ES2015+ aware because it is built using the Babel toolchain. It is written as a set of Babel plugins, consumable with the babili preset.

示例​

🌐 Example

假设我们目标是最新版本的 Chrome、Firefox、Edge 和 Safari——它们都支持 ES2015 类。那么,把 ES2015 类编译成构造函数和原型方法(ES5)会生成更多代码,并且可能丢失浏览器对类优化的任何好处。

🌐 Say we're targeting the latest versions of Chrome, Firefox, Edge, and Safari -- all of which support ES2015 classes. Then, compiling ES2015 classes to a constructor function and prototype methods (ES5) results in more code (and potentially loses any optimizations browsers might have for classes).

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

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

🌐 Before, we might run Babel to transpile the class into a function and run Uglify on the compiled code to send to the browser.

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

使用 Babili,你只需运行适用于 ES2015 代码的压缩器就行了。

🌐 With Babili, you can just run the minifier which works on ES2015 code.

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 版本。

🌐 Also it's important to note that this isn't specific to ES2015. Because Babel updates as ECMAScript updates (with ES2015, ES2016, and now ES2017) and follows the proposal process for experimental features (with our stage-x presets), the minifier should be able to output whatever version of ECMAScript that is supported.

将来,我们可能会利用在编译时掌握的 ES2015+ 语法信息(例如,我们知道某个函数是箭头函数,或者某个绑定是块级作用域等)来进行高级优化。我们也可以创造性地利用我们面向的是 ES2015+ 环境这个事实。

🌐 In the future, we might make use of the ES2015+ syntax information we have at compile time (e.g. we know that given function is an arrow function or that a given binding is block-scoped etc) to do advanced optimizations. And we can make use of the knowledge that we're targeting an ES2015+ environment in creative ways.

我们才刚刚开始,所以如果你有任何想法,告诉我们吧!

🌐 We're just getting started so let us know if you have any ideas!

一些插件​

🌐 Some of the Plugins

来大致了解一下其中的一些变换:

🌐 To give an idea of some of the transforms:

babel-plugin-minify-constant-folding:

尝试计算表达式并内联结果。目前只处理数字和字符串。

🌐 Tries to evaluate expressions and inline the result. For now only deals with numbers and strings.

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:

有上下文和作用域意识的变量重命名。

🌐 Context- and scope- aware variable renaming.

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

用法​

🌐 Usage

Babel 预设​

🌐 Babel Preset

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

🌐 If you already use Babel, you can just add the babili preset (babel-preset-babili) to your config.

你只会想在生产环境中启用这个,所以使用 env 选项,它会使用 process.env.BABEL_ENV 或 process.env.NODE_ENV

🌐 You will want to enable this only in production, so use the env option which uses either process.env.BABEL_ENV or process.env.NODE_ENV

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

使用 Babili 作为预设的一个问题是,Babili 只会对每个文件运行,而不是整个打包文件。通常像 webpack 里的 "UglifyJsPlugin" 一样,代码压缩是在打包之后进行的。不过,打包后再压缩的话,就会失去在同一步做转换/压缩带来的速度优势(需要测量)。另外,这也是我们需要考虑的事情:如何把更多信息传给打包器,或者怎么整合它。

🌐 One issue with using Babili as a preset is that then Babili would only run per-file rather than on the whole bundle. Minification usually happens after bundling as with the "UglifyJsPlugin" in webpack. However, running it after bundling would lose the speed benefits (needs to be measured) of doing the transpiling/minification in the same step. Again, this is something we need to think about: how to integrate or pass more information to the bundler.

Babili 命令行工具​

🌐 Babili CLI

如果你不使用 Babel,你可以使用我们的独立 CLI 工具 babili。(目前它只是 babel-cli + 预设的一个封装器)。在转译(或者不转译)之后,你可以用它来替代 Uglify。

🌐 If you don't use Babel, you can use our standalone CLI tool babili. (Currently it's just a wrapper for babel-cli + the preset). You could run this after transpiling (or not) in place of 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 的预设。

🌐 You can just use the preset with 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 使用(由 @boopathi 制作,他也参与了 Babili 的开发)。

🌐 Or use it separately with the babili-webpack-plugin (made by @boopathi, who also works on Babili).

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。

🌐 We want to have a better story with integration with Webpack/bundlers in the near future! Also check out #100.

优点/缺点​

🌐 Pros/Cons

Uglify 优点

  • 如果你已经在进行压缩,现有工具无需更改。
  • 经过战斗考验/生产就绪(已经存在多年并被广泛采用(我们大家都在用)!)
  • 它非常快,而且已经输出很小的代码了。

Uglify 的缺点

  • 自定义解析器/工具,所以很难输出/压缩 ES2015+ 并进行修改。
  • 不模块化,目前也没有办法在核心之外创建自己的插件/压缩策略。

Babili 优点:

  • 支持 ES2015 及以上(不需要特别处理,因为我们可以使用 Babylon 解析器),而且 Babel 会随着标准和浏览器的更新而更新。
  • 使用现有的 Babel 工具链,可以作为 Babel 预设或独立使用。
  • 为 React/Flow 等自定义智能转换的潜力
  • 可以用 Flow/TypeScript 注解来实现高级压缩。
  • 每个压缩步骤都可以拆分成自己的插件,而且有很多定制选项。这让贡献代码和查找/提交特定问题都更容易。这也意味着人们可以独立创建自己实验性的插件,然后再把它们合并到核心中。
    • 例如:这个 只是把 true 变成 !0,写起来很直接。
  • 如果大家已经熟悉用 Babel 进行转译的话,过渡应该很容易。

Babili 缺点:

  • 我们提前发布了它,所以目前用户还不多。早期用户一开始得应付一个没有像 Uglify 那样经过大量实战检验的工具。
  • 目前,在我们的基准测试中,性能更差/体积比 Uglify 更大。不过,这是我们接下来会重点改进的地方。

TL;DR:Babili 应该能够跟上 ECMAScript 标准的新特性,同时还能面向你需要支持的环境。它潜力很大:目前可能没有 Uglify 那么适合生产环境,因为它刚刚发布,但随着更多用户的优化,它应该足够强大。

🌐 TL;DR: Babili should be able to keep up with the ECMAScript standard as new features get added as well as target the environments you need to support. It has a lot of potential: it may not be as production-ready as Uglify at the moment since it was just released but as we continue to optimize with more users it should be more than capable.

如何帮忙​

🌐 How to Help

Amjad 已经在这个项目上工作了一段时间,但我们决定提前以测试版的形式发布,让社区可以试用,同时通过报告错误和提供补丁来贡献。

这个项目还处于早期阶段,所以还有很多事情需要帮忙!我们接下来的重点是让 Babili 更稳定、更可靠,以便推出 1.0.0 版本。

🌐 It's still early days for this project so there's a lot to help out with! Our next priority is to make Babili more stable/robust for a 1.0.0 release.

我们将努力让它尽可能快,并在简单模式下生成的代码大小尽量像 Uglify/Closure Compiler 一样小。

🌐 We will be working to get it as fast and produce code sizes as small as Uglify/Closure Compiler in simple mode.

  • 在更多代码库上测试:这会对大家有很大帮助。因为压缩器会运行在所有代码上,所以它可能会遇到很多我们基本单元测试覆盖不到的边缘情况/bug。希望我们能建立一个方便报告问题的方式;现在 repl 支持压缩器,应该更容易复现/关联 bug。未来我们希望能有启用特定插件的选项,这样就能更精确地定位最小复现步骤。
  • 项目基础设施/维护:我们想要创建更健全的基准测试,在流行的开源项目上设置集成测试(先运行压缩工具,然后运行项目的所有单元测试)。
  • 检查输出:如果有些东西可以更简化,创建一个问题并建议对现有插件进行新的转换或者创建一个新插件应该很简单。我们有模块化的优势,所以任何人也可以自己制作插件,然后我们可以再决定是否将它们包含在核心预设中。

非常感谢 Amjad (@amasad) 启动这个项目,也感谢 Facebook 允许我们将其作为 MIT 许可证项目在 Babel 组织下发布!Sebastian (@kittens) 显然在这个过程中起了很大作用,因为没有 Babel 这件事根本不可能实现。还要感谢 James (@thejameskyle)、Juriy (@kangax) 帮助把这个项目顺利发布!同时也要给 Boopathi (@boopathi) 一个 shoutout,我们看到他自己的 babel-minify 项目后,邀请他来帮忙!

🌐 Huge thanks to Amjad (@amasad) for starting this project and Facebook for allowing us to release this under the Babel organization as an MIT licensed project! Sebastian (@kittens) was obviously a big part of this given this wouldn't have been possible without Babel. Also thanks to James (@thejameskyle), Juriy (@kangax) for helping see this through to release! Also want to give a shoutout to Boopathi (@boopathi) who we invited to help us out after seeing the work on his own babel-minify project!