PostCss插件系统设计

100 阅读1分钟

插件DEMO

.my-class {
  color: red;
}

对应的AST如下:

{
  type: 'rule',
  selector: '.my-class',
  nodes: [
    // 声明节点...
  ],
  // 其他属性...
}

module.exports = {
  postcssPlugin: "px-to-rem",
  Rule(rule) {
    // 使用正则表达式只匹配类选择器,并在它们前添加前缀 '.my-prefix-'
    //注意相当于去改ast里的selector属性
    rule.selector = rule.selector.replace(/\.([^\s>+~.]+)/g, '.my-prefix-$1');
  }
  Declaration(decl) {
    // 如果声明的值中包含 'px' 单位
    if (decl.value.includes('px')) {
      // 使用正则表达式匹配 'px' 单位,并将其转换为 'rem' 单位
      decl.value = decl.value.replace(/(\d*\.?\d+)px/g, (match, value) => {
        // 假设基准字体大小为16px
        const baseFontSize = 16;
        const remValue = parseFloat(value) / baseFontSize;
        return `${remValue}rem`;
      });
    }
  }
  
}
module.exports.postcss = true;

插件系统核心代码

AST核心Node节点: Rule、AtRule、Decalaration
流程: css => ast => 插件1 => 插件2 => ..... => stringify => css


class Node {
  constructor() {
    this.raws = {}; // 用于存储节点的原始信息(如空格、分号等)
  }

  toString() {
    throw new Error('toString method must be implemented in derived classes');
  }
}

class Root extends Node {
  constructor() {
    super();
    this.nodes = [];
  }

  toString() {
    return this.nodes.map(node => node.toString()).join('');
  }
}

class Rule extends Node {
  constructor(selector) {
    super();
    this.selector = selector;
    this.nodes = [];
  }
  
  toString() {
    const before = this.raws.before || '';
    const between = this.raws.between || ' ';
    const semicolon = this.raws.semicolon ? ';' : '';
    const after = this.raws.after || '\n';
  
    const declarations = this.nodes.map(node => node.toString()).join('');
  
    return `${before}${this.selector}${between}{${declarations}${semicolon}}${after}`;
  }
}


class Declaration extends Node {
  constructor(prop, value) {
    super();
    this.prop = prop;
    this.value = value;
  }

  toString() {
    const before = this.raws.before || '\n  ';
    const between = this.raws.between || ': ';
    const semicolon = this.raws.semicolon ? ';' : '';
  
    return `${before}${this.prop}${between}${this.value}${semicolon}`;
  }
}



class PostCss {
  contructor(plugins) {
    this.plugins = plugins || []
  }

  parse() {
    this.ast = new Parse(css)
    return this.ast
  }

  stringify(root) {
    let css = '';
    root.walk(node => {
      css += node.toString();
    });
    return css;
  }
  
  process(css) {
    //ast
    let ast = this.parse(css)
    let result = {root: ast}
    //plugins
    this.run(result)
    //stringifer
    let css = this.stringify(result)
    //css
    return this.result.css = css
  }

  run(root) {
      this.plugins.forEach(plugin => {
      // 调用插件的 'Once' 钩子
        if (plugin.Once) {
          plugin.Once(root, this.result);
        }
        root.walk(node => {
          if(node instanceOf Rule && plugin.Rule) {
            plugin.Rule(node, this.result)
          } else if(node instanceOf Declaration && plugin.Declaration) {
            plugin.Declaration(node, this.result)
          } else if(node instanceOf AtRule && plugin.AtRule) {
            plugin.AtRule(node, this.result)
          }
        })
      })
  }
}