关于Babel最新使用方式

文章总结的时间是 2017/11/20

本文是为了梳理 Babel 配置及使用而整理,因为看过使用 Babel 配置项目和文章,存在项目插件使用混乱、文章各种照搬、插件使用听风是雨、插件升级文章内容不再适用的问题。这里就目前最新使用的配置组合进行整理,涉及的插件包括以下三个:

  • @babel/preset-env(^7.0.0-beta.32)
  • @babel/preset-stage-x(7.0.0-beta.32), x-0,1,2,3
  • @babel/polyfill(^7.0.0-beta.32)

@babel/preset-env

特性

替换之前所有babel-presets-es20xx插件

A Babel preset that compiles ES2015+ down to ES5 by automatically determining the Babel plugins and polyfills you need based on your targeted browser or runtime environments.

也就是说,这是一个能根据运行环境为代码做相应的编译,@babel/preset-env的推出是为了解解决个性化输出目标代码的问题,通过browserslist语法解析需要支持的目标环境,根据环境将源代码转义到目标代码,可以实现代码精准适配。

此外,@babel/preset-env不包含state-x一些列插件,只支持最新推出版本的 JavaScript 语法(state-4),关于state-x后面会介绍。

更进一步说明,请参考Dr. Axel Rauschmayer的介绍。这个插件对特殊平台的开发有很大帮助,比如:Electron、大屏、移动端(只考虑 webkit)等。

替换@babel/plugin-transform-runtime的使用

@babel/plugin-transform-runtime插件是为了解决:

  • 多个文件重复引用相同 helpers(帮助函数)-> 提取运行时
  • 新 API 方法全局污染 -> 局部引入

这个插件推荐在编写 library/module 时使用。当然,以上问题可通过设置useBuiltIns搞定。

支持实例方法按需引入

传统方式是手动从core-js引入需要的 ES6+特性,

require('core-js/fn/set');
require('core-js/fn/array/from');
require('core-js/fn/array/find-index');
...
...

或者一股脑全部引入:

import "@babel/polyfill";
// or
require("@babel/polyfill");

同样,以上问题可通过设置useBuiltIns搞定。

使用说明

默认情况下,@babel/preset-env的效果和@babel/preset-latest一样,虽然上面的说明有提到 polyfills,但是也需要在配置中设置useBuiltIns才会生效。

主要配置

targets

设置支持环境,支持的key包括:chrome, opera, edge, firefox, safari, ie, ios, android, node, electron。

例如:

{
  "presets": [
    [
      "@babel/env",
      {
        "targets": {
          "node": "current",
          "chrome": 52,
          "browsers": ["last 2 versions", "safari 7"]
        }
      }
    ]
  ]
}

其中,browserslist可在package.json中配置,这个和设置 CSS 的 Autoprefixer 一致,配置优先级如下:

targets.browsers > package.json/browserslist

此外,browserslist的配置满足最大匹配原则,比如需要同时支持在 IE 8 和 Chrome 55 下运行,则preset-env提供所有 IE 8 下需要的插件,即使 Chrome 55 可能不需要。

modules

选项用于模块转化规则设置,可选配置包括:“amd” | “umd” | “systemjs” | “commonjs” | false, 默认使用 “commonjs”。即,将代码中的 ES6 的import转为require

如果你当前的 webpack 构建环境是 2.x/3.x,推荐将modules设置为false,即交由 Webpack 来处理模块化,通过其 TreeShaking 特性将有效减少打包出来的 JS 文件大小。这部分参考这里的回答:ECMAScript 6 的模块相比 CommonJS 的 require (…)有什么优点?

useBuiltIns

A way to apply @babel/preset-env for polyfills (via @babel/polyfill).

可选值包括:“usage” | “entry” | false, 默认为 false,表示不对 polyfills 处理,这个配置是引入 polyfills 的关键

“useBuiltIns”:“usage”

在文件需要的位置单独按需引入,可以保证在每个 bundler 中只引入一份。例如:

In

a.js

var a = new Promise();

b.js

var b = new Map();

Out (if environment doesn’t support it)

import "core-js/modules/es6.promise";
var a = new Promise();
import "core-js/modules/es6.map";
var b = new Map();

Out (if environment supports it)

var a = new Promise();
var b = new Map();

!!注意!!

当前模式类似于@babel/plugin-transform-runtime,polyfill 局部使用,制造一个沙盒环境,不造成全局污染,但是如上配置后,@babel/preset-env能按需引入新实例方法,例如:

