Babel 团队在经历了差不多 2 年磨一剑之后,终于在今天宣布 —— Babel 7 发布啦。这次 Babel 7 的发布,距离上次 Babel 6 的发布差不多过去了整整 3 年的时间!另外,Babel 团队成员 Henry Zhu 还提醒大家,在 Babel 7 发布的这周内,肯定还有很多东西需要动态调整,所以他希望大家能够理解(理解万岁!!!)。
babel-core
会变成 @babel/core
preset-es2015
等)(详情)。@babel/preset-env
会取代这些 presets
,这是因为 @babel/preset-env
囊括了所有 yearly presets
的功能,而且 @babel/preset-env
还具备了针对特定浏览器进行“因材施教”的能力Stage
presets(@babel/preset-stage-0
等),选择支持单个 proposal。相似的地方还有,会默认移除 @babel/polyfill
对 proposals 支持(详情)。想知道更多相关的细节,可以考虑阅读整篇博文@babel/plugin-proposal
开头,替换之前的 @babel/plugin-transform
(详情)。所以 @babel/plugin-transform-class-properties
变成 @babel/plugin-proposal-class-properties
@babel/core
加上 peerDependency
(详情)babel-upgrade
,Babel 团队开发的新工具,旨在用来处理升级过程中的琐事(changes):目前只是针对 package.json
的 dependencies
以及 .babelrc
配置。
Babel 团队推荐在 git 项目里面直接运行 npx babel-upgrade
,或者你可以通过 npm install -g babel-upgrade
的方式,在全局安装 babel-upgrade
。
如果你想修改文件,你可以传 --write
以及 --install
。
npx babel-upgrade --write --install
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
配置项的方式来做这件事):
var env = process.env.NODE_ENV;
module.exports = {
plugins: [
env === "production" && "babel-plugin-that-is-cool"
].filter(Boolean)
};
与 .babelrc
相比,针对查找配置文件的这件事,Babel 对 babel.config.js
有着不同的解决方案。Babel 总是会从 babel.config.js
来获取配置内容,与之相对的是,Babel 会针对每个文件都做一次向上查找的操作,直至找到配置文件为止。这使得 babel.config.js
新特性 overrides (下文将会提到)的使用变成了可能。
overrides
,实现配置有选择性的“表达”module.exports = {
presets: [
// defeault config...
],
overrides: [{
test: ["./node_modules"],
presets: [
// config for node_modules
],
}, {
test: ["./tests"],
presets: [
// config for tests
],
}]
};
这样就实现在一个项目当中,可以针对测试代码、客户端代码以及服务端代码使用不同的编译配置(compilation configs),就很好的避免了需要你为每个文件夹都创建一个新的 .babelrc 配置文件的情况。
Babel 团队认为,Babel 本身运行的速度就很快,所以 Babel 应该花更少的时间在构建上!为了优化代码,Babel 团队做了很多改变,也接受了来自 v8 团队的补丁。
Babel 支持(开发者)在配置文件中使用 preset 和 plugin 配置项,已经有一段时间啦。简要的概括一下,(就是)你可以把插件的名称放在一个数组,然后把配置对象传给该插件:
{
"plugins": [
- "pluginA",
+ ["pluginA", {
+ // options here
+ }],
]
}
针对一些插件的 loose
配置项,Babel 7 做了一些改动,并且还给其它的插件增添了一些新的配置项!注意,使用这些(新增的)配置项,可能在一些情况下,会导致一些问题。比如在你不编译的情况下,直接使用 JavaScript 原生支持的语法。
classCallCheck
helper 函数。class A {}
var A = function A() {
- _classCallCheck(this, A);
};
["transform-for-of", { "assumeArray": true }]
let elm;
for (elm of array) {
console.log(elm);
}
let elm;
for (let _i = 0, _array = array; _i < _array.length; _i++) {
elm = _array[_i];
console.log(elm);
}
preset-env
的情况下,不再使用 transform-typeof-symbol
插件的 loose
模式在 #6209 之后,Babel 编译 ES6 class 之后的代码,会带有 /*#__PURE__*/
注释,至于为啥要这样做呢?是因为 Babel 要给压缩工具留一些线索,以便于压缩工具,比如 Uglify、babel-minify 去掉 dead code。这些注释也会用于其它的 helper 函数。
class C {
m() {}
}
var C =
/*#__PURE__*/
function () {
// ...
}();
下面列出的是,Babel 所支持的一些新语法,并且有一些语法已经被 Babel 7 原生支持:
使用 @babel/preset-typescript
,可以让 Babel 解析/转换 typescript 的 type 语法,同样的事情还有,使用 @babel/preset-flow
可以让 Babel 处理 flow
。
获取更多详情,可以去看 TypeScript team 写的这篇博文
在没有使用 @babel/preset-typescript
之前(包含 type 语法):
interface Person {
firstName: string;
lastName: string;
}
function greeter(person : Person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
在使用 @babel/preset-typescript
之后(移除 type 语法)
function greeter(person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
早在 Babel v7.0.0-beta.31,就已经支持 JSX Fragment。
render() {
return (
<>
<ChildA />
<ChildB />
</>
);
}
// output ?
render() {
return React.createElement(
React.Fragment,
null,
React.createElement(ChildA, null),
React.createElement(ChildB, null)
);
}
之前的 @babel/runtime
已经被分成 @babel/runtime
以及 @babel/runtime-corejs2
(PR)。前者只保留了 Babel helper 函数,后者保留了 polyfill 函数(例如,Symbol,Promise)。
Babel 可能会在(编译之后的)代码里面注入一些可以被复用的函数。我们把这些称之为 “helper functions”,(至于为啥取这个名字,是因为它们)就像函数,可以在模块之间进行共享。
例子就是,编译 class(不开启 loose 模式):
依照规范,你是需要通过 new Person()
的方式来调用 class,但如果 class 被编译成函数,从技术的角度来说,你是可以直接调用 Person()
,而不用加 new
。针对这种情况,我们在函数里面增加了运行时检查的机制。
class Person {}
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var Person = function Person() {
_classCallCheck(this, Person);
};
在使用 @babel/plugin-transform-runtime
以及 @babel/runtime
(as a dependency)的情况下,Babel 会把 helper 函数给抽离出来,然后只需要 require 这个 modular 函数,就能让 output 更小,就像这样:
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");
var Person = function Person() {
_classCallCheck(this, Person);
};
在环境不支持这些特性而你需要开启这些特性如 Promise,Symbol 的情况下,就需要 Polyfills。能够区分得出 Babel 在作为编译器(转换语法)以及 作为 polyfill(实现内置的方法/对象)都做了哪些事情,这很重要。
只需要简单的引入一套完整的解决方案(something that covers everything),比如 @babel/polyfill
:
import "@babel/polyfill";
但是 @babel/polyfill
会把整个 polyfill 都 import 进来,而且如果浏览器已经支持一些特性,那么就没有必要把整个 polyfill 给 import 进来。@babel/preset-env
所解决的语法问题和 polyfill 所解决的问题,实属同一个问题,所以在这里会把 @babel/preset-env
应用在解决 polyfill 问题上。设置 useBuiltins: "entry" 可以解决整个 polyfill 被 import 的问题(把整个 polyfill 拆解成多个 polyfill,然后只 import 必要的几个 polyfill)。
其实我们能做的更好,我们可以只 import 代码中用到的 polyfill。设置 useBuiltIns: "usage" 就可以开启类似的功能(文档)。
Babel 会遍历每个文件,并且会在每个文件的头部注入一行 import 语句。如果在这时候,发现代码中有用内置特性,例如:
import "core-js/modules/es6.promise";
var a = new Promise();
上述结论还不够完美,因为在接下来的例子中,可能会存在误报的情况。
import "core-js/modules/es7.array.includes";
a.includes // assume a is an []
Babel 工具做的最好的一点,就是它的插拔性。经过这么些年,Babel 已经从一个纯“6to5”编译器蜕变成代码转换的平台,这一转变,使得用户体验以及开发体验得到了不可思议的提升。针对一些库以及用例,已经开发出一大堆 Babel 插件,目的是用来提升库 API 的性能和功能,反过来就不行(有些库是完全不需要 transpiled 的,这样就导致了运行时的缺失)。
不幸的是,在代码库中增加这些插件,需要更改配置(有些工具,比如 create-react-app 就不允许修改配置),并且这还会增加代码的复杂性。这是因为开发者务必要知道哪些 Babel 插件会操作这个文件以及知道他们写的代码会发生什么事情。这些问题已经可以用 babel-plugin-macros 插件解决啦。
一旦安装了 babel-plugin-macros ,并且在配置中添加了 babel-plugin-macros(已经包含在 create-react-app v2 中),你就没必要去担心配置是否可以使用宏。此外,根据用例(用例的范围特定于应用程序或者一部分代码)来写自定义转换的代码甚至会更容易。
Babel 一直试图在转换后的体积以及给 JavaScript 作者所提供的功能之间做出平衡。在 Babel 7 中,通过配置 Babel 的方式来支持日益流行的模块/非模块模式变得更加容易。
值得注意地是,一些流行的 Web 框架(1,2)的 CLI 工具已经在支持模块/非模块模式,这就导致经 Babel 转换之后的 JavaScript 代码量减少了大约 20%。
我们已经为 @babel/core
增添了 caller 选项,所以我们的工具能够把 metadata 传给 presets/plugins。例如,babel-loader
能够加 supportsStaticESM 选项,因此 preset-env
能够自动禁用模块转换(和 rollup 一样):
babel.transform("code;", {
filename,
presets: ["@babel/preset-env"],
caller: {
name: "babel-loader",
supportsStaticESM: true,
},
});
在 Babel 不支持 class 继承原生内置接口(Array、Error 等)的情况下,仍然使用 class 继承原生内置接口,会导致 Babel 发出警告。不过现在是可以使用 class 继承原生内置接口!在 Babel 7 出来之前,我们就已经收到很多关于这个问题的 issue;? 也正是由于 Babel 团队对 class 插件的修改,使得这个特性会在你使用 preset-env 的情况下自动开启。
Babel 官网的框架已经从 Jekyll 给迁到了 Docusaurus。
扫码关注w3ctech微信公众号
共收到0条回复