深入理解 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);
}();
此逻辑会:
- 动态加载
polyfills-legacy-xxx.js
(SystemJS runtime); - 再通过
System.import()
加载index-legacy-xxx.js
(legacy bundle); - 保证在旧浏览器中应用依然可用。
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)
根据官方文档,存在以下不足:
- Modern bundle 仍会被下载
所有支持 ESM 的浏览器都会先请求 modern bundle,即使最终使用 legacy bundle。 - 语法错误可见
对不支持某些特性的浏览器,modern bundle 会抛出SyntaxError
,这是预期行为。 - 依赖 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 (⚠️不推荐) |
- 其它配置保持默认即可
参考资料: