PostCSS:如何根据当前规则中的声明有条件地添加新规则?

PostCSS: How to conditionally add a new rule based on declaration in current rule?

你好,我一直在尝试 PostCSS,并且在编写一个插件时遇到了一些困难,该插件在发现某些 CSS 属性时会附加一条新规则。

我想要达到的目标...

开始 CSS:

.selector {
    color: red;
    not-yet-standard-property-a: 10px;
    not-yet-standard-property-b: 20px;
}

PostCSS插件后:

.selector {
    color: red;
    not-yet-standard-property-a: 10px;
    not-yet-standard-property-b: 20px;
}

.ie .selector {
    standard-property-a: 10px;
    standard-property-b: 20px;
}

每当我看到其中一个时,我很容易添加新规则not-yet-standard-property-*

return function (css) {
    css.walkRules(function (rule) {
        rule.walkDecls('not-yet-standard-property-a', function (decl) {
            css.insertAfter(rule, '.ie ' + rule.selector + '{ standard-property-a: '+ decl.value +' }');
        });
        rule.walkDecls('not-yet-standard-property-b', function (decl) {
            css.insertAfter(rule, '.ie ' + rule.selector + '{ standard-property-b: '+ decl.value +' }');
        });
        rule.walkDecls('not-yet-standard-property-c', function (decl) {
            css.insertAfter(rule, '.ie ' + rule.selector + '{ standard-property-c: '+ decl.value +' }');
        });
    });
}

但是输出并不理想……

.selector {
    color: red;
    not-yet-standard-property-a: 10px;
    not-yet-standard-property-b: 20px;
    not-yet-standard-property-c: 53px;
}
.ie .selector {
    standard-property-c: 53px;
}
.ie .selector {
    standard-property-b: 20px;
}
.ie .selector {
    standard-property-a: 10px;
}

理想情况下,新规则只会在遍历整个规则后添加一次,但 PostCSS 似乎不允许在 walkRules 函数中使用条件,所以我不确定如何阻止它为它看到的每个规则创建新规则。

我已经链接到上述代码的演示。任何关于更好架构的建议将不胜感激,就像我之前说过的,我对此很陌生。谢谢!

Demo

编辑:已解决。非常感谢 Andrey Sitnik!我已将他的回答标记为正确,因为其中的概念正是我所需要的。对于那些寻找完整解决方案的人,请参阅以下代码(和演示!):

function isNotYetStandardProp (propName) {
    if (propName.indexOf('not-yet-standard-property-') > -1) {
      return true;
    }
    else {
      return false;
    }
}

function transformPropName (propName) {
    return propName.replace("not-yet-", "");
}

var skipNext = false;
var convertedProps = [];
var newRule;
var newNode;

return function (css) {

    css.walkRules(function(rule) {
        // The skipNext flag is used to prevent walkRules from walking onto any
        // newly created rules and recursively creating new rules based on the
        // newly added rule.
        if (!skipNext) {
            // Reset to remove values from last loop
            convertedProps = [];
            newRule = null;
            newNode = null;

            // Search through nodes of current rule and build up array
            // containing each property that needs to change.
            rule.nodes.filter(function (node) {
                if ((node.type === 'decl') && isNotYetStandardProp(node.prop)) {
                    newNode = node.clone();
                    newNode.prop = transformPropName(node.prop); 
                    convertedProps.push(newNode);
                }
            });

            // If there were properties to convert in this rule create a new
            // rule to put them in.
            if (convertedProps.length > 0) {
                newRule = rule.cloneAfter({ selector: '.ie ' + rule.selector });
                newRule.removeAll().append(convertedProps);
                skipNext = true;
            }
        }
        else {
            // We only want to skipNext once.
            skipNext = false;
        }
    })

}

Demo of solution

你不需要 walkRules 里面的 walkDecls,只需使用 rule.nodes:

css.walkRules(rule => {
    const nonStandard = rule.nodes.filter(node => {
        return if node.type === 'decl' && checkPropName(node.prop);
    });
    if ( nonStandard.length > 0 ) {
        const clone = rule.cloneAfter({ selector: '.ie ' + rule.selector });
        clone.append(nonStandard);
    }
})