关于javascript:手把手教你如何配置Babel3项目中如何配置最完美的polyfill

3次阅读

共计 11575 个字符,预计需要花费 29 分钟才能阅读完成。

公众号

什么是 Polyfill

polyfill 直译为 垫片 补丁 腻子 ,能够了解为兜底的货色,指的是宿主环境不反对的 API 或语法进行 兜底 操作,说白了就是打补丁。

例子:

[1, 2, 3].fill(4);   // [4, 4, 4]

打补丁:

if (!Array.prototype.fill) {
  Object.defineProperty(Array.prototype, 'fill', {value: function(value) {
      // 这里省略一堆解决逻辑...
      return O;
    }
  });
}

打补丁办法

总的来说,打补丁次要有三种办法:

  1. 手动打补丁
  2. 依据覆盖率主动打补丁
  3. 依据浏览器个性,按需打补丁

三种形式能够独立应用,也能够互相组合。接下来别离看一下三种形式如何应用。

手动打补丁

提到手动打补丁,我就须要先理解一下 @babel/polyfill

@babel/polyfill

曾经在 Babel@7.4.*废除了。上面 @babel/preset-env会提到

@babel/polyfill 是 core-jsregenerator-runtime两个 npm 包的汇合。

core-js 最新的版本是 3.x.x。@babel/polyfill依赖的 core-js 是 2.x.x 版本,core-js@2 版本短少了一些性能。比方 Array.prorotype.includes。所以,从 Babel@7.4.x 就不在举荐应用 @babel/polyfill 了。

但咱们依然把传统 @babel/polyfill 的应用形式在本节进行解说,这对于了解 手动打补丁形式 是十分有帮忙的

接下来看一下如何应用:

办法 1:间接在 html 文件中引入 Babel 的 polyfill.js 文件

Demo 代码仓库地址 tutor-polyfill01

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <!-- 全局引入 pollyfill.js -->
  <script src="./lib/polyfill.js"></script>
  <script src="./index.js"></script>
</body>
</html>

下面示例是 把 @babel/polyfill/dist/polyfill.js 文件 拷贝到我的项目中,间接在 html 外面全局引入

办法 2:我的项目构建入口引入 @babel/polyfill

Demo 代码仓库地址 tutor-polyfill03

装置 @babel/polyfill

npm i @babel/polyfill

次要代码局部

import "@babel/polyfill"

var promise = Promise.resolve('hello babel')
console.log(promise)

html 局部

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="./dist/main.js"></script>
</body>
</html>

办法 3:我的项目构建入口引入 core-js/stable 与 regenerator-runtime/runtime

Demo 代码仓库地址 tutor-polyfill04

该办法须要独自装置 core-js@3regenerator-runtime这两个 npm 包,这种形式 core-js 是默认是 3.x.x 版本。

 npm install --save core-js@3 regenerator-runtime
 # + regenerator-runtime@0.13.7
 # + core-js@3.12.1

这种形式不须要装置 @babel/polyfill,因为装置 @babel/polyfill 的时,也会主动把 core-jsregenerator-runtime装置上,而 @babel/polyfill 应用的 core-js 曾经锁死为 2.x.x 版本了。core-js 的 2.x.x 版本里并没有 stable 文件目录,所以装置 @babel/polyfill 后再引入 core-js/stable 会报错。

如果遇到 Cannot find module 'core-js@3/library/fn/**'. 这种类型的谬误,要检查一下 core-js 版本号

其实这个办法和下面的例子也是十分像,就是把一个 @babel/polyfill 包换成了 core-js@3regenerator-runtime这两个 npm 包。不一样的中央具体如下
次要代码局部

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

var promise = Promise.resolve('hello babel')
console.log(promise)

html 局部

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="./dist/main.js"></script>
</body>
</html>

办法 4:Webpack 构建的配置文件入口里引入

Demo 代码仓库地址 tutor-polyfill05

这种办法对应三种不同的入口文件,这里为了节俭篇幅,只拿 @babel/polyfill 形式做演示

