babel结合core-js使用指南

7,551 阅读7分钟

1 概述

babel本身是个编译器,但是本文的侧重点在于官网上的副标题,即怎么样利用babel帮助我们在当前运行时(比如浏览器)未实现相关规范(包括es262web standard)的情况下使用最新版本的js,编译部分的介绍参考我的这篇文章,建议两篇文章结合阅读 。

要想实现上述目的,babel需要实现两个功能

  1. 将新版js的语法用旧版语法实现,从而在对应运行时运行,比如箭头函数
  2. 为旧版运行时中打补丁(也被称为polyfill),从而可以使用新版js中定义但在旧版运行时为提供的功能,包括三类
    1. 新定义的内置对象,比如Promise
    2. 原有内置对象新添加的静态方法,比如Array.from
    3. 原有内置对象新添加的实例方法,比如Array.prototype.includes

对于第一个功能,babel可以借助各种插件对各种新语法进行转换,怎么样开发插件和转换原理也可以在上面提的那篇文章找到。

对于第二个功能,babel需要借助一个找工作找了好几年的人开发的core-js来完成。

下面对以上两个功能的完成进行详细介绍,其中第一部分会对babel的主要内容进行讨论,第二部分介绍core-js和与babel的结合。

2 babel和语法转换

使用babel转换语法之前需要以下步骤

  1. 先安装各种包
  • babel核心包@babel/core
  • 如果是命令行调用需要安装@babel/cli
  • 安装语法转换时需要的插件
  1. 使用相关配置,指定转换过程中使用的插件和其他选项

完成以上步骤后,执行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在模块内其他代码之前将相关模块按需引入

  1. 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
  1. 不污染全局变量 前文提到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因为不是跨平台的特性,作者没实现。

完结撒花