PostCSS 架构(PostCSS Architecture)
¥PostCSS Architecture
PostCSS 架构的总体概述。对于每个希望为核心做出贡献或更好地理解该工具的人来说,它都是有用的。
¥General overview of the PostCSS architecture. It can be useful for everyone who wishes to contribute to the core or develop a better understanding of the tool.
目录
¥Table of Contents
概述(Overview)
¥Overview
本节描述 PostCSS 背后的想法
¥This section describes ideas lying behind PostCSS
在深入探讨 PostCSS 的开发之前,我们先简要介绍一下什么是 PostCSS,什么不是。
¥Before diving deeper into the development of PostCSS let's briefly describe what is PostCSS and what is not.
PostCSS
-
不是像
Sass
或Less
这样的样式预处理器。¥is NOT a style preprocessor like
Sass
orLess
.它没有定义自定义语法和语义,它实际上不是一种语言。PostCSS 与 CSS 一起使用,并且可以轻松地与上述工具集成。也就是说,任何有效的 CSS 都可以由 PostCSS 处理。
¥It does not define a custom syntax and semantics, it's not actually a language. PostCSS works with CSS and can be easily integrated with the tools described above. That being said any valid CSS can be processed by PostCSS.
-
是 CSS 语法转换的工具
¥is a tool for CSS syntax transformations
它允许你定义自定义 CSS 之类的语法,这些语法可以被插件理解和转换。话虽这么说,PostCSS 并不是严格意义上的 CSS 规范,而是 CSS 的语法定义方式。通过这种方式,你可以定义自定义语法结构,例如 @ 规则,这对于围绕 PostCSS 构建工具非常有帮助。PostCSS 扮演着构建 CSS 操作优秀工具的框架的角色。
¥It allows you to define custom CSS like syntax that could be understandable and transformed by plugins. That being said PostCSS is not strictly about CSS spec but about syntax definition manner of CSS. In such a way you can define custom syntax constructs like at-rule, that could be very helpful for tools build around PostCSS. PostCSS plays the role of a framework for building outstanding tools for CSS manipulations.
-
是 CSS 生态系统的重要参与者
¥is a big player in CSS ecosystem
PostCSS 生态系统上构建了大量迷人的工具,例如
Autoprefixer
、Stylelint
、CSSnano
。你很有可能已经隐式使用了它,只需检查你的node_modules
😀¥A Large amount of lovely tools like
Autoprefixer
,Stylelint
,CSSnano
were built on PostCSS ecosystem. There is a big chance that you already use it implicitly, just check yournode_modules
:smiley:
工作流程(Workflow)
¥Workflow
这是整个 PostCSS 工作流程的高级概述
¥This is a high-level overview of the whole PostCSS workflow
从上图中可以看出,PostCSS 架构非常简单,但其中的某些部分可能会被误解。
¥As you can see from the diagram above, PostCSS architecture is pretty straightforward but some parts of it could be misunderstood.
你可以看到一个叫做 Parser 的部分,这个结构将在稍后详细描述,现在把它想象成一个可以理解你的 CSS 语法并创建它的对象表示的结构。
¥You can see a part called Parser, this construct will be described in details later on, just for now think about it as a structure that can understand your CSS like syntax and create an object representation of it.
话虽如此,编写解析器的方法很少。
¥That being said, there are few ways to write a parser.
-
写入一个包含字符串到 AST 转换的文件
¥Write a single file with string to AST transformation
这种方式很流行,比如 Rework 分析器 就是用这种方式写的。但如果代码库很大,代码就会变得难以阅读并且速度相当慢。
¥This method is quite popular, for example, the Rework analyzer was written in this style. But with a large code base, the code becomes hard to read and pretty slow.
-
将其拆分为词法分析/解析步骤(源字符串 → 标记 → AST)
¥Split it into lexical analysis/parsing steps (source string → tokens → AST)
这是我们在 PostCSS 中的做法,也是最流行的一种。很多像
@babel/parser
(Babel 背后的解析器)、CSSTree
这样的解析器都是这样写的。将标记化与解析步骤分开的主要原因是性能和抽象复杂性。¥This is the way of how we do it in PostCSS and also the most popular one. A lot of parsers like
@babel/parser
(parser behind Babel),CSSTree
were written in such way. The main reasons to separate tokenization from parsing steps are performance and abstracting complexity.
我们来思考一下为什么第二种方式更能满足我们的需求。
¥Let's think about why the second way is better for our needs.
首先,因为字符串到标记步骤比解析步骤花费更多时间。我们对大型源字符串进行操作并逐个字符地处理它,这就是为什么它在性能方面是非常低效的操作,我们应该只执行一次。
¥First of all, because string to tokens step takes more time than parsing step. We operate on large source string and process it char by char, this is why it is very inefficient operation in terms of performance and we should perform it only once.
但从其他侧令牌到 AST 的转换在逻辑上更加复杂,因此通过这种分离,我们可以编写非常快的分词器(但有时难以阅读代码)和易于阅读(但速度慢)的解析器。
¥But from other side tokens to AST transformation is logically more complex so with such separation we could write very fast tokenizer (but from this comes sometimes hard to read code) and easy to read (but slow) parser.
总结起来分为两个步骤可以提高性能和代码可读性。
¥Summing it up splitting into two steps improve performance and code readability.
现在让我们更仔细地了解在 PostCSS 工作流程中起主要作用的结构。
¥So now let's look more closely on structures that play the main role in PostCSS workflow.
核心结构(Core Structures)
¥Core Structures
-
分词器
lib/tokenize.js
(Tokenizerlib/tokenize.js
)¥Tokenizer
lib/tokenize.js
分词器(又名 Lexer)在语法分析中发挥着重要作用。
¥Tokenizer (aka Lexer) plays important role in syntax analysis.
它接受 CSS 字符串并返回标记列表。
¥It accepts CSS string and returns a list of tokens.
令牌是一个简单的结构,描述了语法的某些部分,如
at-rule
、comment
或word
。它还可以包含更多描述性错误的位置信息。¥Token is a simple structure that describes some part of syntax like
at-rule
,comment
orword
. It can also contain positional information for more descriptive errors.例如,如果我们考虑遵循 CSS
¥For example, if we consider following CSS
.className { color: #FFF; }
PostCSS 的相应标记将是
¥corresponding tokens from PostCSS will be
[ ["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
令牌没有位置信息。¥As you can see from the example above a single token represented as a list and also
space
token doesn't have positional information.让我们更仔细地看看像
word
这样的单一令牌。正如所说,每个令牌都代表一个列表并遵循这种模式。¥Let's look more closely on single token like
word
. As it was said each token represented as a list and follow such pattern.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 的座右铭是性能和简单性。Tokenization 是一个复杂的计算操作,需要大量的语法分析时间(~90%),这就是为什么 PostCSS 的 Tokenizer 看起来很脏,但它针对速度进行了优化。任何高级构造(例如类)都可能会大大减慢分词器的速度。
¥There are many patterns how tokenization could be done, PostCSS motto is performance and simplicity. Tokenization is a complex computing operation and takes a large amount of syntax analysis time ( ~90% ), that why PostCSS' Tokenizer looks dirty but it was optimized for speed. Any high-level constructs like classes could dramatically slow down tokenizer.
PostCSS 的 Tokenizer 使用某种流/链接 API,你可以在其中向解析器公开
nextToken()
方法。通过这种方式,我们为解析器提供了一个干净的接口,并通过仅存储几个标记而不是整个标记列表来减少内存使用。¥PostCSS' Tokenizer uses some sort of streaming/chaining API where you expose
nextToken()
method to Parser. In this manner, we provide a clean interface for Parser and reduce memory usage by storing only a few tokens and not the whole list of tokens. -
解析器
lib/parse.js
、lib/parser.js
(Parserlib/parse.js
,lib/parser.js
)¥Parser
lib/parse.js
,lib/parser.js
Parser 是负责传入 CSS 的 语法分析 的主要结构。解析器生成一个名为 抽象语法树 (AST) 的结构,稍后可以通过插件对其进行转换。
¥Parser is the main structure responsible for syntax analysis of incoming CSS. Parser produces a structure called Abstract Syntax Tree (AST) that could then be transformed by plugins later on.
解析器与 Tokenizer 共同工作,并且对标记进行操作,而不是对源字符串进行操作,因为这将是一个非常低效的操作。
¥Parser works in common with Tokenizer and operates over tokens, not source string, as it would be a very inefficient operation.
它主要使用 Tokenizer 提供的
nextToken
和back
方法来获取单个或多个 token,然后构造 AST 的一部分,称为Node
。¥It uses mostly
nextToken
andback
methods provided by Tokenizer for obtaining single or multiple tokens and then construct part of AST calledNode
.PostCSS 可以生成多种节点类型,但它们都继承自基本节点 class。
¥There are multiple Node types that PostCSS could produce but all of them inherit from base Node class.
-
处理器
lib/processor.js
(Processorlib/processor.js
)¥Processor
lib/processor.js
处理器是一个非常简单的结构,用于初始化插件并运行语法转换
¥Processor is a very plain structure that initializes plugins and runs syntax transformations
它仅公开一些公共 API 方法。它们的描述可以在 API 上找到
¥It exposes only a few public API methods. Description of them could be found on API
-
字符串化器
lib/stringify.js
、lib/stringifier.js
(Stringifierlib/stringify.js
,lib/stringifier.js
)¥Stringifier
lib/stringify.js
,lib/stringifier.js
Stringifier 是一个基类,它将修改后的 AST 转换为纯 CSS 字符串。Stringifier 从提供的 Node 开始遍历 AST,并调用相应的方法生成它的原始字符串表示形式。
¥Stringifier is a base class that translates modified AST to pure CSS string. Stringifier traverses AST starting from provided Node and generates a raw string representation of it calling corresponding methods.
最重要的方法是
Stringifier.stringify
,它接受初始节点和分号指示符。你可以通过检查 stringifier.js 了解更多信息¥The most essential method is
Stringifier.stringify
that accepts initial Node and semicolon indicator. You can learn more by checking stringifier.js
API 参考(API Reference)
¥API Reference
此处 可以找到更多描述性 API 文档
¥More descriptive API documentation could be found here