前端 polyfill

776 阅读4分钟

polyfill是什么?

  • Polyfill是一种JavaScript库,它提供了原生的替代实现,使得现代Web特性在老旧浏览器中得以正常工作。Polyfill的主要目的是解决浏览器兼容性问题,确保代码在多种浏览器中正常运行。

  • 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 差异,在旧的浏览器上支持新的特性。

polyfill的作用

  • 兼容性:Polyfill提供了原生的替代实现,使得现代Web特性在老旧浏览器中得以正常工作。

  • 功能实现:Polyfill可以实现一些现代Web特性,如Promise、fetch API等,即使老旧浏览器不支持这些特性。

  • 性能优化:Polyfill可以帮助开发者优化代码性能,例如通过实现requestAnimationFrame等API。

babel-polyfill

  • Babel
    

    默认只转换新的

    Javascript
    

    语法,而不转换新的API,比如

    • Iterator, Generator, Set, Maps, Proxy, Reflect,Symbol,Promise 等全局对象
    • 在全局对象上的方法,比如说ES6在Array对象上新增了Array.find方法,Babel就不会转码这个方法
  • 如果想让这个方法运行,必须使用 babel-polyfill来转换等

  • Babel 7.4之后不再推荐使用@babel/polyfill

  • babel v7 推荐使用@babel/preset-env代替以往的诸多polyfill方案

  • babel-preset-env

  • babel-polyfill

  • babel-runtime

  • babel-plugin-transform-runtime

安装

npm i @babel/polyfill

targets

  • 配置参数targets表示我们需要支持哪些平台和哪些版本
  • browserslist
{
    test: /\.js?$/,
    exclude: /node_modules/,
    use: {
        loader: 'babel-loader',
        options: {
            targets: {
                //"browsers": ["ie >= 8", "chrome >= 62"],// 推荐使用 .browserslist
                "browsers": [">1%"]
            }
        }
    }
}

useBuiltIns: false

  • babel-polyfill 它是通过向全局对象和内置对象的prototype上添加方法来实现的。比如运行环境中不支持Array.prototype.find方法,引入polyfill, 我们就可以使用ES6方法来编写了,但是缺点就是会造成全局空间污染
  • @babel/preset-env为每一个环境的预设
  • @babel/preset-env默认只支持语法转化,需要开启useBuiltIns配置才能转化API和实例方法
  • useBuiltIns可选值包括:"usage" | "entry" | false, 默认为 false,表示不对polyfills 处理,这个配置是引入 polyfills 的关键
  • useBuiltIns: false 此时不对 polyfill 做操作。如果引入 @babel/polyfill,则无视配置的浏览器兼容,引入所有的 polyfill

src/index.js

src/index.js

import '@babel/polyfill';
let sum = (a, b) => a + b;
let promise = Promise.resolve();
console.log([1, 2, 3].find(item => item === 2));

webpack.config.js

webpack.config.js

const path = require('path');
module.exports = {
    mode: 'development',
    devtool: false,
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'main.js'
    },
    module: {
        rules: [
            {
                test: /\.js?$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        sourceType: "unambiguous",
+                       presets: [["@babel/preset-env", { useBuiltIns: false }]]
                    }
                }
            }
        ]
    },
    plugins: []
};

useBuiltIns: "entry"

  • 在项目入口引入一次(多次引入会报错)
  • "useBuiltIns": "entry" 根据配置的浏览器兼容,引入浏览器不兼容的 polyfill。需要在入口文件手动添加 import '@babel/polyfill',会自动根据 browserslist 替换成浏览器不兼容的所有 polyfill
  • 这里需要指定 core-js 的版本,corejs默认是2,
  • 如果配置 corejs: 3, 则import '@babel/polyfill' 需要改成 import 'core-js/stable';import 'regenerator-runtime/runtime';
    • corejs默认是2
  • 50.8 KiB

安装

 npm install --save core-js@2    npm install --save core-js@3

src\index.js

src\index.js core-js@2

import '@babel/polyfill';
let sum = (a, b) => a + b;
let promise = Promise.resolve();
console.log([1, 2, 3].find(item => item === 2));

core-js@3

import 'core-js/stable';
import 'regenerator-runtime/runtime';
let sum = (a, b) => a + b;
let promise = Promise.resolve();
console.log([1, 2, 3].find(item => item === 2));

webpack.config.js

{
  test: /\.js?$/,
  exclude: /node_modules/,
  use: {
      loader: 'babel-loader',
      options: {
+         presets: [["@babel/preset-env", { useBuiltIns: 'entry', corejs: { version: 3 } }]]
      }
  }
}
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

"useBuiltIns": "usage"

  • "useBuiltIns": "usage" usage 会根据配置的浏览器兼容,以及你代码中用到的 API 来进行 polyfill,实现了按需添加
  • 当设置为usage时,polyfill会自动按需添加,不再需要手工引入@babel/polyfill

