共计 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;
}
});
}
打补丁办法
总的来说,打补丁次要有三种办法:
- 手动打补丁
- 依据覆盖率主动打补丁
- 依据浏览器个性,按需打补丁
三种形式能够独立应用,也能够互相组合。接下来别离看一下三种形式如何应用。
手动打补丁
提到手动打补丁,我就须要先理解一下 @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 = 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-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@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
。
最初
关注我的公众号 – 前端学社或掘金账号,精彩不容错过