升级Babel到7版本

8月底Babel官方宣布正式推出 Babel 7.0。这次 Babel 7 的发布,距离上次 Babel 6 的发布差不多过去了整整 3 年的时间。之前的两篇文章都是基于Babel 6,最近我们升级了Babel 7,总结一下变化。

“babel 7”的图片搜索ç"“æžœ

主要变化

这次更新号称断崖式变更,更新内容较多,我会挑选一些我注意到的重要变化,完整的列表可以参考官网,并且肯定还有很多东西需要动态调整,所以大家有什么觉得不对的地方建议去官网验证并且回来留言告诉我。

Renames(命名变化)

Scoped Packages

Babel 团队会从7版本开始通过使用 “scoped” packages 的方式,来给自己的 babel package name 加上 @babel 命名空间(详情),这样以便于区分官方 package 以及 非官方 package,所以 babel-core 会变成 @babel/core。至于选择scoped packages更多的好处可以看这里

plugin统一使用proposal

放弃 Stage presets(@babel/preset-stage-0 等),选择支持单个 proposal。相似的地方还有,会默认移除 @babel/polyfill对 proposals 支持(详情)。想知道更多相关的细节,可以考虑阅读整篇博文

所有 TC39 proposal plugin 的名字都已经变成以 @babel/plugin-proposal 开头,替换之前的 @babel/plugin-transform详情)。有些 package 已经换名字,所以 @babel/plugin-transform-class-properties 变成 @babel/plugin-proposal-class-properties

Drop the year from the plugin name

如果插件名称中包含了规范名称 (-es2015-, -es3- 之类的),一律删除。例如 babel-plugin-transform-es2015-classes 变成了 @babel/plugin-transform-classes

presets

弃用年份 presets

弃用(并且停止发布)所有与 yearly 有关的 presets(preset-es2015 等)(详情)。@babel/preset-env 会取代这些 presets,这是因为 @babel/preset-env 囊括了所有 yearly presets 的功能,而且@babel/preset-env 还具备了针对特定浏览器进行“因材施教”的能力,而不需要开发者投入精力。凡是使用 es201x 的开发者,都应当使用 env 进行替换。但这里的弃用(原文 deprecated)并不是删除,只是不推荐使用了(好像马上就准备删了)。

弃用 stage-x

如上面所说,stage-x 不再维护了。这是因为 babel 团队认为为这些 “不稳定的草案” 花费精力去更新 preset 相当浪费。stage-x 虽然停止维护(同样也是deprecated),但它包含的插件并没有删除(只是被更名了),我们依然可以显式地声明这些插件来获得等价的效果。完整列表

babel-runtime

拆分 babel-runtime

@babel/runtime 是 babel 生态里最让人困惑的一个包,好像到处都有。而在 babel 7 下,我们还多了一个 @babel/runtime-corejs2。之前的 @babel/runtime 已经被分成 @babel/runtime 以及 @babel/runtime-corejs2PR)。前者保留了 regenerator-runtime 函数和 helpers,后者保留了 polyfill 函数(例如,Symbol,Promise,其实就是拆出了core-js,运行时扩展包括的两个内容拆开了)、regenerator-runtime 函数和 helpers(目前看到是这样的)。corejs2的使用方法如下:

1
2
3
"plugins": [
["@babel/plugin-transform-runtime",{"corejs": 2}]
]

所以现在使用@babel/plugin-transform-runtime搭配@babel/runtime的话其实只有 regenerator 的转换,填充是没有的,具体配置请看这里(设为true的话是从core-js作填充,所以必须要装core-js,不过装@babel/register的话会带core-js)。

Remove proposal polyfills in @babel/polyfill

基于和废弃stage相似的思想,@babel/polyfillSource)中的 polyfill proposals也被删除。现在@babel/polyfill 几乎就是@babel/runtime-corejs2的一个别名,包含的内容只有core-jsregenerator-runtime(说几乎可能是因为@babel/runtime-corejs2多了helpers)。

自动 polyfill(实验性质)

在环境不支持一些新特性如 Promise,Symbol,而你需要开启的情况下,就需要 Polyfills。能够区分得出 Babel 在作为编译器(转换语法)以及 作为 polyfill(实现内置的方法/对象)都做了哪些事情,这很重要。