"foobar".includes("foo");

@babel/plugin-transform-runtime不行,需要自行从core-js中按需引入。我测试的情况和下面这篇文章所说完全不一致。

原文:https://zhuanlan.zhihu.com/p/29506685

当 useBuiltIns 设置为 usage 时,Babel 会在你使用到 ES2015+ 新特性时,自动添加 babel-polyfill 的引用,并且是 partial 级别的引用。

请注意: usage 的行为类似 babel-transform-runtime,不会造成全局污染,因此也会不会对类似 Array.prototype.includes() 进行 polyfill

“useBuiltIns”:“entry”

在项目入口引入一次(多次引入会报错)

import "@babel/polyfill";
// or
require("@babel/polyfill");

插件@babel/preset-env会将把@babel/polyfill根据实际需求打散,只留下必须的,例如:

In

import "@babel/polyfill";

Out (different based on environment)

import "core-js/modules/es6.promise";
import "core-js/modules/es7.string.pad-start";
import "core-js/modules/es7.string.pad-end";
import "core-js/modules/es7.array.includes";

除了新 API,也可以是实例方法,不过最终包体积比使用"useBuiltIns":"usage"大 30kb 左右(因项目而异)。

“useBuiltIns”: false

不在代码中使用 polyfills,表现形式和@babel/preset-latest一样,当使用 ES6+语法及 API 时,在不支持的环境下会报错。

@babel/preset-stage-x

这里需要说明下 ES 特性支持的提案,不同阶段的提案支持的内容不同,其中stage-4阶段提案中的特性将会在未来发布。

关于各个 Stage 的说明参考这里。上面介绍的@babel/preset-env或者@babel/preset-latest就是下面提到的stage-4

The TC39 categorizes proposals into the following stages:

  • Stage 0 - Strawman: just an idea, possible Babel plugin.
  • Stage 1 - Proposal: this is worth working on.
  • Stage 2 - Draft: initial spec.
  • Stage 3 - Candidate: complete spec and initial browser implementations.
  • Stage 4 - Finished: will be added to the next yearly release.

Stage 的包含顺序是:左边包含右边全部特性,即 stage-0 包含右边 1 / 2 / 3 的所有插件。

stage-0 > ~1 > ~2 > ~3 > ~4:

疑问

1. 这个和@babel/preset-env的区别

@babel/preset-env会根据预设的浏览器兼容列表从stage-4选取必须的 plugin,也就是说,不引入别的stage-x@babel/preset-env将只支持到stage-4

建议

1. 如果是 React 用户,建议配到@babel/preset-stage-0

其中的两个插件对于写 JSX 很有帮助。

  • transform-do-expressions:if/else 三目运算展开
  • transform-function-bind:this 绑定

2. 通常使用建议配到@babel/preset-stage-2

插件包括:

  • syntax-dynamic-import: 动态 import
  • transform-class-properties:用于 class 的属性转化
  • transform-object-rest-spread:用来处理 rest spread
  • transform-async-generator-functions:用来处理 async 和 await

@babel/polyfill

这个插件是对core-jsregenerator-runtime的再次封装,在@babel/preset-env中的useBuiltIns: entry用到,代码不多

if (global._babelPolyfill) {
  throw new Error("only one instance of @babel/polyfill is allowed");
}
global._babelPolyfill = true;

import "core-js/shim";
import "regenerator-runtime/runtime";

core-js/shim

shim only: Only includes the standard methods.

只包含了纳入标准的API实例化方法,例如下列常见的。注意,这里没有 generator/async,如果需要,那就安装插件@babel/preset-stage-3(或者 0 / 1 / 2)

...
require('./modules/es6.string.trim');
require('./modules/es6.string.includes');
require('./modules/es7.array.includes');
require('./modules/es6.promise');
...

regenerator-runtime/runtime

Standalone runtime for Regenerator-compiled generator and async functions.

主要是给 generator/async 做支持的插件。

总结

这里需要理解下三个概念:

  • 最新 ES 语法:比如,箭头函数
  • 最新 ES API:,比如,Promise
  • 最新 ES 实例方法:比如,String.protorype.includes

@babel/preset-env默认支持语法转化,需要开启useBuiltIns配置才能转化API实例方法

此外,不管是写项目还是写 Library/Module,使用@babel/preset-env并正确配置就行。多看英文原稿说明,中文总结看看就好,别太当真。

参考

Show Comments