一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

HTML模块加载性能:借助modulepreload提前预加载关键JS模块的方法

时间:2026-07-09 11:30:57 编辑:袖梨 来源:一聚教程网

modulepreload不会加速模块执行,只提前拉取网络资源;其与defer、async的本质区别在于:defer控制执行时机但不预下载,async异步下载并立即执行会破坏依赖顺序,而modulepreload仅在HTML解析早期发起fetch,不解析、不执行、不构建模块图。

modulepreload 不会让模块执行变快,它只提前拉取网络资源;写错路径、漏 crossorigin、没列子模块,就等于没写。

modulepreload 和 defer/async 的根本区别在哪

很多人加了 modulepreload 还在等模块执行,其实它根本不碰执行逻辑。浏览器拿到后只 fetch,不 parse、不构建 module graph、不执行任何代码。

  • defer:DOM 解析完才执行,但下载时机由 parser 决定,可能晚于首屏渲染
  • async:下载完立刻执行,破坏模块顺序,无法用于有 import 依赖的链路
  • modulepreload:只做一件事——在 HTML 解析早期发起 fetch,把模块和显式声明的依赖放进缓存,后续 <script type="module" src="..."> 才真正触发解析与执行

为什么写了 modulepreload 却没生效

常见静默失效,控制台不报错,Network 面板也看不到请求,问题基本锁定在这几个硬性条件上:

  • href 值必须和后续 <script type="module" src="...">src 完全一致——协议、域名、路径、查询参数(如 ?v=1.2.3)一个都不能差
  • 只要模块走 CDN 或跨域服务(哪怕只是静态托管平台),<link> 必须带 crossorigin 属性,否则浏览器直接忽略
  • 不支持相对路径:./main.mjs../utils.js 会被跳过,必须用绝对路径或根相对路径(如 /assets/main.mjs
  • modulepreload 不递归加载子模块——main.mjsimport './utils.js',你得手动再加一行 <link rel="modulepreload" href="/assets/utils.js" crossorigin>

怎么写一个真正有效的 modulepreload 链

手写容易漏、难维护,尤其构建产物带 hash 时。但理解规则才能 debug:

立即学习“前端免费学习笔记(深入)”;

  • 多个 <link rel="modulepreload"> 标签建议按执行依赖顺序排列:先 vendor.mjs,再 main.mjs,部分浏览器会复用 TCP 连接
  • 每个 href 必须指向最终解析后的 URL(比如 Vite 构建后是 /assets/main.a1b2c3.js,不是 src/main.js
  • 不能对非模块脚本使用:type="text/javascript" 的文件加 modulepreload 会静默失败
  • 必须放在 <head> 中,且早于首个 <script type="module">;放在 <body> 里无效

真实项目别手写——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,和预加载无关。

热门栏目