modulepreload仅提前发起ES模块网络请求,不执行、不解析、不阻塞;根本区别在于defer控制执行时机但不预下载,async破坏执行顺序,而modulepreload纯调度下载。

modulepreload 不是用来“优化模块执行”的,它只做一件事:在 HTML 解析早期就发起对 ES 模块及其直接依赖的网络请求,不执行、不阻塞、不改变执行顺序。用错场景或写法,它就等于没写。
modulepreload 和 script defer/async 的根本区别在哪
很多人以为加了 modulepreload 就能“让模块更快执行”,其实完全不是——它连执行都不管。
-
defer:控制执行时机(DOM 解析完再执行),但不保证提前下载;浏览器可能等<script defer>解析到才开始拉资源 -
async:异步下载 + 下载完立刻执行,破坏模块执行顺序,且无法用于有依赖的模块链 -
modulepreload:纯下载调度,只触发 fetch,不执行、不解析、不构建 module graph;后续<script type="module" src="...">才真正触发执行逻辑
典型错误:给 main.mjs 写了 modulepreload,但忘了它 import 的 utils.mjs 没列出来 → 浏览器仍要等 main.mjs 下载并解析后,才发现要拉 utils.mjs,预加载失效。
为什么写了 modulepreload 却没生效,控制台还报 Failed to load module script
最常见原因就两个:路径不一致、crossorigin 缺失。
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
立即学习“前端免费学习笔记(深入)”;
-
href值必须和后续<script type="module" src="...">的src**完全一致**,包括协议、域名、路径、查询参数(如?v=1.2.3) - 只要模块服务启用了 CORS(比如 CDN 域名、静态资源托管平台),
<link rel="modulepreload">就必须带crossorigin属性,否则浏览器直接忽略该预加载 - 不能用
modulepreload加载非模块脚本(如type="text/javascript"),会静默失败,无报错 - 路径必须是绝对路径或根相对路径(如
/assets/main.mjs),不支持./main.mjs或../vendor.mjs
怎么写一个真正有效的 modulepreload 链
手动写容易漏依赖、错路径、过期失效。真实项目应靠构建工具生成,但理解底层规则才能 debug。
- 多个模块按执行依赖顺序排列
<link>标签:先vendor.mjs,再main.mjs,利于 TCP 连接复用 - 每个
<link>必须显式列出,modulepreload不递归预加载被 import 的子模块 - 示例有效写法:
<link rel="modulepreload" href="/assets/vendor.mjs" crossorigin> <link rel="modulepreload" href="/assets/main.mjs" crossorigin>
- Vite/Rollup/Webpack 插件(如
vite-plugin-module-preload)可自动分析入口模块的依赖图并注入完整列表
modulepreload 用多了反而拖慢首屏?
会。它本质是高优先级 fetch,不加节制就会抢带宽。
- 预加载超过 3 个模块,尤其体积大、非首屏必需的,大概率挤占
critical.css或首屏图片的下载机会 - 实测数据:只预加载 1–2 个核心入口(如路由主模块 + 公共工具包),通常能减少 100–300ms 首屏 JS 加载延迟;预加载 5 个以上,FCP 可能反而变慢
- 它不解决模块体积问题——如果
main.mjs本身是未拆分的 800KB bundle,预加载只是更快地把瓶颈拉下来而已
最容易被忽略的一点:你得确认这个模块真正在首屏渲染路径里被同步 import,而不是藏在某个点击后才触发的 import() 里。否则,它就不是“当前导航中马上要用”的资源,不该用 modulepreload。

















