core-js;polyfill;@babel/preset-env + useBuiltins + preset-env targets;regenerator-runtime;
疑问
配置@babel/preset-env + useBuiltins + preset-env targets 方案时,由 Babel 根据 preset-env targets 配置的支持环境,配置项为“@babel/env”,是否写错了?应该是“@babel/preset-env”吧?
摘录&心得
core-js 是一个 JavaScript 标准库
- 包含了 ECMAScript 2020 在内的多项特性的 polyfills
- 包含了 ECMAScript 在 proposals 阶段的特性、WHATWG/W3C 新特性等。
- core-js 和 Babel 深度绑定
- core-js与一个前端极具特色的概念polyfill(垫片/补丁)息息相关
红宝书在写演示代码时,经常会写很多浏览器支持检测代码,这些代码在日常开发中基本不用,就是因为core.js提供的polyfill替我们做了这部分工作
1 core-js工程一览
core-js 是一个由 Lerna 搭建的 Monorepo 风格的项目
- 五个相关包:
- core-js
- core-js-pure
- core-js-compact
- core-js-builder
- core-js-bundle
以Array.proptotype.every为例,core-js 需要在数组 Array 的原型上,以“污染数组原型”的方式来扩展方法。而 core-js-pure 则单独维护了一份 export 镜像../internals/export
core-js
实现基础垫片能力,是整个 core-js 的逻辑核心。
引入全局 polyfills
import 'core-js';
按需在业务项目的入口引入某些 polyfills
import 'core-js/features/array/from';
core-js-pure
提供了不污染全局变量的垫片能力
import _from from 'core-js-pure/features/array/from';
import _flat from 'core-js-pure/features/array/flat';
core-js-compact
维护了按照browserslist规范的垫片需求数据
比如筛选出全球使用份额大于 2.5% 的浏览器范围,并提供在这个范围下需要支持的垫片能力:
require('core-js-builder')({
targets: '> 0.5%',
filename: './my-core-js-bundle.js',
}).then(code => {}).catch(error => {});
可以被 Babel 生态使用,由 Babel 分析出根据环境需要按需加载的垫片;
core-js-builder
可以结合 core-js-compact 以及 core-js。
- 可以被 Node.js 服务使用,构建出不同场景的垫片包。 ```javascript // 把符合需求的 core-js 垫片打包到my-core-js-bundle.js文件当中 require(‘./packages/core-js-builder’)({ filename: ‘./packages/core-js-bundle/index.js’ }) .then(done) .catch(error => { console.error(error); process.exit(1); });
<a name="0ugtM"></a>
# 2 polyfill/垫片/补丁
- polyfill 就是用社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上,使用该新特性。
- 手动打补丁直接简单,也天然能实现“按需”,但这不是工程化方案。
- 一个趋于完美的 polyfill 设计应该满足的核心原则是**按需加载补丁**
- 按照用户终端环境
- 按照业务代码使用情况
- 按需加载补丁,意味着更小的 bundle size,直接决定了应用的性能
<a name="zHuaX"></a>
## 2.1 使用babel-polyfill
- babel-polyfill 融合了 core-js 和 regenerator-runtime
- 目前已经计划废弃,新的 Babel 生态(@babel/preset-env V7.4.0 版本)鼓励开发者直接在代码中引入 core-js 和 regenerator-runtime。
> regenerator-runtime模块来自facebook的regenerator模块。
> 生成器函数、async、await函数经babel编译后,regenerator-runtime模块用于提供功能实现。
- 【不推荐】粗暴地使用 babel-polyfill 一次性全量导入到项目中,
- babel-polyfill 会将其所包含的所有补丁都应用在项目当中
- 直接造成了项目 size 过大的问题,且存在污染全局变量的潜在问题
- 【推荐】结合 **@babel/preset-env、useBuiltins(entry)、preset-env targets **
<a name="3yibc"></a>
### @babel/preset-env + useBuiltins + preset-env targets 方案
<a name="TaWHL"></a>
#### 1、useBuiltins: 'entry'
- @babel/preset-env定义了 Babel 所需插件预设
- 由 Babel 根据 preset-env targets 配置的支持环境,自动按需加载 polyfills
```javascript
{
"presets": [
["@babel/env", {
useBuiltIns: 'entry',
targets: { chrome: 44 }
}]
]
}
2、useBuiltins: ‘usage’
我们再思考一个问题:如果某个业务代码中,并没有用到配置环境填充的 polyfills,那么这些 polyfills 的引入依然出现了引用浪费的情况。实际上环境需要是一回事儿,代码是否需要却是另一回事儿。比如,我的 MPA(多页面应用)项目需要提供 Promise Polyfill,但是某个业务页面中,并没有使用 Promise 特性,理想情况并不需要在当前页面中引入 Promise Polyfill bundle。
- 将useBuiltins 配置为 usage,它可以真正根据代码情况,分析 AST(抽象语法树)进行更细粒度的按需引用。
但是这种基于静态编译的按需加载补丁也是相对的
在线补丁工具
- 以 Polyfill.io 为代表,它提供了 CDN 服务,使用者可以按照所需环境
- 生成打包链接:https://polyfill.io/v3/url-builder/
- 在高版本浏览器上,可能会返回空内容,因为该浏览器已经支持了 ES2015 特性。如果在低版本浏览器上,将会得到真实的 polyfills bundle。
- 如https://polyfill.io/v3/polyfill.min.js?features=es2015,在业务中我们可以直接引入 polyfills bundle:
<script src="https://polyfill.io/v3/polyfill.min.js?features=es2015"></script>