之前我们只需要简单的引入一套完整的解决方案,比如 @babel/polyfill

1
import "@babel/polyfill";

但是 @babel/polyfill 会把整个 polyfill 都 import 进来(something that covers everything),而且如果浏览器已经支持一些特性,那么就没有必要把整个 polyfill 给 import 进来。@babel/preset-env 所解决的语法问题和 polyfill 所解决的问题,其实是同一个问题——兼容性,所以在这里会把 @babel/preset-env 应用在解决 polyfill 问题上。设置 useBuiltins: “entry” 可以解决整个 polyfill 被 import 的问题(根据target中浏览器版本的支持,将polyfills拆分引入,仅引入不支持的polyfill)。

但其实我们能做的更好,我们可以只 import 代码中用到的 polyfill,最好可以检测代码中ES6/7/8等的使用情况,仅仅加载代码中用到的polyfills。设置 useBuiltIns: “usage” 就可以开启类似的功能(文档)。

tip:这到底是如何做到的呢?对于JS这种动态语言来说,只静态分析应该无法做到这一步,必须要加载执行后才可以,还需要经过大量测试,解析AST甚至是模拟引擎执行?

JavaScript 配置文件

babel.config.js

Babel 7 正在引入babel.config.js。注意:使用 babel.config.js 并不是一个必要条件;或者也可以这样说,babel.config.js甚至不是 .babelrc 的替代品,不过在一些特定的场景下,使用 babel.config.js 还是有帮助的。

*.js 来做配置文件的做法,在 JavaScript 的生态里面已经相当常见。ESlint 和 Webpack 都已经考虑到用 .eslintrc.js 以及 webpack.config.js 来做配置文件。

下面说的就是这个例子 —— 只会在“生产”环境中使用 babel-plugin-that-is-cool 插件来编译(当然,你也可以在 .babelrc 配置文件,通过配置 env 配置项的方式来做这件事):

1
2
3
4
5
6
var env = process.env.NODE_ENV;
module.exports = {
plugins: [
env === "production" && "babel-plugin-that-is-cool"
].filter(Boolean)
};

.babelrc的查找行为

Babel6会在正在被转录的文件的当前目录中查找一个 .babelrc 文件。 如果不存在,它会遍历目录树,直到找到一个 .babelrc 文件,或一个 package.json 文件中有 "babel": {} 。比如使用babel-register,你可以使用选项,包括 pluginspresets等等,但是每个文件最接近的 .babelrc 仍然适用,并且优先于你在此处传入的任何选项,如果不想查找只能在选项中使用 “babelrc”: false 来停止查找行为,或者提供–no-babelrc CLI 标志。

.babelrc 相比,针对查找配置文件的这件事,Babel 对 babel.config.js 有着不同的解决方案。Babel 总是会先从 babel.config.js 来获取配置内容。和 .babelrcpackage.json中的 babel 字段不同,这个配置文件不会使用基于文件位置的方案,而是会一致地运用到项目根目录以下的所有文件,包括 node_modules 内部的依赖。

这样就可以利用发布的下一个功能,overrides

使用 overrides,实现配置有选择性的“表达”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
presets: [
// defeault config...
],
overrides: [{
test: ["./node_modules"],
presets: [
// config for node_modules
],
}, {
test: ["./tests"],
presets: [
// config for tests
],
}]
};

这样就实现在一个项目当中,可以针对测试代码、客户端代码以及服务端代码使用不同的编译配置(compilation configs),就很好的避免了需要你为每个文件夹都创建一个新的 .babelrc 配置文件的情况。

更多

Peer dependencies

为所有的 plugins(例如@babel/plugin-class-properties),presets(@babel/preset-env),and 顶级包(@babel/clibabel-loader),都加上了 @babel/core 作为 peerDependency详情)。

peerDependencies are dependencies expected to be used by your code, as opposed to dependencies only used as an implementation detail — Stijn de Witt via StackOverflow

babel-loader 已经把 babel-core作为peerDependency,所以只是改成了@babel/core

支持 “Pure” 注释