const path = require('path')
module.exports = {// entry: ['./src/polyfill.js', './src/index.js'],  第一种:间接引入源文件
  entry: ['@babel/polyfill', './src/index.js'], // 第二种:@babel/polyfill
  // entry: ['core-js/stable', 'regenerator-runtime/runtime', './src/index.js'], // 第三种
  output: {
    filename: "./dist/main.js",
    path: path.resolve(__dirname, '')
  }
}

小结 1:

以上四种形式都属于 手动打补丁 形式,手动打补丁的形式把整个 polyfill.js 塞到了我的项目中,因为 polyfill 文件十分大,所以会影响页面首屏工夫。实在的我的项目中个别不会采纳此形式

持续看第二种打补丁形式 按需编译和按需打补丁

按需编译和按需打补丁

按需编译和按需打补丁的形式须要用到 @babel/preset-env, 它会依据指标环境配置表和 Babel 配置来进行语法转换和打补丁。例如上面配置:

{
  "presets": [
    [
      "@babel/preset-env",//> @babel/env 是 @babel/preset-env 的简写
      {
        //https://babeljs.io/docs/en/babel-preset-env#targets
        "targets": "defaults"
      }
    ]
  ]
}

指标环境配置表如何应用和配置,咱们在《手把手教你如何配置 Babel(1)》节曾经讲过了。
这为了不便大家浏览,咱们在说一遍

指标环境配置表

不晓得你留神到没有,在 Vue 或者 React 等框架的脚手架工具初始化的我的项目中,package.json 外面会有一个叫 browserslist 的货色。具体看一下上面配置含意:

  "browserslist": [
    "> 2%",
    "not ie <= 8"
  ]

下面的配置含意是,指标环境是市场份额大于 2% 的浏览器并且不思考 IE8 及以下的 IE 浏览器。

指标环境配置表的作用是 指定了代码要运行在那些浏览器或者 node.js 环境,确定了代码运行的指标环境。

AutoprefixerPostcss等就能够依据 browserslist的配置,主动判断是否要减少 CSS 前缀(例如 ’-webkit-‘)。@babel/preset-env预设也会读取 browserslist 的配置

如果咱们的 @babel/preset-env 不设置任何参数,Babel 就会齐全依据 browserslist 的配置来做语法转换。如果没有browserslist,那么 Babel 就会把所有 ES6 的语法转换成 ES5 版本

上面的例子中,既没有增加 browserslist,也没有给 @babel/preset-env设置参数,ES6 箭头函数语法会被转换成了 ES5 的函数定义语法。

编译前:

let number1 = 10
let number2 = 20
const sum = (num1, num2) => num1 + num2

编译后:

"use strict";

var number1 = 10;
var number2 = 20;

var sum = function sum(num1, num2) {return num1 + num2;};

接下来,咱们在 Demo 代码仓库地址 tutor-preset_env 我的项目的根目录上面,增加 .browserslistrc 配置, 内容如下:

chrome 62

留神:字符串两边不要增加引号

运行 babel ./src/index.js --out-file ./dist/index.js 查看 dist/index.js 后果

// 新的语法
let number1 = 10;
let number2 = 20;

const sum = (num1, num2) => num1 + num2;

转换后的代码依然是箭头函数,因为 Chrome62 曾经实现了箭头函数语法,所以不会转换成 ES5 的函数定义语法。

当初把 .browserslistrc 中的 Chrome62 降级为 Chrome36, 运行babel ./src/index.js --out-file ./dist/index.js 查看 dist/index.js 后果

var number1 = 10;
var number2 = 20;

var sum = function sum(num1, num2) {return num1 + num2;};

转换后的代码是 ES5 的函数定义语法,因为 Chrome36 不反对箭头函数语法。

以上内容,展现了 @babel/preset-env 对源代码中,在 browserslist 设定的指标环境不反对语法进行了编译降级解决。

接下来持续学习 @babel/preset-env 是如何通过设置参数,去实现对指标环境不反对的个性 API 进行局部援用。

最新的@babel/preset-env 参数总共有 15 个,咱们重点学习四个:

  • targets
  • useBuiltIns
  • modules
  • corejs

