modulepreload仅负责提前拉取网络资源,不解析执行。与defer控制执行时机、async破坏依赖顺序不同,它只早期发起fetch。常见失效原因包括路径不一致、缺少crossorigin、不支持相对路径、不递归加载子模块。正确使用需放入,用绝对路径,构建工具可自动处理。
modulepreload预加载关键JS模块优化HTML模块加载性能
时间:2026-07-26
浏览:125
文章目录
在优化模块加载性能时,modulepreload 是一个常被误解的工具。很多人误以为它能加速模块执行,但实际上它只负责提前拉取网络资源。一旦写错路径、遗漏 crossorigin 属性,或者没有列全子模块,预加载就会失效。

modulepreload 和 defer/async 的根本区别在哪
一个常见问题是:加了 modulepreload 之后,模块应该跑得更快吧?其实它根本不碰执行逻辑。浏览器拿到后只做 fetch,不解析、不构建模块图、不执行任何代码。关键区别如下:
defer:DOM 解析完才执行,但下载时机由解析器决定,可能晚于首屏渲染。async:下载完立刻执行,但会破坏模块顺序,对于有 import 依赖的链路来说几乎不可接受。modulepreload:只做一件事——在 HTML 解析早期发起 fetch,把模块和显式声明的依赖放进缓存,后续才真正触发解析与执行。
简单说,defer 控制执行时机但不预下载,async 异步下载并立即执行会破坏依赖顺序,而 modulepreload 仅负责在 HTML 解析早期发起 fetch,不解析、不执行、不构建模块图。
为什么写了 modulepreload 却没生效
常见静默失效,控制台不报错,Network 面板也看不到请求,问题基本锁定在以下几个硬性条件:
href值必须和后续的src完全一致——协议、域名、路径、查询参数(如v=1.2.3)一个都不能差。- 只要模块走 CDN 或跨域服务(即使只是静态托管平台),
必须带crossorigin属性,否则浏览器直接忽略。 - 不支持相对路径:
./main.mjs或../utils.js会被跳过,必须用绝对路径或根相对路径(如/assets/main.mjs)。 modulepreload不递归加载子模块——main.mjs里import './utils.js',你得手动再加一行。
怎么写一个真正有效的 modulepreload 链
手写容易漏、难维护,尤其构建产物带 hash 时。但理解规则才能 debug:
- 多个
标签建议按执行依赖顺序排列:先vendor.mjs,再main.mjs,部分浏览器会复用 TCP 连接。 - 每个
href必须指向最终解析后的 URL(比如 Vite 构建后是/assets/main.a1b2c3.js,不是src/main.js)。 - 不能对非模块脚本使用:
type="text/ja vascript"的文件加modulepreload会静默失败。 - 必须放在
中,且早于首个;放在里无效。真实项目别手写——Vite 默认开启
build.modulePreload,Webpack 可配webpack-preload-plugin,Rollup 推荐rollup-plugin-module-preload。它们能静态分析 AST,提取所有静态 import 路径并注入完整列表。最容易被忽略的是:
modulepreload只对静态 import 生效,动态import()和 import map 映射的 bare specifier 不在它的作用范围内;而且即使预加载成功,模块语法错误、路径拼错、CSP 拦截这些执行期问题,依然会导致Failed to load module script,和预加载无关。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。