zoukankan      html  css  js  c++  java
  • postcss

    postcss

    workflow

    wokflow

    PostCSS parses CSS to the tree of nodes (we call it AST). This tree may content:

    • Root: node of the top of the tree, which represent CSS file.
    • AtRule: statements begin with @ like @charset "UTF-8" or @media (screen) {}.
    • Rule: selector with declaration inside. For instance input, button {}.
    • Declaration: key-value pair like color: black;
    • Comment: stand-alone comment. Comments inside selectors, at-rule parameters and values are stored in node’s raws property.

    Core Structures

    • Tokenizer lib/tokenize.js

      Tokenizer (aka Lexer) plays important role in syntax analysis.

      It accepts CSS string and returns a list of tokens.

      Token is a simple structure that describes some part of syntax like at-rule, comment or word. It can also contain positional information for more descriptive errors.

      For example, if we consider following CSS

      .className { color: #FFF; }
      

      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]
      ]
      

      As you can see from the example above a single token represented as a list and also space token doesn't have positional information.

      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
      ]
      

      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 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.

    • Parser lib/parse.js, lib/parser.js

      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.

      Parser works in common with Tokenizer and operates over tokens, not source string, as it would be a very inefficient operation.

      It uses mostly nextToken and back methods provided by Tokenizer for obtaining single or multiple tokens and then construct part of AST called Node.

      There are multiple Node types that PostCSS could produce but all of them inherit from base Node class.

    • Processor lib/processor.js

      Processor is a very plain structure that initializes plugins and runs syntax transformations

      It exposes only a few public API methods. Description of them could be found on API

    • Stringifier lib/stringify.js, lib/stringifier.js

      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.

      The most essential method is Stringifier.stringify
      that accepts initial Node and semicolon indicator.
      You can learn more by checking stringifier.js

    how to use

    new plugin

    module.exports = (opts = { }) => {
    
      // Work with options here
    
      return {
        postcssPlugin: 'PLUGIN_NAME',
        /*
        Root (root, postcss) {
          // Transform CSS AST here
        }
        */
    
        /*
        Declaration (decl, postcss) {
          // The faster way to find Declaration node
        }
        */
    
        /*
        Declaration: {
          color: (decl, postcss) {
            // The fastest way find Declaration node if you know property name
          }
        }
        */
      }
    }
    module.exports.postcss = true
    

    use plugin

    await postcss([plugin]).process('a { color: black }', { from })
    

    源码阅读

    function postcss (...plugins) {
      if (plugins.length === 1 && Array.isArray(plugins[0])) {
        plugins = plugins[0]
      }
      return new Processor(plugins, postcss)
    }
    
    
    class Processor {
      constructor (plugins = []) {
        this.version = '8.1.10'
        this.plugins = this.normalize(plugins)
      }
    
      normalize (plugins) {
        let normalized = []
    
        ......
    
        // 添加插件
        normalized.push(i)
    
        ......
    
        return normalized
      }
    
      .....
    
      process (css, opts = {}) {
        ......
        return new LazyResult(this, css, opts)
      }
    
    }
    
    class LazyResult {
      constructor (processor, css, opts) {
        this.stringified = false
        this.processed = false
    
        let root
    
        ......
    
        // parser css 
        root = parser(css, opts)
    
        ......
    
        this.result = new Result(processor, root, opts)
        this.helpers = { ...postcss, result: this.result, postcss }
    
        // 获取plugin代码
        this.plugins = this.processor.plugins.map(plugin => {
          if (typeof plugin === 'object' && plugin.prepare) {
            return { ...plugin, ...plugin.prepare(this.result) }
          } else {
            return plugin
          }
        })
      }
    
    
      then (onFulfilled, onRejected) {
        .... 
    
        // 执行
        return this.async().then(onFulfilled, onRejected)
      }
    
      async () {
        if (this.error) return Promise.reject(this.error)
        if (this.processed) return Promise.resolve(this.result)
        if (!this.processing) {
          this.processing = this.runAsync()
        }
        return this.processing
      }
    
      stringify () {
        if (this.error) throw this.error
        if (this.stringified) return this.result
        this.stringified = true
    
        this.sync()
    
        let opts = this.result.opts
        let str = stringify
        if (opts.syntax) str = opts.syntax.stringify
        if (opts.stringifier) str = opts.stringifier
        if (str.stringify) str = str.stringify
    
        let map = new MapGenerator(str, this.result.root, this.result.opts)
        let data = map.generate()
        this.result.css = data[0]
        this.result.map = data[1]
    
        return this.result
      }
    
    
      async runAsync () {
        this.plugin = 0
        for (let i = 0; i < this.plugins.length; i++) {
          let plugin = this.plugins[i]
          
          // 执行插件代码
          let promise = this.runOnRoot(plugin)
          if (isPromise(promise)) {
            try {
              await promise
            } catch (error) {
              throw this.handleError(error)
            }
          }
        }
    
        this.prepareVisitors()
    
        this.processed = true
    
        // 返回结果
        return this.stringify()
      }
    
      ......
    }
    
    
    function parse (css, opts) {
      let input = new Input(css, opts)
      let parser = new Parser(input)
    
      ......
      //css 解析
      parser.parse()
    
      //第一步解析为token
      //第二步根据token来解析css
     
      ......
    
      return parser.root
    }
    
    

    autoprefixer

    PostCSS plugin to parse CSS and add vendor prefixes to CSS rules using values from Can I Use.

    源代码

    
    module.exports = (...reqs) => {
    
      ......
    
      // 返回 postcss 插件类型
      return {
        postcssPlugin: 'autoprefixer',
    
        prepare (result) {
          let prefixes = loadPrefixes({
            from: result.opts.from,
            env: options.env
          })
    
          return {
            Once (root) {
              timeCapsule(result, prefixes)
              if (options.remove !== false) {
                prefixes.processor.remove(root, result)
              }
              if (options.add !== false) {
                prefixes.processor.add(root, result)
              }
            }
          }
        },
    
        info (opts) {
          opts = opts || {}
          opts.from = opts.from || process.cwd()
          return info(loadPrefixes(opts))
        },
    
        options,
        browsers: reqs
      }
    }
    
  • 相关阅读:
    EasyUI-datagrid中load,reload,loadData方法的区别
    CAD ObjectARX扩展工具的源码(二)
    CAD ObjectARX扩展工具的源码(一)
    ObjectARX常用类和函数
    acdb类杂记
    ARX错误1 LINK : fatal error LNK1104: 无法打开文件“D:win10Desktop123x64Debugxxx.arx”
    关键功能函数是 (VLAX-Add-Cmd)。
    ObjectARX_2020函数收集
    得到ARX程序当前的路径
    objectarx向量的用法2
  • 原文地址:https://www.cnblogs.com/SLchuck/p/14041560.html
Copyright © 2011-2022 走看看