【免费下载链接】treat
:candy: Themeable, statically extracted CSS‑in‑JS with near‑zero runtime.
treat 是一个「构建时静态提取 CSS-in-JS、运行时几乎零开销」的样式方案:样式写在特殊的 treat 文件(.treat.js/.treat.ts)中,在构建阶段于 Node 里编译执行,所有 CSS 规则提前生成并输出为静态样式文件;浏览器里只保留普通的类名字符串。下面结合源码,逐步拆解这套编译流程。
一、核心思想:把「写样式」变成构建期的一次执行
传统 CSS-in-JS 库在浏览器运行时才拼接、注入样式;Sass / Less 用预处理器语言在构建时生成 CSS。treat 的思路介于两者之间:
| 对比维度 | Sass / Less | 传统 CSS-in-JS | treat |
|---|---|---|---|
| 样式书写语言 | 自定义 DSL | JavaScript | JavaScript |
| CSS 生成时机 | 构建时 | 运行时 | 构建时 |
| 浏览器运行时开销 | 无 | 较大 | 近零(不主题化时为零) |
| 静态 CSS 文件输出 | ✅ | ❌(多为运行时注入) | ✅ |
关键点在于文件分离:treat 把样式代码放进带特殊后缀的treat 文件(如Button.treat.js),使它们可以在构建时执行,而不是被打进浏览器 bundle。文件里导出的不再是「样式对象」,而是生成的类名:
// Button.treat.js 构建后等效于: export var button = 'GENERATED_CLASS_NAME';原理详见官方文档 docs/how-it-works.md。
二、编译流程全景图
整个流程由 webpack 插件 TreatPlugin 驱动,可以概括为 6 步:
- 识别:
test: /\.treat\.(js|ts)$/规则匹配 treat 文件,交给专用 loader; - 预编译:用 webpack「子编译器」把 treat 文件在 Node 环境下打包成一段可执行脚本;
- 执行:在 Node 中执行脚本,通过 adapter 拦截样式 API 调用,收集样式与主题;
- 转 CSS:每块样式生成一个虚拟的
.treatcss文件,走 css-loader → 输出 loader 管线; - 序列化:把 treat 文件的导出替换为「类名字符串」模块;
- 后处理:重建过期模块、删除未使用的 CSS、修正样式顺序。
💡 一句话总结:treat 文件在构建期"消失",CSS 和类名在构建期"诞生"。
三、逐步拆解编译管线
步骤 1:识别 .treat 文件,接管编译
TreatPlugin 在apply阶段向 webpack 注入两条规则:一条匹配*.treat.(js|ts)走 treat loader;另一条匹配生成的*.treatcss文件,走css-loader+ 你配置的outputLoaders(默认style-loader,可换成 mini-css-extract-plugin 产出静态 CSS 文件),见 index.js#L379-L421。
loader 并不直接处理源码,而是在pitch阶段截获编译,核心逻辑集中在 produce 函数。
步骤 2:子编译器在 Node 中「预编译」treat 文件
compileTreatSource 会为每个 treat 文件创建一个child compiler(名称treat-webpack-loader):
- 以
NodeTargetPlugin为目标环境编译,产物是 CommonJS 脚本; - 用
ExternalsPlugin('commonjs', 'treat')把treat包标记为外部依赖——真正执行时由 loader 注入实现; - 结果带
fileDependencies(依赖了哪些文件),watch 模式下据此失效缓存; - 编译结果有内存缓存,改动文件只会失效相关条目,不会全部重算。
这样 treat 文件里可以写任意 JavaScript 逻辑(循环、计算、依赖其他工具函数),而不只是静态的样式声明。
步骤 3:执行脚本,adapter 拦截样式调用
loader.js#L175-L189 会在执行前给脚本「垫」一行代码:require('treat/adapter').setAdapter(__webpack_treat__)。
treat 库的所有样式 API(style、createTheme等)实际都经过 adapter 层转发。构建时注入的__webpack_treat__对象提供 5 个能力:
| 方法 | 作用 |
|---|---|
addLocalCss | 收集非主题化样式块 |
addThemedCss | 按主题收集主题化样式块 |
addTheme/getThemes | 向全局 store 注册/读取主题 |
getIdentName | 依据localIdentName/themeIdentName生成类名(dev 可读、prod 哈希) |
全局 store 负责记录「模块 ↔ 主题 ↔ CSS 请求」的三方关系,是后续去重、重建、裁剪的数据基础。
步骤 4:样式块 → 虚拟 .treatcss 文件
执行完毕后,loader 把收集到的每一块样式交给 processCss 处理(按browsers目标做兼容转换,生产模式自动 minify),然后:
- 把 CSS 内容 base64 编码,包成
virtual-resource-loader虚拟资源; - 按内容哈希生成
*.treatcss文件名——相同 CSS 自动去重; - 该文件匹配第二条 webpack 规则,进入
css-loader与outputLoaders管线,最终由 mini-css-extract-plugin 等插件落成静态 CSS 文件。
也就是说,treat 生成的 CSS 与你手写的 CSS 走的是同一条 webpack 管线,享受同样的缓存、HMR 与提取能力。
步骤 5:导出被替换为「类名字符串」
serializeTreatModule 把执行结果序列化为一行行export var xxx = '...';形式的纯代码,并生成对.treatcss的 import。
这里有一道安全护栏:treat 文件只允许导出纯对象、数组、字符串、数字、null/undefined,其余类型直接报错——这保证了产物中绝不含样式逻辑。
步骤 6:编译后整理——重建、裁剪与排序
插件在finishModules、optimizeDependencies、afterChunks三个钩子中做收尾(index.js#L80-L362):
- 主题哈希重建:每个 treat 模块记录编译时的主题集合哈希,主题文件晚加载导致样式不全的「过期模块」会被自动重建,保证每个主题都拿到完整 CSS;
- 未用 CSS 裁剪:owner 模块或主题模块未被引用时,对应 CSS 模块的依赖会被移除,实现样式层面的 tree-shaking(测试覆盖见 tests/fixtures/unused-modules/);
- 顺序修正:reIndexModules 重排模块与依赖的索引,确保 CSS 的文档顺序与 treat 文件的书写顺序一致,主题化样式保持在更高优先级位置。
四、主题化:一个主题生成一份 CSS
treat 的主题方案是「编译期展开」:为每个主题生成独立的一组类名,例如两个主题 + 一个text样式,构建出:
.text_greenTheme { color: green; } .text_redTheme { color: red; }由此带来的两个规则值得记住:
- 主题化样式优先级固定高于非主题化样式,与书写顺序无关;
- 因此官方建议不要依赖跨类的样式覆盖,优先在单个 treat 文件内用条件逻辑控制样式。
主题还能通过 webpack 动态 import 做主题级 bundle 拆分,按需加载主题对应的 CSS。
五、运行时:一次查表而已
如果应用不用主题化,运行时完全不需要 treat——bundle 里只有类名字符串。
启用主题时,只需调用 resolveStyles(React 场景用useStylesHook):它根据主题引用,把样式对象深拷贝并把每个类名解析为该主题对应的生成类。由于导出结构是静态的,解析结果按「模块 + 主题」缓存在内存 Map 中,整个应用生命周期每个组合只解析一次。
⚡ 这就是「near-zero runtime」的来源:运行时不计算样式、不拼接选择器、不注入 CSS,只做一次字符串查表。
六、总结:构建期与运行期的分工
| 阶段 | 发生的事 | 对应源码 |
|---|---|---|
| 构建期 | 识别.treat文件 | webpack-plugin/index.js |
| 构建期 | 子编译器 Node 化预编译 | treatCompiler.js |
| 构建期 | 执行 + adapter 收集样式/主题 | loader.js、adapter.ts |
| 构建期 | CSS 生成、去重、提取 | processCss.js |
| 构建期 | 重建、裁剪、排序 | store.js |
| 运行期 | 按主题查表取类名(可选) | resolveStyles.ts |
理解了这条管线,你就能解释 treat 的几个标志性行为:为什么样式代码不会进 bundle、为什么 CSS 可以静态提取、为什么主题切换零成本。若想动手验证,可以阅读 docs/setup.md 的 webpack 配置说明,或参考官方示例 examples/next-treat-example/ 与测试夹具 packages/treat/tests/fixtures/ 中的 simple、themed、unused-modules 等场景。
【免费下载链接】treat
:candy: Themeable, statically extracted CSS‑in‑JS with near‑zero runtime.
相关推荐
PointRCNN进阶开发指南:如何扩展支持多类别3D物体检测
PointRCNN进阶开发指南:如何扩展支持多类别3D物体检测 PointRCNN作为CVPR 2019提出的 3D物体检测 先进框架,在自动驾驶和机器人感知领
人工智能深度学习计算机视觉自动驾驶如何用pyre2防御ReDoS攻击:线性时间正则表达式的安全实战指南
如何用pyre2防御ReDoS攻击:线性时间正则表达式的安全实战指南 ReDoS 攻击 (Regular Expression Denial of Servic
OpenSCAD架构深度解析:揭秘3D建模编译器的完整工作流程
OpenSCAD架构深度解析:揭秘3D建模编译器的完整工作流程 OpenSCAD是一款独特的 程序员专用3D CAD建模软件 ,它采用脚本驱动的建模方式,将3D
图形学3D建模桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考