cssts-compiler
v0.2.87
Published
CssTs compiler - parser, AST transformer, and type generator for CSS-in-TS
Maintainers
Readme
cssts-compiler
CssTs 编译器 - 解析、转换、生成
⚡ 用户零配置使用
用户通过 vite-plugin-cssts 使用,无需直接操作 cssts-compiler!
# 用户只需安装 vite 插件
npm install vite-plugin-cssts -D// vite.config.js - 零配置!
import cssTsPlugin from 'vite-plugin-cssts'
export default {
plugins: [cssTsPlugin(), vue()]
}| 功能 | 自动化 | 说明 |
|------|--------|------|
| 类型定义 | ✅ 自动 | 插件启动时自动生成到 node_modules/@types/cssts-ts/ |
| IDE 提示 | ✅ 自动 | TypeScript 自动发现 @types 目录 |
| CSS 生成 | ✅ 自动 | 按需收集样式,通过 virtual:cssts.css 注入 |
| Vue SFC | ✅ 自动 | <script lang="cssts"> 自动转换为 <script lang="ts"> |
| HMR | ✅ 自动 | 文件修改后自动热更新 |
本文档面向 cssts-compiler 的开发者,如果你是使用者,请查看 vite-plugin-cssts。
核心职责
cssts-compiler 负责所有编译时的工作:
- 解析 - 解析
.cssts文件中的css { }语法 - 转换 - CST 到 AST 转换,生成
csstsAtom.xxx引用 - CSS 生成 - 根据使用的样式生成 CSS
- 类型生成 - 生成
.d.ts类型定义文件
整体架构
┌────────────────────────────────────────────────────────────────────┐
│ cssts-compiler │
├────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Parser │ -> │ CST │ -> │ AST │ -> │Generator │ │
│ │ 解析器 │ │ 转换器 │ │ 转换器 │ │ 代码生成 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ ↑ │
│ ┌─────┴─────┐ ┌──────────────────────────────────┐ │
│ │ Token │ │ DTS Generator │ │
│ │ 词法分析 │ │ .d.ts 类型定义生成 │ │
│ └───────────┘ └──────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────┘编译流程(4个阶段)
阶段 1: 解析 (Parser)
// 输入源代码
const button$$hover = css { cursorPointer, backgroundColorBlue }
// CssTsParser 继承 SlimeParser,添加 CssExpression 语法
const parser = new CssTsParser(code)
const cst = parser.Program() // -> 解析成 CST (Concrete Syntax Tree)作用:识别 css { } 语法,生成 CST 节点
阶段 2: CST → AST 转换 (Factory)
// CssTsCstToAst 继承 SlimeCstToAst
const ast = CssTsCstToAstUtils.toFileAst(cst)
// 关键拦截点:createPrimaryExpressionAst
if (cst.name === 'CssExpression') {
return this.createCssExpressionAst(cst) // 转换 css { }
}作用:
- 识别
CssExpression节点 - 转换为
cssts.$cls(csstsAtom.xxx, ...)调用 - 收集使用的原子类到
usedAtoms集合
阶段 3: 代码生成 (Generator)
const result = SlimeGenerator.generator(ast, tokens)
// 输出
const button$$hover = cssts.$cls(
csstsAtom["button$$hover"],
csstsAtom.cursorPointer,
csstsAtom.backgroundColorBlue
)作用:将 AST 转回 JavaScript 代码
阶段 4: CSS 生成 (Transform)
// 根据收集的样式名生成 CSS
const css = generateStylesCss(context.styles, pseudoUtils)
/* 输出 */
.cursor_pointer { cursor: pointer }
.background-color_blue { background-color: blue }
.button--hover:hover { cursor: pointer; background-color: blue }完整数据流
源代码 (.cssts)
const btn = css { displayFlex, colorRed }
↓
① CssTsParser.Program()
↓
CST (Concrete Syntax Tree)
↓
② CssTsCstToAstUtils.toFileAst()
↓
AST + usedAtoms: Set(['displayFlex', 'colorRed'])
↓
③ SlimeGenerator.generator()
↓
JavaScript 代码
const btn = cssts.$cls(csstsAtom.displayFlex, csstsAtom.colorRed)
↓
④ generateStylesCss(usedAtoms)
↓
CSS 代码
.display_flex { display: flex }
.color_red { color: red }伪类原子类
内置的伪类原子类,提供交互视觉反馈:
// 使用内置伪类原子类
const buttonStyle = css {
colorWhite,
backgroundColorBlue,
hover, // .hover:hover { filter: brightness(1.15) }
active // .active:active { filter: brightness(0.85) }
}伪类效果来自 pseudoClassConfig 配置,可自定义。
classGroup 类组合
classGroup 可以将多个原子类(包括伪类)组合成一个新类:
// 配置
classGroup: {
click: ['hover', 'active', 'focus', 'disabled', 'cursorPointer'],
ddClick: ['click', 'colorRed'] // 可引用其他组合
}生成流程:
- 解析组合配置,递归展开引用的组合
- 对于伪类项(如
hover):查找pseudoClassConfig,生成.click:hover { ... } - 对于原子类项(如
colorRed):查找原子类定义,生成.click { color: red }
核心函数:
generateClassGroupAtoms()- 生成类组合原子类定义generateClassGroupDts()- 生成 DTS 声明expandClassGroup()- 递归展开组合配置
模块结构
cssts-compiler/
├── generator/ # 数据和类型生成脚本(开发时使用)
│ ├── datajson/ # 原始数据文件(JSON)
│ ├── generator-data.ts # 生成 src/data/
│ └── generator-type.ts # 生成 src/config/types/
├── src/
│ ├── config/ # 配置系统
│ │ ├── types/ # 配置类型定义(自动生成)
│ │ └── CsstsDefaultConfig.ts
│ ├── data/ # 生成的数据文件
│ ├── dts/ # DTS 生成器(运行时使用)
│ │ ├── atom-generator.ts # 原子类生成核心逻辑
│ │ ├── dts-writer.ts # DTS 文件写入
│ │ └── dts-cli.ts # CLI 入口
│ ├── factory/ # AST 转换器
│ ├── parser/ # 解析器
│ ├── transform/ # 核心转换功能(CSS 生成)
│ └── utils/ # 工具函数
│ ├── cssClassName.ts # CSS 类名生成
│ ├── cssUtils.ts # CSS 样式收集
│ ├── config-utils.ts # 配置工具
│ └── unitCategory.ts # 单位分类查询
├── target/ # 生成的 .d.ts 文件输出目录
└── tests/ # 测试文件模块职责说明
| 目录 | 职责 | 说明 |
|------|------|------|
| parser/ | 解析 | 继承 slime-parser,添加 css { } 语法解析 |
| factory/ | CST→AST | 将 CST 转换为 AST,拦截 CssExpression 节点 |
| transform/ | 核心转换 | 调用 parser + factory + generator 完成完整转换 |
| dts/ | 类型生成 | 生成 @types/cssts-ts/index.d.ts 供 IDE 提示 |
| utils/ | 工具函数 | CSS 类名生成、配置处理、单位分类等 |
| data/ | 数据集 | 所有 CSS 属性、关键字、颜色、伪类等数据 |
| config/ | 配置系统 | 原子类生成配置(属性、单位、步长等) |
关键设计决策
| 决策 | 原因 |
|------|------|
| 继承 slime-parser | 复用成熟的 TS/JS 解析器,只需添加 css { } 语法 |
| 全局注册机制 | 让子类能覆盖转换逻辑,解决继承时内部调用不走子类的问题 |
| 原子类按需收集 | 只生成实际使用的 CSS,减少打包体积 |
| 伪类用 $$ 分隔 | 避免与 JS 变量命名冲突($ 是合法标识符) |
CssTsCstToAst 扩展机制
CssTsCstToAst 继承自 slime-parser 的 SlimeCstToAst,使用全局注册模式扩展 CST → AST 转换。
为什么需要全局注册
slime-parser 内部各转换器通过 SlimeCstToAstUtils.xxx() 调用。直接继承重写方法不会生效,因为内部调用不经过子类。
架构设计:继承链自动注册
核心机制
- Proxy 代理模式:导出的
cssTsCstToAstUtils是 Proxy,动态代理到全局注册的实例 - 构造函数自动注册:
SlimeCstToAst和CssTsCstToAst构造函数自动调用注册 - 继承链传递:子类调用
super()时,this是子类实例,自动注册到所有层
实现方式
// cssts-compiler/src/factory/CssTsCstToAstUtils.ts
import { SlimeCstToAst, registerSlimeCstToAstUtil } from 'slime-parser'
export class CssTsCstToAst extends SlimeCstToAst {
constructor() {
super() // 父类构造中会调用 registerSlimeCstToAstUtil(this)
registerCssTsCstToAst(this) // 注册到 cssts 层
}
// 重写方法,拦截 CssExpression 节点
createPrimaryExpressionAst(cst) {
const first = cst.children?.[0]
if (first?.name === 'CssExpression') {
return this.createCssExpressionAst(first)
}
return super.createPrimaryExpressionAst(cst)
}
// 处理 css { atom } 语法
createCssExpressionAst(cst) {
// 转换为 cssts.$cls() 调用
// ...
}
}
// 全局注册机制
let _cssTsCstToAstUtils: CssTsCstToAst
export function registerCssTsCstToAst(instance: CssTsCstToAst): void {
_cssTsCstToAstUtils = instance
}
// Proxy: 动态代理到当前注册的实例
export const cssTsCstToAstUtils = new Proxy({} as CssTsCstToAst, {
get(_, prop) {
const val = (_cssTsCstToAstUtils as any)[prop]
return typeof val === 'function' ? val.bind(_cssTsCstToAstUtils) : val
}
})
// 初始化默认实例
new CssTsCstToAst()
export default cssTsCstToAstUtils继承链示例(OVS 扩展)
// ovs-compiler 继承 cssts-compiler
class OvsCstToSlimeAst extends CssTsCstToAst {
constructor() {
super() // 继承链自动注册到 cssts 和 slime 层
registerOvsCstToSlimeAst(this) // 只注册到 ovs 层
}
}
// 实例化时的注册流程:
// new OvsCstToSlimeAst()
// → OvsCstToSlimeAst.constructor()
// → super() → CssTsCstToAst.constructor()
// → super() → SlimeCstToAst.constructor()
// → registerSlimeCstToAstUtil(this) // this = OvsCstToSlimeAst 实例 ✅
// → registerCssTsCstToAst(this) // this = OvsCstToSlimeAst 实例 ✅
// → registerOvsCstToSlimeAst(this) // this = OvsCstToSlimeAst 实例 ✅
// 结果:三层全局变量都指向同一个 OvsCstToSlimeAst 实例工作原理
CssTsCstToAst构造函数调用super(),触发父类构造- 父类
SlimeCstToAst构造中调用registerSlimeCstToAstUtil(this) - 此时
this是CssTsCstToAst实例(或更深层的子类实例) - 父类注册完成后,
CssTsCstToAst构造继续,调用registerCssTsCstToAst(this) - 所有层的全局变量都指向同一个最终子类实例
createPrimaryExpressionAst拦截CssExpression节点,转换为cssts.$cls()调用
注意事项
⚠️ 避免循环引用:全局变量必须先声明再初始化
// ❌ 错误:循环引用
let _cssTsCstToAstUtils: CssTsCstToAst = new CssTsCstToAst()
// ✅ 正确:分两步
let _cssTsCstToAstUtils: CssTsCstToAst // 先声明
export function registerCssTsCstToAst(instance: CssTsCstToAst): void {
_cssTsCstToAstUtils = instance
}
// ... Proxy 定义 ...
new CssTsCstToAst() // 再初始化核心 API
transformCssTs - 单文件转换
import { transformCssTs, type TransformContext } from 'cssts-compiler'
const context: TransformContext = { styles: new Set<string>() }
const result = transformCssTs(code, context)
// result.code - 转换后的 JS 代码
// result.hasStyles - 是否有样式parseStyleName - 样式名解析
import { parseStyleName } from 'cssts-compiler'
parseStyleName('displayFlex')
// { baseName: 'displayFlex', pseudos: [] }
parseStyleName('clickable$$hover$$active')
// { baseName: 'clickable', pseudos: ['hover', 'active'] }generateAtoms - 原子类生成
import { generateAtoms, generateDts, generateStats } from 'cssts-compiler'
const atoms = generateAtoms()
const dtsContent = generateDts()
const stats = generateStats()Groups 组合原子类配置
Groups 允许将多个 CSS 属性组合成一个原子类,支持三种配置方式。
类名生成规则
最终类名 = prefix + name + [自动生成部分] + suffixprefix: 前缀(可选)name: 名称(可选)[自动生成部分]: 根据配置类型自动生成suffix: 后缀(可选)
1. numberProperties - 数值组合
将多个属性绑定到相同的数值,继承数值配置(min/max/step)。
// 配置
{ name: 'marginX', numberProperties: ['marginLeft', 'marginRight'] }
// 生成
marginX10px → margin-left: 10px; margin-right: 10px;
marginX20px → margin-left: 20px; margin-right: 20px;2. keywordProperties - 固定关键字组合
生成固定样式组合的单个原子类。
// 配置
{ name: 'flexRow', keywordProperties: { display: 'flex', flexDirection: 'row' } }
// 生成
flexRow → display: flex; flex-direction: row;支持数值:
{ name: 'flexCol', keywordProperties: { display: 'flex', flexDirection: 'column', flex: 1 } }
// 生成:flexCol → display: flex; flex-direction: column; flex: 1;3. keywordIterations - 遍历关键字组合
遍历属性的关键字值,生成多个原子类(笛卡尔积)。
基础用法
// 配置
{
keywordIterations: {
display: ['flex'],
flexDirection: ['row', 'column'],
}
}
// 生成
flexRow → display: flex; flex-direction: row;
flexColumn → display: flex; flex-direction: column;详细配置:value + alias + prefix
每个值支持三种写法:
// 1. 简写:直接写值
flexDirection: ['row', 'column']
// 2. 简写:数值
flex: [0, 1, 'auto', 'none']
// 3. 详细配置:带 alias 或 prefix
flexDirection: [
{ value: 'row', alias: 'r' }, // 使用别名 → R
{ value: 'column', prefix: 'c' } // 使用前缀 → CColumn
]alias vs prefix 的区别
| 配置 | 作用 | 示例 |
|------|------|------|
| alias | 替换整个值的显示名称 | { value: 'row', alias: '' } → 不显示 |
| prefix | 在值前面添加前缀 | { value: 'center', prefix: 'x' } → XCenter |
属性级别配置
支持在属性级别设置 prefix/alias,里层配置优先级更高:
{
keywordIterations: {
// 属性级别配置
alignItems: {
prefix: 'y', // 所有值默认使用 y 前缀
values: [
'start', // 使用外层 prefix → YStart
'center', // 使用外层 prefix → YCenter
{ value: 'end', prefix: 'x' } // 里层覆盖 → XEnd
]
}
}
}完整示例:Flexbox 布局组合
groups: [
// row + justifyContent(x轴) + alignItems(y轴)
{
keywordIterations: {
display: [{ value: 'flex', alias: '' }], // 不显示
flexDirection: [{ value: 'row', alias: '' }], // 不显示
justifyContent: [
{ value: 'start', prefix: 'x' }, // XStart
{ value: 'center', prefix: 'x' }, // XCenter
{ value: 'space-between', prefix: 'x' }, // XSpaceBetween
],
alignItems: {
prefix: 'y',
values: ['start', 'center', 'end'] // YStart, YCenter, YEnd
}
}
}
]
// 生成(笛卡尔积):
// xStartYStart, xStartYCenter, xStartYEnd,
// xCenterYStart, xCenterYCenter, xCenterYEnd,
// xSpaceBetweenYStart, xSpaceBetweenYCenter, xSpaceBetweenYEnd命名转换规则
| 输入 | 转换规则 | 输出 |
|------|----------|------|
| 'row' | kebab → PascalCase | Row |
| 'space-between' | kebab → PascalCase | SpaceBetween |
| { value: 'row', alias: '' } | 空别名 | `` (不显示) |
| { value: 'row', alias: 'r' } | 别名首字母大写 | R |
| { value: 'center', prefix: 'x' } | 前缀首字母大写 + 值 | XCenter |
| 1 (数值) | 直接使用 | 1 |
默认配置示例
groups: [
// 数值组合
{ name: 'marginX', numberProperties: ['marginLeft', 'marginRight'] },
{ name: 'marginY', numberProperties: ['marginTop', 'marginBottom'] },
{ name: 'paddingX', numberProperties: ['paddingLeft', 'paddingRight'] },
{ name: 'paddingY', numberProperties: ['paddingTop', 'paddingBottom'] },
// 固定组合
{ name: 'flexRow', keywordProperties: { display: 'flex', flexDirection: 'row' } },
{ name: 'flexCol', keywordProperties: { display: 'flex', flexDirection: 'column' } },
// 遍历组合:row + flex
{
keywordIterations: {
display: [{ value: 'flex' }],
flexDirection: [{ value: 'row' }],
flex: [0, 1, 'auto', 'none'],
}
},
// 遍历组合:row + alignItems (y轴)
{
keywordIterations: {
display: [{ value: 'flex' }],
flexDirection: [{ value: 'row' }],
alignItems: {
prefix: 'y',
values: ['start', 'center', 'end', 'stretch', 'baseline']
}
}
}
]命名规范
| TS 变量名 | CSS 类名 | CSS 规则 |
|-----------|----------|----------|
| displayFlex | display_flex | display: flex |
| paddingTop16px | padding-top_16px | padding-top: 16px |
| opacity0p9 | opacity_0.9 | opacity: 0.9 |
| width50pct | width_50% | width: 50% |
| zIndexN1 | z-index_-1 | z-index: -1 |
| 转义符 | 含义 | 示例 |
|--------|------|------|
| N | - 负数 | N10 → -10 |
| p | . 小数点 | 0p5 → 0.5 |
| pct | % 百分号 | 50pct → 50% |
| s | / 斜杠 | 16s9 → 16/9 |
Vendor Prefix 处理
以 - 开头的 vendor prefix keyword 会去掉开头的 -,然后转换为 PascalCase:
| CSS Keyword | TS 变量名 |
|-------------|-----------|
| -moz-box | displayMozBox |
| -webkit-flex | displayWebkitFlex |
| -ms-grid | displayMsGrid |
| -webkit-inline-box | displayWebkitInlineBox |
配置系统
配置层级
Property → NumberCategory → NumberUnit
→ ColorType → Color
→ Keyword配置字段概览
| 列表配置 | 详情配置 | 用途 |
|----------|----------|------|
| properties | propertiesConfig | CSS 属性 |
| excludeProperties | - | 排除属性 |
| numberCategories | numberCategoriesConfig | 数值类别 |
| excludeNumberCategories | - | 排除类别 |
| numberUnits | numberUnitsConfig | 数值单位 |
| excludeUnits | - | 排除单位 |
| colorTypes | colorTypesConfig | 颜色类型 |
| excludeColorTypes | - | 排除颜色类型 |
数值类别 (NumberCategory)
| Category | Units | |----------|-------| | pixel | px | | fontRelative | em, rem, ch, ex, cap, ic, lh, rlh | | physical | cm, mm, in, pt, pc, Q | | percentage | %, vw, vh, vmin, vmax, svw, svh, lvw, lvh, dvw, dvh, vi, vb | | angle | deg, grad, rad, turn | | time | s, ms | | frequency | Hz, kHz | | resolution | dpi, dpcm, dppx, x | | flex | fr | | unitless | (无单位) |
步长配置 (CssStepConfig)
interface CssStepConfig {
step?: number | number[] | CssProgressiveRange[];
min?: number;
max?: number;
presets?: number[];
}step 支持三种格式:
// 1. 单一步长
step: 1
// 2. 多个步长值
step: [1, 5, 10]
// 3. 渐进步长范围
step: [
{ max: 10, divisors: [1] },
{ max: 100, divisors: [5, 10] },
{ max: 1000, divisors: [50, 100] }
]配置示例
import { CsstsConfig } from 'cssts-compiler'
const config: CsstsConfig = {
// 属性列表
properties: ['width', 'height', 'margin', 'padding'],
excludeProperties: ['azimuth'],
// 属性详细配置
propertiesConfig: [
{
zIndex: {
unitless: { min: -1, max: 9999, presets: [-1, 999, 9999] }
}
},
{
opacity: {
unitless: { min: 0, max: 1, step: 0.1 }
}
}
],
// 数值类别
numberCategories: ['pixel', 'fontRelative', 'percentage'],
excludeNumberCategories: ['physical', 'resolution'],
// 数值类别详细配置
numberCategoriesConfig: [
{ pixel: { min: 0, max: 1000, step: 1 } },
{ percentage: { min: 0, max: 100, step: 5 } }
],
// 颜色
colorTypes: ['namedColor', 'systemColor'],
excludeColorTypes: ['deprecatedSystemColor'],
// 伪类
pseudoClasses: ['hover', 'focus', 'active'],
excludePseudoClasses: ['visited'],
// 渐进步长
progressiveRanges: [
{ max: 10, divisors: [1] },
{ max: 100, divisors: [5, 10] },
{ max: 1000, divisors: [50, 100] }
]
}配置优先级
属性级配置 (propertiesConfig) > 全局类别配置 (numberCategoriesConfig) > 默认值属性列表优先级:
- 有
properties→ 直接使用 - 没有
properties,有excludeProperties→ 所有属性 - 排除项 - 都没有 → 使用所有属性
DTS 生成器
设计理念
核心问题:IDE 提示与编译转换的统一
用户在 css { } 中输入时:
- 输入
d→ IDE 应提示displayFlex,displayBlock等 - 输入完成
displayFlex→ 编译器转换为csstsAtom.displayFlex
解决方案:全局常量声明
生成的 .d.ts 文件将每个原子类声明为全局常量:
// node_modules/@types/cssts-ts/index.d.ts
declare const displayFlex: { 'display_flex': true };
declare const displayBlock: { 'display_block': true };
declare const paddingTop16px: { 'padding-top_16px': true };
// ... 所有原子类这样设计的好处:
- IDE 自动补全 - 用户在
css { }中输入时,IDE 会提示所有已声明的全局常量 - 类型安全 - 如果用户写了不存在的原子类名(如
deephahah),IDE 不会提示,用户立即知道这不是有效的原子类 - 编译时验证 - 编译器可以通过检查标识符是否匹配已知原子类来决定是否转换
- 统一的数据源 - IDE 提示和编译转换使用同一份类型定义,保证一致性
工作流程:
用户输入 displayFlex
↓
IDE 识别为全局常量,提供补全和类型检查
↓
编译器识别为原子类名,转换为 csstsAtom.displayFlex
↓
运行时从虚拟模块获取 { 'display_flex': true }生成逻辑详解
1. 原子类定义结构
每个原子类由 AtomDefinition 描述:
interface AtomDefinition {
name: string; // 原子类名称 (camelCase),如 'displayFlex'
property: string; // CSS 属性名 (kebab-case),如 'display'
value: string; // CSS 值,如 'flex'
unit?: string; // 单位(可选),如 'px'
number?: number; // 数值(可选),如 16
}2. 原子类名生成规则
// 属性名 (camelCase) + 值 (PascalCase)
atomName = propertyName + formatValue(value)
// 示例:
// display + Flex → displayFlex
// paddingTop + 16px → paddingTop16px
// opacity + 0.9 → opacity0p9 (小数点转 p)
// width + 50% → width50pct (百分号转 pct)
// zIndex + -1 → zIndexN1 (负数前缀 N)3. CSS 类名生成规则
// 属性名 (kebab-case) + 分隔符 + 值
cssClassName = `${property}_${value}`
// 示例:
// display_flex
// padding-top_16px
// opacity_0.9
// width_50%
// z-index_-14. 全局常量声明生成
generateDts() 函数遍历所有原子类,生成全局常量声明:
// atom-generator.ts 中的 generateDts 函数
export function generateDts(options?: GeneratorOptions): string {
const atoms = generateAtoms(options);
const lines: string[] = [
'/**',
' * CSSTS 原子类全局常量声明(自动生成)',
' * ',
' * 这些全局常量用于 css { } 语法中的 IDE 自动补全',
' */',
'',
];
for (const atom of atoms) {
// 生成 CSS 类名:property_value
const cssClassName = `${atom.property}_${atom.value}`;
// 生成全局常量声明
lines.push(`declare const ${atom.name}: { '${cssClassName}': true };`);
}
lines.push('');
return lines.join('\n');
}5. 文件写入
generateDtsFiles() 函数将生成的内容写入文件:
// dts-writer.ts
export function generateDtsFiles(options?: DtsGenerateOptions): DtsGenerateResult {
const { outputDir = 'node_modules/@types/cssts-ts' } = options ?? {};
// 1. 生成 package.json
const packageJson = { name: '@types/cssts-ts', version: '0.0.0', types: 'index.d.ts' };
fs.writeFileSync(path.join(outputDir, 'package.json'), JSON.stringify(packageJson, null, 2));
// 2. 生成 index.d.ts(全局常量声明)
const dtsContent = generateDts(options);
fs.writeFileSync(path.join(outputDir, 'index.d.ts'), dtsContent, 'utf-8');
return { files: [...], atomCount: atoms.length };
}6. Vite 插件调用
Vite 插件在 configResolved 钩子中调用生成函数:
// vite-plugin-cssts/src/index.ts
import { generateDtsFiles } from 'cssts-compiler'
configResolved(resolvedConfig) {
if (enableDts) {
const outputDir = path.join(config.root, 'node_modules/@types/cssts-ts');
generateDtsFiles({ outputDir });
}
}API
import { generateAtoms, generateDts, generateDtsFiles, generateStats } from 'cssts-compiler'
// 生成原子类定义数组
const atoms = generateAtoms({ config: userConfig })
// 生成 DTS 文件内容(全局常量声明格式)
const dtsContent = generateDts({ config: userConfig })
// 生成 DTS 文件到指定目录
generateDtsFiles({ outputDir: 'node_modules/@types/cssts-ts' })
// 生成统计信息
const stats = generateStats({ config: userConfig })
console.log(`总原子类数: ${stats.totalAtoms}`)生成文件结构
node_modules/@types/cssts-ts/
├── package.json # { "name": "@types/cssts-ts", "types": "index.d.ts" }
└── index.d.ts # 全局原子类常量声明只生成一个类型文件,包含所有原子类的全局常量声明:
// node_modules/@types/cssts-ts/index.d.ts
/**
* CSSTS 原子类全局常量声明(自动生成)
*
* 这些全局常量用于 css { } 语法中的 IDE 自动补全
*/
declare const displayFlex: { 'display_flex': true };
declare const displayBlock: { 'display_block': true };
declare const paddingTop16px: { 'padding-top_16px': true };
// ... 约 29000+ 个原子类不需要的东西:
- ❌
CsstsAtoms接口 - 用户不直接使用csstsAtom.xxx,编译器自动转换 - ❌
declare module 'virtual:csstsAtom'- 虚拟模块运行时由 Vite 提供,不需要类型声明 - ❌
declare module 'virtual:cssts.css'- 同理
0 值处理
生成规则:
- 如果
min <= 0 && max >= 0,则包含 0 - 否则不包含 0
去重优化:
- CSS 规范中
0不需要单位,0px、0em、0rem等效于0 - 生成器会自动去重,只生成一个
top0: '0',而不是top0px、top0em、top0rem等多个 - 这大幅减少了生成的原子类数量(例如 top 属性从 1013 个减少到 888 个)
// 生成结果示例
declare const top0: { 'top_0': true }; // ✅ 只生成一个
declare const top1px: { 'top_1px': true };
declare const top1em: { 'top_1em': true };
// top0px, top0em, top0rem 不会生成(去重)数据来源
主要数据源
| 数据源 | 说明 | 提取内容 |
|--------|------|----------|
| csstree | CSS 语法解析库 | 属性名、keywords、颜色、数值类型 |
| datajson/numberMapping.json | 自定义映射 | 单位到 category 的分类映射 |
| datajson/pseudo-standards.json | 自定义数据 | 标准伪类和伪元素列表 |
| datajson/propertyInheritance.json | 自定义数据 | 属性继承关系(如 margin → marginTop) |
从 csstree 提取的数据
- 属性名 - 所有标准 CSS 属性(排除
-开头的 vendor prefix 属性) - Keywords - 每个属性支持的关键字值
- 颜色类型 -
namedColor、systemColor、deprecatedSystemColor、nonStandardColor - 数值类型 -
length、percentage、angle、time等
数据映射设计
所有 *_NAME_MAP 的 key 统一使用 camelCase,value 为原始的 kebab-case:
// 所有 NAME_MAP 统一格式:camelCase → kebab-case
CSS_PROPERTY_NAME_MAP = {
'backgroundColor': 'background-color',
'fontSize': 'font-size',
}
KEYWORD_NAME_MAP = {
'crispEdges': 'crisp-edges',
'mozBox': '-moz-box',
}
COLOR_NAME_MAP = {
'transparent': 'transparent',
'aliceblue': 'aliceblue',
}
PSEUDO_CLASS_NAME_MAP = {
'focusVisible': 'focus-visible',
'firstChild': 'first-child',
}
PSEUDO_ELEMENT_NAME_MAP = {
'firstLine': 'first-line',
'fileSelectorButton': 'file-selector-button',
}设计原因:
- 数据源格式:csstree 和其他数据源的原始数据格式是
kebab-case - TypeScript 使用:在 TypeScript 中使用
camelCase作为变量名 - 查找流程:
- 遍历数据源时获取
kebab-case格式的原始数据 - 将
kebab-case转换为camelCase - 用
camelCase作为 key 查找 Map - 获取对应的原始
kebab-case值用于生成 CSS
- 遍历数据源时获取
// 使用示例
const kebabKeyword = 'crisp-edges'; // 原始数据(来自 csstree)
const camelKey = kebabToCamel(kebabKeyword); // 转换为 'crispEdges'
const original = KEYWORD_NAME_MAP[camelKey]; // 查找得到 'crisp-edges'
// 生成原子类时
const atomName = `display${camelKey}`; // 'displayCrispEdges'
const cssValue = original; // 'crisp-edges'统一设计的好处:
- 所有 Map 使用相同的 key 格式,减少混淆
- 查找逻辑一致:先转 camelCase,再查 Map
- 便于去重:相同 camelCase 的不同 kebab-case 会被自动去重
特殊处理
CSS 全局关键字
CSS-wide keywords 是所有 CSS 属性都支持的全局关键字,但 csstree 不会为每个属性显式声明它们。生成器会手动将这些关键字添加到每个属性的 keywords 列表中:
inherit- 继承父元素的值initial- 使用属性的初始值unset- 可继承属性用 inherit,否则用 initialrevert- 回退到用户代理样式表的值revert-layer- 回退到上一个级联层的值
这些关键字同时也存在于 KEYWORD_NAME_MAP 中(从 csstree 提取),确保映射完整。
颜色数据
| 关键字 | 来源 | 说明 |
|--------|------|------|
| transparent | csstree namedColor 类型 | 作为命名颜色存在 |
| currentColor | csstree keyword | 作为独立关键字存在于 KEYWORD_NAME_MAP |
颜色属性(有 colorTypes 的属性)会自动支持所有配置的颜色类型。
Vendor Prefix 处理
以 - 开头的 keyword(如 -moz-box、-webkit-flex):
- 去掉开头的
- - 转换为 PascalCase
- 与属性名拼接生成原子类名
示例:-webkit-flex → WebkitFlex → displayWebkitFlex
Keyword 去重
当不同的 CSS keyword 转换为 camelCase 后产生相同的名称时,生成器会自动去重,保留第一个(按字母排序)。
| CSS Keyword | camelCase | 处理结果 |
|-------------|-----------|----------|
| crisp-edges | crispEdges | ✅ 保留 |
| crispEdges | crispEdges | ❌ 跳过(重复) |
这确保了生成的 TypeScript 类型定义中不会出现重复的属性名。
数值单位分类
单位按功能分为 10 个 category:
| Category | 单位 | 说明 | |----------|------|------| | pixel | px | 像素单位 | | fontRelative | em, rem, ch, ex, cap, ic, lh, rlh | 字体相对单位 | | percentage | %, vw, vh, vmin, vmax, svw, svh, lvw, lvh, dvw, dvh, vi, vb | 百分比和视口单位 | | physical | cm, mm, in, pt, pc, Q | 物理单位 | | angle | deg, grad, rad, turn | 角度单位 | | time | s, ms | 时间单位 | | frequency | Hz, kHz | 频率单位 | | resolution | dpi, dpcm, dppx, x | 分辨率单位 | | flex | fr | 弹性单位 | | unitless | (无) | 无单位数值 |
生成脚本
generator-data.ts(阶段1:数据生成)
从 csstree 提取 CSS 数据:
tsx generator/generator-data.ts输出到 src/data/:
cssColorData.ts- 颜色数据cssKeywordsData.ts- 关键词数据cssNumberData.ts- 数值类型和单位数据cssPropertyColorTypes.ts- 属性支持的颜色类型cssPropertyKeywords.ts- 属性支持的关键词cssPropertyNameMapping.ts- 属性名称映射cssPropertyNumber.ts- 属性支持的数值类型cssPseudoData.ts- 伪类/伪元素数据
generator-type.ts(阶段2:类型生成)
从数据文件生成类型定义:
tsx generator/generator-type.ts输出到 src/types/:
cssPropertyConfig.d.ts- 属性配置类型csstsConfig.d.ts- CSSTS 配置类型
执行顺序
# 1. 先生成数据文件
tsx generator/generator-data.ts
# 2. 再生成类型文件
tsx generator/generator-type.ts核心属性列表
基于 Tailwind CSS 设计理念的精简属性集合(41 个属性,约 1,092 个原子类):
import { CORE_PROPERTIES } from 'cssts-compiler'
const config = {
properties: CORE_PROPERTIES
}包含:布局、Flexbox、Grid、尺寸、间距、背景、文本、边框、阴影、变换、过渡等常用属性。
License
MIT
