234 lines
6.5 KiB
JavaScript
234 lines
6.5 KiB
JavaScript
import postcss, { list } from 'postcss';
|
|
|
|
// clean the raws of the node
|
|
var cleanNode = (function (node) {
|
|
node.raws = Object.assign(node.raws.between ? { between: node.raws.between } : {}, node.raws.semicolon ? { semicolon: true } : {}, node.raws.important ? { important: node.raws.important } : {});
|
|
|
|
return node;
|
|
});
|
|
|
|
var comma = list.comma;
|
|
|
|
// merge params
|
|
|
|
var mergeParams = (function (fromParams, toParams) {
|
|
return comma(fromParams).map(function (params1) {
|
|
return comma(toParams).map(function (params2) {
|
|
return params1 + ' and ' + params2;
|
|
}).join(', ');
|
|
}).join(', ');
|
|
});
|
|
|
|
// move nodes after the current node into a cloned parent node
|
|
var transformAfterNodes = (function (node) {
|
|
// affected nodes after the current node
|
|
var affectedNodes = node.parent.nodes.slice(node.parent.nodes.indexOf(node) + 1).map(cleanNode);
|
|
|
|
if (affectedNodes.length) {
|
|
// insert an empty parent clone after the parent
|
|
var afterParent = cleanNode(node.parent.clone()).removeAll();
|
|
|
|
node.parent.after(afterParent);
|
|
|
|
// append the affected nodes to the empty parent clone
|
|
afterParent.append(affectedNodes);
|
|
|
|
return afterParent;
|
|
}
|
|
|
|
return undefined;
|
|
});
|
|
|
|
// transform a bubbling atrule (e.g. @document, @media, @supports)
|
|
var transformBubblingAtrule = (function (node) {
|
|
// clean node
|
|
cleanNode(node);
|
|
|
|
// affected nodes after the current node moved into a cloned parent node
|
|
transformAfterNodes(node);
|
|
|
|
// inner nodes within the current node
|
|
var innerNodes = node.nodes.slice(0).map(cleanNode);
|
|
|
|
// prepend an empty parent clone to the node
|
|
var parentCloneForNodesWithinAtrule = cleanNode(node.parent.clone()).removeAll();
|
|
|
|
node.prepend(parentCloneForNodesWithinAtrule);
|
|
|
|
// append the inner nodes to the empty parent clone
|
|
parentCloneForNodesWithinAtrule.append(innerNodes);
|
|
|
|
// swap semicolon raws
|
|
var semicolon = node.raws.semicolon;
|
|
|
|
node.raws.semicolon = node.parent.raws.semicolon;
|
|
node.parent.raws.semicolon = semicolon;
|
|
|
|
// move the node after the parent
|
|
var parent = node.parent.after(node);
|
|
|
|
if (!parent.nodes.length) {
|
|
// conditionally remove the original empty parent
|
|
parent.remove();
|
|
}
|
|
|
|
// if the node and the parent are both media atrules
|
|
if (node.parent.type === 'atrule' && node.name === node.parent.name) {
|
|
// affected nodes after the current node moved into a cloned parent node
|
|
transformAfterNodes(node);
|
|
|
|
// merge media params
|
|
node.params = mergeParams(node.parent.params, node.params);
|
|
|
|
// move the node after the parent
|
|
var subparent = node.parent.after(node);
|
|
|
|
if (!subparent.nodes.length) {
|
|
// conditionally remove the original empty parent
|
|
subparent.remove();
|
|
}
|
|
}
|
|
});
|
|
|
|
// whether the node is a bubbling atrule (e.g. @document, @media, @supports)
|
|
var test = function test(node) {
|
|
return node.type === 'atrule' && ['document', 'media', 'supports'].indexOf(node.name) !== -1 && node.parent && node.parent.type === 'rule';
|
|
};
|
|
|
|
// return the closest rule
|
|
var getClosestRule = (function (node) {
|
|
var selectorParent = node.parent;
|
|
|
|
while (selectorParent && selectorParent.type !== 'rule') {
|
|
selectorParent = selectorParent.parent;
|
|
}
|
|
|
|
return selectorParent;
|
|
});
|
|
|
|
// tooling
|
|
var comma$1 = list.comma;
|
|
|
|
// merge selectors
|
|
|
|
var mergeSelectors = (function (fromSelectors, toSelectors) {
|
|
return (typeof fromSelectors === 'string' ? comma$1(fromSelectors) : fromSelectors).reduce(function (selectors, fromSelector) {
|
|
return selectors.concat((typeof toSelectors === 'string' ? comma$1(toSelectors) : toSelectors).map(function (toSelector) {
|
|
return toSelector.replace(/&/g, fromSelector);
|
|
}));
|
|
}, []);
|
|
});
|
|
|
|
var comma$2 = list.comma;
|
|
|
|
// transform a nesting atrule (e.g. @nest .something &)
|
|
|
|
var transformNestingAtRule = (function (node) {
|
|
// clean node and child nodes
|
|
cleanNode(node).nodes.forEach(cleanNode);
|
|
|
|
// affected nodes after the current node moved into a cloned parent node
|
|
var afterParent = transformAfterNodes(node);
|
|
|
|
// get the closest rule
|
|
var selectorParent = getClosestRule(node);
|
|
|
|
// clone of the atrule as a rule
|
|
var rule = postcss.rule({
|
|
// merge selectors
|
|
selectors: mergeSelectors(selectorParent && selectorParent.selectors || '', node.params),
|
|
source: node.source
|
|
});
|
|
|
|
// clone atrule semicolon raws
|
|
rule.raws = node.raws.semicolon ? { semicolon: true } : {};
|
|
|
|
// move the clone after the parent
|
|
var parent = node.parent.after(rule);
|
|
|
|
// remove the original node
|
|
node.remove();
|
|
|
|
// move child nodes into the clone
|
|
rule.append(node.nodes);
|
|
|
|
if (!parent.nodes.length) {
|
|
// conditionally remove the original empty parent
|
|
parent.remove();
|
|
}
|
|
|
|
// if the next sibling shares the same selector
|
|
if (afterParent && afterParent.selector === rule.selector) {
|
|
rule.append(afterParent.nodes);
|
|
|
|
afterParent.remove();
|
|
}
|
|
|
|
// if the previous sibling shares the same selector
|
|
if (parent.parent && parent.next() === rule && parent.selector === rule.selector) {
|
|
parent.append(rule.nodes);
|
|
|
|
rule.remove();
|
|
|
|
return parent;
|
|
}
|
|
|
|
return rule;
|
|
});
|
|
|
|
// whether the node is a nesting atrule (e.g. @nest .something &)
|
|
var test$1 = function test(node) {
|
|
return node.type === 'atrule' && node.name === 'nest' && node.parent && node.parent.type === 'rule' && comma$2(node.params).every(function (selector) {
|
|
return selector.split('&').length === 2 && /&([^\w-|]|$)/.test(selector);
|
|
});
|
|
};
|
|
|
|
// transform a nesting rule (e.g. &.something)
|
|
var transformNestingRule = (function (node) {
|
|
// clean node and child nodes
|
|
cleanNode(node).nodes.forEach(cleanNode);
|
|
|
|
// move nodes after the current node into a cloned parent node
|
|
transformAfterNodes(node);
|
|
|
|
// merge selectors
|
|
node.selectors = mergeSelectors(node.parent.selectors, node.selectors);
|
|
|
|
// move the node after the parent
|
|
var parent = node.parent.after(node);
|
|
|
|
if (!parent.nodes.length) {
|
|
// conditionally remove the original empty parent
|
|
parent.remove();
|
|
}
|
|
});
|
|
|
|
// whether the node is a nesting rule (e.g. &.something)
|
|
var test$2 = function test(node) {
|
|
return node.type === 'rule' && node.parent && node.parent.type === 'rule' && node.selectors.every(function (selector) {
|
|
return selector.trim().lastIndexOf('&') === 0 && /^&([^\w-|]|$)/.test(selector);
|
|
});
|
|
};
|
|
|
|
// conditionally transform a nesting rule
|
|
var transform = (function (node) {
|
|
if (test(node)) {
|
|
// conditionally transform a bubbling atrule
|
|
transformBubblingAtrule(node);
|
|
} else if (test$1(node)) {
|
|
// conditionally transform a nesting atrule
|
|
transformNestingAtRule(node);
|
|
} else if (test$2(node)) {
|
|
// conditionally transform a nesting rule
|
|
transformNestingRule(node);
|
|
}
|
|
});
|
|
|
|
var index = postcss.plugin('postcss-nesting', function () {
|
|
return function (root) {
|
|
return root.walk(transform);
|
|
};
|
|
});
|
|
|
|
export default index;
|