w3ctech

Babel 7 于今天发布

Babel 团队在经历了差不多 2 年磨一剑之后,终于在今天宣布 —— Babel 7 发布啦。这次 Babel 7 的发布,距离上次 Babel 6 的发布差不多过去了整整 3 年的时间!另外,Babel 团队成员 Henry Zhu 还提醒大家,在 Babel 7 发布的这周内,肯定还有很多东西需要动态调整,所以他希望大家能够理解(理解万岁!!!)。

断崖式变更

  • 对那些已经不维护的 node 版本不予支持,包括 0.10、0.12、4、5(详情
  • Babel 团队会通过使用 “scoped” packages 的方式,来给自己的 babel package name 加上 @babel 命名空间(详情),这样以便于区分官方 package 以及 非官方 package,所以 babel-core 会变成 @babel/core
  • 移除(并且停止发布)所有与 yearly 有关的 presets(preset-es2015 等)(详情)。@babel/preset-env 会取代这些 presets,这是因为 @babel/preset-env 囊括了所有 yearly presets 的功能,而且 @babel/preset-env 还具备了针对特定浏览器进行“因材施教”的能力
  • 放弃 Stage presets(@babel/preset-stage-0 等),选择支持单个 proposal。相似的地方还有,会默认移除 @babel/polyfill 对 proposals 支持(详情)。想知道更多相关的细节,可以考虑阅读整篇博文
  • 有些 package 已经换名字:所有 TC39 proposal plugin 的名字都已经变成以 @babel/plugin-proposal 开头,替换之前的 @babel/plugin-transform详情)。所以 @babel/plugin-transform-class-properties 变成 @babel/plugin-proposal-class-properties
  • 针对一些用户会手动安装(user-facing)的 package(例如 babel-loader,@babel/cli 等),会给 @babel/core 加上 peerDependency详情

babel-upgrade

babel-upgrade,Babel 团队开发的新工具,旨在用来处理升级过程中的琐事(changes):目前只是针对 package.jsondependencies 以及 .babelrc配置。

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

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

npx babel-upgrade --write --install

JavaScript 配置文件

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 团队的补丁

用于 Output 的配置项

Babel 支持(开发者)在配置文件中使用 preset 和 plugin 配置项,已经有一段时间啦。简要的概括一下,(就是)你可以把插件的名称放在一个数组,然后把配置对象传给该插件:

{
  "plugins": [
-   "pluginA",
+   ["pluginA", {
+     // options here
+   }],
  ]
}

针对一些插件的 loose 配置项,Babel 7 做了一些改动,并且还给其它的插件增添了一些新的配置项!注意,使用这些(新增的)配置项,可能在一些情况下,会导致一些问题。比如在你不编译的情况下,直接使用 JavaScript 原生支持的语法。

  • 针对 classes,class A {} 经过 Babel 7 编译之后的代码,不再包含 classCallCheck helper 函数。
class A {}
var A = function A() {
-  _classCallCheck(this, A);
};
  • 如果 for of 循环遍历的对象只是一个数组,那么 Babel 7 会给该插件增添一个新的配置项:["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 模式

支持 "Pure" 注释

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

class C {
  m() {}
}
var C =
/*#__PURE__*/
function () {
  // ...
}();

语法

支持 TC39 Proposals

下面列出的是,Babel 所支持的一些新语法,并且有一些语法已经被 Babel 7 原生支持:

  • ES2018: Object Rest Spread (var a = { b, ...c })
  • ES2018 (new): Unicode Property Regex
  • ES2018 (new): JSON Superset
  • ES2015 (new): new.target
  • Stage 3 (new): Class Private Instance Fields (class A { #b = 2 })
  • Stage 3 (WIP): Static Class Fields, Private Static Methods (class A { static #a() {} })
  • Stage 3 (new): Optional Catch Binding try { throw 0 } catch { do() }
  • Stage 3 (new): BigInt (syntax only)
  • Stage 3: Dynamic Import (import("a"))
  • Stage 2 (new): import.meta (syntax only) (import.meta.url)
  • Stage 2 (new): Numeric Seperators (1_000)
  • Stage 2 (new): function.sent
  • Stage 2: export-namespace-from (export * as ns from 'mod'), split from export-extensions
  • Stage 2: Decorators. Check below for an update on our progress!
  • Stage 1: export-default-from (export v from 'mod'), split from export-extensions
  • Stage 1 (new): Optional Chaining (a?.b)
  • Stage 1 (new): Logical Assignment Operators (a &&= b; a ||= b)
  • Stage 1 (new): Nullish Coalescing Operator (a ?? b)
  • Stage 1 (new): Pipeline Operator (a |> b)
  • Stage 1 (new): Throw Expressions (() => throw new Error("a"))

支持 TypeScript(@babel/preset-typescript)

使用 @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;
}

支持 JSX Fragment()

早在 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 Helpers 改动的地方

之前的 @babel/runtime 已经被分成 @babel/runtime 以及 @babel/runtime-corejs2PR)。前者只保留了 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);
};

自动 polyfill(实验性质)

在环境不支持这些特性而你需要开启这些特性如 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 []

Misc

Babel 宏(Macros) 🎣

Babel 工具做的最好的一点,就是它的插拔性。经过这么些年,Babel 已经从一个纯“6to5”编译器蜕变成代码转换的平台,这一转变,使得用户体验以及开发体验得到了不可思议的提升。针对一些库以及用例,已经开发出一大堆 Babel 插件,目的是用来提升库 API 的性能和功能,反过来就不行(有些库是完全不需要 transpiled 的,这样就导致了运行时的缺失)。

不幸的是,在代码库中增加这些插件,需要更改配置(有些工具,比如 create-react-app 就不允许修改配置),并且这还会增加代码的复杂性。这是因为开发者务必要知道哪些 Babel 插件会操作这个文件以及知道他们写的代码会发生什么事情。这些问题已经可以用 babel-plugin-macros 插件解决啦。

一旦安装了 babel-plugin-macros ,并且在配置中添加了 babel-plugin-macros(已经包含在 create-react-app v2 中),你就没必要去担心配置是否可以使用宏。此外,根据用例(用例的范围特定于应用程序或者一部分代码)来写自定义转换的代码甚至会更容易。

Module Targetting

Babel 一直试图在转换后的体积以及给 JavaScript 作者所提供的功能之间做出平衡。在 Babel 7 中,通过配置 Babel 的方式来支持日益流行的模块/非模块模式变得更加容易。

值得注意地是,一些流行的 Web 框架(12)的 CLI 工具已经在支持模块/非模块模式,这就导致经 Babel 转换之后的 JavaScript 代码量减少了大约 20%。

Caller Metadata and Better Defaults

我们已经为 @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,
  },
});

class C extends HTMLElement {}

在 Babel 不支持 class 继承原生内置接口(Array、Error 等)的情况下,仍然使用 class 继承原生内置接口,会导致 Babel 发出警告。不过现在是可以使用 class 继承原生内置接口!在 Babel 7 出来之前,我们就已经收到很多关于这个问题的 issue;🎉 也正是由于 Babel 团队对 class 插件的修改,使得这个特性会在你使用 preset-env 的情况下自动开启。

Website Changes 🌏

Babel 官网的框架已经从 Jekyll 给迁到了 Docusaurus

REPL

repl

w3ctech微信

扫码关注w3ctech微信公众号

共收到0条回复