ES模块预加载需满足三个硬性条件:路径完全一致、跨域配CORS、仅支持绝对或根相对路径;且必须显式声明所有关键模块(不递归),适用于共用核心模块等场景。

ES 模块(ESM)利用 rel="modulepreload" 预加载关键模块,核心是让浏览器在 HTML 解析阶段就提前获取并解析模块及其静态依赖,而不是等到执行到 import 语句时才发起请求。它不改变执行时机,但能显著缩短“首次 import”的网络等待时间。
必须写对的三个硬性条件
缺一不可,否则静默失效(控制台无报错,Network 面板也看不到请求):
-
路径完全一致:
href值必须与后续import或<script type="module" src="..."></script>中写的路径逐字匹配——包括协议、域名、大小写、扩展名(如.mjs)、查询参数(如?v=2.1.0) -
跨域必须配 CORS:若模块来自 CDN 或其他源,
<link>标签需带crossorigin属性,且服务端响应头中必须包含有效的Access-Control-Allow-Origin -
路径格式受限:只支持绝对路径(
https://cdn.com/a.mjs)或根相对路径(/assets/main.mjs),不支持./或../开头的相对路径
要手动列全所有关键模块,不能只写入口
modulepreload 不自动递归分析依赖,只预加载你显式声明的模块。如果 main.mjs 静态导入了 utils.mjs 和 chart.mjs,而你只写了 main.mjs 的预加载,那后两者仍会形成瀑布链。
正确做法是把整条关键路径上的模块都单独声明:
立即学习“Java免费学习笔记(深入)”;
<link rel="modulepreload" href="/assets/main.mjs"><br><link rel="modulepreload" href="/assets/utils.mjs"><br><link rel="modulepreload" href="/assets/chart.mjs"><br><link rel="modulepreload" href="https://cdn.example.com/d3.mjs">
特别注意:深层嵌套也要覆盖,比如 chart.mjs → d3.mjs → wasm-loader.mjs,三者都得写。
适用场景和避坑要点
真正带来收益的用法有明确边界:
- 适合:被多个入口共用的核心模块(如状态管理、路由、基础 UI 组件);用户操作前可预判的模块(如悬停菜单时预加载对应页面);有深层静态依赖的主模块
-
不适合:仅被一处 import 且体积很小的模块;动态
import()的路径含变量(如import(`./${name}.js`));未构建的源码文件(如.ts);路径含哈希但 HTML 未同步更新 -
不要加
as属性:写成rel="modulepreload" as="script"会让浏览器降级为普通 preload,失去模块图解析能力 -
位置很重要:必须放在
内,且早于任何触发模块加载的脚本(<script type="module"></script>或首次import()调用)
验证是否生效的实操方法
打开 Chrome DevTools 的 Network 面板:
- 筛选 JS 类型,查看目标资源的 Initiator 列:显示
Parser或Other表示由 HTML 解析阶段触发,即modulepreload生效;若显示Script,说明仍是按需加载 - 切换到 Timing 选项卡,对比预加载资源的
Start Time是否明显早于首次import()执行时刻 - 更直接的方式:在代码中加计时器,如
console.time('load'); await import('./utils.mjs');,对比加/不加modulepreload的耗时差异


















