Template System 收官构建:多主题矩阵、Vendor 抽离、Manifest 联合、Cache 治理
这篇是上一篇的延续:在已有的「远程 ESM 组件发布」架构上,把多主题构建、CSS 哈希、Vendor 独立包、清单(manifest)合并与 Cloudflare Pages 的缓存头串成一条可发布的产线。
目标升级
-
多主题矩阵:同一套流水线按
THEME变量批量构建(cool、general、…)。 -
CSS 哈希与解耦:CSS 独立产出、带 hash、与字体分离。
-
Vendor 抽离:
react/react-dom/react/jsx-runtime单独打包到/v1/vendor/,版本可见、所有主题共用。 -
Manifest 统一:主题 JS + 主题 CSS + vendor 入口,统一生成清单文件,供运行时定位资源。
-
缓存与回滚:hash + 版本目录(
/v1/…)+_headers精准管控缓存策略。
产物布局

我到底在解决什么问题
-
我需要把主题以远程 ESM 组件的方式发布,供宿主页面按需加载。
-
我希望产物可版本化、可长缓存、可观测,并且不会被开发期依赖拖胖。
-
我还要在多主题场景下维持同一套流水线,避免重复劳动和缓存冲突。
一句话:让“主题作为远程模块”像 NPM 包一样可发布、可回滚、可审计,而且首屏更轻。
为什么把 Vendor(React 等)单独打一个包
目的:用 import map 固定运行时依赖,一处托管,多处共享。
-
React、React‑DOM、
react/jsx-runtime这些与主题无关,却常被重复打进每个主题里,既浪费带宽又浪费缓存。 -
把它们抽成独立的 vendor 产物,发布到统一目录,由 import map 绑定,所有主题公用同一份运行时。
-
这带来三个直接好处:
-
缓存命中率高:一次下载,多主题复用;
-
可控升级:想升级 React 只需替换 vendor 映射,不动各主题;
-
问题定位清晰:主题问题与运行时问题分层排查。
-
取舍:vendor 多了一步构建,但换来稳定的运行时基础与长期的缓存收益,在远程组件体系里是“强建议”。
为什么所有文件名都带“版本 + hash”,还要有 manifest
目的:让“缓存友好”和“立即更新”同时成立。
-
hash 让文件具备不可变性(内容一变,URL 必变),可以放心给 immutable 的长缓存头。
-
版本目录(例如
/v1/…)是对一批产物的快照标记,方便灰度与回滚。 -
manifest 把“逻辑名 → 真实文件名(含 hash)”统一映射:
-
运行时只认识“逻辑名”(比如
cool-shop-main), -
实际 URL 由 manifest 决定(比如
v1/cool/cool-shop-main.abc123.js)。
这样宿主逻辑稳定、资源地址可变更,避免被浏览器或 CDN 的旧缓存“卡死”。
-
缓存策略上,我将:
-
**静态资源(JS/CSS)**设为长缓存 +
immutable; -
清单(manifest)设为协商缓存(no-cache),保证每次都能感知新版本,同时走 304 降低带宽。
为什么 CSS 不交给 Vite,而是单独用 PostCSS
目的:让 Tailwind 的“扫描范围”完全受主题控制,避免全站打包与误扫。
-
Tailwind 的体积取决于
content扫描范围。多主题场景里,每个主题都应该只扫描自己的源文件,才能得到最小化、无泄漏的 CSS。 -
用 PostCSS 独立产线,我可以非常直观地:
-
通过环境变量切换到
tailwind.<theme>.config; -
针对每个主题产出“只包含自己类名”的 CSS;
-
与字体加载完全解耦(字体走
<link>,不掺在 CSS 里)。
-
-
这条 CSS 产线独立于 JS,不会被 JS 的入口组织方式牵着走;每个主题的 CSS 都是可预测的、可复用的静态资源。
取舍:实现上多了一条流水线,但换来极致可控的裁剪与更干净的部署心智模型。
为什么最后还专门“改名加 hash”,再生成一次 manifest
目的:把“内容指纹”和“加载映射”做成两件独立的、可审计的工序。
-
Tailwind/PostCSS 的任务是生成内容,它不一定对产物命名负责;
我把“加 hash 改名”放到一个小而透明的步骤里:-
算一遍内容指纹;
-
改名为
主题.指纹.css; -
把结果写入主题的 manifest(或总清单)。
-
-
这样一来,内容 → 指纹 → 映射的链路就很清晰:
任何异常(内容不变、指纹改变、映射缺失)都能被单点定位。 -
这一步也为后面做 SRI(子资源完整性)预留了“自然的插钩位”。
取舍:再多一个脚本,但在工程审计与发布安全上非常值。
为什么最后还要生成 _headers(以 Cloudflare Pages 为例)
目的:把缓存策略写进工件里,确保部署端“一字不差”。
-
我不希望“缓存策略”散落在口头约定或平台面板里,所以在
dist/根写一个_headers:-
manifest:
no-cache(协商缓存); -
带 hash 的静态资源:
max-age=31536000, immutable; -
可选的报表/HTML:短缓存或禁缓存。
-
-
这让缓存行为对任何环境都可移植、可版本化,更适合自动化与回滚。
这套设计的“工程价值”到底在哪里
-
发布可控:版本目录 + hash + manifest → 确定性上线与回滚;
-
性能友好:vendor 公用、资源长缓存、字体解耦,首屏负担更低;
-
多主题可扩展:通过环境变量与独立产线,增删主题不影响既有产物;
-
可观测:体积、依赖、清单、缓存头都变成看得见、比得出的工件;
-
清晰边界:JS 产物、CSS 产物、运行时 vendor、缓存策略各司其职,故障域清楚。
一句话总结
-
把运行时抽出来(vendor + import map),让主题变轻;
-
把命名与映射标准化(版本 + hash + manifest),让发布可控;
-
把 CSS 的裁剪权交还给主题(PostCSS 独立、按主题配置),让体积精准;
-
把缓存策略变成工件(
_headers),让部署不靠运气。

浙公网安备 33010602011771号