这四个参数很重要,其余的参数很少应用,非凡场景须要时,再去看官网文档

对于preset,当不须要对其设置参数的时,其只须要把该 preset 的名字放入 presets 对于的数组里即可,例如

{"presets": ["@babel/preset-env"]
}

如果须要对某个 preset 设置参数,格局如下:

{"presets": [ ["@babel/preset-env",{"targets": "defaults"}] ]
}

targets

取值范畴

  • 字符串:string
  • 字符串数组:Array<string>
  • 字符串对象 {[string]: string }
    默认值空对象{}。参数项的写法与 browserslist 是一样的,例如:

    {
    "presets": [
      [
        "@babel/preset-env",
        {
        "targets": {
           "chrome": "58",
           "ie": "11"
         }
        }
      ]
    ]
    }

    @babel/preset-env中的 target 优先级高于 browserslist 的配置。

    举荐应用 browserslist 的配置, 不要去独自配置 @babel/preset-env 的 targets

useBuiltIns

取值范畴:

  • usage
  • entry
  • false
    默认值为:false

useBuiltIns 的取值 决定了 @babel/preset-env 如何解决polyfill

  • 没有设置该参数或者取了默认值 false,polyfill 就会全副引入。
  • 取值为 ”entry” 或 ”usage” 的时候,会依据配置的指标环境找出须要的 polyfill 进行局部引入

useBuiltIns 的 entry 参数

Demo 代码地址
@babel/preset-env 定义了 Babel 所需插件预设,同时由 Babel 依据 browserslist 配置的反对环境,主动按需加载该 配置环境 须要的 所有 polyfills留神这里用词是 配置环境须要,而不是代码须要。什么意思呢?意思就是 浏览器环境须要,就增加,即便你代码中没有用到这个 API,还是会给你增加,你说气不气人?先看一下如何配置吧

Babel 配置

{
  "presets": [
    [
      "@babel/preset-env",
      {"useBuiltIns": "entry"}
    ]
  ]
}

装置 npm 包

  npm install --save-dev @babel/cli @babel/core  @babel/preset-env
  npm install --save @babel/polyfill

编译前代码:

// 手动引入 @babel/polyfill
//useBuiltIns 的取值为 entry 的之后,须要手动的显示导入 `@babel/polyfill`(或者在 webpack 的 entry 入口导入)
import '@babel/polyfill'
const array1 = ['a', 'b', 'c', 'd', 'e']

console.log(array1.copyWithin(0, 3, 4))

console.log(array1.copyWithin(1, 3))

指定指标环境 browserslist 配置
因为 Array.prototype.copyWithin()在 Chrome 45 版本才反对。咱们设置成 90,查看后果

chrome 90

运行npm run build

npm run build                                                                              

chrome 90 编译后果:

"use strict";

require("core-js/modules/web.timers.js");

require("core-js/modules/web.immediate.js");

require("core-js/modules/web.dom.iterable.js");

const array1 = ['a', 'b', 'c', 'd', 'e'];
console.log(array1.copyWithin(0, 3, 4));
console.log(array1.copyWithin(1, 3));

Babel 针对 Chrome 90 不反对的 API 个性进行援用,一共引入了 3 个 core-js 的 API 补齐模块。同时也能够看到,因为 Chrome 90 曾经反对 Array.prototype.copyWithin()个性,所以没有引入相干的 API 补齐模块。

上面批改 browserslist 里 chrome 40。运行npm run build

chrome 40 编译后果:

require("core-js/modules/es6.array.copy-within.js");
...
...

require("core-js/modules/es6.typed.int8-array.js");

require("core-js/modules/es6.typed.uint8-array.js");
....
...

require("regenerator-runtime/runtime.js");

因为援用太多,这里只显示了局部
能够看到require("core-js/modules/es6.array.copy-within.js")

思考:
useBuiltIns 取值为 entry 的时候,由 Babel 依据 browserslist 配置的反对环境,主动按需加载该配置环境须要的 所有 polyfills。换句话说,此计划是依据指标环境须要什么就加载什么,而不思考业务代码中是否应用到相干的个性或 API。所以@babel/polyfill 联合 @babel/preset-env + useBuiltins(entry) + browserslist 的计划尽管比拟风行,但不是最优计划。

