Appearance
CSSTree 源代码导览
项目结构
csstree-course/
├── 04-core-feature/ # 核心功能实现
│ ├── src/
│ │ ├── lexer.js # 词法分析器
│ │ ├── parser.js # 语法分析器
│ │ └── generator.js # 生成器
│ ├── test/
│ │ ├── lexer.test.js
│ │ ├── parser.test.js
│ │ └── generator.test.js
│ ├── package.json
│ └── README.md
├── 05-lesson-plan.md # 课程计划
├── 01-intro.md # 背景研究
├── 02-arch.md # 架构分析
└── 03-code-walkthrough.md # 源代码导览核心文件解析
1. lexer.js - 词法分析器
文件路径: src/lexer.js
核心功能:
- 标记化 CSS 源代码
- 处理空白和注释
- 识别 CSS 标记
关键代码:
javascript
// 词法分析器
export class Lexer {
constructor(options = {}) {
this.position = 0
this.source = ''
this.options = options
}
// 标记化
tokenize(source) {
this.source = source
this.position = 0
const tokens = []
while (this.position < this.source.length) {
const token = this.nextToken()
if (token) {
tokens.push(token)
}
}
return tokens
}
// 读取下一个标记
nextToken() {
// 跳过空白
this.skipWhitespace()
if (this.position >= this.source.length) {
return null
}
const char = this.source[this.position]
// 识别不同类型的标记
if (this.isIdentifierStart(char)) {
return this.readIdentifier()
} else if (char === '{') {
this.position++
return { type: 'LeftCurlyBracket', value: '{' }
} else if (char === '}') {
this.position++
return { type: 'RightCurlyBracket', value: '}' }
} else if (char === ':') {
this.position++
return { type: 'Colon', value: ':' }
} else if (char === ';') {
this.position++
return { type: 'Semicolon', value: ';' }
}
// 其他情况
this.position++
return { type: 'Unknown', value: char }
}
// 读取标识符
readIdentifier() {
const start = this.position
while (this.position < this.source.length) {
const char = this.source[this.position]
if (this.isIdentifierChar(char)) {
this.position++
} else {
break
}
}
return {
type: 'Identifier',
value: this.source.slice(start, this.position)
}
}
// 跳过空白
skipWhitespace() {
while (this.position < this.source.length) {
const char = this.source[this.position]
if (this.isWhitespace(char)) {
this.position++
} else {
break
}
}
}
// 判断是否为空白
isWhitespace(char) {
return /\s/.test(char)
}
// 判断是否为标识符起始字符
isIdentifierStart(char) {
return /[a-zA-Z_-]/.test(char)
}
// 判断是否为标识符字符
isIdentifierChar(char) {
return /[a-zA-Z0-9_-]/.test(char)
}
}设计要点:
- 简单的状态机
- 支持流式处理
- 准确的标记识别
2. parser.js - 语法分析器
文件路径: src/parser.js
核心功能:
- 将标记转换为 AST
- 验证 CSS 语法
- 错误恢复
关键代码:
javascript
// 语法分析器
export class Parser {
constructor(options = {}) {
this.tokens = []
this.position = 0
this.options = options
}
// 解析标记
parse(tokens) {
this.tokens = tokens
this.position = 0
const ast = {
type: 'StyleSheet',
children: []
}
while (this.position < this.tokens.length) {
const rule = this.parseRule()
if (rule) {
ast.children.push(rule)
}
}
return ast
}
// 解析规则
parseRule() {
const selector = this.parseSelector()
if (!selector) {
return null
}
this.expect('LeftCurlyBracket')
const declarations = []
while (this.peek()?.type !== 'RightCurlyBracket') {
const declaration = this.parseDeclaration()
if (declaration) {
declarations.push(declaration)
}
}
this.expect('RightCurlyBracket')
return {
type: 'Rule',
selector: selector,
children: declarations
}
}
// 解析选择器
parseSelector() {
const token = this.peek()
if (token?.type === 'Identifier') {
this.consume()
return token.value
}
return null
}
// 解析声明
parseDeclaration() {
const property = this.parseProperty()
if (!property) {
return null
}
this.expect('Colon')
const value = this.parseValue()
this.expect('Semicolon')
return {
type: 'Declaration',
property: property,
value: value
}
}
// 解析属性
parseProperty() {
const token = this.peek()
if (token?.type === 'Identifier') {
this.consume()
return token.value
}
return null
}
// 解析值
parseValue() {
const token = this.peek()
if (token?.type === 'Identifier') {
this.consume()
return token.value
}
return null
}
// 查看下一个标记
peek() {
return this.tokens[this.position]
}
// 消耗标记
consume() {
return this.tokens[this.position++]
}
// 期望特定类型的标记
expect(type) {
const token = this.peek()
if (token?.type === type) {
this.consume()
} else {
throw new Error(`Expected ${type}, got ${token?.type}`)
}
}
}设计要点:
- 递归下降解析
- 错误恢复
- 清晰的语法结构
3. generator.js - 生成器
文件路径: src/generator.js
核心功能:
- 将 AST 转换为 CSS
- 格式化输出
- 压缩输出
关键代码:
javascript
// 生成器
export class Generator {
constructor(options = {}) {
this.options = options
}
// 生成 CSS
generate(ast) {
let css = ''
for (const child of ast.children) {
css += this.generateNode(child)
}
return css
}
// 生成节点
generateNode(node) {
switch (node.type) {
case 'StyleSheet':
return this.generateStyleSheet(node)
case 'Rule':
return this.generateRule(node)
case 'Declaration':
return this.generateDeclaration(node)
default:
return ''
}
}
// 生成样式表
generateStyleSheet(node) {
let css = ''
for (const child of node.children) {
css += this.generateNode(child)
}
return css
}
// 生成规则
generateRule(node) {
const { format = true } = this.options
let css = node.selector
if (format) {
css += ' {\n'
} else {
css += '{'
}
for (const child of node.children) {
if (format) {
css += ' '
}
css += this.generateNode(child)
if (format) {
css += '\n'
}
}
css += '}'
if (format) {
css += '\n\n'
}
return css
}
// 生成声明
generateDeclaration(node) {
const { format = true } = this.options
let css = node.property
if (format) {
css += ': '
} else {
css += ':'
}
css += node.value
if (format) {
css += ';'
}
return css
}
}设计要点:
- 简单的递归生成
- 支持格式化
- 支持压缩
关键设计决策
1. 分离 Lexer 和 Parser
原因:
- 职责分离
- 便于测试
- 支持流式处理
实现:
javascript
const lexer = new Lexer()
const tokens = lexer.tokenize(source)
const parser = new Parser()
const ast = parser.parse(tokens)2. 递归下降解析
原因:
- 简单易懂
- 易于扩展
- 错误恢复
实现:
javascript
function parseRule() {
const selector = parseSelector()
const declarations = []
while (peek()?.type !== 'RightCurlyBracket') {
declarations.push(parseDeclaration())
}
return { type: 'Rule', selector, children: declarations }
}3. AST 结构
原因:
- 易于操作
- 易于生成
- 易于验证
实现:
javascript
{
type: 'Rule',
selector: '.class',
children: [
{ type: 'Declaration', property: 'color', value: 'red' }
]
}测试策略
单元测试
javascript
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { Lexer } from '../src/lexer.js'
describe('Lexer 测试', () => {
it('应该标记化 CSS', () => {
const lexer = new Lexer()
const tokens = lexer.tokenize('.class { color: red; }')
assert.ok(tokens.length > 0)
})
})总结
CSSTree 的源代码体现了以下设计原则:
- 职责分离:Lexer 和 Parser 分离
- 递归下降:简单的解析策略
- AST 优先:使用 AST 表示 CSS
- 易于测试:清晰的接口
理解源代码有助于更好地使用和优化 CSSTree。
参考资源

扫描二维码关注"架构师AI杜"公众号,获取更多技术内容和最新动态
