UnoCSS Extracting 机制完全解析:构建时按需扫描、Safelist 与 Blocklist 工程化实践

发布时间:2026/9/13 12:34:42
UnoCSS Extracting 机制完全解析:构建时按需扫描、Safelist 与 Blocklist 工程化实践
UnoCSS Extracting 机制完全解析构建时按需扫描、Safelist 与 Blocklist 工程化实践【免费下载链接】unocssThe instant on-demand atomic CSS engine.项目地址: https://gitcode.com/GitHub_Trending/un/unocssUnoCSS 的核心工作方式是在你的代码库中搜索工具类utilities的使用并**按需on-demand**生成对应的 CSS这个过程在官方文档中被称为Extracting提取。本文以 docs/guide/extracting.md 为骨架系统讲解 UnoCSS 的三种内容来源构建工具管道、文件系统、内联文本、配套的魔法注释magic comments以及构建时提取带来的固有局限与三大应对方案——safelist、静态组合列表和功能强大的blocklist。读完本文你将掌握如何精准配置 UnoCSS 的扫描范围并能用blocklist在大规模协作项目中强制执行统一的工具类规范。什么是 ExtractingUnoCSS 并不是一个预先编译好全部 CSS 的框架而是通过搜索代码库中的工具类使用情况按需生成对应的 CSS。这个搜索—生成过程即Extracting。这一设计的关键约束在于UnoCSS 工作于构建时build time。引擎在编译阶段扫描源码中的静态字符串提取出工具类 token再经由规则rules、变体variants、快捷方式shortcuts解析后输出 CSS。这意味着只有静态出现的工具类才会被生成并交付到应用中——运行时动态拼接或从外部资源获取的工具类很可能无法被检测到。这一局限性正是后文safelist、静态组合与blocklist等方案存在的根本原因。从源码结构看UnoCSS 的核心引擎 packages-engine/core/src/generator.ts 中UnoGenerator.generate()会先通过applyExtractors将输入内容拆解为 token 集合再逐个 token 进行变体匹配、规则解析与 CSS 字符串化最终汇总为完整样式表。而各个构建工具集成Vite、Webpack、PostCSS 等负责把各自渠道获取的源码文本喂给这个引擎。内容来源Content SourcesUnoCSS 支持从三种来源提取工具类不同来源提取到的 token 会被合并后统一生成最终 CSS构建工具管道Pipeline——直接从 Vite / Webpack 的转换流程中提取文件系统Filesystem——通过读取并监听文件来提取内联文本Inline——从内联的纯文本中提取。对应的配置类型定义在 packages-engine/core/src/types.ts 的ContentOptions中包含filesystemglob 字符串数组、inline字符串 /{ code, id }对象 / 异步函数和pipelinefalse或{ include, exclude }过滤器三个字段。从构建工具管道提取Pipeline管道提取由 Vite 与 Webpack 集成支持。UnoCSS 直接读取流经构建管道的内容并提取工具类这是最高效、最精准的方式——只提取应用中真实使用到的工具类且在提取过程中不产生额外的文件 I/O。默认扫描范围管道提取默认覆盖.jsx、.tsx、.vue、.md、.html、.svelte、.astro、.marko等文件.js和.ts默认不包含。默认的 include 正则与 exclude 正则定义在 virtual-shared/integration/src/defaults.ts 中// picomatch patterns, used with rollups createFilter export const defaultPipelineExclude [cssIdRE] export const defaultPipelineInclude [/\.(vue|svelte|[jt]sx|vine.ts|mdx?|astro|elm|php|phtml|marko|html)($|\?)/]可以看到 exclude 默认只有cssIdRE即样式文件本身其余源码文件都在候选范围内。在 virtual-shared/integration/src/context.ts 中集成层通过createFilter将用户配置的content.pipeline.include/exclude或上述默认值编译为一个rollupFilter作为管道过滤的判定依据。自定义扫描范围在uno.config.ts中配置export default defineConfig({ content: { pipeline: { include: [ // the default /\.(vue|svelte|[jt]sx|vine.ts|mdx?|astro|elm|php|phtml|marko|html)($|\?)/, // include js/ts files src/**/*.{js,ts}, ], // exclude files // exclude: [] }, }, })include与exclude均支持正则表达式和picomatchglob 模式。若想完全关闭管道提取可将pipeline设为false——这在 packages-engine/core/src/types.ts 的类型注释与集成层rollupFilter () false的处理中均有体现。魔法注释unocss-include如果只想让某单个.js/.ts文件或任何默认范围外的文件被强制扫描可以在文件任意位置添加unocss-include魔法注释// ./some-utils.js // since .js files are not included by default, // the following comment tells UnoCSS to force scan this file. // unocss-include export const classes { active: bg-primary text-white, inactive: bg-gray-200 text-gray-500, }该机制在 virtual-shared/integration/src/constants.ts 中定义INCLUDE_COMMENT unocss-include。集成层的filter函数见 virtual-shared/integration/src/context.ts会优先检查代码中是否包含该注释——只要文件包含unocss-include无论其扩展名是否在管道范围内都会被提取function filter(code: string, id: string) { if (code.includes(IGNORE_COMMENT)) return false return code.includes(INCLUDE_COMMENT) || code.includes(CSS_PLACEHOLDER) || rollupFilter(id.replace(/\?v\w$/, )) }魔法注释unocss-ignore 与 unocss-skipunocss-ignore添加后UnoCSS 将跳过整个文件的扫描与转换。上面的filter第一行即体现了这一逻辑——文件包含IGNORE_COMMENT时直接返回false。unocss-skip-start与unocss-skip-end用于跳过文件中的某一段代码块必须成对使用才有效p classtext-green text-xlGreen Large/p !-- unocss-skip-start -- !-- text-red will not be extracted -- p classtext-redRed/p !-- unocss-skip-end --实现上virtual-shared/integration/src/constants.ts 用SKIP_COMMENT_RE正则匹配unocss-skip-start与unocss-skip-end之间的所有内容兼容//、/* */、!-- --三种注释风格并在extract时通过code.replace(SKIP_COMMENT_RE, )将这段内容从提取输入中剔除见 virtual-shared/integration/src/context.ts。顺带一提UnoCSS 的 VS Code 扩展还提供了对应的命令见 packages-integrations/vscode/src/commands.ts可一键为选区插入成对的unocss-skip-start/unocss-skip-end注释避免手写配对出错。从文件系统提取Filesystem当集成方式无法访问构建管道时——例如 PostCSS 插件或者代码运行在后端框架中、根本不经过构建管道——可以手动指定要提取的文件。被匹配到的文件会直接从文件系统读取并在开发模式下被监听变更变更后触发重新提取与 HMRexport default defineConfig({ content: { filesystem: [ src/**/*.php, public/*.html, ], }, })从源码看virtual-shared/integration/src/defaults.ts 还提供了defaultFilesystemGlobs作为 PostCSS 等插件的默认文件系统 glob// micromatch patterns, used in postcss plugin export const defaultFilesystemGlobs [ **/*.{html,js,ts,jsx,tsx,vue,svelte,astro,elm,php,phtml,mdx,md,marko}, ]注意这里与管道默认值的差异文件系统提取的默认 glob 是包含.js/.ts的而管道提取默认排除它们——因为后端模板如 PHP经常把工具类写在.js字符串里。从内联文本提取Inline此外还可以从别处获取的内联文本中提取工具类。除了纯字符串也支持传入异步函数返回内容——但要注意该函数只在构建时被调用一次export default defineConfig({ content: { inline: [ // plain text div classp-4 text-redSome text/div, // async getter async () { const response await fetch(https://example.com) return response.text() }, ], }, })构建时提取的局限与解决方案Limitations由于 UnoCSS 工作于构建时只有静态呈现的工具类才会被生成并随应用交付。运行时动态构造或从外部资源获取的工具类可能无法被检测到。针对这类场景官方提供三种应对方案。Safelist强制白名单当代码中出现动态拼接时构建时静态提取无法预知所有组合div classp-${size}/div !-- this wont work! --此时可以配置safelist让对应的 CSS始终被生成safelist: p-1 p-2 p-3 p-4.split( )对应的 CSS 会无条件产出.p-1 { padding: 0.25rem; } .p-2 { padding: 0.5rem; } .p-3 { padding: 0.75rem; } .p-4 { padding: 1rem; }也可以更灵活地编程生成safelist: [ ...Array.from({ length: 4 }, (_, i) p-${i 1}), ]源码印证在 packages-engine/core/src/generator.ts 的generate()中safelist 默认启用safelist true为GenerateOptions默认值引擎构造一个包含generator与theme的SafeListContext然后遍历this.config.safelist——支持字符串和返回字符串数组的函数两种形式——将结果去重后并入 token 集合统一参与 CSS 生成this.config.safelist .flatMap(s typeof s function ? s(safelistContext) : s) .forEach((s) { const trimmedS s.trim() if (trimmedS !tokens.has(trimmedS)) tokens.add(trimmedS) })如果你追求的是运行时真正的动态生成可以了解 unocss/runtime 包。静态组合列表绕过动态构造局限的另一种方式是用一个对象静态地列出所有组合。例如你希望支持div classtext-${color} border-${color}/div !-- this wont work! --可以创建一个列出全部组合的对象前提是你清楚color所有可能取值// Since they are static, UnoCSS will be able to extract them at build time const classes { red: text-red border-red, green: text-green border-green, blue: text-blue border-blue, }然后在模板中使用div class${classes[color]}/div这样所有组合在源码中以静态字符串存在构建时即可被完整提取。实际项目中也可配合unocss-include使用将该对象定义在独立的.js/.ts模块中并强制扫描。Blocklist强制规范的黑名单与safelist相对blocklist用于排除某些工具类防止提取误报false positives。但它的能力远不止简单排除在大型多贡献者项目中UnoCSS 的灵活性——同一视觉结果可由多种语法实现——反而成为负担。不同开发者可能分别写border、border-1或b表达相同输出导致样式表出现重复规则、评审时代码不一致、CSS 体积膨胀。blocklist通过封禁冗长或非标准写法并引导开发者使用首选形式在整条代码库上强制执行唯一的规范语法。再配合unocss/eslint-plugin它相当于一份自动化的代码风格指南将工具类限制在设计系统 token 内、强制使用最短别名、禁止任意值——在规模化场景下保持 CSS 输出精简与代码一致性。匹配器类型Matcher Typesblocklist接受三种匹配器字符串——精确匹配blocklist: [ p-1, // blocks p-1 exactly tab, // blocks tab exactly ]正则——模式匹配使用.test()blocklist: [ /^p-[2-4]$/, // blocks p-2, p-3, p-4 /^border$/, // blocks border but not border-2 ]函数——自定义逻辑返回真值即封禁blocklist: [ s s.endsWith(px), // block all px-suffixed classes s s.split(-).length 4, // block deeply nested utilities ]源码印证核心实现位于 packages-engine/core/src/generator.ts 的TokenProcessor.isBlocked/getBlocked三种类型在同一个三元判断中处理isBlocked(raw: string, blocklist: BlocklistRule[]) { if (!raw) return true for (const rule of blocklist) { const value Array.isArray(rule) ? rule[0] : rule if (typeof value function ? value(raw) : isString(value) ? value raw : value.test(raw)) return true } return false }消息Messages每个匹配器可以可选地包装成元组附带一条message说明封禁原因。消息可以是静态字符串也可以是接收被匹配选择器并返回字符串的回调blocklist: [ // static message [/^border$/, { message: use shorter b }], // dynamic message — receives the blocked selector [/^border(?:-[btrlxy])?$/, { // e.g. border-y → use shorter b-y message: v use shorter ${v.replace(/^border/, b)} }], ]当配合unocss/eslint-plugin使用时消息会出现在 lint 输出中border is in blocklist: use shorter b源码印证该 ESLint 规则实现在 packages-integrations/eslint-plugin/src/rules/blocklist.ts其 report 消息模板为{{name}} is in blocklist{{reason}}其中reason即来自BlocklistMeta.message静态字符串或动态回调结果并在 packages-integrations/eslint-plugin/src/rules/blocklist.test.ts 中有完整的静态/动态消息测试用例。规则内部通过 worker 进程调用syncAction(context.settings.unocss?.configPath, blocklist, ...)读取你的uno.config.ts中的 blocklist 配置见 packages-integrations/eslint-plugin/src/worker.ts。不使用 ESLint 插件时被 blocklist 封禁的工具类只会被静默地从 CSS 生成中排除开发者得不到任何反馈。因此强烈建议将blocklist与unocss/eslint-plugin配合使用在开发期就暴露可操作的信息。变体感知Variant Awarenessblocklist 会在变体剥离之前和之后都检查选择器。这意味着封禁p-1的规则同样会封禁hover:p-1、md:p-1、dark:p-1等——你无需在 blocklist 模式中为变体前缀费心。源码印证在 packages-engine/core/src/generator.ts 的 token 解析入口先对原始字符串经preprocess处理执行generator.isBlocked(current)随后matchVariants剥离出变体结果后又检查generator.isBlocked(i[1])——i[1]即剥离变体后的纯工具类主体。任何一步命中 blocklist 都会使该 token 被标记为 blocked 并跳过解析。合并行为Merging Behavior来自所有 preset 和用户配置的 blocklist 数组会被合并merged——它们不断累加互不覆盖。只要被任何 preset 或用户配置封禁该工具类就会保持封禁状态。源码印证在 packages-engine/core/src/config.ts 中resolveConfig通过getMerged(blocklist)/getMerged(safelist)将预设与用户配置中的数组扁平化合并这也解释了为何一个预设可以叠加你的封禁策略。类型参考Type Referencetype BlocklistValue string | RegExp | ((selector: string) boolean | null | undefined) type BlocklistRule BlocklistValue | [BlocklistValue, BlocklistMeta] interface BlocklistMeta { /** * Custom message to show why this selector is blocked. */ message?: string | ((selector: string) string) }BlocklistRule[]即UserConfig.blocklist的类型见 packages-engine/core/src/types.ts。Blocklist 实战模式Blocklist Patterns以下模式来自官方文档可在uno.config.ts中直接复制使用均以封禁 引导正确写法为设计目标。强制使用更短别名Enforcing Shorter Aliases当同一 CSS 输出存在多种语法时封禁冗长形式并提示更短写法blocklist: [ // border → b, border-t → b-t [/^border(?:-[btrlxy])?$/, { message: v use shorter ${v.replace(/^border/, b)} }], // opacity-50 → op-50 // backdrop-opacity-50 → backdrop-op-50 [/^(?:backdrop-)?opacity-(.)$/, { message: v use shorter ${v.replace(/opacity-/, op-)} }], // whitespace-nowrap → ws-nowrap [/^whitespace-.$/, { message: v use shorter ${v.replace(/^whitespace-/, ws-)} }], // simple static aliases work well for one-to-one replacements [/^flex-grow$/, { message: use shorter grow }], [/^flex-shrink$/, { message: use shorter shrink }], [/^inline-block$/, { message: use shorter i-block }], // you can point to your custom shortcuts as well ]限制在设计系统令牌内Restricting to Design System Tokens可以从设计系统配置动态构建blocklist 模式确保只使用有效 token。利用负向前瞻negative lookahead放行合法值、封禁其余一切import { theme } from ./my-design-system // Helper to join object keys into a regex alternation const keys (obj: Recordstring, any) Object.keys(obj).join(|) blocklist: [ // Only allow font families defined in the design system [new RegExp(^font-(?!(?:${keys(theme.fontFamily)}|\\$)$).$), { message: use design system font families: ${Object.keys(theme.fontFamily).join(, )} }], // Only allow shadow values from the design system [new RegExp(^shadow-(?!(?:${keys(theme.boxShadow)}|\\$)).$), { message: only design system shadow values are allowed. }], ]::: tip 负向前瞻中的\\$用于放行 CSS 变量引用如font-$myVar——这类值在运行时才解析无法静态校验因此需要放行。 :::将原始单位转换为尺度值Converting Raw Units to Scale Values如果项目采用 UnoCSS 默认间距尺度1 单位 0.25rem 4px可以封禁原始px/rem值并提示换算后的尺度值blocklist: [ // mt-16px → mt-4 // p-[8px] → p-2 // w-2rem → w-8 [/^.-\[?[\d.](?:px|rem)\]?$/, { message: (s) { // since message() only receives the matched selector string, not the regex // we have to match it again to extract capture groups from the blocklist matcher const m s.match(/\[?(?v[\d.])(?upx|rem)\]?$/)! const { v, u } m.groups! const scale u rem ? v * 4 : v / 4 return use spacing scale value: ${s.slice(0, -m[0].length)}${scale} } }], ]注意动态message回调只收到被匹配的选择器字符串而非正则因此示例中需要在回调内再次匹配以提取捕获组。去除多余方括号Removing Unnecessary BracketsUnoCSS 的任意值方括号[...]在值本身合法时往往多余可以封禁并提示简写blocklist: [ // w-[50%] → w-50% [/^(w|h|min-[wh]|max-[wh]|top|right|bottom|left)-\[\d%\]$/, { message: (v) { const value v.match(/\[(\d%)\]/)?.[1] || return use shorter ${v.replace(/-\[\d%\]/, -${value})} } }], // outline-[#ff0000] → outline-#ff0000 [/^[a-z-]-\[#[0-9a-fA-F]{3,6}\]$/, { message: v use shorter ${v.replace(/\[#/, #).replace(/\]/, )} }], ]强制执行项目约定Enforcing Conventions封禁违反项目架构决策的模式blocklist: [ // Prevent redundant breakpoint — if sm equals 0, its always active // in mobile-first responsive design and should not be specified [/^sm:/, { message: v sm: breakpoint is redundant, use ${v.replace(/^sm:/, )} }], // Force separate utility classes instead of slash opacity notation. // Separate utilities have a higher chance of reuse than slashed combinations, // which helps reduce the resulting CSS bundle size // bg-red-500/50 → bg-red-500 bg-op-50 [/^(c|bg)-.\/\d$/, { message: use separate opacity class instead of slash notation (e.g., bg-red bg-op-50). }], // Decompose shorthands into reusable individual properties. // Same principle — separate utilities reduce CSS bundle size // size-4 → w-4 h-4 [/^size-(.)$/, { message: (v) { const size v.match(/^size-(.)$/)?.[1] return use w-${size} h-${size} for independent control } }], ]小结Extracting是 UnoCSS 构建时按需生成的根基管道Pipeline、文件系统Filesystem、内联文本Inline三种来源的 token 最终被合并输出为 CSS。魔法注释提供了精细的扫描控制unocss-include强制扫描单文件、unocss-ignore跳过整个文件、unocss-skip-start/unocss-skip-end成对跳过代码块——其常量与处理逻辑可参见 virtual-shared/integration/src/constants.ts 与 virtual-shared/integration/src/context.ts。构建时限制决定了动态拼接的工具类无法被提取safelist与静态组合列表是两条实用出路。blocklist支持字符串 / 正则 / 函数三种匹配器、可选静态或动态消息、自动感知变体前缀、跨 preset 与用户配置合并配合unocss/eslint-plugin规则实现在 packages-integrations/eslint-plugin/src/rules/blocklist.ts可以在大型团队中充当自动化代码风格指南统一工具类语法、收敛 CSS 体积。【免费下载链接】unocssThe instant on-demand atomic CSS engine.项目地址: https://gitcode.com/GitHub_Trending/un/unocss创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考