New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

cssts-compiler

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cssts-compiler

CssTs compiler - parser, AST transformer, and type generator for CSS-in-TS

latest
npmnpm
Version
0.2.87
Version published
Weekly downloads
5
400%
Maintainers
1
Weekly downloads
 
Created
Source

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-parserSlimeCstToAst,使用全局注册模式扩展 CST → AST 转换。

为什么需要全局注册

slime-parser 内部各转换器通过 SlimeCstToAstUtils.xxx() 调用。直接继承重写方法不会生效,因为内部调用不经过子类。

架构设计:继承链自动注册

核心机制

  • Proxy 代理模式:导出的 cssTsCstToAstUtils 是 Proxy,动态代理到全局注册的实例
  • 构造函数自动注册SlimeCstToAstCssTsCstToAst 构造函数自动调用注册
  • 继承链传递:子类调用 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)
  • 此时 thisCssTsCstToAst 实例(或更深层的子类实例)
  • 父类注册完成后,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 + [自动生成部分] + suffix
  • prefix: 前缀(可选)
  • 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 → PascalCaseRow
'space-between'kebab → PascalCaseSpaceBetween
{ 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 规则
displayFlexdisplay_flexdisplay: flex
paddingTop16pxpadding-top_16pxpadding-top: 16px
opacity0p9opacity_0.9opacity: 0.9
width50pctwidth_50%width: 50%
zIndexN1z-index_-1z-index: -1
转义符含义示例
N- 负数N10-10
p. 小数点0p50.5
pct% 百分号50pct50%
s/ 斜杠16s916/9

Vendor Prefix 处理

- 开头的 vendor prefix keyword 会去掉开头的 -,然后转换为 PascalCase:

CSS KeywordTS 变量名
-moz-boxdisplayMozBox
-webkit-flexdisplayWebkitFlex
-ms-griddisplayMsGrid
-webkit-inline-boxdisplayWebkitInlineBox

配置系统

配置层级

Property → NumberCategory → NumberUnit
         → ColorType → Color
         → Keyword

配置字段概览

列表配置详情配置用途
propertiespropertiesConfigCSS 属性
excludeProperties-排除属性
numberCategoriesnumberCategoriesConfig数值类别
excludeNumberCategories-排除类别
numberUnitsnumberUnitsConfig数值单位
excludeUnits-排除单位
colorTypescolorTypesConfig颜色类型
excludeColorTypes-排除颜色类型

数值类别 (NumberCategory)

CategoryUnits
pixelpx
fontRelativeem, rem, ch, ex, cap, ic, lh, rlh
physicalcm, mm, in, pt, pc, Q
percentage%, vw, vh, vmin, vmax, svw, svh, lvw, lvh, dvw, dvh, vi, vb
angledeg, grad, rad, turn
times, ms
frequencyHz, kHz
resolutiondpi, dpcm, dppx, x
flexfr
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_-1

4. 全局常量声明生成

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 不需要单位,0px0em0rem 等效于 0
  • 生成器会自动去重,只生成一个 top0: '0',而不是 top0pxtop0emtop0rem 等多个
  • 这大幅减少了生成的原子类数量(例如 top 属性从 1013 个减少到 888 个)
// 生成结果示例
declare const top0: { 'top_0': true };      // ✅ 只生成一个
declare const top1px: { 'top_1px': true };
declare const top1em: { 'top_1em': true };
// top0px, top0em, top0rem 不会生成(去重)

数据来源

主要数据源

数据源说明提取内容
csstreeCSS 语法解析库属性名、keywords、颜色、数值类型
datajson/numberMapping.json自定义映射单位到 category 的分类映射
datajson/pseudo-standards.json自定义数据标准伪类和伪元素列表
datajson/propertyInheritance.json自定义数据属性继承关系(如 margin → marginTop)

从 csstree 提取的数据

  • 属性名 - 所有标准 CSS 属性(排除 - 开头的 vendor prefix 属性)
  • Keywords - 每个属性支持的关键字值
  • 颜色类型 - namedColorsystemColordeprecatedSystemColornonStandardColor
  • 数值类型 - lengthpercentageangletime

数据映射设计

所有 *_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,否则用 initial
  • revert - 回退到用户代理样式表的值
  • revert-layer - 回退到上一个级联层的值

这些关键字同时也存在于 KEYWORD_NAME_MAP 中(从 csstree 提取),确保映射完整。

颜色数据

关键字来源说明
transparentcsstree namedColor 类型作为命名颜色存在
currentColorcsstree keyword作为独立关键字存在于 KEYWORD_NAME_MAP

颜色属性(有 colorTypes 的属性)会自动支持所有配置的颜色类型。

Vendor Prefix 处理

- 开头的 keyword(如 -moz-box-webkit-flex):

  • 去掉开头的 -
  • 转换为 PascalCase
  • 与属性名拼接生成原子类名

示例:-webkit-flexWebkitFlexdisplayWebkitFlex

Keyword 去重

当不同的 CSS keyword 转换为 camelCase 后产生相同的名称时,生成器会自动去重,保留第一个(按字母排序)。

CSS KeywordcamelCase处理结果
crisp-edgescrispEdges✅ 保留
crispEdgescrispEdges❌ 跳过(重复)

这确保了生成的 TypeScript 类型定义中不会出现重复的属性名。

数值单位分类

单位按功能分为 10 个 category:

Category单位说明
pixelpx像素单位
fontRelativeem, rem, ch, ex, cap, ic, lh, rlh字体相对单位
percentage%, vw, vh, vmin, vmax, svw, svh, lvw, lvh, dvw, dvh, vi, vb百分比和视口单位
physicalcm, mm, in, pt, pc, Q物理单位
angledeg, grad, rad, turn角度单位
times, ms时间单位
frequencyHz, kHz频率单位
resolutiondpi, dpcm, dppx, x分辨率单位
flexfr弹性单位
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

Keywords

cssts

FAQs

Package last updated on 15 Jan 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts