模块缓存由javascript引擎的module map管理,与http缓存无关,其缓存键为完全解析的绝对url(含协议、域名、路径、查询参数及大小写),url相同则只下载、解析、执行一次;更新缓存唯一方式是修改url,如构建时重命名或添加稳定哈希参数。

模块缓存和HTTP缓存是两套完全独立的机制
浏览器对 type="module" 脚本及其 import 依赖的缓存,不是靠 Cache-Control 或 ETag 控制的——它由 JavaScript 引擎在内存中维护一张 URL → 模块记录(ModuleRecord)的映射表,叫 Module Map。哪怕你服务器返回 Cache-Control: no-cache,模块一旦解析并执行过,就永远留在这个映射表里,直到标签页关闭。
这意味着:
-
import './utils.js'和import './utils.js?v=2'被视为两个模块,各自缓存、各自执行 - 改了文件内容但没改 URL?浏览器根本不重新执行,连 fetch 请求都不会发
- Service Worker 无法拦截或修改模块缓存行为;localStorage、
location.reload()也清不掉
重复 import 同一模块只执行一次,但前提是 URL 完全一致
模块缓存的 key 是**完全解析后的绝对 URL**,包括协议、域名、路径、查询参数、甚至大小写。所以这些写法都会触发不同缓存项:
-
import './api.js'vsimport './API.js'(Linux/macOS 下路径区分大小写) -
import '/src/api.js'vsimport 'http://localhost:8080/src/api.js'(即使同源,协议+主机名不同) -
import './config.js?env=prod'vsimport './config.js?env=dev'
只要 URL 字符串逐字节相同,无论被多少个模块 import,都只下载一次、只解析一次、只执行一次顶层代码——这是模块系统天然的单例保障,不用手写 if (!window.__loaded) 这类判断。
使用 Puppeteer + Chrome 将 HTML 渲染为中文 PDF,自动处理图表等待、Tab 展开、动画、测高、白边消除、防分页,适用于看板、报表、网页和交互图表转 PDF。
modulepreload 不预执行,只提前进 Module Map 的 fetching 状态
<link rel="modulepreload" href="./main.js"> 的作用非常具体:它让浏览器提前发起网络请求,并把该 URL 标记为 fetching 状态,防止后续 <script type="module" src="./main.js"></script> 触发重复请求。但它不会解析、不会实例化、更不会执行。
关键约束:
-
href必须和<script type="module" src></script>的src完全一致(含查询参数) - 跨域模块必须加
crossorigin,否则静默失败 - 它不能替代构建时的版本哈希——预加载
utils.js,上线后改了内容,仍会复用旧缓存
真正更新模块缓存的唯一方式是改 URL
浏览器没有 clearModuleCache(),也没有 “强制刷新模块” 的 API。你想让新代码生效,只能从 URL 层面打破缓存键:
- 构建时重命名文件:
utils.a8f3b2d.js,而非utils.js - 添加稳定哈希查询参数:
utils.js?hash=9e8c2a(注意:不能用时间戳或随机数) - 用
<link rel="modulepreload">配合新 URL 预加载,再在<script type="module" src></script>中引用它
最容易被忽略的一点:开发时用 npx serve 启服务能绕过 file:// 限制,但若忘了在构建产物里注入哈希,线上用户可能永远卡在旧模块缓存里——因为模块缓存一旦进入 evaluated 状态,就不可逆。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










