vite-plugin-legacy 实战解析

深入理解 Vite 的浏览器兼容策略:vite-plugin-legacy 实战解析

在现代前端开发中,Vite 以其极速构建和模块化开发体验而受到广泛欢迎。但在某些场景下,我们仍需兼容旧版浏览器,比如旧版 Edge 或部分企业内部浏览器,这些浏览器虽然支持 ES Modules,却不完全支持一些现代语法特性。

为了解决这个问题,Vite 提供了官方插件 ------ vite-plugin-legacy,用于在构建时生成同时兼容现代与旧版浏览器的产物。

本文通过一份实际的构建后 HTML 文件,结合官方文档,对其工作原理、代码结构及兼容策略进行深入解析。


一、vite-plugin-legacy 的作用

Vite 默认输出的构建文件面向现代浏览器(支持 ES Modules)。 然而,一些"半现代"浏览器虽然支持 ESM,但在遇到如下语法时仍会报错:

  • dynamic import
  • import.meta
  • async generator

这些语法被官方称为 "widely-available features"(广泛可用特性)

为了兼容不支持它们的浏览器,vite-plugin-legacy 提供了降级机制

以移动端举例:

ES Modules从chrome61+、iOS10.3+开始支持,但是dynamic import从chrome63+、iOS11.3+开始支持, import.meta从chrome64+、iOS12+开始支持,async generator从chrome63+、iOS12+开始支持。也就是说chrome62、63、iOS10.3到12在不使用vite-plugin-legacy插件的情况下是无法运行的。

通过在 HTML 中注入检测脚本,如果浏览器不支持这些特性,则自动加载使用 SystemJS 的 legacy 构建版本。


二、构建后 HTML 示例

以下是 Vite 在启用 vite-plugin-legacy 后生成的 HTML 文件部分内容(节选):

html 复制代码
<!doctype html>
<html lang="en">
  <head>
    <script type="module" crossorigin src="/index-D5PMgvr3.js"></script>
    <link rel="stylesheet" crossorigin href="/index-uC42trf7.css" />

    <script type="module">
      import.meta.url;
      import("_").catch(() => 1);
      (async function*() {})().next();
      if (location.protocol != "file:") {
        window.__vite_is_modern_browser = true;
      }
    </script>

    <script type="module">
      !function() {
        if (window.__vite_is_modern_browser)
          return;
        console.warn("vite: loading legacy chunks...");
        var e = document.getElementById("vite-legacy-polyfill"),
            n = document.createElement("script");
        n.src = e.src;
        n.onload = function() {
          System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'));
        };
        document.body.appendChild(n);
      }();
    </script>
  </head>

  <body>
    <div id="app"></div>
    <script nomodule>
      !function() {
        var e = document, t = e.createElement("script");
        if (!("noModule" in t) && "onbeforeload" in t) {
          var n = !1;
          e.addEventListener("beforeload", function(e) {
            if (e.target === t) n = !0;
            else if (!e.target.hasAttribute("nomodule") || !n) return;
            e.preventDefault();
          }, !0);
          t.type = "module";
          t.src = ".";
          e.head.appendChild(t);
          t.remove();
        }
      }();
    </script>
    <script nomodule crossorigin id="vite-legacy-entry" data-src="/index-legacy-BcmeP17z.js">
      System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))
    </script>
  </body>
</html>

三、兼容原理分析

1️⃣ 现代浏览器路径

现代浏览器能够解析以下语法:

typescript 复制代码
import.meta.url;
import("_").catch(() => 1);
(async function*() {})().next();

如果执行成功,会设置:

ini 复制代码
window.__vite_is_modern_browser = true;

表示浏览器完全支持"广泛可用特性",无需加载 legacy 构建,只会执行:

xml 复制代码
<script type="module" src="/assets/index-D5PMgvr3.js"></script>

2️⃣ 不完全支持现代特性的浏览器路径

对于某些支持 ESM 但不支持这些语法的浏览器(如旧版 Edge):

  • 上述检测语法会抛出 SyntaxError
  • window.__vite_is_modern_browser 不会被设置;
  • 页面会执行降级逻辑:
ini 复制代码
!function() {
  if (window.__vite_is_modern_browser) return;
  var e = document.getElementById("vite-legacy-polyfill"),
      n = document.createElement("script");
  n.src = e.src;
  n.onload = function() {
    System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'));
  };
  document.body.appendChild(n);
}();

此逻辑会:

  1. 动态加载 polyfills-legacy-xxx.js(SystemJS runtime);
  2. 再通过 System.import() 加载 index-legacy-xxx.js(legacy bundle);
  3. 保证在旧浏览器中应用依然可用。

3️⃣ nomodule 补丁

HTML 中还有一段 <script nomodule>

ini 复制代码
<script nomodule>
!function() {
  var e = document, t = e.createElement("script");
  if (!("noModule" in t) && "onbeforeload" in t) {
    var n = !1;
    e.addEventListener("beforeload", function(e) {
      if (e.target === t) n = !0;
      else if (!e.target.hasAttribute("nomodule") || !n) return;
      e.preventDefault();
    }, !0);
    t.type = "module";
    t.src = ".";
    e.head.appendChild(t);
    t.remove();
  }
}();
</script>

用于修复早期 Safari 对 nomodule 的错误解析,避免错误加载不该执行的脚本。

4️⃣ 工作机制总结

浏览器类型 支持 ESM 支持 import.meta / async generator 加载构建类型 加载方式
Chrome / Safari / Edge 新版等现代化浏览器 Modern <script type="module">
chrome62、63等支持esm但不支持import()等wide-widely-available features的半现代化浏览器 Legacy SystemJS via System.import()
IE11等完全不支持 ESM的浏览器 Legacy <script nomodule>

四、官方缺点(Drawbacks)

根据官方文档,存在以下不足:

  1. Modern bundle 仍会被下载
    所有支持 ESM 的浏览器都会先请求 modern bundle,即使最终使用 legacy bundle。
  2. 语法错误可见
    对不支持某些特性的浏览器,modern bundle 会抛出 SyntaxError,这是预期行为。
  3. 依赖 SystemJS
    Legacy 构建使用 SystemJS,体积较大、性能略差。

五、配置与实践建议

Vite 配置示例

javascript 复制代码
// vite.config.js
import legacy from '@vitejs/plugin-legacy';

export default {
  plugins: [
    legacy({
      modernPolyfills: ['Array.prototype.flat', 'Promise.allSettled']
    })
  ]
};

配置建议

  • modernPolyfills
场景 建议配置
你只支持现代浏览器 modernPolyfills: false
需要少量兼容性特性(如 Array.flat modernPolyfills: ['Array.prototype.flat']
你想"一次性全加上"并不在乎体积 modernPolyfills: true(⚠️不推荐)
  • 其它配置保持默认即可

参考资料:

复制代码
相关推荐
前端赵哈哈2 小时前
Vue I18n 完整安装与使用指南
前端·vue.js·面试
秋田君2 小时前
Vue3 + VitePress 搭建部署组件库文档平台(结合 Element Plus 与 Arco Design Vue)—— 超详细图文教程
前端·vue.js·arco design
code_Bo2 小时前
前端使用snapdom报错问题
前端·javascript·vue.js
一壶浊酒..3 小时前
什么是AJAX
前端·javascript·ajax
智能化咨询3 小时前
基于Spring Boot + Vue 3的乡村振兴综合服务平台性能优化与扩展实践
vue.js·spring boot·性能优化
fruge3653 小时前
从零到一:我在 Rokid Glasses 上“画”出一个远程协作系统
前端
BumBle3 小时前
高频扫码场景下的去重与接口调用方案
前端·javascript
Mapmost3 小时前
半透明模型渲染全白?Mapmost Studio一招搞定
前端