针对下面的问题,@babel/preset-env + useBuiltins(usage) + browserslist 计划就呈现了,留神这里的 useBuiltins 配置为 usage,它能够真正依据代码状况,剖析 AST(形象语法树)进行更细粒度的按需援用。这是打补丁的第三种形式也是目前最优的形式 –依据浏览器个性,按需打补丁。上面具体看一下如何配置

依据浏览器个性,按需打补丁

useBuiltIns 的 usage 参数

Demo 代码地址
Babel 配置文件如下:

{
  "presets": [
    [
      "@babel/preset-env",
      {"useBuiltIns": "usage"}
    ]
  ]
}

装置 npm 包

npm install --save-dev @babel/cli @babel/core  @babel/preset-env
npm install --save @babel/polyfill

指定指标环境 browserslist 配置
因为 Array.prototype.copyWithin()在 Chrome 45 版本才反对。咱们设置成 40,这样能够动静引入 polyfill

chrome 40

编译前代码

// 这里不须要手动导入 @babel/polyfill
// 新的语法
const array1 = ['a', 'b', 'c', 'd', 'e']

console.log(array1.copyWithin(0, 3, 4))

console.log(array1.copyWithin(1, 3))

编译后代码

"use strict";

require("core-js/modules/es6.array.copy-within.js");
var array1 = ['a', 'b', 'c', 'd', 'e'];
console.log(array1.copyWithin(0, 3, 4));
console.log(array1.copyWithin(1, 3));

应用 useBuiltIns:”usage” 后,同样的指标环境,编译后的代码只引入了require("core-js/modules/es6.array.copy-within.js");。做到了 ES6 个性 API 在指标环境缺失的时候,Babel 才会引入 core-js 的 API 补齐模块。

总结 ‘entry’ 与 ’usage’ 两个参数值的区别

  • entry形式 须要在我的项目入口手动增加 polyfill,usage 不须要
  • usage形式 会依据代码内容按需加载 polyfill,entry 不能够

corejs

只有 useBuiltIns 设置为 ’usage’ 或 ’entry’ 时,才会失效。
该参数项的取值能够是 2 或 3,没有设置的时候默认是 2, 并且编译时候会有正告信息如下

> $ npm run build                                                                                                                                                    ⬡ 12.21.0 [±master ●●]

> tutor-preset_env02@1.0.0 build /Users/liujianwei/Documents/personal_code/tutor-babel/packages/tutor-preset_env02
> babel ./src/index.js --out-file ./dist/index.js


WARNING (@babel/preset-env): We noticed you're using the `useBuiltIns` option without declaring a core-js version. Currently, we assume version 2.x when no version is passed. Since this default version will likely change in future versions of Babel, we recommend explicitly setting the core-js version you are using via the `corejs` option.

You should also be sure that the version you pass to the `corejs` option matches the version specified in your `package.json`'s `dependencies` section. If it doesn't, you need to run one of the following commands:

  npm install --save core-js@2    npm install --save core-js@3
  yarn add core-js@2              yarn add core-js@3

More info about useBuiltIns: https://babeljs.io/docs/en/babel-preset-env#usebuiltins
More info about core-js: https://babeljs.io/docs/en/babel-preset-env#corejs

取默认值或 2 的时候,Babel 编译的时候应用的是 core-js@2 版本(即 core-js2.x.x)。因为某些新 API 只有 core-js@3 里才有,例如 Array.prototype.flat()办法,咱们须要应用 core-js@3 的 API 模块进行补齐,这个时候咱们就把该项设置为 3

corejs 取值为 2 的时候,须要装置并引入 core-js@2 版本,或者间接装置 @babel/polyfill 也能够。如果 corejs 取值为 3,必须装置并引入 core-js@3 版本才能够.

咱们先看一下 Array.prototype.flat() 通过 core-js@2 版本打补丁的例子
Demo 代码展现地址 tutor-preset_env04

装置 npm 包

