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

    1. import 'core-js';
  • 按需在业务项目的入口引入某些 polyfills

    1. import 'core-js/features/array/from';

    core-js-pure

    提供了不污染全局变量的垫片能力

    1. import _from from 'core-js-pure/features/array/from';
    2. import _flat from 'core-js-pure/features/array/flat';

    core-js-compact

  • 维护了按照browserslist规范的垫片需求数据

  • 比如筛选出全球使用份额大于 2.5% 的浏览器范围,并提供在这个范围下需要支持的垫片能力:

    1. require('core-js-builder')({
    2. targets: '> 0.5%',
    3. filename: './my-core-js-bundle.js',
    4. }).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); });
  1. <a name="0ugtM"></a>
  2. # 2 polyfill/垫片/补丁
  3. - polyfill 就是用社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上,使用该新特性。
  4. - 手动打补丁直接简单,也天然能实现“按需”,但这不是工程化方案。
  5. - 一个趋于完美的 polyfill 设计应该满足的核心原则是**按需加载补丁**
  6. - 按照用户终端环境
  7. - 按照业务代码使用情况
  8. - 按需加载补丁,意味着更小的 bundle size,直接决定了应用的性能
  9. <a name="zHuaX"></a>
  10. ## 2.1 使用babel-polyfill
  11. - babel-polyfill 融合了 core-js 和 regenerator-runtime
  12. - 目前已经计划废弃,新的 Babel 生态(@babel/preset-env V7.4.0 版本)鼓励开发者直接在代码中引入 core-js 和 regenerator-runtime。
  13. > regenerator-runtime模块来自facebook的regenerator模块。
  14. > 生成器函数、async、await函数经babel编译后,regenerator-runtime模块用于提供功能实现。
  15. - 【不推荐】粗暴地使用 babel-polyfill 一次性全量导入到项目中,
  16. - babel-polyfill 会将其所包含的所有补丁都应用在项目当中
  17. - 直接造成了项目 size 过大的问题,且存在污染全局变量的潜在问题
  18. - 【推荐】结合 **@babel/preset-env、useBuiltins(entry)、preset-env targets **
  19. <a name="3yibc"></a>
  20. ### @babel/preset-env + useBuiltins + preset-env targets 方案
  21. <a name="TaWHL"></a>
  22. #### 1、useBuiltins: 'entry'
  23. - @babel/preset-env定义了 Babel 所需插件预设
  24. - 由 Babel 根据 preset-env targets 配置的支持环境,自动按需加载 polyfills
  25. ```javascript
  26. {
  27. "presets": [
  28. ["@babel/env", {
  29. useBuiltIns: 'entry',
  30. targets: { chrome: 44 }
  31. }]
  32. ]
  33. }

由于将useBuiltIns配置为entry,因此:
image.png

2、useBuiltins: ‘usage’

我们再思考一个问题:如果某个业务代码中,并没有用到配置环境填充的 polyfills,那么这些 polyfills 的引入依然出现了引用浪费的情况。实际上环境需要是一回事儿,代码是否需要却是另一回事儿。比如,我的 MPA(多页面应用)项目需要提供 Promise Polyfill,但是某个业务页面中,并没有使用 Promise 特性,理想情况并不需要在当前页面中引入 Promise Polyfill bundle。

  • 将useBuiltins 配置为 usage,它可以真正根据代码情况,分析 AST(抽象语法树)进行更细粒度的按需引用。
  • 但是这种基于静态编译的按需加载补丁也是相对的

    • 因为 JavaScript 是一种弱规则的动态语言,比如:无法分辨数组的includes和字符串的includes

      2.2 在线动态打补丁

  • 在线补丁工具

  • 在高版本浏览器上,可能会返回空内容,因为该浏览器已经支持了 ES2015 特性。如果在低版本浏览器上,将会得到真实的 polyfills bundle。
  • https://polyfill.io/v3/polyfill.min.js?features=es2015,在业务中我们可以直接引入 polyfills bundle:
    1. <script src="https://polyfill.io/v3/polyfill.min.js?features=es2015"></script>