1 概述
babel本身是个编译器,但是本文的侧重点在于官网上的副标题,即怎么样利用babel帮助我们在当前运行时(比如浏览器)未实现相关规范(包括es262和web standard)的情况下使用最新版本的js,编译部分的介绍参考我的这篇文章,建议两篇文章结合阅读 。
要想实现上述目的,babel需要实现两个功能
- 将新版js的语法用旧版语法实现,从而在对应运行时运行,比如箭头函数
- 为旧版运行时中打补丁(也被称为polyfill),从而可以使用新版js中定义但在旧版运行时为提供的功能,包括三类
- 新定义的内置对象,比如
Promise
- 原有内置对象新添加的静态方法,比如
Array.from
- 原有内置对象新添加的实例方法,比如
Array.prototype.includes
- 新定义的内置对象,比如
对于第一个功能,babel可以借助各种插件对各种新语法进行转换,怎么样开发插件和转换原理也可以在上面提的那篇文章找到。
对于第二个功能,babel需要借助一个找工作找了好几年的人开发的core-js来完成。
下面对以上两个功能的完成进行详细介绍,其中第一部分会对babel的主要内容进行讨论,第二部分介绍core-js和与babel的结合。
2 babel和语法转换
使用babel转换语法之前需要以下步骤
- 先安装各种包
- babel核心包@babel/core
- 如果是命令行调用需要安装@babel/cli
- 安装语法转换时需要的插件
- 使用相关配置,指定转换过程中使用的插件和其他选项
完成以上步骤后,执行cli命令或者运行相关脚本即可实现babel的转化。
2.1 涉及到的packages
2.1.1 @babel/core
就是babel本身,在没有插件的情况下什么也不做直接返回,否则按照相关插件对源码进行操作。
2.1.2 @babel/cli
提供了一种在命令行执行babel的方式,相关api参考这里
2.1.3 相关插件
插件用于指导相关语法解析和ast处理。前者被称为Syntax Plugins,后者被称为Transform Plugins
Syntax Plugins一般不会直接操作,会在对应Transform Plugins使用时自动启用
Transform Plugins包含以下几类
- 各个版本的ES
- Modules
- 实验性的语法
- 实现压缩
- react
- 其他,比如typescript和后面会介绍的runtime
为了更方便的使用各类插件,babel将各种插件进行了对应的组合,这种组合被称为Presets,他们共享同一个配置,官方的presets包括
- @babel/preset-env 包含目标环境所需的所有新语法插件,是最常用的preset,具体配置会在下一章结合core-js介绍
- @babel/preset-flow 用于flow
- @babel/preset-react 用于react
- @babel/preset-typescript 用于ts
也可以自定义presets
2.2 配置
2.2.1 配置文件分类
babel接受的配置文件分很多种,包括
- babel.config.json
- .babelrc.json
- package.json
或者在使用cli或者api时指定,配置项主要是指定plugin和preset,具体格式参考这里
2.2.2 指定路径
在指定plugin或preset时可以有以下形式
- 如果是npm下载的,可以直接传递插件名字,babel会去node_modules中查看,具体使用时可以省略其中的
babel-plugin-
部分(使用preset省略的是babel-preset-
),比如
{
"plugins": [
"myPlugin",
"babel-plugin-myPlugin" // equivalent
]
}
//或
{
"plugins": [
"@org/babel-plugin-name",
"@org/name" // equivalent
]
}
- 也可以指定插件的 相对/绝对 路径
{
"plugins": ["./node_modules/asdf/plugin"]
}
2.2.3 顺序
plugin或preset的顺序影响插件中visitor的执行顺序,会按照如下规则
- plugin优先于preset
- plugin从前到后
- preset从后到前
3 polyfill与core-js
本部分参考core-js官方
3.1 polyfill
说到polyfill,很多人会想到@babel/polyfill,不过已经过时了,根据前面链接中的考古资料的说明,其被废弃的原因有两个
- 这个包仅仅是引入了stable core-js和regenerator-runtime/runtime,其中后者可以使用插件@babel/plugin-transform-regenerator代替
- 这个包不能从core-js@2 平滑过渡到 core-js@3
下面我们具体认识一下core-js
3.2 core-js
3.2.1 core-js是什么
可以从以下四个方面理解core-js
- 是一个js标准库的polyfill,其支持
- 最新的es标准
- es标准的提案
- web standard
- 最大程度的模块化,可以只引入需要的部分
- 可以被使用而不污染全名命名空间
- 和babel紧密结合,做了很多相关优化
core-js最新也是推荐版本为core-js@3,其又分为三个包
- core-js 定义了全局的polyfill
- core-js-pure 不污染全局变量的版本,主要只有在版本3才实现了实例方法
- core-js-bundle 打包后的core-js,是一个独立的文件,由于我们通常会模块化处理,因此后文会不考虑这种情况,和core-js的区别参考这里
3.2.2 core-js怎么单独使用:CommonJS API
core-js既可以单独使用,又可以结合babel使用,这里先介绍单独使用的方法,和babel结合方式请参考下一章。
这种情况下用法是直接使用import
在模块内其他代码之前将相关模块按需引入
- modules path 在具体node_modules文件夹中又分为以下子目录可以选择引入
- es 包含 stable ECMAScript features的features
- features 包含all core-js features是按需引入的推荐方式
- proposals 包含提案特性
- stable 包含all stable core-js features,是按需引入的推荐方式
- stage 包含ECMAScript proposals的features
- web 包含 WHATWG / W3C的features
也可以直接引入具体对象或方法等具体子文件
import 'core-js/features/array/from'; // <- at the top of your entry point
import 'core-js/features/array/flat'; // <- at the top of your entry point
import 'core-js/features/set'; // <- at the top of your entry point
import 'core-js/features/promise'; // <- at the top of your entry point
Array.from(new Set([1, 2, 3, 2, 1])); // => [1, 2, 3]
[1, [2, 3], [4, [5]]].flat(2); // => [1, 2, 3, 4, 5]
Promise.resolve(32).then(x => console.log(x)); // => 32
如果不带子目录,会包含es、proposals、web三个子目录的features,比如
import 'core-js'; // <- at the top of your entry point
Array.from(new Set([1, 2, 3, 2, 1])); // => [1, 2, 3]
[1, [2, 3], [4, [5]]].flat(2); // => [1, 2, 3, 4, 5]
Promise.resolve(32).then(x => console.log(x)); // => 32
- 不污染全局变量 前文提到core-js-pure是不污染全局变量的版本,静态方法和静态方法可以直接使用,如果当前环境含有该feature时,比如Set,会被引入的模块覆盖,可以考虑使用别名。
import from from 'core-js-pure/features/array/from';
import flat from 'core-js-pure/features/array/flat';
import Set from 'core-js-pure/features/set';
import Promise from 'core-js-pure/features/promise';
from(new Set([1, 2, 3, 2, 1])); // => [1, 2, 3]
flat([1, [2, 3], [4, [5]]], 2); // => [1, 2, 3, 4, 5]
Promise.resolve(32).then(x => console.log(x)); // => 32
当处理实例方法时,不能直接将原型方法像其他场景一样转化为静态方法,在core-js@3中利用绑定运算符(即::
)加进一步编译实现了该功能。
具体引入时要引入/virtual/目录下的方法
import fill from 'core-js-pure/features/array/virtual/fill';
import findIndex from 'core-js-pure/features/array/virtual/find-index';
Array(10)::fill(0).map((a, b) => b * b)::findIndex(it => it && !(it % 8)); // => 4
// or
import { fill, findIndex } from 'core-js-pure/features/array/virtual';
Array(10)::fill(0).map((a, b) => b * b)::findIndex(it => it && !(it % 8)); // => 4
4 babel和core-js结合使用
利用babel中的相关插件可以简化core-js的使用。注意以下两种不要同时配置core-js,不然会发生冲突。
4.1 @babel/preset-env
通过useBuiltIns选项可以指示全局版本的core-js的使用,core-js的版本使用corejs参数表示,还可以添加proposals: true
来实现提案中的特性
corejs: { version: '3.8', proposals: true }
另外不要忘了安装对应全局版本的core-js包
npm install core-js@3 --save
更多preset-env的用法参考官方文档
4.2 @babel/runtime和@babel/plugin-transform-runtime
可以和pure版本的core-js一起使用,简化core-js-pure的用法,即直接使用实例方法等而不污染全局变量。具体配置和在preset-env用法一致,其中的runtime要根据corejs的配置下载不同版本(比如corejs3对应@babel/runtime-corejs3),注意只有版本3才提供实例方法。
@babel/plugin-transform-runtime会将
var sym = Symbol();
var promise = Promise.resolve();
var check = arr.includes("yeah!");
console.log(arr[Symbol.iterator]());
转化为
import _getIterator from "@babel/runtime-corejs3/core-js/get-iterator";
import _includesInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/includes";
import _Promise from "@babel/runtime-corejs3/core-js-stable/promise";
import _Symbol from "@babel/runtime-corejs3/core-js-stable/symbol";
var sym = _Symbol();
var promise = _Promise.resolve();
var check = _includesInstanceProperty(arr).call(arr, "yeah!");
console.log(_getIterator(arr));
更多功能和技术细节参考官方文档
5 core-js不包含的polyfill
- String#normalize相关的polyfill很大,如果需要请使用单独的包unorm,类似的还有ECMA-402 Intl
- Proxy 无法polyfill,proxy-polyfill提供了部分功能
- window.fetch因为不是跨平台的特性,作者没实现。
完结撒花