type="module"启用ES模块机制,强制路径显式、作用域隔离、默认defer且按模块图拓扑序执行,file://协议下完全失效,须HTTP服务运行。

type="module" 脚本默认就是 defer,但不是“延迟执行”那么简单
它本质是启用模块图(module graph)加载机制:浏览器会先下载所有 import 依赖,按拓扑顺序解析、实例化、再执行——这个过程天然异步,且严格保序。即使你没写 defer,它也不会阻塞 HTML 解析;写了 async 也只影响“何时开始下载”,不破坏模块间的执行顺序。
常见误解:defer 是让脚本等 DOM 就绪后执行,而 type="module" 的“延迟”来自模块生命周期本身——它必须等整个依赖树就绪才能执行顶层代码。所以两个 <script type="module" src="a.js"></script> 和 <script type="module" src="b.js"></script> 一定按 DOM 顺序执行,哪怕 b.js 下载更快。
- 多个
type="module"脚本不会并行执行顶层代码,而是串行等待前一个模块执行完 -
async对模块只改变初始 fetch 时机,不影响模块图构建和执行顺序 - 内联
<script type="module">import('./x.js')</script>会立即触发动态导入,但静态import仍走完整模块图流程
import 路径不合法,模块根本不会发起网络请求
浏览器对 import 路径做静态检查,失败直接报 Failed to resolve module specifier,连 HTTP 请求都不会发。这不是 404,是解析阶段就卡死。
-
import './utils'❌ 缺扩展名,多数浏览器拒绝(Chrome/Firefox/Safari 均不补.js) -
import 'lodash'❌ 裸包名,原生不支持,除非配了<script type="importmap"> -
import '/utils.js'✅ 绝对路径,从站点根目录找 -
import './utils.js'✅ 相对路径,从当前模块文件所在 URL 解析,不是 HTML 文件位置
file:// 协议下 type="module" 完全失效,不是 bug 是策略
Chrome、Firefox、Safari 在 file:/// 下直接禁用所有模块加载逻辑——控制台可能只显示笼统的 CORS 错误,甚至无任何日志。这不是路径或语法问题,是浏览器硬性拦截。
立即学习“前端免费学习笔记(深入)”;
- 双击打开 HTML 文件?立刻失败。必须用 HTTP 服务:如
npx serve、python3 -m http.server、VS Code Live Server - 跨域模块(比如 CDN 上的
https://cdn.jsdelivr.net/npm/lodash-es@4/index.js)必须返回Access-Control-Allow-Origin: *,否则报 CORS error - 普通
<script src="xxx">在file://下能跑,容易让人误以为“只是模块写错了”
模块作用域隔离导致全局变量不可见,别指望 document.currentScript
每个 type="module" 有独立词法作用域,顶层声明的 const、function 不挂到 window,document.currentScript 在模块里永远是 null。
- 老项目靠
window.utils = {...}共享逻辑?迁移到模块必须改用export/import - 想在模块里获取当前
<script>标签?做不到。得靠document.currentScript的替代方案,比如给 script 加id后用document.getElementById() - 多个模块脚本之间变量完全不共享,哪怕它们都在同一个 HTML 里 —— 这是设计,不是缺陷
import,而是没意识到 ./utils.js 是相对于模块文件、不是 HTML 文件,也没意识到 file:// 下连第一个 import 都不会发请求。



















