公众号
什么是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; } });}
打补丁办法
总的来说,打补丁次要有三种办法:
- 手动打补丁
- 依据覆盖率主动打补丁
- 依据浏览器个性,按需打补丁
三种形式能够独立应用,也能够互相组合。接下来别离看一下三种形式如何应用。
手动打补丁
提到手动打补丁,我就须要先理解一下 @babel/polyfill
@babel/polyfill
曾经在Babel@7.4.*
废除了。上面@babel/preset-env
会提到
@babel/polyfill是core-js
和regenerator-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@3
与regenerator-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-js
与regenerator-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@3
与regenerator-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环境,确定了代码运行的指标环境。
Autoprefixer
、Postcss
等就能够依据 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-js
和 regenerator-runtime
。
既然如此,咱们也能够不应用@babel/polyfill
,而间接应用 core-js@3
和 regenerator-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
。
最初
关注我的公众号--前端学社或掘金账号,精彩不容错过