postcss-css-variables
Advanced tools
Comparing version
# v0.3.1 - 2015-5-5 | ||
# 0.2.3 - 2015-5-4 | ||
- Large overhaul of code to make it more robust on proper scope resolution. | ||
- Fix #2 | ||
# v0.2.3 - 2015-5-4 | ||
- Add support for CSS4 descendant selector `>>` syntax | ||
# 0.2.2 - 2015-5-1 | ||
# v0.2.2 - 2015-5-1 | ||
- Automatically prefix any variables defined in `options.variables` with `--` (according to CSS custom property syntax). | ||
# 0.2.1 - 2015-4-30 | ||
# v0.2.1 - 2015-4-30 | ||
@@ -19,4 +23,4 @@ - Added support for descendant selector nesting instead of just physical space nesting | ||
# 0.1.0 - 2015-4-29 | ||
# v0.1.0 - 2015-4-29 | ||
- First release |
299
index.js
// PostCSS CSS Variables (postcss-css-variables) | ||
// v0.2.3 | ||
// v0.3.1 | ||
// | ||
@@ -30,2 +30,92 @@ // https://github.com/MadLittleMods/postcss-css-variables | ||
// Splice on a parent scope onto a node | ||
// And return a detached clone | ||
function cloneSpliceParentOntoNodeWhen(node, parent, /*optional*/whenCb) { | ||
whenCb = whenCb || function() { | ||
return true; | ||
}; | ||
var cloneList = []; | ||
// Gather node ancestors and clone along the way | ||
var current = node; | ||
var isWhenNow = false; | ||
while(current && !isWhenNow) { | ||
if(current.type === 'decl') { | ||
cloneList.push(current.clone()); | ||
} | ||
else { | ||
cloneList.push(current.clone().removeAll()); | ||
} | ||
isWhenNow = whenCb(current); | ||
current = current.parent; | ||
} | ||
// Gather parent ancestors all the way up and clone along the way | ||
var cloneParentList = []; | ||
var currentParent = parent; | ||
while(currentParent) { | ||
cloneParentList.push(currentParent.clone().removeAll()); | ||
currentParent = currentParent.parent; | ||
} | ||
// Assign parents to our parent clones | ||
cloneParentList.forEach(function(parentClone, index, cloneParentList) { | ||
// Keep assigning parents detached until the very end | ||
if(index+1 < cloneParentList.length) { | ||
parentClone.parent = cloneParentList[index+1]; | ||
} | ||
}); | ||
// Assign parents to our node clones | ||
cloneList.forEach(function(clone, index, cloneList) { | ||
// Keep assigning parents detached until the very end | ||
if(index+1 < cloneList.length) { | ||
clone.parent = cloneList[index+1]; | ||
// Then splice on the new parent scope | ||
} else { | ||
// Set the highest parent ancestor to back to where we should splice in | ||
cloneParentList.slice(-1)[0].parent = current; | ||
// Set the node clone to the lowest parent ancestor | ||
clone.parent = cloneParentList[0]; | ||
} | ||
}); | ||
return cloneList[0]; | ||
} | ||
// Find a node starting from the given node that matches | ||
function findNodeAncestorWithSelector(selector, node) { | ||
var matchingNode; | ||
var currentNode = node; | ||
var stillFindingNode = true; | ||
// Keep going until we run out of parents to search | ||
// or we found the node | ||
while(currentNode.parent && !matchingNode) { | ||
// A trick to get the selector split up. Generate a scope list on a clone(clean parent) | ||
var currentNodeScopeList = generateScopeList(currentNode.clone(), true); | ||
currentNodeScopeList.some(function(scopePieces) { | ||
return scopePieces.some(function(scopePiece) { | ||
if(scopePiece === selector) { | ||
matchingNode = currentNode; | ||
return true; | ||
} | ||
return false; | ||
}); | ||
}); | ||
currentNode = currentNode.parent; | ||
} | ||
return matchingNode; | ||
} | ||
function generateScopeList(node, /*optional*/includeSelf) { | ||
@@ -40,25 +130,37 @@ includeSelf = includeSelf || false; | ||
while(currentNodeParent) { | ||
// Loop through each comma separated piece | ||
var selectorPieces = currentNodeParent.selectors || []; | ||
if(currentNodeParent.type === 'root') { | ||
selectorPieces = [':root']; | ||
} | ||
else if(currentNodeParent.type === 'atrule') { | ||
selectorPieces = [].concat(currentNodeParent.params).map(function(param, index) { | ||
return '@' + currentNodeParent.name + ' ' + param; | ||
// `currentNodeParent.selectors` is a list of each comma separated piece of the selector | ||
var scopePieces = (currentNodeParent.selectors || []).map(function(selectorPiece) { | ||
return { | ||
value: selectorPiece, | ||
type: 'selector' | ||
}; | ||
}); | ||
// If it is a at-rule, then we need to construct the proper piece | ||
if(currentNodeParent.type === 'atrule') { | ||
scopePieces = [].concat(currentNodeParent.params).map(function(param, index) { | ||
return { | ||
value: '@' + currentNodeParent.name + ' ' + param, | ||
type: 'atrule' | ||
}; | ||
}); | ||
} | ||
selectorPieces.forEach(function(selector, index) { | ||
// Branch each current scope | ||
if(index > 0) { | ||
selectorScopeList.concat(selectorScopeList); | ||
} | ||
// Branch each current scope for each comma separated selector | ||
// Otherwise just keep the [1] branch going | ||
var branches = (scopePieces.length > 0 ? scopePieces : [1]).map(function() { | ||
return selectorScopeList.map(function(scopePieces) { | ||
return scopePieces.slice(0); | ||
}); | ||
}); | ||
scopePieces.forEach(function(scopeObject, index) { | ||
// Update each selector string with the new piece | ||
selectorScopeList = selectorScopeList.map(function(scopeStringPieces) { | ||
// Just make sure we don't repeat the root twice at the end | ||
if(selector !== ':root' || (selector === ':root' && scopeStringPieces[0] !== ':root')) { | ||
var descendantPieces = selector.split(RE_SELECTOR_DESCENDANT_SPLIT) | ||
branches[index] = branches[index].map(function(scopeStringPieces) { | ||
var descendantPieces = [scopeObject.value]; | ||
// Split at any descendant combinators to properly make the scope list | ||
if(scopeObject.type === 'selector') { | ||
descendantPieces = scopeObject.value.split(RE_SELECTOR_DESCENDANT_SPLIT) | ||
.filter(function(piece) { | ||
@@ -77,6 +179,7 @@ if(piece.length > 0) { | ||
}); | ||
} | ||
// Add to the front of the array | ||
scopeStringPieces.unshift.apply(scopeStringPieces, descendantPieces); | ||
} | ||
// Add to the front of the array | ||
scopeStringPieces.unshift.apply(scopeStringPieces, descendantPieces); | ||
return scopeStringPieces; | ||
@@ -86,2 +189,7 @@ }); | ||
selectorScopeList = []; | ||
branches.forEach(function(branch) { | ||
selectorScopeList = selectorScopeList.concat(branch); | ||
}); | ||
currentNodeParent = currentNodeParent.parent; | ||
@@ -102,6 +210,13 @@ } | ||
var wasEveryPieceFound = scopeNodeScopePieces.every(function(scopePiece) { | ||
var pieceOffset = currentPieceOffset || 0; | ||
// Start from the previous index and make sure we can find it | ||
var foundIndex = nodeScopePieces.indexOf(scopePiece, currentPieceOffset || 0); | ||
var isFurther = currentPieceOffset === undefined || foundIndex > currentPieceOffset; | ||
var foundIndex = nodeScopePieces.indexOf(scopePiece, pieceOffset); | ||
// If it is a star or root, then it is valid no matter what | ||
// We might consider adding `html` and `body` to this list as well | ||
if(foundIndex < 0 && (scopePiece === '*' || scopePiece === ':root')) { | ||
foundIndex = pieceOffset + 1; | ||
} | ||
var isFurther = foundIndex > pieceOffset || (foundIndex >= 0 && currentPieceOffset === undefined); | ||
currentPieceOffset = foundIndex; | ||
@@ -125,11 +240,3 @@ return isFurther; | ||
// This means, feel free to run everything through this function | ||
var resolveValue = function(decl, map, /*optional*/mimicChildOf) { | ||
// A list of parents that you want the decl to mimic being a child of as well | ||
// Good for consuming other variables declared in different scopes | ||
// Coerce into an array | ||
// Add the declaration parent because we handle everything via this | ||
mimicChildOfList = [].concat(decl.parent ? [decl.parent] : []); | ||
// Then add on the additional list that was passed in | ||
mimicChildOfList = mimicChildOfList.concat(mimicChildOf ? mimicChildOf : []); | ||
var resolveValue = function(decl, map) { | ||
@@ -151,20 +258,19 @@ var resultantValue = decl.value; | ||
var matchingVarDeclMapItem; | ||
mimicChildOfList.forEach(function(mimicParentNode) { | ||
(map[variableName] || []).forEach(function(varDeclMapItem) { | ||
// Make sure the variable declaration came from the right spot | ||
// And if the current matching variable is already important, a new one to replace it has to be important | ||
var isRoot = varDeclMapItem.parent.type === 'root' || varDeclMapItem.parent.selectors[0] === ':root'; | ||
(map[variableName] || []).forEach(function(varDeclMapItem) { | ||
// Make sure the variable declaration came from the right spot | ||
// And if the current matching variable is already important, a new one to replace it has to be important | ||
var isRoot = varDeclMapItem.parent.type === 'root' || varDeclMapItem.parent.selectors[0] === ':root'; | ||
var mimicDeclParent = decl.parent; | ||
//console.log(generateScopeList(mimicParentNode, true)); | ||
//console.log(generateScopeList(varDeclMapItem.parent, true)); | ||
//console.log('isunderscope', isUnderScope(mimicParentNode, varDeclMapItem.parent), varDeclMapItem.value); | ||
if( | ||
isUnderScope(mimicParentNode, varDeclMapItem.parent) && | ||
// And if the currently matched declaration is `!important`, it will take another `!important` to override it | ||
(!(matchingVarDeclMapItem || {}).isImportant || varDeclMapItem.isImportant) | ||
) { | ||
matchingVarDeclMapItem = varDeclMapItem; | ||
} | ||
}); | ||
//console.log(generateScopeList(mimicDeclParent, true)); | ||
//console.log(generateScopeList(varDeclMapItem.parent, true)); | ||
//console.log('isunderscope', isUnderScope(mimicDeclParent, varDeclMapItem.parent), varDeclMapItem.value); | ||
if( | ||
isUnderScope(mimicDeclParent, varDeclMapItem.parent) && | ||
// And if the currently matched declaration is `!important`, it will take another `!important` to override it | ||
(!(matchingVarDeclMapItem || {}).isImportant || varDeclMapItem.isImportant) | ||
) { | ||
matchingVarDeclMapItem = varDeclMapItem; | ||
} | ||
}); | ||
@@ -272,2 +378,4 @@ | ||
// Collect all of the variables defined | ||
// --------------------------------------------------------- | ||
// --------------------------------------------------------- | ||
var addedRules = []; | ||
@@ -304,3 +412,3 @@ css.eachRule(function(rule) { | ||
isImportant: decl.important || false, | ||
// variables inside root or @rules (eg. @media, @support) | ||
// variables inside root or at-rules (eg. @media, @support) | ||
parent: splitOutRule, | ||
@@ -332,46 +440,83 @@ isUnderAtRule: splitOutRule.parent.type === 'atrule' | ||
// Resolve variables everywhere | ||
// --------------------------------------------------------- | ||
// --------------------------------------------------------- | ||
css.eachDecl(function(decl) { | ||
// Ignore any variable declarations that we may be preserving from earlier | ||
// Don't worry, they are already processed | ||
// If not a variable decalaraton... then resolve | ||
if(!RE_VAR_PROP.test(decl.prop)) { | ||
// Grab the balue for this declarations | ||
var valueResults = logResolveValueResult(resolveValue(decl, map)); | ||
// Resolve the cascade | ||
// Now find any @rule declarations that need to be added below each rule | ||
// Now find any at-rule declarations that need to be added below each rule | ||
// Loop through the variables used | ||
valueResults.variablesUsed.forEach(function(variableUsedName) { | ||
// Find anything in the map that corresponds to that variable | ||
(map[variableUsedName] || []).forEach(function(varDeclMapItem) { | ||
if(varDeclMapItem.isUnderAtRule) { | ||
// Create the clean atRule for which we place the declaration under | ||
var atRuleNode = varDeclMapItem.parent.parent.clone().removeAll(); | ||
var ruleClone = decl.parent.clone().removeAll(); | ||
var declClone = decl.clone(); | ||
declClone.value = logResolveValueResult(resolveValue(decl, map, [varDeclMapItem.parent])).value; | ||
// 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 in any of them | ||
var varDeclScopeList = generateScopeList(varDeclMapItem.parent, true); | ||
var selector = varDeclScopeList[0].slice(-1)[0]; | ||
// Add the declaration to our new rule | ||
ruleClone.append(declClone); | ||
// Add the rule to the atRule | ||
atRuleNode.append(ruleClone); | ||
var currentNodeToSpliceParentOnto = findNodeAncestorWithSelector(selector, decl.parent); | ||
var varDeclAtRule = varDeclMapItem.parent.parent; | ||
var mimicDecl = cloneSpliceParentOntoNodeWhen(decl, varDeclAtRule, function(ancestor) { | ||
return ancestor === currentNodeToSpliceParentOnto; | ||
}); | ||
// Since that atRuleNode can be nested in other atRules, we need to make the appropriate structure | ||
var parentAtRuleNode = atRuleNode; | ||
var currentAtRuleNode = varDeclMapItem.parent.parent; | ||
while(currentAtRuleNode.parent.type === 'atrule') { | ||
// Create a new clean clone of that at rule to nest under | ||
var newParentAtRuleNode = currentAtRuleNode.parent.clone().removeAll(); | ||
// Append the old parent | ||
newParentAtRuleNode.append(parentAtRuleNode); | ||
// Then set the new one as the current for next iteration | ||
parentAtRuleNode = newParentAtRuleNode; | ||
currentAtRuleNode = currentAtRuleNode.parent; | ||
//console.log('amd og', generateScopeList(decl.parent, true)); | ||
//console.log('amd', generateScopeList(mimicDecl.parent, true)); | ||
//console.log(generateScopeList(varDeclMapItem.parent, true)); | ||
//console.log('amd isunderscope', isUnderScope(mimicDecl.parent, varDeclMapItem.parent), varDeclMapItem.value); | ||
// If it is under the proper scope | ||
// Then lets create the new rules | ||
if(isUnderScope(mimicDecl.parent, varDeclMapItem.parent)) { | ||
// Create the clean atRule for which we place the declaration under | ||
var atRuleNode = varDeclMapItem.parent.parent.clone().removeAll(); | ||
var ruleClone = decl.parent.clone().removeAll(); | ||
var declClone = decl.clone(); | ||
declClone.value = logResolveValueResult(resolveValue(mimicDecl, map)).value; | ||
// Add the declaration to our new rule | ||
ruleClone.append(declClone); | ||
// 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 = varDeclMapItem.parent.parent; | ||
while(currentAtRuleNode.parent.type === 'atrule') { | ||
// Create a new clean clone of that at rule to nest under | ||
var newParentAtRuleNode = currentAtRuleNode.parent.clone().removeAll(); | ||
// Append the old parent | ||
newParentAtRuleNode.append(parentAtRuleNode); | ||
// Then set the new one as the current for next iteration | ||
parentAtRuleNode = newParentAtRuleNode; | ||
currentAtRuleNode = currentAtRuleNode.parent; | ||
} | ||
createNodeCallbackList.push(function() { | ||
// Put the atRuleStructure after the declaration's rule | ||
decl.parent.parent.insertAfter(decl.parent, parentAtRuleNode); | ||
}); | ||
} | ||
createNodeCallbackList.push(function() { | ||
// Put the atRuleStructure after the declaration's rule | ||
decl.parent.parent.insertAfter(decl.parent, parentAtRuleNode); | ||
}); | ||
} | ||
@@ -387,3 +532,3 @@ }); | ||
// Set the new value after we are done dealing with @rule stuff | ||
// Set the new value after we are done dealing with at-rule stuff | ||
decl.value = valueResults.value; | ||
@@ -393,3 +538,3 @@ }); | ||
else { | ||
// Set the new value after we are done dealing with @rule stuff | ||
// Set the new value after we are done dealing with at-rule stuff | ||
decl.value = valueResults.value; | ||
@@ -396,0 +541,0 @@ } |
{ | ||
"name": "postcss-css-variables", | ||
"version": "0.2.3", | ||
"version": "0.3.1", | ||
"description": "PostCSS plugin to transform CSS Custom Properties(CSS variables) syntax into a static representation", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -11,3 +11,3 @@ [](http://badge.fury.io/js/postcss-css-variables) [](https://travis-ci.org/MadLittleMods/postcss-css-variables) | ||
## Latest Version: 0.2.3 | ||
## Latest Version: v0.3.1 | ||
### [Changelog](https://github.com/MadLittleMods/postcss-css-variables/blob/master/CHANGELOG.md) | ||
@@ -29,3 +29,3 @@ | ||
``` | ||
```js | ||
var postcss = require('postcss'); | ||
@@ -57,3 +57,3 @@ var cssvariables = require('postcss-css-variables'); | ||
``` | ||
```css | ||
:root { | ||
@@ -76,3 +76,3 @@ --width: 100px; | ||
``` | ||
```css | ||
.box { | ||
@@ -94,3 +94,6 @@ width: 100px; | ||
``` | ||
Run `postcss-nested` before `postcss-css-variables` so that `postcss-nested` can properly expand the `&` references before we start resolving variable values. | ||
```js | ||
var postcss = require('postcss'); | ||
@@ -121,3 +124,3 @@ var cssvariables = require('postcss-css-variables'); | ||
``` | ||
```css | ||
.box-foo { | ||
@@ -135,3 +138,3 @@ --some-width: 150px; | ||
``` | ||
```css | ||
.box-foo { | ||
@@ -150,3 +153,3 @@ width: 150px; | ||
``` | ||
```css | ||
:root { | ||
@@ -173,3 +176,3 @@ --some-width: 150px; | ||
``` | ||
```css | ||
.box-foo { | ||
@@ -176,0 +179,0 @@ width: 150px; |
30429
17.81%450
31.96%313
0.97%