src/index.js

src/index.js

console.log([1, 2, 3].find(item => item === 2));
console.log(Array.prototype.find);
console.log(Array.prototype.hasOwnProperty('find'));

webpack.config.js

webpack.config.js

{
  test: /\.js?$/,
  exclude: /node_modules/,
  use: {
      loader: 'babel-loader',
      options: {
+         presets: [["@babel/preset-env", { useBuiltIns: 'usage', corejs: { version: 3 } }]]
      }
  }
}

babel-runtime

  • Babel为了解决全局空间污染的问题,提供了单独的包babel-runtime用以提供编译模块的工具函数
  • 简单说 babel-runtime 更像是一种按需加载的实现,比如你哪里需要使用 Promise,只要在这个文件头部import Promise from 'babel-runtime/core-js/promise'就行了

安装

npm i babel-runtime --save-dev

src/index.js

src/index.js

import Promise from 'babel-runtime/core-js/promise';
const p = new Promise(()=> {});

babel-plugin-transform-runtime

  • @babel/plugin-transform-runtime
    

    插件是为了解决

    • 多个文件重复引用 相同helpers(帮助函数)=>提取运行时
    • 新API方法全局污染 -> 局部引入
  • 启用插件babel-plugin-transform-runtime后,Babel就会使用babel-runtime下的工具函数

  • babel-plugin-transform-runtime插件能够将这些工具函数的代码转换成require语句,指向为对babel-runtime的引用

  • babel-plugin-transform-runtime
    

    就是可以在我们使用新 API 时自动 import

    babel-runtime
    

    里面的

    polyfill
    
    • 当我们使用 async/await 时,自动引入 babel-runtime/regenerator
    • 当我们使用 ES6 的静态事件或内置对象时,自动引入 babel-runtime/core-js
    • 移除内联babel helpers并替换使用babel-runtime/helpers 来替换

安装

npm i @babel/plugin-transform-runtime @babel/runtime-corejs3 --save-dev

webpack.config.js

webpack.config.js

{
    test: /\.js?$/,
    exclude: /node_modules/,
    use: {
        loader: 'babel-loader',
        options: {
            sourceType: "unambiguous",
            presets: [["@babel/preset-env", { useBuiltIns: 'usage', corejs: { version: 3 } }]],
            plugins: [
                [
                    "@babel/plugin-transform-runtime",
                    {
                        corejs: 3,
                        helpers: true,
                        regenerator: true
                    }
                ],
            ]
        }
    }
 }

corejs: 3

  • 当我们使用 ES6 的静态事件或内置对象时自动引入 babel-runtime/core-js

    //var _Promise = __webpack_require__("./node_modules/@babel/runtime-corejs3/core-js-stable/promise.js");
    const p = new Promise(() => { });
    console.log(p);
    

helpers: true

  • 移除内联babel helpers并替换使用babel-runtime/helpers 来替换
  • 避免内联的 helper 代码在多个文件重复出现
class A {

}
class B extends A {

}
console.log(new B());

regenerator: true

  • 是否开启generator函数转换成使用regenerator runtime来避免污染全局域
//var _regeneratorRuntime = __webpack_require__(/*! @babel/runtime-corejs3/regenerator */ "./node_modules/@babel/runtime-corejs3/regenerator/index.js");

function* gen() {

}
console.log(gen());

最佳实践

  • @babel/preset-env和plugin-transform-runtime二者都可以设置使用corejs来处理polyfill

项目开发

  • useBuiltIns使用usage

  • plugin-transform-runtime只使用其移除内联复用的辅助函数的特性,减小打包体积

    {
      "presets": [
          [
              "@babel/preset-env",
              {
                  "useBuiltIns": "usage",
                  "corejs": 3
              }
          ]
      ],
      "plugins": [
          [
              "@babel/plugin-transform-runtime",
              {
                  "corejs": false
              }
          ]
      ]
    }
    

类库开发

  • 类库开发尽量不使用污染全局环境的polyfill,因此@babel/preset-env只发挥语法转换的功能
  • polyfill由@babel/plugin-transform-runtime来处理,推荐使用core-js@3
{
    "presets": [
        [
            "@babel/preset-env"
        ]
    ],
    "plugins": [
        [
            "@babel/plugin-transform-runtime",
            {
                "corejs": {
                    "version": 3
                }
            }
        ]
    ]
}

polyfill-service

  • polyfill.io自动化的 JavaScript Polyfill 服务
  • polyfill.io通过分析请求头信息中的 UserAgent 实现自动加载浏览器所需的 polyfills
<script src="https://polyfill.io/v3/polyfill.min.js"></script>

参考资料