#6209 之后,Babel 编译 ES6 class 之后的代码,会带有 /*#__PURE__*/ 注释,至于为啥要这样做呢?是因为 Babel 要给压缩工具留一些线索,以便于压缩工具,比如 Uglifybabel-minify 去掉 dead code。这些注释也会用于其它的 helper 函数。

1
2
3
4
5
6
7
8
class C {
m() {}
}
var C =
/*#__PURE__*/
function () {
// ...
}();

@babel/node 从 @babel/cli 中独立

和 babel 6 不同,如果要使用 @babel/node,就必须单独安装,并添加到依赖中。

不再支持低版本 node

对那些已经不维护的 node 版本不予支持,包括 0.10、0.12、4、5(详情),相当于要求 nodejs >= 6。

这里的不再支持,指的是在这些低版本 node 环境中不能使用 babel 转译代码,但 babel 转译后的代码依然能在这些环境上运行,这点不要混淆。

babel-upgrade

babel-upgrade,Babel 团队开发的新工具,旨在用来处理升级过程中的琐事(changes):目前只是针对 package.jsondependencies 以及 .babelrc配置,它会检测 babel 配置中的 stage-x 并且替换成对应的 plugins。除此之外它还有其他功能,但大目标就是让你更加平滑地迁移到 babel 7。

Babel 团队推荐在项目里面直接运行 npx babel-upgrade,或者你可以通过 npm install -g babel-upgrade 的方式,在全局安装 babel-upgrade

如果你想修改文件,你可以传 --write 以及 --install

1
npx babel-upgrade --write --install

这款升级工具的功能包括:(这里并不列出完整列表,只列出比较重要和常用的内容)

  • package.json
  1. 把依赖(和开发依赖)中所有的 babel-* 替换为 @babel/*
  2. 并把这些 @babel/* 依赖的版本更新为最新版 (例如 ^7.0.0),如果 scripts 中有使用 babel-node,自动添加 @babel/node 为开发依赖
  3. 如果有 babel 配置项,检查其中的 pluginspresets,把短名(env) 替换为完整的名字 (@babel/preset-env
  • .babelrc
  1. 检查其中的 pluginspresets,把短名(env)替换为完整的名字(@babel/preset-env
  2. 检查是否包含 preset-stage-x,如有替换为对应的插件并添加到 plugins

总结

v7包名(v6包名) 作用 备注
@babel/cli(babel-cli) 命令行使用bable命令 少了一些包,并把@babel/core作为peerDependencies
@babel/node(babel-node) 命令行直接转译 + 执行node文件 image-20181116171602076
依然是babel-polyfill+babel-register,同样把@babel/core作为了peerDependencies
@babel/register(babel-register) 改写require命令,为其加载的文件进行转码,但是不会对当前文件转码,包含了corejs image-20181116173312093
同样把@babel/core作为了peerDependencies
@babel/polyfill(babel-polyfill) 现在包括corejs和regenerator image-20181116171914153
@babel/runtime(babel-runtime) 现在包括regenerator和helpers image-20181116214155724
@babel/runtime-corejs2 现在包括corejs、regenerator和helpers image-20181116171806763
@babel/plugin-transform-runtime(babel-plugin-transform-runtime) 让 babel 自动且按需的进行 polyfill(不污染全局变量和prototype) 同样把@babel/core作为了peerDependencies
image-20181116171624202
@babel/core(babel-core) 原来只是在代码里使用 babel,现在目前来看基本是最重要的一个包了,不管装什么都对把其作为peerDependencies image-20181116171551115
@babel/preset-env 整合了 es201x,并自动根据目标平台分析需要用哪些插件 plugin太多,不截图了,把除了babel开头的截一下,同样把@babel/core作为了peerDependencies
image-20181120222334071
babel-loader(babel-loader) 使用webpack时作为一个loader,在代码混淆前进行转码 新版把@babel/core作为peerDependencies
babel-eslint(babel-eslint) 使用eslint时,进行前置转码 依然把eslint作为Peer Dependencies

暂时先写到这里,其实核心机制方面没有变化,插件,preset,解析转译生成这些都没有变化,但是断舍离方面改动很大。重点看应用吧,下一篇讲应用。

参考链接

Babel 7 于今天发布

Nearing the 7.0 Release/Babel 7 发布