npm i -D @babel/cli @babel/core  @babel/preset-env  
npm i @babel/polyfill

指标环境 因为 Array.prototype.flat() 在 chrome 69 反对,所以指标环境设置成 chrome 68

chrome 68

babel 配置

{
  "presets": [
    [
      "@babel/preset-env",
      {
        // "corejs": 2,// 默认值为 2
        "useBuiltIns": "usage"
      }
    ]
  ]
}

编译前代码:

const arr1 = [0, 1, 2, [3, 4]]

console.log(arr1.flat())

编译后代码:

"use strict";
const arr1 = [0, 1, 2, [3, 4]];
console.log(arr1.flat());

能够看到 并没有增加 flat 的 polyfill,是因为 core-js@2 版本中没有对应的 polyfill。

接下来,咱们把 .babelrc 外面的配置批改一下:
Demo 代码仓库 tutor-preset_env05

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "corejs": 3, // 指定 3 版本
        "useBuiltIns": "usage"
      }
    ]
  ]
}

装置 npm 包

npm i -D @babel/cli @babel/core  @babel/preset-env  
npm i @babel/polyfill
# 指定 core-js@3
npm install --save core-js@3

编译后果:

"use strict";

require("core-js/modules/es.array.flat.js");

const arr1 = [0, 1, 2, [3, 4]];
console.log(arr1.flat());

能够比照一下,这时候 require("core-js/modules/es.array.flat.js"); 被引入了。

到这里,大家可能很纳闷。为什么 @babel/polyfill 曾经废除了,为什么这里还在应用? 其实,下面内容曾经提到了 @babel/polyfill 交融了 core-jsregenerator-runtime
既然如此,咱们也能够不应用 @babel/polyfill,而间接应用 core-js@3regenerator-runtime一言不合就上代码展现:

Demo 代码仓库 tutor-preset_env06

装置 npm 包

npm install --save-dev @babel/cli @babel/core  @babel/preset-env  
npm install --save core-js@3  

没有显示的装置 regenerator-runtime 是因为在装置@babel/preset-env 的时候,regenerator-runtime作为依赖曾经被装置过了

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "corejs": 3, // 指定 3 版本
        "useBuiltIns": "usage"
      }
    ]
  ]
}

装置 npm 包

npm i -D @babel/cli @babel/core  @babel/preset-env  
npm i @babel/polyfill
# 指定 core-js@3
npm install --save core-js@3

编译后果:

"use strict";

require("core-js/modules/es.array.flat.js");

const arr1 = [0, 1, 2, [3, 4]];
console.log(arr1.flat());

间接应用 core-js@3 的后果,同 @babel/polyfill+core-js@3 是同样的成果。

modules

"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false, defaults to "auto"默认值是auto。该项用来设置是否把 ES6 的模块化语法改成其它模块化语法。

咱们常见的模块化语法有两种:

  • ES6 的模块法语法 —import 与 export
  • commonjs 模块化语法 —require 与 module.exports
    前几个例子中,没有设置 modules, 代码编译之后,import 被替换成了require

如果将参数项改成 false,那么就不会对 ES6 模块化进行更改,还是应用 import 引入模块。
Demo 代码仓库地址 tutor-preset_env07
babel 配置如下

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "corejs": 3, // 指定 3 版本
        "useBuiltIns": "usage",
        "modules":false
      }
    ]
  ]
}

装置 npm

 npm i @babel/core @babel/cli @babel/preset-env
 npm i core-js@3 

编译后的后果:

import "core-js/modules/es.array.flat.js";
const arr1 = [0, 1, 2, [3, 4]];
console.log(arr1.flat());

应用 ES6 模块化语法有什么益处?

在应用 Webpack 一类的打包工具,能够进行动态剖析,从而能够做 Tree-Shaking 等优化措施

以上的内容 配置的 Babel 足以解决业务类型的我的项目了。

如果你想 公布一个 npm 包给他人用,还须要理解下一个大节解说的 @babel/runtime@babel/plugin-transform-runtime

最初

关注我的公众号 – 前端学社或掘金账号,精彩不容错过

正文完
 0