@zhoumutou/postcss-wrap-layer
v0.1.2
Published
PostCSS plugin that wraps top-level CSS nodes into @layer blocks with rule-based matching, insertion strategies, merging, and virtual (no-path) content support.
Maintainers
Readme
@zhoumutou/postcss-wrap-layer
将顶层“裸露”的 CSS 节点按规则自动包裹进指定的 @layer。支持基于规则的匹配、灵活的插入位置、可选与现有同名 layer 合并以及幂等安全。
English | 中文
功能特性
- 声明式规则:按文件路径或虚拟内容映射到 layer 名称
- 灵活插入位置:
end、start、after-ignored、preserve - 可选与已存在的同名顶层
@layer合并(合并时忽略插入位置,直接追加) - 若文件已完全包裹则跳过,幂等安全
- 支持无真实路径(虚拟 / 内存)CSS,通过
contentTest判断 - 正则安全处理(剥离
g/y)与 TypeScript 友好
安装
npm i -D @zhoumutou/postcss-wrap-layer
# 或
pnpm add -D @zhoumutou/postcss-wrap-layer
# 或
yarn add -D @zhoumutou/postcss-wrap-layer快速开始
// postcss.config.ts
import PostcssWrapLayer from '@zhoumutou/postcss-wrap-layer'
export default {
plugins: [
PostcssWrapLayer({
rules: [
{
pattern: /components\.css$/,
layerName: 'components',
insertPosition: 'start',
},
],
}),
],
}输入 (components.css):
.button { padding: 4px }输出:
@layer components {
.button { padding: 4px }
}配置说明
/**
* 插件选项(均为可选)。
*/
interface PluginOptions {
/**
* 有序的包裹规则;为空或缺省则不处理。
* 默认: []
*/
rules?: Rule[]
/**
* 完整替换的忽略 at-rule 名称列表。
* 这些名称的 at-rule 会保持在顶层,也用作插入定位锚点。
* 默认: ['charset','import','layer']
*/
ignoreNames?: string[]
/**
* 多规则匹配策略:
* - 'first': 仅使用第一个命中的规则
* - 'warn' : 警告一次后与 first 相同
* - 'all' : 顺序全部应用;每个规则只看到“剩余”裸节点
* 默认: 'first'
*/
multiMatch?: 'first' | 'warn' | 'all'
/**
* 当满足:
* - 没有剩余裸节点
* - 且存在同名顶层 @layer
* 时跳过包裹。
* 默认: true
*/
skipIfAlreadyWrapped?: boolean
/**
* 若为 true,若已存在同名顶层 @layer,则直接把裸节点追加进去,
* 而不是创建新块(有助于保持原级联锚点)。
* 发生合并时会忽略 insertPosition(总是追加到该 layer 末尾)。
* 默认: true
*/
mergeSameLayer?: boolean
}
/**
* 单条包裹规则。
*/
interface Rule {
/**
* 仅在存在真实文件路径时测试的正则。
* 内部会移除状态标志 g / y。
*/
pattern: RegExp
/**
* 目标(或合并目标)@layer 名称。必须非空且不含换行或分号。
*/
layerName: string
/**
* 新建 layer 的插入方式:
* - 'end' (默认): 追加在末尾
* - 'start' : 紧随开头连续的 @charset/@import
* - 'after-ignored': 在最后一个 ignoreNames 列表内的 at-rule 之后
* - 'preserve' : 放在被采集裸节点中最早出现的位置
* 若发生合并 (mergeSameLayer) 则此值被忽略。
*/
insertPosition?: 'end' | 'start' | 'after-ignored' | 'preserve'
/**
* 仅在无真实文件路径(虚拟 / 内存 CSS)时调用的判定函数。
* 接收完整 CSS 文本(必要时序列化一次)。
*/
contentTest?: (css: string) => boolean
}示例:
// postcss.config.ts
import PostcssWrapLayer from '@zhoumutou/postcss-wrap-layer'
export default {
plugins: [
PostcssWrapLayer({
multiMatch: 'all',
rules: [
{ pattern: /global\.css$/, layerName: 'base', insertPosition: 'start' },
{ pattern: /global\.css$/, layerName: 'components', insertPosition: 'after-ignored' },
{ pattern: /virtual\.css$/, layerName: 'runtime', contentTest: css => css.includes('/*runtime*/') },
],
})
]
}工作原理
- 规范化与校验规则(剥离正则的
g/y标志)。 - 若无真实路径且存在需要
contentTest的规则,则序列化一次 CSS。 - 匹配规则:
- 有真实路径 → 用
pattern测试 - 无路径 → 使用
contentTest
- 有真实路径 → 用
- 根据多匹配策略筛选应用的规则。
- 针对每条应用的规则:
- 收集“裸”节点(顶层,且不在
ignoreNames中)。 - 若满足幂等跳过条件则忽略。
- 若开启合并且已有同名 layer → 直接追加;否则创建新 layer。
- 计算插入位置(
preserve使用最早的原始索引;若合并则忽略)。 - 将裸节点移动进目标 layer。
- 收集“裸”节点(顶层,且不在
裸节点定义:任意顶层非 @rule 节点,或名称不在 ignoreNames 列表中的 @rule。
使用提示
- 使用
preserve保持新 layer 靠近其原始首个节点的位置。 mergeSameLayer=true将节点直接追加到已存在的同名 layer,忽略insertPosition。- 若希望生成多个同名 block(仍被 CSS 视为一个逻辑 layer),可关闭
mergeSameLayer。 - 想让特定 at-rule(如
font-face)保持顶层可加入ignoreNames。 - 保持
contentTest逻辑轻量;它只在无路径且有需要时执行一次。 - 虚拟输入无法使用
pattern,要依赖contentTest。
边界情况
| 情况 | 结果 | | --------------------------------------------- | -------------------------------- | | 无规则 | 不做任何处理 | | layerName 非法(空 / 含换行 / 分号) | 警告并跳过 | | 已存在同名 layer + mergeSameLayer=true | 直接追加,忽略插入位置 | | 已存在同名 layer + mergeSameLayer=false | 新建另一个同名 layer 块 | | 无裸节点 | 不添加任何内容 | | multiMatch='all' 且 insertPosition='preserve' | 每次规则重新计算裸节点与插入位置 |
许可证
MIT
