深入理解 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 示例

以下是 Vite 在启用 vite-plugin-legacy 后生成的 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️⃣ 现代浏览器路径

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

import.meta.url;
import("_").catch(() => 1);
(async function*() {})().next();

如果执行成功,会设置:

window.__vite_is_modern_browser = true;

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

<script type="module" src="/assets/index-D5PMgvr3.js"></script>

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

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

  • 上述检测语法会抛出 SyntaxError
  • window.__vite_is_modern_browser 不会被设置;
  • 页面会执行降级逻辑:
!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>

<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的半现代化浏览器LegacySystemJS 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 配置示例

// vite.config.js
import legacy from '@vitejs/plugin-legacy';

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

配置建议

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

参考资料:

  • Vite 官方文档:Browser Compatibility
  • SystemJS 官方文档
  • MDN:<script nomodule>
本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]