PostCSS 架构 
致读者
本译文在忠于原文的基础上,融入了译者基于自身领域知识所添加的解释性内容和本地化表达。这些精心添加的内容旨在增进读者对原文核心信息的理解。如对内容有任何疑问,欢迎您参与评论区讨论,或查阅原文以作参考。
这篇文章主要面向想要为 postcss 核心贡献代码或深入理解这个工具的开发者。
概述 
在深入 postcss 的开发之前,我们需要先明确 postcss 的定位:
- 不是像 - sass或- less那样的- style预处理器- postcss自身不自定义语法和语义规则且实际上并非是一种独立的编程语言。- postcss专注于处理标准的- css,并且可以很容易地与- sass、- less等工具集成。这意味着任何有效的- css代码都可以被- postcss处理。
- 是 - css语法转译工具- 它允许开发者定义类似于自定义 - css的语法,这些语法可被插件理解和转换。- postcss并不局限于- css规范本身,而是专注于- css的语法定义方式。通过这种方式,开发者可以定义诸如- at-rule之类的自定义语法结构,在以- postcss作为上游转译工具的构建的工具中会非常有帮助。- postcss扮演着构建出色的用于- css操作工具框架角色。
- 在 - css生态系统中占据着重要地位- 大量优秀的工具(如 - autoprefixer(自动添加浏览器前缀)、- stylelint(- css代码检查工具)、- cssnano(- css代码压缩工具))都是基于- postcss生态系统构建的。
工作流程 
postcss 的工作流程设计得相当直观,但有些核心部分需要特别说明:

解析器(Parser)是其中的关键组件。它能够理解 css 语法并创建相应的对象表示。解析器的实现主要有两种方式:
- 单文件字符串到 - ast转换- 这是一种较为简单的方法,比如 Rework 分析器 就采用这种方式。但随着代码库增大,这种方式会导致代码难以维护,且性能较差。 
- 词法分析与解析分离(源字符串 → 词法标记( - tokens) →- ast),将整个过程分为 词法分析和语法解析两个步骤- 这种方法在 - postcss中使用,也是目前最流行的做法。许多解析器, 如- Babel的 [@babel/parser]((https://github.com/babel/babel/tree/master/packages/babel-parser) 和- CSSTree都采用这种方式。- 转译的工作流程分为两步骤的的主要原因是: - 性能优化: - 字符串转换为词法标记( - tokenization)是一个耗时操作,需要逐字符处理源代码。将这一步骤独立出来,确保只需执行一次。
- 抽象复杂性: - 词法标记到 - ast的转换在逻辑上更复杂,但通过分离,我们可以实现一个高性能的词法分析器(代码可能较难理读)和一个易于理解的解析器(虽然相对较慢)。
 - 通过这种拆分,我们可以在性能和代码可读性之间取得平衡。词法分析保证速度,语法解析保证可读性。 - 总的来说,这种分离策略既提升了整体性能,又提高了代码的可维护性,是一种在 - postcss等工具中广泛采用的有效策略。
核心结构 
接下来,文章提到将详细介绍在 postcss 工作流程中起主要作用的几个核心结构:
Tokenizer 
Tokenizer 是 postcss 的词法分析器,实现目录可见 lib/tokenize.js。主要负责将 css 字符串转换为标记(tokens)列表。
- 也称为词法分析器(Lexer)
- 接收 css字符串,返回标记(tokens)列表
- 每个标记描述语法的一部分,如 at-rule,comment或word
- 标记可包含位置信息,便于生成更详细的错误信息
- 标记以列表形式表示,包含类型、内容、开始位置、结束位置等信息
- postcss的标记生成器优化了性能,代码可能看起来较复杂
- postcss的- Tokenizer使用一种流/链接- api,在其中向- Parser公开- nextToken()方法。 通过这种方式,我们为- Parser提供了清晰的接口,并通过仅存储少量标记而不是整个标记列表来减少内存使用量。
例子:
.className {
  color: #fff;
}对应的PostCSS解析后的tokens如下
[
    ["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。就像所说的那样,每个标记都表示为一个列表,并遵循这种模式。
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 
Parser 是 postcss 的语法分析器,实现目录可见 lib/parse.js、lib/parser.js。它的主要任务是将 词法标记 转换成 抽象语法树(ast)。这个过程可以类比为将散落的拼零件(词法标记)组装成完整的工具(抽象语法树(ast))。
- 不直接处理源代码字符串,而是处理 词法分析器(Tokenizer) 提供的 标记(tokens)。
- 使用词法分析器提供的 nextToken和back方法来获取和回溯标记。
- 根据获取的 标记(tokens) 构建ast节点(ast node)。
Processor 
Processor 是 postcss 的调度中心,实现目录可见 lib/processor.js。它的主要职责包括:
- 初始化插件。
- 协调执行语法转换。
- 提供公共 api接口。
虽然处理器的结构相对简单,但它是连接各个组件的关键纽带。
Stringifier 
Stringifier 是 postcss 的字符串化工具,实现目录可见 lib/stringify.js、lib/stringifier.js。它的主要职责是将经过修改的 css ast 转换回 css 字符串。这个过程就像是将抽象的树结构(cssom)重新"具象化"为可以直接使用的 css 代码。
字符串生成器的工作方式是:
- 从提供的起始节点开始遍历 css ast。
- 在遍历过程中根据不同类型的节点调用相应的生成方法。
- 最终拼接出完整的 css字符串。
总结 
通过这些核心组件的协同工作,postcss 能够灵活而高效地处理各种 css 转换需求,同时保持良好的可扩展性和可维护性。每个组件都专注于自己的职责,又能够无缝配合,这正是 postcss 设计的精妙之处。
- Tokenizer将- css字符串转换为标记(- tokens)。
- Parser使用这些标记构建- css ast。
- Processor应用插件对- css ast进行转换。
- Stringifier将修改后的- css ast转换回- css字符串。
 XiSenao
 XiSenao