nomodule 是现代浏览器的“跳过指令”,需与 type="module" 配对使用;旧浏览器因不识别该属性而执行其脚本,但 legacy-bundle.js 必须是兼容 es5、含 polyfill 的 iife/umd 格式,且 safari 10.1 需额外运行时检测规避双执行。

nomodule 属性不是为老旧浏览器“开后门”,而是现代浏览器的“跳过指令”——它让 Chrome 61+、Firefox 60+、Safari 11+ 等识别 type="module" 的浏览器主动忽略带该属性的脚本;而 IE11、Android 4.4 WebView 等旧环境因根本不认识 nomodule,会把它当普通 <script></script> 处理并执行。真正的降级防错,靠的是 成对存在、语义分离、执行隔离。
nomodule 必须和 type="module" 配对使用才有效
单独写 <script nomodule src="fallback.js"></script> 是危险的:
- 现代浏览器跳过它,不发请求、不解析、不执行
- 旧浏览器(如 IE11)虽会尝试加载,但若前面没有
type="module"脚本兜底,整个页面可能白屏 - 更糟的是,IE11 实际上会静默忽略整行
<script nomodule></script>标签,既不报错也不执行
正确结构只有一种可靠形式:
<script type="module" src="app.mjs"></script><script nomodule src="legacy-bundle.js"></script>
行为分叉点只取决于是否支持 type="module":
- 支持者:加载执行
app.mjs,跳过第二行 - 不支持者:第一行被丢弃,第二行当作
<script src></script>同步加载执行
nomodule 脚本必须是真正兼容的 ES5 环境产物
nomodule 只控制“是否加载”,不负责“能否运行”。哪怕脚本成功进了 IE11,以下任一情况都会立刻崩溃:
- 语法错误:含
const、let、=>、${}、class→ 直接SyntaxError中断 - API 缺失:调用
fetch()、Promise、Array.from、Object.assign→ReferenceError或静默失败 - DOM 方法不可用:
element.classList.add()(IE9–)、querySelector()(IE7–)、addEventListener()(IE8–)→ 报错或无响应
因此 legacy-bundle.js 必须满足:
- 构建目标显式设为
targets: { ie: "11" },不能依赖 Babel 默认 preset - 手动引入
core-js/stable,且 polyfill 必须在所有业务代码之前执行 - 输出格式为 IIFE 或 UMD,不含任何
import/export,不使用动态import() - 避免依赖未垫片的现代特性(如
Map、Set、Proxy),Vue3 项目还需额外处理响应式降级
Safari 10.1 的特殊陷阱与绕过方案
Safari 10.1(macOS 10.12.4 / iOS 10.3)是个例外:它识别 type="module",却错误地也执行 nomodule 脚本,导致两套逻辑同时触发——UI 初始化两次、事件重复绑定、接口请求翻倍。
缓解方式是加一层运行时检测,只在“半吊子”浏览器中注入 fallback:
<script nomodule>
if (!('noModule' in document.createElement('script'))) {
document.write('<script src="legacy-bundle.js"><\/script>');
}
</script>
利用 noModule 属性是否存在来判断浏览器是否真正理解 nomodule 语义,仅 Safari 10.1 这类浏览器会进入 document.write 分支。
旧浏览器加载时的真实限制与优化建议
nomodule 脚本在旧浏览器中等效于同步 <script src></script>,默认阻塞 HTML 解析:
- 若
legacy-bundle.js体积大、执行慢,IE11 会卡住首屏渲染 -
async和defer在 IE10 及更早版本中无效,写了也白搭
优化手段包括:
- 将关键初始化逻辑内联进 HTML,减少网络请求
- 使用 UglifyJS 或 SWC 压缩 legacy bundle,剔除未用代码
- 对高频 DOM 操作做兼容封装(如用
getElementById替代querySelector,attachEvent替代addEventListener) - 避免在
nomodule脚本里做 UA 判断再异步加载 polyfill——旧浏览器解析慢,极易超时或中断
不复杂但容易忽略











