PostCSS 架构
PostCSS 架构的一般概述。对于希望为核心做出贡献或更好地了解该工具的每个人来说,它都很有用。
概述
本节介绍 PostCSS 背后的理念
在深入了解 PostCSS 的开发之前,让我们简单描述一下什么是 PostCSS,什么不是 PostCSS。
PostCSS
-
不是像
Sass
或Less
这样的样式预处理器。它不定义自定义语法和语义,它实际上不是一门语言。PostCSS 使用 CSS,可以轻松地与上述工具集成。也就是说,PostCSS 可以处理任何有效的 CSS。
-
是 CSS 语法转换工具
它允许你定义自定义的类似 CSS 的语法,插件可以理解和转换这些语法。也就是说,PostCSS 不完全是关于 CSS 规范,而是关于 CSS 的语法定义方式。通过这种方式,你可以定义自定义语法结构(如 at 规则),这对围绕 PostCSS 构建的工具非常有帮助。PostCSS 扮演了一个框架的角色,用于构建出色的 CSS 操作工具。
-
是 CSS 生态系统中的重要参与者
大量优秀的工具(如
Autoprefixer
、Stylelint
、CSSnano
)都是建立在 PostCSS 生态系统之上的。你很有可能已经在不知不觉中使用了它,只需检查你的node_modules
😀
工作流
这是整个 PostCSS 工作流的高级概述
从上面的图表中可以看到,PostCSS 架构非常简单,但其中某些部分可能会被误解。
你可以看到一个名为 Parser 的部分,这个结构将在后面详细描述,现在只需把它想象成一个可以理解你的类似 CSS 的语法并创建它的对象表示形式的结构即可。
也就是说,有几种方法可以编写解析器。
-
用一个文件将字符串转换为 AST
这种方法非常流行,例如,Rework 分析器就是用这种风格编写的。但是对于大型代码库,代码会变得难以阅读且非常慢。
-
将其拆分为词法分析/解析步骤(源字符串 → 令牌 → AST)
这是我们在 PostCSS 中采用的方式,也是最流行的一种方式。很多解析器,例如
@babel/parser
(Babel 背后的解析器)、CSSTree
,都是用这种方式编写的。将标记化与解析步骤分开的的主要原因是性能和抽象复杂性。
让我们思考一下为什么第二种方式更适合我们的需求。
首先,因为将字符串转换为标记的步骤比解析步骤花费更多时间。我们对大型源字符串进行操作并逐个字符地处理它,这就是为什么它在性能方面非常低效,我们应该只执行一次。
但另一方面,将标记转换为 AST 的转换在逻辑上更复杂,因此通过这种分离,我们可以编写非常快速的标记器(但这有时会导致代码难以阅读)和易于阅读(但速度较慢)的解析器。
总结一下,拆分为两个步骤可以提高性能和代码可读性。
现在让我们更仔细地了解在 PostCSS 工作流中发挥主要作用的结构。
核心结构
-
标记器
lib/tokenize.js
标记器(又称词法分析器)在语法分析中扮演着重要角色。
它接受 CSS 字符串并返回标记列表。
标记是一种简单的结构,它描述语法的一部分,例如
at-rule
、comment
或word
。它还可能包含位置信息,以提供更具描述性的错误。例如,如果我们考虑以下 CSS
.className { color: #FFF; }
PostCSS 中对应的标记将是
[ ["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] ]
正如您从上面的示例中看到的那样,单个标记表示为列表,并且
space
标记没有位置信息。让我们更仔细地了解单个标记,例如
word
。如前所述,每个标记都表示为列表并遵循这样的模式。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 的座右铭是性能和简单性。标记化是一个复杂的计算操作,并且占用了大量的语法分析时间(~90%),这就是为什么 PostCSS 的标记器看起来很脏,但它已经针对速度进行了优化。任何像类这样的高级构造都可能极大地减慢标记器的速度。
PostCSS 的 Tokenizer 使用某种流/链式 API,您可以在其中向 Parser 公开
nextToken()
方法。通过这种方式,我们为 Parser 提供了一个简洁的界面,并且通过仅存储少数几个标记(而不是整个标记列表)来减少内存使用量。 -
Parser
lib/parse.js
,lib/parser.js
Parser 是负责对传入 CSS 进行 语法分析 的主要结构。Parser 会生成一个称为 抽象语法树 (AST) 的结构,然后插件可以稍后对其进行转换。
Parser 与 Tokenizer 共同工作,并对标记进行操作,而不是源字符串,因为这将是一个非常低效的操作。
它主要使用 Tokenizer 提供的
nextToken
和back
方法来获取单个或多个标记,然后构建称为Node
的 AST 部分。PostCSS 可以生成多种 Node 类型,但它们全部继承自基础 Node 类。
-
Processor
lib/processor.js
Processor 是一个非常简单的结构,用于初始化插件和运行语法转换
它仅公开少数几个公共 API 方法。可以在 API 上找到它们的说明
-
Stringifier
lib/stringify.js
,lib/stringifier.js
Stringifier 是一个基础类,用于将修改后的 AST 转换为纯 CSS 字符串。Stringifier 从提供的 Node 开始遍历 AST,并通过调用相应的方法生成它的原始字符串表示形式。
最基本的方法是
Stringifier.stringify
,它接受初始 Node 和分号指示符。您可以通过查看 stringifier.js 了解更多信息
API 参考
可以在 此处 找到更具描述性的 API 文档