nomodule不是降级开关,而是现代浏览器的“跳过指令”;单独使用时,Chrome/Firefox/Safari新版本跳过不执行,IE11等旧浏览器静默忽略整行标签,导致新旧用户均白屏。

nomodule 不是降级开关,它只是现代浏览器的“跳过指令”——单独使用会直接导致新旧用户都白屏。
为什么 nomodule 单独写就失效
Chrome 61+、Firefox 60+、Safari 11+ 看到 nomodule 就完全跳过对应 <script>:不发请求、不解析、不执行;IE11 和 Android 4.4 WebView 根本不认识这个属性,会静默忽略整行标签,既不报错也不加载。结果就是:nomodule 脚本在新浏览器里没机会运行,在旧浏览器里也没被真正执行。
常见错误现象包括:
- Chrome DevTools Network 面板搜不到
legacy-bundle.js(说明被跳过) - IE11 控制台执行
typeof module返回"undefined",但脚本也未运行 - 页面 JS 功能完全缺失,无报错、无日志、无网络请求
必须和 type="module" 成对出现
只有两者并存,浏览器才能按语义做互斥选择:
立即学习“前端免费学习笔记(深入)”;
- 支持
type="module"的浏览器:执行第一行,跳过第二行 - 不支持
type="module"的浏览器:丢弃第一行,把第二行当普通<script src>同步加载执行
推荐写法(顺序无关,但模块优先更易读):
<script type="module" src="app.mjs"></script> <script nomodule src="legacy-bundle.js"></script>
注意:legacy-bundle.js 必须是真正 ES5 兼容的 IIFE 或 UMD 格式,且不能含 import/export;否则即使进了 IE11,也会因语法错误崩溃。
nomodule 脚本里调用现代 API 仍会崩溃
nomodule 只控制“是否加载”,不负责“能否运行”。旧浏览器执行时遇到以下任一情况,立刻失败:
- 语法层面:
const、let、箭头函数、模板字面量、class→SyntaxError - API 层面:
fetch()、Promise、Array.from()、Object.assign()→ReferenceError或静默失败 - DOM 层面:
element.classList.add()(IE9–)、querySelector()(IE7–)、addEventListener()(IE8–)→ 报错或无响应
构建时需显式配置 targets: { ie: "11" },手动引入 core-js/stable,且 polyfill 必须在所有业务代码之前执行。
Safari 10.1 的双执行陷阱与绕过方式
Safari 10.1(macOS 10.12.4 / iOS 10.3)识别 type="module",却错误地同时执行 nomodule 脚本,造成 UI 初始化两次、事件重复绑定、数据请求翻倍。
最稳妥的缓解方式是加一层运行时检测,只在“半吊子”浏览器中注入 fallback:
if (!('noModule' in HTMLScriptElement.prototype)) {
document.write('<script src="app-legacy.js"></script>');
}
该判断比 UA 更可靠,且避免了 fetch+eval 带来的 CSP 风险和额外 polyfill 依赖。
真正容易被忽略的点在于:构建输出的 legacy-bundle.js 必须是完整可运行的闭环,不是简单转译,而是要包含 polyfill + DOM 兜底 + 全局变量清理;否则就算加载成功,运行时照样崩。



















