由于 JavaScript 在不断地发展,各种新标准和提案也就层出不穷,用户使用的浏览器也不同(尤其是旧版的 IE),这就导致了有些用户无法使用 JavaScript 的一些新特性,如果开发人员想使用新的语法(如 class A {}),在旧浏览器上就会因为这个问题出现 SyntaxError。
由于它能够转换 JavaScript 代码,还可用于实现新功能,因此它已成为 TC39 获取社区对 JavaScript 反馈的重要桥梁。
如今,Babel 是 JavaScript 开发的基础。它在 npm 上,每月有 1700 万次的下载量,用户包括主流框架(React,Vue,Ember,Polymer)的开发者和企业(Facebook,Netflix,Airbnb)。即使你自己没有使用它,你的依赖项也很可能正在使用 Babel。
Babel 由志愿者驱动
虽然 Babel 对 JavaScript 语言的未来,甚至对社区和 js 生态都产生了巨大的影响,但你肯定想不到,他们的维护者只是由社区的几个志愿者组成。
重大变更
-
不再支持不在维护中的 Node 版本:0.10、0.12、4、5;
-
使用 @babel 命名空间,因此 babel-core 就变成了 @babel/core;
-
移除(并停止发布)任何年度预设(preset-es2015 等),@babel/preset-env 取代了对这些内容的需求 ;
-
移除“Stage” 预设(@babel/preset-stage-0 等),同时移除了 @babel/polyfill 中的提议;
-
重命名了某些包:任何 TC39 提议插件现在都是 -proposal 而不是 -transform,所以 @babel/plugin-transform-class-properties 变成了 @babel/plugin-proposal-class-properties;
-
针对某些面向用户的包(例如 babel-loader、@babel/cli 等)在 @babel/core 中引入 peerDependency;
babel-upgrade
babel-upgrade 是一个用于进行自动升级的新工具:目前在 package.json 和.babelrc 配置文件中配置了依赖关系。
建议直接在 git 仓库上运行 npx babel-upgrade,或者使用 npm i babel-upgrade -g 进行全局安装。
如果你想修改文件,可以使用参数 --write 和 --install。
npx babel-upgrade --write --install
JavaScript 配置文件
Babel 7.0 引入了 babel.config.js。
*.js 配置文件在 JavaScript 生态系统中相当常见。ESLint 和 Webpack 分别使用了.eslintrc.js 和 webpack.config.js 配置文件。但这并不意味着它们就取代了.babelrc 或 package.json,这不是必要条件,但在某些情况下这可能很有用。以下是仅在“生产”环境中使用插件进行编译的配置。
babel.config.js 的配置解析方式与.babelrc 不同。它始终会解析该文件中的配置,而不会从每个文件向上查找,直到找到配置为止。这样就可以利用 overrides 特性。
var env = process.env.NODE_ENV; module.exports = { plugins: [ env === "production" && "babel-plugin-that-is-cool" ].filter(Boolean) };
使用 overrides 进行选择性配置
module.exports = { presets: [ // defeault config... ], overrides: [{ test: ["./node_modules"], presets: [ // config for node_modules ], }, { test: ["./tests"], presets: [ // config for tests ], }] };
有些应用程序需要针对测试、客户端代码和服务器代码使用不同的编译配置选项,通过这种方式就不需要在每个文件夹下创建.babelrc 文件了。
速度
Babel 团队做了很多变更来优化代码,并接受了来自 V8 团队的一些补丁( https://twitter.com/rauchg/status/924349334346276864 )。
输出选项
Babel 支持预设和插件选项有一段时间了。比如,你可以将插件包装在一个数组中,并将选项对象传给插件:
// 减号表示移除,加号表示增加: { "plugins": [ - "pluginA", + ["pluginA", { + // options here + }], ] }
Babel 团队还对部分插件的 loose 选项做了一些修改,并为部分插件添加了一些新选项!
比如,对于类 class A {},现在不包含 _classCallCheck。
class A {} ------------------- // 减号表示移除: var A = function A() { - _classCallCheck(this, A); };
如果 for-of 循环的是一个数组,那么可以使用这个新选项:[“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); }
在 loose 模式下将 transform-typeof-symbol 插件排除在外。
“纯粹”的注解支持
转换后的 ES6 类使用 /*#__PURE__*/ 进行注解,这样就可以告诉 Uglify 和 babel-minify 移除死代码。这些注解也被添加到其他辅助函数中。
class C { m() {} } --------------------- var C = /*#__PURE__*/ function () { // ... }();
TC39 提案支持
以下是 Babel 支持的一些新语法以及已经添加到 v7 中的语法清单:
-
ES2018:Object Rest Spread (var a = { b, ...c })
-
ES2018(新):Unicode 属性正则表达式
-
ES2018(新):JSON 超集
-
ES2015(新):new.target
-
阶段 3(新):类私有实例字段(class A { #b = 2 })
-
阶段 3(WIP):静态类字段、私有静态方法(class A { static #a() {} })
-
阶段 3(新):可选 Catch 绑定 try { throw 0 } catch { do() }
-
第 3 阶段(新):BigInt(仅限语法)
-
第 3 阶段:动态导入(import("a"))
-
第 2 阶段(新):import.meta(仅限语法)(import.meta.url)
-
第 2 阶段(新):数字分隔器(1_000)
-
第 2 阶段(新):function.sent
-
第 2 阶段:export-namespace-from(export * as ns from'mod'),从 export-extensions 中拆分出来
-
第 2 阶段:装饰器
-
第 1 阶段:export-default-from(export v from 'mod'),从 export-extensions 中拆分出来
-
第 1 阶段(新):可选链接(a?.b)
-
第 1 阶段(新):逻辑赋值(a &&= b; a ||= b)
-
第 1 阶段(新):Nullish Coalescing(a ?? b)
-
第 1 阶段(新):管道操作符(a |> b)
-
第 1 阶段(新):throw 表达式(() => throw new Error("a"))
TypeScript 支持
Babel 团队与 TypeScript 团队合作,让 Babel 使用 @babel/preset-typescript 解析 / 转换类型语法,类似于使用 @babel/preset-flow 处理 Flow 的方式。
之前(有类型):
interface Person { firstName: string; lastName: string; } function greeter(person : Person) { return "Hello, " + person.firstName + " " + person.lastName; }
之后(移除了类型):
function greeter(person) { return "Hello, " + person.firstName + " " + person.lastName; }
JSX Fragment 支持(<>)
正如 React 博客中所提到的,从 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 辅助函数的变化
@babel/runtime 已经被分为 @babel/runtime 和 @babel/runtime-corejs2。前者仅包含 Babel 的辅助函数,后者包含辅助函数以及 polyfill 函数(例如 Symbol、Promise)。
规范规定了需要通过 new Person() 实例化一个类,但如果它被编译成一个函数,就可以直接调用 Person(),所以他们提供了一个运行时检查。
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(作为依赖项),Babel 可以提取单个函数,而且只需要模块函数来获得较小的输出,如下所示:
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck"); var Person = function Person() { _classCallCheck(this, Person); };
自动 polyfill(实验性质)
要使用 polyfill,只需导入 @babel/polyfill 即可:
import "@babel/polyfill";
但这样会导入整个 polyfill,如果浏览器已经支持某些特性,就不需要导入所有内容。可以使用选项 useBuiltins:”entry”来导入需要用到的部分。
可以更进一步,通过选项 useBuiltIns:”usage”只将需要用到的 polyfill 导入到代码库中。
它将遍历每个文件,并在每个使用了内置 polyfill 的文件的顶部注入一个导入语句。例如:
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 的一大特点是它的可插拔性。多年来,Babel 从一个“6to5”编译器转变为代码转换平台,为用户和开发人员提供了出色的优化选项。人们已经为特定库和用例开发了大量的 Babel 插件,用以提升库 API 的性能和功能。
可惜的是,将这些插件添加到代码库中需要更改配置,从而增加了代码复杂性。由 Kent C. Dodds 开发的 babel-plugin-macros(https://github.com/kentcdodds/babel-plugin-macros)已经解决了这个!
在安装了 babel-plugin-macros 并将其添加到配置中(它已包含在 create-react-app v2 中)之后,你就不必费心配置就可以使用宏了。此外,为特定应用程序或代码编写自定义转换也变得更加容易。
模块目标
Babel 一直试图在转换的影响范围和功能之间做出平衡。在 Babel 7 中,通过配置 Babel 来支持模块 / 非模块模式变得更加容易。
值得注意的是,一些流行的 Web 框架的 CLI 工具已经在利用这些支持,这让转换后的 JavaScript 代码减少了大约 20%。
class C extends HTMLElement {}
Babel 总是会警告说它不支持扩展原生内置元素(Array、Error 等),但现在可以了!Babel 团队对类插件进行了修改,如果你使用了 preset-env,那么默认就自动启用了这个特性。
网站变更
Babel 网站从 Jekyll(https://jekyllrb.com/)搬到了 Docusaurus(https://docusaurus.io/)。
REPL
Babel 将 REPL 重写为 React 组件,并更好地与 CodeSandbox 集成。这样你就可以在 REPL 中安装来自 npm 的任何插件或预设,并获取 CodeSandbox 的任何更新。
接下来要做什么
Babel 本质上就是要与 JavaScript 紧紧联系在一起,包括在语法变得“稳定”之前花时间和精力来实现和维护语法。Babel 团队关心的是整个过程:升级路径、新特性的传播、标准 / 语言设计的教学、易用性以及与其他项目的集成。
在 Nicolò的帮助下,Babel 团队几乎完成了新的装饰器的实现。这是一个漫长的旅程,因为新的提案完全不同,而且比旧的更强大。它可能会在下一个次要版本中发布。
还有很多新特性还在开发当中:插件顺序、更好的验证 / 错误、速度提升、新的 loose/spec 选项、缓存、异步使用 Babel、从 CI 构建、冒烟测试、运行 test262。
查看英文原文: https://babeljs.io/blog/2018/08/27/7.0.0
感谢覃云对本文的审校及策划。
评论