轻云听书app
17.87MB · 2025-10-15
在现代前端开发中,Vite 以其极速构建和模块化开发体验而受到广泛欢迎。但在某些场景下,我们仍需兼容旧版浏览器,比如旧版 Edge 或部分企业内部浏览器,这些浏览器虽然支持 ES Modules,却不完全支持一些现代语法特性。
为了解决这个问题,Vite 提供了官方插件 —— vite-plugin-legacy,用于在构建时生成同时兼容现代与旧版浏览器的产物。
本文通过一份实际的构建后 HTML 文件,结合官方文档,对其工作原理、代码结构及兼容策略进行深入解析。
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插件的情况下是无法运行的。
以下是 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>
现代浏览器能够解析以下语法:
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>
对于某些支持 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);
}();
此逻辑会:
polyfills-legacy-xxx.js
(SystemJS runtime);System.import()
加载 index-legacy-xxx.js
(legacy bundle);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
的错误解析,避免错误加载不该执行的脚本。
浏览器类型 | 支持 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> |
根据官方文档,存在以下不足:
SyntaxError
,这是预期行为。// vite.config.js
import legacy from '@vitejs/plugin-legacy';
export default {
plugins: [
legacy({
modernPolyfills: ['Array.prototype.flat', 'Promise.allSettled']
})
]
};
场景 | 建议配置 |
---|---|
你只支持现代浏览器 | modernPolyfills: false |
需要少量兼容性特性(如 Array.flat ) | modernPolyfills: ['Array.prototype.flat'] |
你想“一次性全加上”并不在乎体积 | modernPolyfills: true (️不推荐) |
参考资料:
<script nomodule>