Windi CSS扫描机制本质是运行时按需解析,不预生成全量CSS,而是根据源码中实际出现的类名即时生成对应规则,因此大型项目热更新延迟从>1s降至毫秒级。

Windi CSS 的扫描机制本质是“运行时按需解析”,不是编译期预生成
它不靠 purge 阶段去暴力删减,而是在开发启动或文件变更时,直接扫描源码中出现的类名(如 text-lg、hover:bg-red-500),然后即时查表、拼接、注入对应 CSS 规则。Tailwind v2 以前必须先生成全量工具类 CSS,再靠 purge 去“筛掉没用的”;Windi CSS 则从一开始就不生成没出现过的。
这种差异带来三个可感知结果:
- 大型项目热更新延迟从 >1s 降到
- 初始构建时间不随
tailwind.config.js中定义的变体数量线性增长(比如你配了 20 个自定义断点,Tailwind 要算 20×N 类,Windi 只算实际模板里用了的那几个) - 支持动态类名拼接的边界更宽——
class={dynamicClass}在 Svelte/Vue 中若值为字符串字面量(如"mt-4 p-2"),Windi 能扫描到;但若运行时拼接(`mt-${size}`),它和 Tailwind 一样无法识别
Windi CSS 的扫描范围默认包含哪些文件?
它默认扫描:.html、.htm、.vue、.svelte、.jsx、.tsx、.md、.mdx,以及所有被 @import 或 @layer 引用的 CSS 文件。但不会扫描 JS 中的字符串模板(除非你显式配置 extractors)。
立即学习“前端免费学习笔记(深入)”;
常见疏漏点:
- JSX/TSX 中写
className={`p-${size}`}—— Windi 不会处理,得靠shortcuts或theme.extend.spacing提前注册合法值 - Markdown 中的 HTML 片段(如
<div class="flex">)会被扫到,但纯 Markdown 语法(如 <code>## title)不会触发响应式类 - 如果你用
vite-plugin-windicss,插件默认不扫描.ts文件里的 JSX 字符串,要手动加extractors配置 - Windi 的
config.theme.extend可以动态影响扫描结果(比如你新增一个colors.myblue,只要模板里写了text-myblue,CSS 就立刻生成) - Windi 的
shortcuts是扫描阶段就展开的,btn→px-4 py-2 rounded,所以最终 CSS 里没有btn这个类,只有展开后的原子类 - Windi 不需要
purge,因为根本没生成过冗余规则;Tailwind JIT 虽快,但仍有少量未命中类残留(尤其配合@apply时)
为什么 Windi CSS 的扫描比 Tailwind JIT 更早“开箱即用”?
Tailwind v2.1 才把 JIT 模式设为默认,且早期 JIT 仍依赖 content 配置项指定路径;Windi CSS 从 v1.0 就强制要求声明 content,并内置了更激进的默认扫描逻辑(比如自动包含 src/**/*.{vue,svelte,jsx,tsx})。
关键区别在于:Tailwind JIT 是“编译时加速版预生成”,Windi 是“运行时懒加载式生成”。这导致:
现在 Windi CSS 已进入 sunsetting 阶段,但它的扫描设计直接影响了 UnoCSS 和 Tailwind v3+ 的内容发现策略。真正容易被忽略的,是它对“模板即事实来源”这一原则的彻底贯彻——不是你在 config 里写了什么,而是你在模板里写了什么,才决定最终 CSS 长什么样。


















