Here is a short reproducible example for the behavior I want to achieve:
var postcss = require('postcss');
var plugin = postcss.plugin('keepme', () => (root) => {
root.walkAtRules(/keepme|removeme/, (atRule) => {
if (atRule.name === 'keepme') {
atRule.replaceWith(atRule.nodes);
} else {
atRule.remove();
}
});
});
postcss([plugin]).process(`
@keepme {
@removeme {
.selector { color: red; }
}
}
`).then(result => console.log(result.css));
Given the input
@keepme {
@removeme {
.selector { color: red; }
}
}
I would like this to return an empty string.
Instead, I receive the output
@removeme {
.selector { color: red; }
}
The @keepme
rule seems to correctly replace itself with its nodes (which is then not executed?).
I'm not sure how to go about this. Any suggestions?
replaceWith
is implemented like this:
/**
* Inserts node(s) before the current node and removes the current node.
*
* @param {...Node} nodes - node(s) to replace current one
*
* @example
* if ( atrule.name == 'mixin' ) {
* atrule.replaceWith(mixinRules[atrule.params]);
* }
*
* @return {Node} current node to methods chain
*/
replaceWith(...nodes) {
if (this.parent) {
for (let node of nodes) {
this.parent.insertBefore(this, node);
}
this.remove();
}
return this;
}
Given the list of at-rules to traverse:
The rule-walker keeps an index of the currently-inspected rule. At index 1 it finds keepme
.
keepme.replaceWith(removeme)
will insert removeme
before keepme
, then continue walking the ast...
Since removeme
was moved ahead, the walker has moved past it, and will not execute that rule.
The fix is to modify replaceWith
so it will move the child nodes after the inspected rule.
root.walkAtRules(/keepme|removeme/, (atRule) => {
if (atRule.name === 'keepme') {
if (atRule.parent) {
for (let node of atRule.nodes.reverse()) {
atRule.parent.insertAfter(atRule, node);
}
}
}
atRule.remove();
});
This works as intended: Interactive fiddle