Skip to content

@tabula-css/stylelint-plugin

Tabula 的 CSS 治理:五条项目 CSS 规则(禁止 @apply、禁止手写规则、自定义属性仅限根级、禁止 @theme inline、禁止 !important)作为 stylelint 规则,外加 tabula check:css 所运行的 PostCSS 内核。

安装

bash
npm install --save-dev @tabula-css/stylelint-plugin

开发依赖。postcss^8.4.0)是一个必需的对等依赖;stylelint^16.0.0)是一个可选的对等依赖——./kernel 这个导出根本不依赖 stylelint,这正是使得 @tabula-css/clicheck:css 得以复用它,而不必把 stylelint 拉进 CLI 的依赖图中的原因。

概览

这个配置档案中的每一条 ESLint 规则治理的都是 .ts/.tsx;在这个包出现之前,系统里从没有任何东西会去打开一个项目的 .css 文件。一个被导入的样式表里手写的 .card .title { color: red },即便其他每一个门禁都是绿色的,也会破坏局部性。@tabula-css/stylelint-plugin 在编写时刻填补了这个缺口——五条规则,通过 stylelint 接入你的编辑器——并与 tabula check:css 共享同一份规则内核,因此这两个强制执行点读取的是完全相同的 PostCSS 逻辑,对一个 .css 文件允许包含什么不可能产生分歧。

启用

js
// stylelint.config.js
import tabula from '@tabula-css/stylelint-plugin/config';

export default {
  ...tabula,
  overrides: [
    // The sanctioned entry — the one file holding `@import "../.tabula/source.css";` — is
    // exempt from `no-raw-rules` only, so it can also hold root-level application CSS.
    { files: ['src/app.css'], rules: { 'tabula/no-raw-rules': [true, { sanctionedEntry: true }] } },
  ],
};

@tabula-css/stylelint-plugin/config 导出一份共享配置,供项目自行拼接进自己的配置——全部五条规则均已开启,没有降低任何严重级别——这些正是 ESLint 插件在 .tsx 中强制执行的同一套封闭词汇表的 CSS 一半。请让 overrides 条目列表与 tabula check:css --css-entry 的标志保持一致;这两个门禁共享规则逻辑,但不共享同一份配置文件,因此受认可入口列表正是二者之间唯一可能出现漂移的地方。

规则

下面每一条规则都作用于项目 CSS——即任何由你或某个智能体编写的 .css 文件——并且,除非另有说明,也作用于 .tabula/ 中生成的样式表(那里出现违规,是生成器的 bug,而不是编写者的失误)。

tabula/no-apply

TAB-E221。禁止在任何地方使用 @apply。它会把一份类列表组合进一个手写的选择器——恰恰是该配置档案要消除的那种间接性。

css
/* ❌ violating */
.card { @apply p-4 rounded-md; }

/* ✅ passing — write the classes on the element instead */

tabula/no-raw-rules

TAB-E222。禁止在受认可的入口样式表之外手写规则。项目 CSS 在顶层只能包含 @import@source@utility@custom-variant@charset、一条无主体的 @layer a, b; 顺序声明,以及注释——其他任何东西(一条样式规则、@theme@media、一个 @layer { … } 代码块)都是通过选择器而非通过类来触达样式。每个顶层代码块只报告一次,且仅限于项目作用域。

css
/* ❌ violating */
.card p { color: red; }

/* ✅ passing */
@import "../.tabula/source.css";

通过 overrides 代码块传入 { sanctionedEntry: true }(如上所示),可以将恰好一个文件——即持有 @import "../.tabula/source.css"; 这一行的文件——从这条规则(且仅从这一条规则)中豁免。

tabula/no-scoped-custom-property

TAB-E223系统中最重要的单一检查项。 禁止在任何非根级主体(:roothtml,或二者经属性选择器细化后的形式——:root[data-theme="dark"] 算数,.card 不算)之外的地方定义一个该配置档案的自定义属性(--tb-* / --d-*)。项目 CSS 完全没有正当理由去写入一个该配置档案的属性:dyn() 才是被认可的逐元素通道,且它写入的是一条内联样式,而不是一份样式表。

css
/* ❌ violating */
.panel { --tb-color-accent: red; }

