modulepreload 是专用于预加载 ES 模块及其依赖的机制,下载后解析模块图但不执行;而 preload 仅下载资源至缓存,后续仍需手动触发解析与执行。

modulepreload 是什么,和 preload 有什么区别
rel="modulepreload" 是 HTML 中 <link> 标签的一种预加载机制,专用于提前获取 ES 模块(type="module" 脚本)及其依赖,但不执行。它和 rel="preload" 看起来相似,但行为关键不同:后者只下载资源并放入浏览器缓存,后续需手动 import() 或 <script type="module"> 触发执行;而 modulepreload 下载后会解析模块图(包括所有静态 import),为后续 import 做好准备,避免重复解析。
常见错误是用 rel="preload" 加载模块文件却期望它“提前准备好导入”,结果发现 import('./foo.js') 仍要重新解析——这是 preload 的固有限制,必须换用 modulepreload。
怎么写才真正生效
只有满足以下全部条件,rel="modulepreload" 才会被浏览器识别并处理:
-
href必须指向一个合法的 ES 模块文件(HTTP 响应头需含Content-Type: text/javascript或application/javascript,且服务器不能返回404或重定向) - 不能省略
as="script"属性(这是强制要求,否则被当作普通资源预加载,不触发模块解析) - 不能和
crossorigin混用不当:如果模块带跨域请求(如 CDN 上的包),必须显式加crossorigin,否则预加载失败且无提示 - 路径必须与实际
import语句中写的路径完全一致(包括大小写、斜杠结尾、查询参数)——浏览器靠这个字符串精确匹配模块记录
正确示例:
<link rel="modulepreload" href="/src/utils.js" as="script">错误示例:
<link rel="modulepreload" href="./utils.js" as="script">(相对路径在非同目录 HTML 中可能解析失败)
立即学习“前端免费学习笔记(深入)”;
哪些地方容易踩坑
modulepreload 不是“写了就快”,实际效果受多个隐性因素影响:
- 它只对静态
import生效(即import { x } from './a.js'),对动态import()无效——除非你提前把动态导入的目标也写进modulepreload - Chrome 89+ 支持,Firefox 91+ 支持,Safari 16.4+ 支持;旧版本浏览器直接忽略该标签,不会报错也不会 fallback,需自行检测或搭配其他方案
- 如果预加载的模块本身有深层嵌套的
import,浏览器会递归预加载整个依赖树,但不会执行任何代码——这点常被误认为“模块已初始化” - 开发时用本地
file://协议打开 HTML,modulepreload会静默失效(CORS 限制),必须走 HTTP 服务(如npm run serve)
要不要用?什么场景下值得加
适合加 modulepreload 的典型场景是:首屏关键模块(如路由入口、核心工具函数)被多个地方 import,且已确认它们体积大、解析耗时高。比如 import { router } from './router.js' 在多个组件中出现,而 router.js 又 import 了十几个子模块。
不建议盲目添加:
- 小文件(
- 模块路径经常变动(如构建后哈希名)时,维护
modulepreload列表成本高,不如交给打包工具自动注入(如 Webpack 的ModuleFederationPlugin或 Vite 的build.rollupOptions.output.manualChunks) - 服务端渲染(SSR)场景下,客户端 hydration 前的模块预加载可能和 SSR 已输出的脚本冲突,需协调执行时机
最易被忽略的一点:即使加了 modulepreload,模块的 top-level await 或副作用代码仍会在首次 import 时执行——它只加速下载和解析,不改变执行时机。



















