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号
浙公网安备 33010602011771号