公众号

什么是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 = 10let number2 = 20const 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-envnpm 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.jsWARNING (@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@3More info about useBuiltIns: https://babeljs.io/docs/en/babel-preset-env#usebuiltinsMore 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@3npm 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@3npm 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

最初

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