插件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)
}
})
})
}
}