Skip to content

PostCSS Architecture

A Note to Our Readers

While maintaining fidelity to the source material, this translation incorporates explanatory content and localized expressions, informed by the translator's domain expertise. These thoughtful additions aim to enhance readers' comprehension of the original text's core messages. For any inquiries about the content, we welcome you to engage in the discussion section or consult the source text for reference.

这篇文章主要面向想要为 postcss 核心贡献代码或深入理解这个工具的开发者。

Overview

在深入 postcss 的开发之前,我们需要先明确 postcss 的定位:

  1. 不是像 sassless 那样的 style 预处理器

    postcss 自身不自定义语法和语义规则且实际上并非是一种独立的编程语言。postcss 专注于处理标准的 css,并且可以很容易地与 sassless 等工具集成。这意味着任何有效的 css 代码都可以被 postcss 处理。

  2. css 语法转译工具

    它允许开发者定义类似于自定义 css 的语法,这些语法可被插件理解和转换。postcss 并不局限于 css 规范本身,而是专注于 css 的语法定义方式。通过这种方式,开发者可以定义诸如 at-rule 之类的自定义语法结构,在以 postcss 作为上游转译工具的构建的工具中会非常有帮助。postcss 扮演着构建出色的用于 css 操作工具框架角色。

  3. css 生态系统中占据着重要地位

    大量优秀的工具(如 autoprefixer(自动添加浏览器前缀)、stylelint(css 代码检查工具)、cssnano(css 代码压缩工具))都是基于 postcss 生态系统构建的。

WorkFlow

postcss 的工作流程设计得相当直观,但有些核心部分需要特别说明:

解析器(Parser)是其中的关键组件。它能够理解 css 语法并创建相应的对象表示。解析器的实现主要有两种方式:

  1. 单文件字符串到 ast 转换

    这是一种较为简单的方法,比如 Rework 分析器 就采用这种方式。但随着代码库增大,这种方式会导致代码难以维护,且性能较差。

  2. 词法分析与解析分离(源字符串 → 词法标记(tokens) → ast),将整个过程分为 词法分析语法解析两个步骤

    这种方法在 postcss 中使用,也是目前最流行的做法。许多解析器, 如 Babel 的 [@babel/parser]((https://github.com/babel/babel/tree/master/packages/babel-parser) 和 CSSTree 都采用这种方式。

    转译的工作流程分为两步骤的的主要原因是:

    1. 性能优化:

      字符串转换为词法标记(tokenization)是一个耗时操作,需要逐字符处理源代码。将这一步骤独立出来,确保只需执行一次。

    2. 抽象复杂性:

      词法标记到 ast 的转换在逻辑上更复杂,但通过分离,我们可以实现一个高性能的词法分析器(代码可能较难理读)和一个易于理解的解析器(虽然相对较慢)。

    通过这种拆分,我们可以在性能和代码可读性之间取得平衡。词法分析保证速度,语法解析保证可读性。

    总的来说,这种分离策略既提升了整体性能,又提高了代码的可维护性,是一种在 postcss 等工具中广泛采用的有效策略。

Core Structures

接下来,文章提到将详细介绍在 postcss 工作流程中起主要作用的几个核心结构:

Tokenizer

Tokenizerpostcss 的词法分析器,实现目录可见 lib/tokenize.js。主要负责将 css 字符串转换为标记(tokens)列表。

  • 也称为词法分析器(Lexer)
  • 接收 css 字符串,返回标记(tokens)列表
  • 每个标记描述语法的一部分,如 at-rule, commentword
  • 标记可包含位置信息,便于生成更详细的错误信息
  • 标记以列表形式表示,包含类型、内容、开始位置、结束位置等信息
  • postcss 的标记生成器优化了性能,代码可能看起来较复杂
  • postcssTokenizer 使用一种流/链接 api,在其中向 Parser 公开 nextToken() 方法。 通过这种方式,我们为 Parser 提供了清晰的接口,并通过仅存储少量标记而不是整个标记列表来减少内存使用量。

例子:

css
.className {
  color: #fff;
}

对应的PostCSS解析后的tokens如下

json
[
    ["word", ".className", 1, 1, 1, 10]
    ["space", " "]
    ["{", "{", 1, 12]
    ["space", " "]
    ["word", "color", 1, 14, 1, 18]
    [":", ":", 1, 19]
    ["space", " "]
    ["word", "#FFF" , 1, 21, 1, 23]
    [";", ";", 1, 24]
    ["space", " "]
    ["}", "}", 1, 26]
]

正如上述示例所见,一个 token 表示一个列表并且 space token 没有位置信息。

让我们更仔细地看一下像 word 这样的单个 token。就像所说的那样,每个标记都表示为一个列表,并遵循这种模式。

js
const token = [
  // represents token type
  'word',

  // represents matched word
  '.className',

  // This two numbers represent start position of token.
  // It is optional value as we saw in the example above,
  // tokens like `space` don't have such information.

  // Here the first number is line number and the second one is corresponding column.
  1,
  1,

  // Next two numbers also optional and represent end position for multichar tokens like this one. Numbers follow same rule as was described above
  1,
  10
];

特别值得注意的是,postcss 的词法分析器采用了流式 api 设计。这意味着它不会一次性生成所有标记,而是通过 nextToken() 方法按需提供标记给解析器。这种设计有两个重要优势:

  • 降低内存使用:因为不需要同时在内存中保存所有标记,尤其是在处理大型 CSS 文件时。
  • 提供清晰的接口:让解析器能够更优雅地处理标记流,解析器可以灵活地控制标记的生成,例如在需要时回滚到之前的标记。

Parser

Parserpostcss 的语法分析器,实现目录可见 lib/parse.jslib/parser.js。它的主要任务是将 词法标记 转换成 抽象语法树(ast)。这个过程可以类比为将散落的拼零件(词法标记)组装成完整的工具(抽象语法树(ast))。

  • 不直接处理源代码字符串,而是处理 词法分析器(Tokenizer) 提供的 标记(tokens)
  • 使用词法分析器提供的 nextTokenback 方法来获取和回溯标记。
  • 根据获取的 标记(tokens) 构建 ast 节点(ast node)。

Processor

Processorpostcss 的调度中心,实现目录可见 lib/processor.js。它的主要职责包括:

  • 初始化插件。
  • 协调执行语法转换。
  • 提供公共 api 接口。

虽然处理器的结构相对简单,但它是连接各个组件的关键纽带。

Stringifier

Stringifierpostcss 的字符串化工具,实现目录可见 lib/stringify.jslib/stringifier.js。它的主要职责是将经过修改的 css ast 转换回 css 字符串。这个过程就像是将抽象的树结构(cssom)重新"具象化"为可以直接使用的 css 代码。

字符串生成器的工作方式是:

  • 从提供的起始节点开始遍历 css ast
  • 在遍历过程中根据不同类型的节点调用相应的生成方法。
  • 最终拼接出完整的 css 字符串。

Summary

通过这些核心组件的协同工作,postcss 能够灵活而高效地处理各种 css 转换需求,同时保持良好的可扩展性和可维护性。每个组件都专注于自己的职责,又能够无缝配合,这正是 postcss 设计的精妙之处。

  1. Tokenizercss 字符串转换为标记(tokens)。
  2. Parser 使用这些标记构建 css ast
  3. Processor 应用插件对 css ast 进行转换。
  4. Stringifier 将修改后的 css ast 转换回 css 字符串。

Contributors

Changelog

Discuss

Released under the CC BY-SA 4.0 License. (2619af4)