共计 3404 个字符,预计需要花费 9 分钟才能阅读完成。
什么是 Polyfilll
Pollfill 一词最早是在 nodemon 的作者 Remy Sharp 于 2010 年 10 月 8 日发表的博客文章 What is a Polyfill? 中首次提到,他对 polyfill 的定义是:
A shim that mimics a future API providing fallback functionality to older browsers.
翻译过去就是:
polyfill 就是一个垫片 / 填充 / 补丁程序,用于抹平浏览器之间的 API 差别,在旧的浏览器上反对新的个性。
无论在晚期或者古代前端工程中,经常有以下几种形式来打补丁:
-
手动
手动将补丁代码引入到我的项目
-
主动
借助工具如 core-js、babel、Polyfill.io 等工具自动化打补丁,并且能够做到依据指标运行环境按需打补丁或动静打补丁。
手动打补丁
在前端工具还不凋敝的晚期,咱们须要手动引入所须要的补丁,比方以 ES6 的 Object.assign 为例,在 IE11 上依然会报错:
所以须要手动引入补丁代码,一种是间接装置第三方包,另外一种是引入来自 MDN 的补丁代码:
// 装置 object-assign (https://github.com/sindresorhus/object-assign) | |
npm i object-assign | |
// 引入 | |
Object.assign = require('object-assign') |
或者在须要的中央搁置来自 MDN 的补丁代码:
if (typeof Object.assign !== 'function') { | |
// Must be writable: true, enumerable: false, configurable: true | |
Object.defineProperty(Object, "assign", {value: function assign(target, varArgs) { // .length of function is 2 | |
'use strict'; | |
if (target === null || target === undefined) {throw new TypeError('Cannot convert undefined or null to object'); | |
} | |
var to = Object(target); | |
for (var index = 1; index < arguments.length; index++) {var nextSource = arguments[index]; | |
if (nextSource !== null && nextSource !== undefined) {for (var nextKey in nextSource) { | |
// Avoid bugs when hasOwnProperty is shadowed | |
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {to[nextKey] = nextSource[nextKey]; | |
} | |
} | |
} | |
} | |
return to; | |
}, | |
writable: true, | |
configurable: true | |
}); | |
} |
很显著,手动引入补丁代码既有劣势和劣势:
- 劣势:最小化引入,不会有额定的冗余代码,保障加载性能
- 劣势:手动引入,不易保护和治理;对于反对多端的利用和多样化的 polyfill 保护老本过大
主动打补丁
按需打补丁
babel 和 browerslist 的呈现,彻底解决了手动打补丁的劣势问题,通过简略的配置就能够实现主动注入补丁代码。
比方有这样一段代码:
// index.js | |
Promise.resolve().finally(); |
在 Windows10 的 Edge 17 中不能运行,因为其不反对 Promise.prototype.finally
办法,借助 babel,运行以下命令:
npx babel index.js --out-file compiled.js
并且存在这样一份配置文件:
// babel.config.js | |
module.exports = { | |
presets: [ | |
[ | |
"@babel/env", | |
{ | |
targets: {edge: "17",}, | |
useBuiltIns: "usage", | |
corejs: "2" | |
}, | |
], | |
], | |
}; |
而后就会输出这样的内容:
"use strict"; | |
require("core-js/modules/es7.promise.finally.js"); | |
Promise.resolve().finally(); |
能够看到,babel 曾经主动的在代码的顶部注入了来自 JavaScript 规范库 core-js 中 Promise.prototype.finally
实现代码。大略的过程是这样的:
npx babel
调用 @babel/cli 提供的终端命令babel
来运行 @babel/core-
@babel/core 开始读取配置、预设,调度各个组件执行编译流程
- 调用 @babel/parser 生成源码的 AST
- 调用 @babel/traverse 提供给插件转换代码的能力 (@babel/polyfill 将在这一阶段染指 )
- 调用 @babel/generator 生成代码
- 写入代码到文件
而这个过程波及到的依赖只有 @babe/core、@babel/preset-env 和 @babel/polyfill。
@babel/core
@babel/core 是 babel 的外围库,蕴含了 @babel/preser、@babel/traverse、@babel/generator 等解析、转换和生成代码的库。它从 @babel/cli 承受参数并调度各个组件执行编译流程,是整个流程的调度者和组织者。
@babel/preset-env
@babel/preset-env 提供了一组默认插件的预设,官网提供的预设有四个:
- @babel/preset-env
- @babel/preset-flow
- @babel/preset-react
- @babel/preset-typescript
而这些预设最终会被解析成一组插件,以 @babel/preset-react 为例,它最终会生成以下插件列表:
- @babel/plugin-syntax-jsx
- @babel/plugin-transform-react-jsx
- @babel/plugin-transform-react-display-name
这些插件则被用来反对 react 和装换 jsx 语法。
@babel/polyfill
自 Babel7.4.0 开始,@babel/polyfill 曾经拆分成了两个包:core-js 和 regenerator runtime,core-js 用于反对 ECMAScript 的新个性,regenerator-runtime 用于反对 generator 函数。这个插件也默认被蕴含在 @babel/preset-env 预设中。
Babel 将查看所有的代码,以查找指标环境中短少的性能,并且利用 @babel/polyfill 注入所需的 polyfill。
动静打补丁
动静打补丁依然有一个最大的缺点:补丁冗余。以 Object.assign
为例,在反对这个个性的浏览器上就没必要引入这个补丁,所以就会造成补丁的冗余。而社区就呈现了依据浏览器个性动静打补丁的计划。
Polyfill.io 就是这样一个服务,它会依据浏览器 user-agent 的不同,返回不同的补丁代码。比方想加载 Promise
的补丁代码,能够间接引入:
<script src="https://polyfill.io/v3/polyfill.js?features=Promise"></script>
在较高版本的 Chrome(89) 浏览器上,就会返回空的内容:
将 User-agent 改为 IE11 后,就会返回残缺的 polyfill 代码:
这个服务还提供了丰盛的 URL 查问参数来定制 polyfill,具体能够查阅文档 url-builder。
同时,如果对 Polyfill.io 的稳定性和安全性有要求,能够借助开源的 polyfill service 搭建本人的服务。
最佳实际
思考目前社区的这些计划,并联合具体的业务场景,如果对加载资源有着极致的性能要求,能够思考基于 Polyfill.io 自建 polyfill 服务,动静注入 polyfill。而对于一些对加载性能不那么敏感的我的项目,则齐全能够应用 babel 来主动注入 polyfill。当然也能够两者联合应用,然而须要均衡保护老本和收益。