postcss-css-variables
Version:
PostCSS plugin to transform CSS Custom Properties(CSS variables) syntax into a static representation
180 lines (130 loc) • 6.38 kB
JavaScript
var resolveValue = require('./resolve-value');
var generateScopeList = require('./generate-scope-list');
var gatherVariableDependencies = require('./gather-variable-dependencies');
var isUnderScope = require('./is-under-scope');
var isNodeUnderScope = require('./is-node-under-scope');
var shallowCloneNode = require('./shallow-clone-node');
var findNodeAncestorWithSelector = require('./find-node-ancestor-with-selector');
var cloneSpliceParentOntoNodeWhen = require('./clone-splice-parent-onto-node-when');
function eachMapItemDependencyOfDecl(variablesUsedList, map, decl, cb) {
// Now find any at-rule declarations that pertains to each rule
// Loop through the variables used
variablesUsedList.forEach(function(variableUsedName) {
// Find anything in the map that corresponds to that variable
gatherVariableDependencies(variablesUsedList, map).deps.forEach(function(mapItem) {
var mimicDecl;
if(mapItem.isUnderAtRule) {
// Get the inner-most selector of the at-rule scope variable declaration we are matching
// Because the inner-most selector will be the same for each branch, we can look at the first one [0] or any of the others
var varDeclScopeList = generateScopeList(mapItem.parent, true);
var innerMostAtRuleSelector = varDeclScopeList[0].slice(-1)[0];
var nodeToSpliceParentOnto = findNodeAncestorWithSelector(innerMostAtRuleSelector, decl.parent);
// Splice on where the selector starts matching the selector inside at-rule
// See: `test/fixtures/cascade-on-nested-rules.css`
var varDeclAtRule = mapItem.parent.parent;
mimicDecl = cloneSpliceParentOntoNodeWhen(decl, varDeclAtRule, function(ancestor) {
return ancestor === nodeToSpliceParentOnto;
});
//console.log('amd og', generateScopeList(decl.parent, true));
//console.log('amd', generateScopeList(mimicDecl.parent, true));
//console.log(generateScopeList(mapItem.parent, true));
//console.log('amd isNodeUnderScope', isNodeUnderScope(mimicDecl.parent, mapItem.parent), mapItem.decl.value);
}
// TODO: use regex from `isUnderScope`
else if(isUnderScope.RE_PSEUDO_SELECTOR.test(mapItem.parent.selector)) {
// Create a detached clone
var ruleClone = shallowCloneNode(decl.parent);
ruleClone.parent = decl.parent.parent;
// Add the declaration to it
mimicDecl = decl.clone();
ruleClone.append(mimicDecl);
var lastPseudoSelectorMatches = mapItem.parent.selector.match(new RegExp(isUnderScope.RE_PSEUDO_SELECTOR.source + '$'));
var lastPseudoSelector = lastPseudoSelectorMatches ? lastPseudoSelectorMatches[2] : '';
ruleClone.selector += lastPseudoSelector;
}
// If it is under the proper scope,
// we need to check because we are iterating over all map entries
if(mimicDecl && isNodeUnderScope(mimicDecl, mapItem.parent, true)) {
cb(mimicDecl, mapItem);
}
});
});
}
// Resolve the decl with the computed value
// Also add in any media queries that change the value as necessary
function resolveDecl(decl, map, /*optional*/shouldPreserve, /*optional*/preserveAtRulesOrder, /*optional*/logResolveValueResult) {
shouldPreserve = (typeof shouldPreserve === "function" ? shouldPreserve(decl) : shouldPreserve) || false;
preserveAtRulesOrder = preserveAtRulesOrder || false;
// Make it chainable
var _logResolveValueResult = function(valueResults) {
if(logResolveValueResult) {
logResolveValueResult(valueResults);
}
return valueResults;
};
// Grab the balue for this declarations
//console.log('resolveDecl 1');
var valueResults = _logResolveValueResult(resolveValue(decl, map));
// Resolve the cascade dependencies
// Now find any at-rule declarations that need to be added below each rule
//console.log('resolveDecl 2');
var previousAtRuleNode;
eachMapItemDependencyOfDecl(valueResults.variablesUsed, map, decl, function(mimicDecl, mapItem) {
var ruleClone = shallowCloneNode(decl.parent);
var declClone = decl.clone();
// Add the declaration to our new rule
ruleClone.append(declClone);
let preserveVariable;
if(typeof shouldPreserve === "function") {
preserveVariable = shouldPreserve(decl);
} else {
preserveVariable = shouldPreserve;
}
if(preserveVariable === true) {
declClone.cloneAfter();
}
// No mangle resolve
declClone.value = _logResolveValueResult(resolveValue(mimicDecl, map, true)).value;
if(mapItem.isUnderAtRule) {
// Create the clean atRule for which we place the declaration under
var atRuleNode = shallowCloneNode(mapItem.parent.parent);
// Add the rule to the atRule
atRuleNode.append(ruleClone);
// Since that atRuleNode can be nested in other atRules, we need to make the appropriate structure
var parentAtRuleNode = atRuleNode;
var currentAtRuleNode = mapItem.parent.parent;
while(currentAtRuleNode.parent.type === 'atrule') {
// Create a new clean clone of that at rule to nest under
var newParentAtRuleNode = shallowCloneNode(currentAtRuleNode.parent);
// Append the old parent
newParentAtRuleNode.append(parentAtRuleNode);
// Then set the new one as the current for next iteration
parentAtRuleNode = newParentAtRuleNode;
currentAtRuleNode = currentAtRuleNode.parent;
}
// Put the first atRuleStructure after the declaration's rule,
// and after that, put them right after the previous one
decl.parent.parent.insertAfter(preserveAtRulesOrder && previousAtRuleNode || decl.parent, parentAtRuleNode);
// Save referance of previous atRuleStructure
previousAtRuleNode = parentAtRuleNode
}
else {
ruleClone.selector = mimicDecl.parent.selector;
// Put the first atRuleStructure after the declaration's rule,
// and after that, put them right after the previous one
decl.parent.parent.insertAfter(preserveAtRulesOrder && previousAtRuleNode || decl.parent, ruleClone);
}
});
// If we are preserving var(...) usage and the value changed meaning it had some
if(shouldPreserve === true && decl.value !== valueResults.value) {
decl.cloneAfter();
}
// Set 'undefined' value as a string to avoid making other plugins down the line unhappy
// See #22
if (valueResults.value === undefined) {
valueResults.value = 'undefined';
}
// Set the new value after we are done dealing with at-rule stuff
decl.value = valueResults.value;
}
module.exports = resolveDecl;