/* ✅ passing */
:root[data-theme="dark"] { --tb-color-accent: #111; }

在生成物作用域中,这条规则只标记那些确实是主题令牌的属性(在生成集合中的某处于 :root 定义)——该配置档案自身的工具类合理地会写入逐元素的组合属性(.ring-accent { --tb-ring-color: … }),这些属性 inherits: false,且按设计是逐元素的。

tabula/no-theme-inline

TAB-E224。禁止 @theme inline(以空白分隔的选项形式匹配,因此 @theme inline reference@theme static inline 都会触发它)。它会将一个令牌的值内联到其使用处,导致根级轴代码块无法再为不同主题重新指向它——这正是 shadcn 生态中的那个陷阱。

css
/* ❌ violating */
@theme inline { --tb-color-accent: red; }

/* ✅ passing */
@theme { --tb-color-accent: red; }

tabula/no-important-css

TAB-E225。禁止项目 CSS 中任何声明使用 !important。它会把一条声明置于 cn()/resolve() 所依赖的排名模型之外。生成的 CSS 是生成器的输出,因此这条规则不在生成物作用域中运行。

css
/* ❌ violating */
.card { color: red !important; }

/* ✅ passing */
.card { color: red; }

@tabula-css/stylelint-plugin/kernel

ts
import {
  checkCss,
  CSS_RULE_CODES,
  collectRootDefinedProperties,
  isRootSubject,
  noApply,
  noImportantCss,
  noRawRules,
  noScopedCustomProperty,
  noThemeInline,
  type CheckCssOptions,
  type CssRuleName,
  type CssScope,
  type CssViolation,
} from '@tabula-css/stylelint-plugin/kernel';

上面每一条规则背后的纯 PostCSS 实现,其导入图中不含 stylelint,也不含任何文件系统——这正是 @tabula-css/clicheck:css 命令直接导入的内容,因此 CLI 与编辑器插件所执行的逻辑字节级一致。

导出项签名(简化)作用
checkCss(root: Root, opts: CheckCssOptions) => CssViolation[]对一份已解析的样式表运行全部五条规则,并按源码顺序返回违规项。
noApplynoRawRulesnoScopedCustomPropertynoThemeInlinenoImportantCss(root: Root, opts: CheckCssOptions) => CssViolation[]各条规则的独立版本,供只需要单独运行某一项检查的调用方使用。
collectRootDefinedProperties(root: Root) => Set<string>在一个根级主体上定义的每一个该配置档案的自定义属性——no-scoped-custom-property 需要用这个集合,在生成物作用域中区分一个令牌与一个组合属性。
isRootSubject(selector: string) => boolean一个选择器字符串是否在每一个逗号分支中都只选中文档根节点。
CSS_RULE_CODESReadonly<Record<CssRuleName, ErrorCode>>规则名 → TAB-Exxx 的映射,已冻结,因此 CLI 与文档不可能与插件产生分歧。

CheckCssOptions 携带 scope'project' | 'emitted',默认 'project')、用于消息的 file 标签、sanctionedEntry(仅豁免 no-raw-rules),以及——仅生成物作用域下——tokenProperties(来自 collectRootDefinedProperties 的集合)。

@tabula-css/stylelint-plugin/config

ts
import tabula, { config } from '@tabula-css/stylelint-plugin/config';

共享的 stylelint 配置对象:{ plugins: ['@tabula-css/stylelint-plugin'], rules: { 'tabula/no-apply': true, 'tabula/no-raw-rules': true, 'tabula/no-scoped-custom-property': true, 'tabula/no-theme-inline': true, 'tabula/no-important-css': true } }。如启用一节所示,将其拼接进你自己的 stylelint.config.js

tabula check:css 运行的内容

tabula check:css(以及未传入 --no-css 时的 tabula build --check)会遍历每一个项目 .css 文件,加上 .tabula/ 中生成的样式表,并对每一个文件调用这个包的 checkCss 内核函数——项目文件使用 scope: 'project',生成的文件作为一个整体使用 scope: 'emitted',并附带来自 collectRootDefinedPropertiestokenProperties。关于其标志与退出行为,参见 @tabula-css/cli

参见

  • @tabula-css/cli — 共享这个包内核的 CI 侧门禁。
  • 概念 — 为什么这五条规则中的每一条都存在。
  • CSS 治理 — 完整全貌,包括这个门禁仍未涵盖的内容。
  • 快速上手 — 接入受认可入口样式表。

Released under the MIT License.