该用 map:它支持任意类型键(如 fs.stats、含参路径)、避免字符串强制转换导致的 key 冲突与乱码,具备 size/clear/有序迭代等缓存管理能力,且 marko 等生产级模板引擎已验证其适用性。

模板碎片缓存该不该用 Map 还是对象字面量
Map 更适合做模板碎片缓存,尤其在服务端构建阶段——它能正确处理任意类型键(比如 templatePath 是字符串,但某些场景下你可能用 fs.Stats 或自定义类作键),而对象字面量只能用字符串或 Symbol。Marko 编译器源码里明确用 new Map() 存模板编译结果,不是没理由的。
常见错误现象:用 {} 缓存碎片路径,但路径含查询参数(如 header.html?theme=dark)时,不同参数被归一化成同一 key;或者路径含 Unicode 字符,对象 key 自动 toString 后乱码。
- Map 支持
size、clear()、迭代器,便于统计缓存命中率或按需清理 - 对象字面量在 Node.js 的 V8 引擎中虽快,但 key 冲突风险高,且无法可靠遍历原始插入顺序
- 若碎片加载依赖标签库(taglib),其缓存模块(
packages/compiler/src/taglib/loader/cache.js)本身就是var cache = {},这是安全的——因为标签库路径是标准化、无参、无编码的绝对路径
碎片缓存失效时机怎么设才不卡住热更新
服务端构建时,碎片缓存不能等到整个构建结束才清,否则开发阶段改一个 footer.html,要等全量重编才能看到效果。Marko 默认在每个宏任务(macrotask)后自动清编译缓存,但这个节奏对模板碎片来说太粗了。
真正该做的,是在文件系统监听到变更时,精准踢掉对应碎片及其依赖项。比如:
- 监听
fragments/header.html变更 → 清除该文件缓存 + 所有include它的父模板缓存 - 避免全局
cache.clear(),否则会连带清掉未改动的nav.html、sidebar.html,拖慢后续构建 - Webpack/Vite 用户注意:若用
html-webpack-plugin注入碎片,它的缓存机制和 Marko 不互通,得额外加compiler.hooks.watchRun钩子同步清理
CDN 缓存 HTML 碎片时为什么总返回旧内容
CDN 缓存的是最终拼装后的 HTML 响应体,不是碎片文件本身。如果你把 GET /fragments/user-card.html 这类接口暴露给 CDN,又没配好缓存头,就极易出问题。
典型坑:CDN 对 /fragments/*.html 路径设置了默认 10 分钟缓存,但你的服务端响应头是 Cache-Control: no-cache,CDN 却忽略它,直接吐旧碎片——结果用户看到的卡片还是昨天的数据。
- 必须在 CDN 后台显式关闭碎片路径的缓存,或设为
Cache-Control: no-store - 更稳妥的做法是:碎片接口只响应 HTMX 请求(检查
HX-Requestheader),非 HTMX 请求一律 404,从源头杜绝被 CDN 缓存 - 如果碎片内容完全静态(如版权信息),可单独走带哈希的静态资源路径:
/static/fragments/footer.a1b2c3.html,再配public, immutable, max-age=31536000
碎片与主模板的缓存版本怎么保持一致
主模板(如 index.html)引用碎片时,靠相对路径或逻辑名({% include "user-info.html" %}),但构建产物里碎片可能被重命名或打哈希。一旦主模板缓存了,而碎片更新了,就出现“新碎片塞进旧壳”的错配。
根本解法不是清缓存,而是让缓存键包含依赖指纹。例如:
- 主模板缓存 key 不只是
index.html,而是index.html + hash(fragments/user-info.html) + hash(fragments/header.html) - Marko 的增量编译已内置此能力:只要碎片文件内容变,它生成的 JS 编译产物 hash 就变,进而触发主模板重新编译
- 手写服务端缓存时,别只缓存 HTML 字符串,缓存结构里必须存
mtime或content-hash,渲染前先校验所有依赖项是否变更
最容易被忽略的是:HTML 模板碎片的缓存策略,本质是构建时长尾问题——它不像 JS/CSS 那样有统一哈希命名,得靠构建工具链或运行时主动维护依赖图,否则“局部更新”就会变成“局部失效”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











