阿西河

所有教程

公众号
🌙
阿西河前端的公众号

我的收藏

    最近访问  (文章)

      教程列表

      抓包专区
      测试专区

      PostCSS 架构

      PostCSS 架构

      PostCSS 体系结构的概述。对于希望为核心做出贡献或更好地了解该工具的每个人来说,它都是有用的。

      目录

      总览

      本节描述了 PostCSS 背后的想法

      在深入探讨 PostCSS 的开发之前,我们先简要介绍一下什么是 PostCSS,什么不是 PostCSS。

      PostCSS

        • 不是样式预处理器 , 例如 Sass or Less.*

          它没有定义自定义语法和语义,实际上不是语言。PostCSS 可与 CSS 一起使用,并且可以轻松地与上述工具集成。话虽如此,任何有效的 CSS 都可以由 PostCSS 处理。

      • 是 CSS 语法转换的工具

        它允许您定义定制 CSS 之类的语法,插件可以理解和转换它们。话虽这么说,PostCSS 并不是严格地关于 CSS 规范,而是关于 CSS 的语法定义方式。通过这种方式,您可以定义自定义语法结构(如 at-rule),这对于围绕 PostCSS 构建工具非常有用。PostCSS 扮演了一个框架的角色,可为 CSS 操作构建出色的工具。

      • 在 CSS 生态系统中扮演重要角色

        可爱的工具,大量的喜欢 Autoprefixer, Stylelint, CSSnano 是建立在 PostCSS 生态系统。您很有可能已经隐式使用它,只需检查您的 node_modules :smiley:

      工作流程

      这是整个 PostCSS 工作流程的高级概述

      workflow

      从上图可以看到,PostCSS 体系结构非常简单,但是其中的某些部分可能会被误解。

      您可以看到一个名为 Parser 的部分,稍后将对此结构进行详细描述,仅现在将其视为可以理解 CSS 语法并为其创建对象表示的结构。

      话虽如此,编写解析器的方法很少。

      • 将带有字符串的单个文件写入 AST 转换

        这种方法非常流行,例如 Rework analyzer 就是用这种风格编写的。但是,由于代码库很大,所以代码变得难以阅读,而且速度很慢。

      • 将其分为词法分析 / 解析步骤(源字符串→标记→AST)

        这是我们在 PostCSS 中也是最受欢迎的方法。很多解析器一样 @babel/parser (parser behind Babel), CSSTree 写于这样的方式。将令牌化与解析步骤分开的主要原因是性能和抽象复杂性。

      让我们考虑一下为什么第二种方法更好地满足了我们的需求。

      首先,因为从字符串到令牌的步骤比解析步骤花费更多的时间。我们对大型源字符串进行操作,然后逐个字符处理它,这就是为什么它在性能方面效率很低的原因,我们只应该执行一次。

      但是从另一端令牌到 AST 转换在逻辑上更加复杂,因此通过这种分离,我们可以编写非常快速的令牌生成器(但有时很难读取代码)和易于读取(但速度较慢)的解析器。

      将其总结为两个步骤,可以提高性能和代码可读性。

      因此,现在让我们更仔细地研究在 PostCSS 工作流程中起主要作用的结构。

      核心结构

      • 标记器 ( lib/tokenize.es6 )

        标记器(又名 Lexer)在语法分析中起着重要的作用。

        它接受 CSS 字符串并返回令牌列表。

        令牌是一种结构简单,描述句法等的某些部分 at-rule``,commentword 。它还可以包含位置信息,以获取更多描述性错误。

        例如,如果我们考虑遵循 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 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.es6, lib/parser.es6 )

        解析器是负责传入 CSS 语法分析的主要结构。解析器生成一种称为抽象语法树(AST)的结构,以后可以通过插件对其进行转换。

        解析器与 Tokenizer 共同使用,并且对令牌(而不是源字符串)进行操作,因为这将是非常低效的操作。

        它主要使用 Tokenizer 提供的方法 nextToken 和 backTokenizer 提供的方法来获取单个或多个令牌,然后构造称为的 AST 的一部分 Node。

        PostCSS 可以产生多种 Node 类型,但是它们都继承自 Node 类。

      • 处理器 ( lib/processor.es6 )

        Processor 是一个非常简单的结构,用于初始化插件并运行语法转换。插件只是在 postcss.plugin 调用中注册的功能。

        它仅公开了一些公共 API 方法。可以在 api.postcss.org/Processor 上找到它们的描述。

      • Stringifier ( lib/stringify.es6, lib/stringifier.es6 )

        Stringifier 是将修改后的 AST 转换为纯 CSS 字符串的基类。Stringifier 从提供的 Node 开始遍历 AST,并生成其原始字符串表示形式并调用相应的方法。

        最基本的方法是 Stringifier.stringify 接受初始 Node 和分号指示符。您可以通过检查 stringifier.es6 了解更多信息

      API 参考

      可在 这里 找到更多描述性的API文档

      目录
      目录