C05 CSS 工程化
C05 CSS 工程化
摘要:样式从源文件到产物需要三件事:合并与分层、压缩、缓存控制。合并解决请求数与顺序问题,压缩减少传输体积,文件名带哈希解决「改了却没更新」。加工路径还有四条可选:原生
@layer、预处理器、CSS Modules、原子化工具类,差异在处理时机与类名归属。本文给出一个不依赖第三方包的构建脚本、四条路径的对照表与发布前必须核对的六项。
关键词:构建,合并,压缩,缓存,产物,作用域方案
所属:frontend/css / C-高级 | 上一篇:C04-无障碍与视觉 | 下一篇:C06-滚动吸附与滚动行为 | 返回:C00-总结
1 引言
手写 CSS 在文件超过五个后出现三类问题:请求数多、顺序易错、缓存不可控。三者都属于交付环节,与代码质量无关。
本文给出最小构建流程与产物检查清单。范围见表 1-1。
| 项 | 本文覆盖 | 本文不覆盖 |
|---|---|---|
| 构建 | 合并、分层顺序、压缩、哈希命名 | 打包器插件体系 |
| 工具 | 原生 @layer、Node 脚本 |
预处理器语法 |
| 交付 | 产物结构、缓存策略、检查清单 | CDN 与持续集成 |
2 预备知识
| 项 | 要求 | 核对方式 |
|---|---|---|
| 环境 | Node 20+ | 终端执行 node -v |
| 文件 | css-practice/src/ 下多个 CSS 文件 |
按层命名 |
| 浏览器 | Chrome 130+ | 用于核对产物 |
| 前置 | C02 | 分层结构已建立 |
3 原理与实现
3.1 从源文件到产物
构建按固定顺序读取源文件、合并、压缩、输出带哈希的文件,如图 3-1 所示。
flowchart LR
A["src/ 分层文件<br/>1-reset … 5-utilities"] --> B["按顺序读取"]
B --> C["合并为一份"]
C --> D["压缩空白与注释"]
D --> E["计算内容哈希"]
E --> F["dist/main.<hash>.css"]
F --> G["HTML 引用新文件名"]
图 3-1 样式产物的生成流程
图上可见:哈希由内容算出,内容不变则文件名不变,浏览器可长期缓存;内容一变文件名即变,避免旧缓存。
3.2 最小构建脚本
// 运行环境:Node 20+;保存为 tools/build-css.mjs,执行 node tools/build-css.mjs
import { readFileSync, writeFileSync, mkdirSync, readdirSync } from 'node:fs';
import { createHash } from 'node:crypto';
import { join } from 'node:path';
const src = 'css-practice/src';
const out = 'css-practice/dist';
const files = readdirSync(src).filter((f) => f.endsWith('.css')).sort();
const merged = files.map((f) => readFileSync(join(src, f), 'utf8')).join('\n');
const minified = merged
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/\s+/g, ' ')
.replace(/\s*([{}:;,>])\s*/g, '$1')
.trim();
const hash = createHash('sha256').update(minified).digest('hex').slice(0, 8);
const name = `main.${hash}.css`;
mkdirSync(out, { recursive: true });
writeFileSync(join(out, name), minified, 'utf8');
console.log(`已生成 ${out}/${name},原始 ${merged.length} 字节,压缩后 ${minified.length} 字节`);
产物与交付要求见表 3-1。
| 项 | 要求 | 原因 |
|---|---|---|
| 文件顺序 | 重置 → 基础 → 布局 → 组件 → 工具 | 依赖方向 |
| 压缩 | 去注释与多余空白 | 传输体积 |
| 文件名 | 含内容哈希 | 缓存与更新 |
| 引用方式 | HTML 指向带哈希文件名 | 保证取到新版本 |
| 分层保留 | 源码用 @layer,压缩后仍生效 |
权重可预测 |
| 体积上限 | 单文件尽量 100 KB 以内 | 首屏解析成本 |
层与构建的关系见表 3-2。
| 做法 | 效果 | 注意点 |
|---|---|---|
| 源文件按层命名 + 排序读取 | 顺序稳定 | 文件名前缀带序号 |
源码内 @layer 声明顺序 |
与文件顺序解耦 | 声明一次即可 |
| 第三方库单独成文件 | 便于升级 | 置于低层 |
| 关键样式内联进 HTML | 首屏无需额外请求 | 仅限最小集合 |
3.3 预处理与作用域方案的取舍
同一套源码有四条加工路径,差异集中在「什么时候求值」与「类名归谁管」,见表 3-3。
| 方案 | 加工时机 | 作用域 | 需要构建 | 适用团队 |
|---|---|---|---|---|
原生 CSS + @layer |
运行时 | 全局 | 否 | 小站点、无构建流程 |
| 预处理器(Sass) | 构建期 | 全局 | 是 | 需要变量、嵌套、混入的中大型项目 |
| CSS Modules | 构建期 | 文件级哈希 | 是 | 组件化项目,要求类名不冲突 |
| 原子化工具类 | 构建期 | 类名即样式 | 是 | 设计系统已定、追求迭代速度 |
表 3-3 四条加工路径的取舍
四条路径的选择依据:@layer 只解决顺序与权重,不解决命名冲突;预处理器提供变量与嵌套,但产物仍是全局类名,需要命名规范配合(见 C02);CSS Modules 把类名换成哈希,冲突由构建器保证,代价是类名不可读、调试需映射;原子化把样式拆成单用途类名,改样式等于改 HTML,适合设计系统已稳定的项目。同一次构建中可以叠加使用,例如 Sass 负责变量、CSS Modules 负责作用域、@layer 负责顺序。
4 验证与讨论
- 执行脚本,确认
dist/下生成带哈希的 CSS 文件。 - 修改任一源文件的一个颜色,再次执行,确认哈希变化、文件名变化。
- 把产物体积与源文件对比,记录压缩比例。
- 在 HTML 中引用产物,刷新确认样式完整、无 404。
| 检查项 | 预期结果 | 说明 |
|---|---|---|
| 产物生成 | main.<hash>.css 存在 |
脚本输出 |
| 哈希稳定 | 内容不变时重跑哈希不变 | 缓存有效 |
| 压缩有效 | 体积明显下降 | 空白与注释已去掉 |
| 顺序正确 | 工具层仍能覆盖组件层 | @layer 或文件顺序 |
| 无死链 | HTML 引用路径正确 | 产物目录结构 |
讨论与局限。 正则压缩无法处理字符串内的空白与特殊写法,正式项目应使用成熟的压缩工具。另内联关键样式会与缓存策略冲突,适用场景仅限首屏极小集合。
5 常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 改了样式线上没变 | 文件名未变,浏览器命中旧缓存 | 产物加内容哈希 |
| 层顺序错乱 | 读取顺序按字典序 | 文件名加序号前缀 |
| 压缩后样式错误 | 正则误删字符串中的空白 | 使用成熟压缩工具 |
| 产物 404 | HTML 未更新引用 | 构建后自动替换 HTML 中的引用 |
| 第三方库覆盖自己的样式 | 库在高层 | 库放低层,或调整 @layer 顺序 |
6 结论
| 序号 | 结论 | 依据 |
|---|---|---|
| 1 | 构建只做合并、压缩、哈希三件事 | 图 3-1 |
| 2 | 哈希由内容决定,用于缓存控制 | 表 3-1 |
| 3 | 层顺序比阅读顺序更重要 | 表 3-2 |
| 4 | 发布前按六项清单核对 | 表 3-1 |
- 上一篇:C04-无障碍与视觉
- 下一篇:C06-滚动吸附与滚动行为
- 返回:C00-总结 | 科目 README
参考文献
[1] MDN Web Docs · CSS 的组织与优化,https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Organizing(访问于 2026-10-31)
[2] Node.js 文档 · crypto 模块,https://nodejs.org/api/crypto.html(访问于 2026-10-31)

评论 0
还没有评论,快来抢沙发吧~