Skip to content

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 的源代码体现了以下设计原则:

  1. 职责分离:Lexer 和 Parser 分离
  2. 递归下降:简单的解析策略
  3. AST 优先:使用 AST 表示 CSS
  4. 易于测试:清晰的接口

理解源代码有助于更好地使用和优化 CSSTree。

参考资源

架构师AI杜公众号二维码

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