Vite 如何做代码降级和 Polyfill

1,734 阅读1分钟

通用解决方案

  • 结合前端成熟的编译工具链 Babel
  • 结合运行时基础库 core.js 做Polyfill

install npm modules

pnpm i @babel/cli @babel/core @babel/preset-env

配置 Babel file

{
  "presets": [
    [
      "@babel/preset-env", 
      {
        // 指定兼容的浏览器版本
        "targets": {
          "ie": "11"
        },
        "corejs": 3,
        // Polyfill 注入策略
        "useBuiltIns": "usage",
        // 不将 ES 模块语法转换为其他模块语法
        "modules": false
      }
    ]
  ]
}

useBuiltIns 可以设置 entry 和 usage 两个值,usage 设置成按需引入Polyfill。

@babel/preset-env 注入 Polyfill 痛点:

  • 全局注入,污染全局环境
  • 重复函数多次出现,文件体积冗余

推荐使用 transform-runtime

pnpm i @babel/plugin-transform-runtime -D
pnpm i @babel/runtime-corejs3 -S

@babel/runtime-corejs3 使用的是 core-js-pure,不全局注入不全量引入。

配置 Babel 文件

{
  "plugins": [
    [
      "@babel/plugin-transform-runtime", 
      {
        "corejs": 3
      }
    ]
  ],
  "presets": [
    [
      "@babel/preset-env", 
      {
        "targets": {
          "ie": "11"
        },
        "corejs": 3,
        // 关闭 @babel/preset-env 默认的 Polyfill 注入
        "useBuiltIns": false,
        "modules": false
      }
    ]
  ]
}

工具函数引入,引入 @babel/runtime-corejs3

Vite 解决方案

@vitejs/plugin-legacy 底部仍然使用 @babel/preset-env 以及 core-js 基础库

配置 vite

// vite.config.ts
import legacy from '@vitejs/plugin-legacy';
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    legacy({
      // 设置目标浏览器,browserslist 配置语法
      targets: ['ie >= 11'],
    })
  ]
})

构建产物

!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/assets/favicon.17e50649.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
    <!-- 1. Modern 模式产物 -->
    <script type="module" crossorigin src="/assets/index.c1383506.js"></script>
    <link rel="modulepreload" href="/assets/vendor.0f99bfcc.js">
    <link rel="stylesheet" href="/assets/index.91183920.css">
  </head>
  <body>
    <div id="root"></div>
    <!-- 2. Legacy 模式产物 -->
    <script nomodule>兼容 iOS nomodule 特性的 polyfill,省略具体代码</script>
    <script nomodule id="vite-legacy-polyfill" src="/assets/polyfills-legacy.36fe2f9e.js"></script>
    <script nomodule id="vite-legacy-entry" data-src="/assets/index-legacy.c3d3f501.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
  </body>
</html>

插件原理

  1. configResolved 钩子调整output属性,打包出一份Legacy 模式的产物
  2. renderChunk 对 Legacy 模式产物进行语法转译和 Polyfill 收集
  3. generateChunk 对先前收集到的 Polyfill 统一打包
  4. transformIndexHtml 将打包产物插入到 HTML 的结构中