nomodule不是兼容开关,而是现代浏览器的跳过指令;必须与type="module"成对使用,单独写会导致新旧浏览器均白屏;nomodule脚本须为ES5兼容的IIFE/UMD格式并含polyfill,且Safari 10.1需运行时检测规避双执行。

nomodule 不是兼容开关,而是现代浏览器的跳过指令
单独写 <script nomodule src="fallback.js"></script> 会导致现代和旧浏览器都可能白屏。Chrome、Firefox、Safari(61+/60+/11+)看到 nomodule 就直接跳过:不发请求、不解析、不执行;IE11 和 Android 4.4 WebView 根本不认识这个属性,但也不会“自动降级”——它会静默忽略整行标签,既不报错也不加载。结果是:新用户没 JS,老用户也没 JS。
真正起作用的是组合行为:type="module" 和 nomodule 必须成对出现,且语义互斥:
- 支持
type="module"的浏览器:执行第一行,跳过第二行 - 不支持
type="module"的浏览器:丢弃第一行,把第二行当普通<script src></script>同步加载执行
推荐写法(模块优先):
<script type="module" src="app.mjs"></script><script nomodule src="legacy-bundle.js"></script>
nomodule 脚本必须是真·ES5 兼容的 IIFE/UMD
nomodule 只控制“要不要加载”,不管“能不能运行”。哪怕脚本进了 IE11,以下任一情况都会立刻崩溃:
- 语法错误:
const、let、=>、${}、class→ 直接SyntaxError - API 缺失:
fetch()、Promise、Array.from()、Object.assign()→ReferenceError或静默失败 - DOM 方法不可用:
element.classList.add()(IE9–)、querySelector()(IE7–)、addEventListener()(IE8–)→ 报错或无响应
构建时必须显式指定目标环境,不能依赖默认 preset:
- Babel 配置需含
targets: { ie: "11" } - 手动引入
core-js/stable,且 polyfill 必须在所有业务代码之前执行 - 输出格式只能是 IIFE 或 UMD,严禁含
import/export,禁用dynamic import()
Safari 10.1 的双执行陷阱必须 runtime 规避
Safari 10.1(macOS 10.12.4 / iOS 10.3)是个特例:它识别 type="module",却错误地也执行 nomodule 脚本。结果是 app.mjs 和 legacy-bundle.js 同时加载、同时执行——UI 初始化两次、事件重复绑定、API 请求翻倍。
最稳妥的缓解方式是加一层运行时检测,只让 Safari 10.1 这类“半吊子”浏览器走降级路径:
if (!('noModule' in document.createElement('script'))) {
const s = document.createElement('script');
s.src = 'legacy-bundle.js';
document.head.appendChild(s);
}
注意:不能用 document.write(),它在 modern HTML 中已不安全;也不能把检测逻辑塞进 legacy-bundle.js 里——那已经晚了。
旧浏览器中 nomodule 脚本是同步阻塞的
在 IE11 等旧环境中,<script nomodule src="legacy-bundle.js"></script> 等效于普通 <script src></script>,没有 async 或 defer 行为。这意味着:
- 它会同步阻塞 HTML 解析,页面渲染卡住直到脚本下载并执行完毕
-
async属性在 IE11 中被忽略,写了也无效 - 体积越大、执行越慢,首屏白屏时间越长
实操建议:
- 把
legacy-bundle.js压缩到最小,用 UglifyJS 或 SWC 配合preset-env做深度 tree-shaking - 关键初始化逻辑(如 DOM 就绪判断、基础 polyfill 注入)可内联进 HTML,避免额外请求
- 不要指望
nomodule脚本能“优雅异步”,它就是个同步 fallback,得按最差体验来优化
最容易被忽略的一点:加载成功 ≠ 能跑通。哪怕你确认 IE11 加载了脚本,只要里面有一行 const x = 1 或 fetch('/api'),它就立刻中断——nomodule 不做任何语法或 API 兼容性兜底。











