11// PostCSS CSS Variables (postcss-css-variables)
2- // v0.3.3
2+ // v0.3.4
33//
44// https://github.com/MadLittleMods/postcss-css-variables
55
@@ -241,7 +241,7 @@ function isUnderScope(nodeScopeList, scopeNodeScopeList) {
241241 return matchesScope ;
242242}
243243
244- function isNodeUnderNode ( node , scopeNode ) {
244+ function isNodeUnderNodeScope ( node , scopeNode ) {
245245
246246 var nodeScopeList = generateScopeList ( node , true ) ;
247247 var scopeNodeScopeList = generateScopeList ( scopeNode , true ) ;
@@ -250,48 +250,109 @@ function isNodeUnderNode(node, scopeNode) {
250250}
251251
252252
253+
254+ // Variables that referenced in some way by the target variable
255+ function gatherVariableDependencies ( variablesUsed , map , _dependencyVariablesList ) {
256+ _dependencyVariablesList = _dependencyVariablesList || [ ] ;
257+ var hasCircularOrSelfReference = false ;
258+
259+ if ( variablesUsed ) {
260+ _dependencyVariablesList = variablesUsed . reduce ( function ( dependencyVariablesList , variableUsedName ) {
261+ var isVariableInMap = ! ! map [ variableUsedName ] ;
262+ var doesThisVarHaveCircularOrSelfReference = ! isVariableInMap ? false : dependencyVariablesList . some ( function ( dep ) {
263+ return map [ variableUsedName ] . some ( function ( mapItem ) {
264+ // If already in the list, we got a circular reference
265+ if ( dep === mapItem ) {
266+ return true ;
267+ }
268+
269+ return false ;
270+ } ) ;
271+ } ) ;
272+ // Update the overall state of dependency health
273+ hasCircularOrSelfReference = hasCircularOrSelfReference || doesThisVarHaveCircularOrSelfReference ;
274+
275+
276+ if ( isVariableInMap && ! hasCircularOrSelfReference ) {
277+ dependencyVariablesList = dependencyVariablesList . concat ( map [ variableUsedName ] ) ;
278+
279+ ( map [ variableUsedName ] || [ ] ) . forEach ( function ( mapItem ) {
280+ var result = gatherVariableDependencies ( mapItem . variablesUsed , map , dependencyVariablesList ) ;
281+ dependencyVariablesList = result . deps ;
282+ hasCircularOrSelfReference = hasCircularOrSelfReference || result . hasCircularOrSelfReference ;
283+ } ) ;
284+ }
285+
286+ return dependencyVariablesList ;
287+ } , _dependencyVariablesList ) ;
288+ }
289+
290+ return {
291+ deps : _dependencyVariablesList ,
292+ hasCircularOrSelfReference : hasCircularOrSelfReference
293+ } ;
294+ }
295+
296+
253297// Pass in a value string to parse/resolve and a map of available values
254298// and we can figure out the final value
255299//
256300// Note: We do not modify the declaration
257301// Note: Resolving a declaration value without any `var(...)` does not harm the final value.
258302// This means, feel free to run everything through this function
259- var resolveValue = function ( decl , map ) {
303+ var resolveValue = function ( decl , map , _debugIsInternal ) {
260304
261305 var resultantValue = decl . value ;
262- var variablesUsedInValue = [ ] ;
263306 var warnings = [ ] ;
264307
308+ var variablesUsedInValueMap = { } ;
309+ // Use `replace` as a loop to go over all occurrences with the `g` flag
310+ resultantValue . replace ( new RegExp ( RE_VAR_FUNC . source , 'g' ) , function ( match , variableName , fallback ) {
311+ variablesUsedInValueMap [ variableName ] = true ;
312+ } ) ;
313+ var variablesUsedInValue = Object . keys ( variablesUsedInValueMap ) ;
314+
315+
316+
265317 // Resolve any var(...) substitutons
266318 var isResultantValueUndefined = false ;
267319 resultantValue = resultantValue . replace ( new RegExp ( RE_VAR_FUNC . source , 'g' ) , function ( match , variableName , fallback ) {
268- variablesUsedInValue . push ( variableName ) ;
269-
270320 // Loop through the list of declarations for that value and find the one that best matches
271321 // By best match, we mean, the variable actually applies. Criteria:
272- // - At the root
273- // - Defined in the same rule
322+ // - is under the same scope
274323 // - The latest defined `!important` if any
275324 var matchingVarDeclMapItem ;
325+ //gatherVariableDependencies(variablesUsedInValue, map)
276326 ( map [ variableName ] || [ ] ) . forEach ( function ( varDeclMapItem ) {
277327 // Make sure the variable declaration came from the right spot
278328 // And if the current matching variable is already important, a new one to replace it has to be important
279329 var isRoot = varDeclMapItem . parent . type === 'root' || varDeclMapItem . parent . selectors [ 0 ] === ':root' ;
280330
281- //console.log(generateScopeList(decl.parent, true));
282- //console.log(generateScopeList(varDeclMapItem.parent, true));
283- //console.log('isNodeUnderNode', isNodeUnderNode(decl.parent, varDeclMapItem.parent), varDeclMapItem.value);
331+
332+ //var debugIndent = _debugIsInternal ? '\t' : '';
333+ //console.log(debugIndent, generateScopeList(decl.parent, true));
334+ //console.log(debugIndent, generateScopeList(varDeclMapItem.parent, true));
335+ //console.log(debugIndent, 'isNodeUnderNodeScope', isNodeUnderNodeScope(decl.parent, varDeclMapItem.parent), varDeclMapItem.value);
336+
284337 if (
285- isNodeUnderNode ( decl . parent , varDeclMapItem . parent ) &&
338+ isNodeUnderNodeScope ( decl . parent , varDeclMapItem . parent ) &&
286339 // And if the currently matched declaration is `!important`, it will take another `!important` to override it
287340 ( ! ( matchingVarDeclMapItem || { } ) . isImportant || varDeclMapItem . isImportant )
288341 ) {
289342 matchingVarDeclMapItem = varDeclMapItem ;
290343 }
291344 } ) ;
292345
293-
294- var replaceValue = ( matchingVarDeclMapItem || { } ) . value || fallback ;
346+ // Default to the calculatedInPlaceValue which might be a previous fallback, then try this declarations fallback
347+ var replaceValue = ( matchingVarDeclMapItem || { } ) . calculatedInPlaceValue || fallback ;
348+ // Otherwise if the dependency health is good(no circular or self references), dive deeper and resolve
349+ if ( matchingVarDeclMapItem !== undefined && ! gatherVariableDependencies ( variablesUsedInValue , map ) . hasCircularOrSelfReference ) {
350+ var asdf = false ;
351+ var mimicDecl = cloneSpliceParentOntoNodeWhen ( matchingVarDeclMapItem . decl , decl . parent . parent ) ;
352+
353+ replaceValue = resolveValue ( mimicDecl , map , true ) . value ;
354+ }
355+
295356 isResultantValueUndefined = replaceValue === undefined ;
296357 if ( isResultantValueUndefined ) {
297358 warnings . push ( [ "variable '" + variableName + "' is undefined and used without a fallback" , { node : decl } ] ) ;
@@ -312,7 +373,6 @@ var resolveValue = function(decl, map) {
312373
313374
314375
315-
316376module . exports = postcss . plugin ( 'postcss-css-variables' , function ( options ) {
317377 var defaults = {
318378 // Allows you to preserve custom properties & var() usage in output.
@@ -330,7 +390,7 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
330390 return function ( css , result ) {
331391 // Transform CSS AST here
332392
333- /* * /
393+ /* */
334394 try {
335395 /* */
336396
@@ -349,34 +409,36 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
349409 map ,
350410 Object . keys ( opts . variables )
351411 . reduce ( function ( prevVariableMap , variableName ) {
352- var variableValue = opts . variables [ variableName ] ;
412+ var variableEntry = opts . variables [ variableName ] ;
353413 // Automatically prefix any variable with `--` (CSS custom property syntax) if it doesn't have it already
354414 variableName = variableName . slice ( 0 , 2 ) === '--' ? variableName : '--' + variableName ;
355-
356-
357- // If they didn't pass a object, lets construct one
358- if ( typeof variableValue !== 'object' ) {
359- variableValue = {
360- value : variableValue ,
361- isImportant : false ,
362- parent : css . root ( ) ,
363- isUnderAtRule : false
364- } ;
365- }
366-
367- prevVariableMap [ variableName ] = ( prevVariableMap [ variableName ] || [ ] ) . concat ( extend ( {
368- value : undefined ,
369- isImportant : false ,
370- parent : css . root ( ) ,
415+ var variableValue = typeof variableEntry === 'object' ? variableEntry . value : variableEntry ;
416+ var isImportant = typeof variableEntry === 'object' ? variableEntry . isImportant : false ;
417+
418+ // Add a node to the AST
419+ var variableRootRule = postcss . rule ( { selector : ':root' } ) ;
420+ css . root ( ) . prepend ( variableRootRule ) ;
421+ var varDecl = postcss . decl ( { prop : variableName , value : variableValue } ) ;
422+ varDecl . moveTo ( variableRootRule ) ;
423+
424+ // Add the entry to the map
425+ prevVariableMap [ variableName ] = ( prevVariableMap [ variableName ] || [ ] ) . concat ( {
426+ decl : varDecl ,
427+ prop : variableName ,
428+ calculatedInPlaceValue : variableValue ,
429+ isImportant : isImportant ,
430+ variablesUsed : [ ] ,
431+ parent : variableRootRule ,
371432 isUnderAtRule : false
372- } , variableValue ) ) ;
433+ } ) ;
373434
374435 return prevVariableMap ;
375436 } , { } )
376437 ) ;
377438
439+
378440 // Chainable helper function to log any messages (warnings)
379- function logResolveValueResult ( valueResult ) {
441+ var logResolveValueResult = function ( valueResult ) {
380442 // Log any warnings that might of popped up
381443 var warningList = [ ] . concat ( valueResult . warnings ) ;
382444 warningList . forEach ( function ( warningArgs ) {
@@ -386,7 +448,7 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
386448
387449 // Keep the chain going
388450 return valueResult ;
389- }
451+ } ;
390452
391453
392454 // Collect all of the variables defined
@@ -410,33 +472,36 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
410472 var prop = decl . prop ;
411473 // If declaration is a variable
412474 if ( RE_VAR_PROP . test ( prop ) ) {
413- var resolvedValue = logResolveValueResult ( resolveValue ( decl , map ) ) . value ;
414- if ( resolvedValue !== undefined ) {
415- // Split out each selector piece into its own declaration for easier logic down the road
416- decl . parent . selectors . forEach ( function ( selector , index ) {
417- // Create a detached clone
418- var splitOutRule = rule . clone ( ) ;
419- rule . selector = selector ;
420- splitOutRule . parent = rule . parent ;
421-
422- map [ prop ] = ( map [ prop ] || [ ] ) . concat ( {
423- prop : prop ,
424- value : resolvedValue ,
425- isImportant : decl . important || false ,
426- // variables inside root or at-rules (eg. @media, @support)
427- parent : splitOutRule ,
428- isUnderAtRule : splitOutRule . parent . type === 'atrule'
429- } ) ;
475+ var valueResults = logResolveValueResult ( resolveValue ( decl , map ) ) ;
476+ // Split out each selector piece into its own declaration for easier logic down the road
477+ decl . parent . selectors . forEach ( function ( selector , index ) {
478+ // Create a detached clone
479+ var splitOutRule = rule . clone ( ) . removeAll ( ) ;
480+ rule . selector = selector ;
481+ splitOutRule . parent = rule . parent ;
482+
483+ var declClone = decl . clone ( ) ;
484+ declClone . moveTo ( splitOutRule ) ;
485+
486+ map [ prop ] = ( map [ prop ] || [ ] ) . concat ( {
487+ decl : declClone ,
488+ prop : prop ,
489+ calculatedInPlaceValue : valueResults . value ,
490+ isImportant : decl . important || false ,
491+ variablesUsed : valueResults . variablesUsed ,
492+ // variables inside root or at-rules (eg. @media, @support)
493+ parent : splitOutRule ,
494+ isUnderAtRule : splitOutRule . parent . type === 'atrule'
430495 } ) ;
431- }
496+ } ) ;
432497
433498 // Remove the variable declaration because they are pretty much useless after we resolve them
434499 if ( ! opts . preserve ) {
435500 decl . removeSelf ( ) ;
436501 }
437502 // Or we can also just show the computed value used for that variable
438503 else if ( opts . preserve === 'computed' ) {
439- decl . value = resolvedValue ;
504+ decl . value = valueResults . value ;
440505 }
441506 // Otherwise just keep them as var declarations
442507 }
@@ -464,39 +529,49 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
464529
465530 // Grab the balue for this declarations
466531 var valueResults = logResolveValueResult ( resolveValue ( decl , map ) ) ;
532+ //console.log('decl v', decl.value);
533+
534+
535+
536+
537+ //console.log('deps', gatherVariableDependencies(valueResults.variablesUsed, map));
538+
467539
468540 // Resolve the cascade
469541 // Now find any at-rule declarations that need to be added below each rule
470542 // Loop through the variables used
471543 valueResults . variablesUsed . forEach ( function ( variableUsedName ) {
544+
545+
472546 // Find anything in the map that corresponds to that variable
473- ( map [ variableUsedName ] || [ ] ) . forEach ( function ( varDeclMapItem ) {
547+ gatherVariableDependencies ( valueResults . variablesUsed , map ) . deps . forEach ( function ( varDeclMapItem ) {
474548 if ( varDeclMapItem . isUnderAtRule ) {
475549
476550
477551 // Get the inner-most selector of the at-rule scope variable declaration we are matching
478- // Because the inner-most selector will be the same for each branch, we can look in any of them
552+ // 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
479553 var varDeclScopeList = generateScopeList ( varDeclMapItem . parent , true ) ;
480- var selector = varDeclScopeList [ 0 ] . slice ( - 1 ) [ 0 ] ;
554+ var innerMostAtRuleSelector = varDeclScopeList [ 0 ] . slice ( - 1 ) [ 0 ] ;
481555
482- var currentNodeToSpliceParentOnto = findNodeAncestorWithSelector ( selector , decl . parent ) ;
556+ var nodeToSpliceParentOnto = findNodeAncestorWithSelector ( innerMostAtRuleSelector , decl . parent ) ;
483557
558+ // Splice on where the selector starts matching the selector inside at-rule
484559 var varDeclAtRule = varDeclMapItem . parent . parent ;
485560 var mimicDecl = cloneSpliceParentOntoNodeWhen ( decl , varDeclAtRule , function ( ancestor ) {
486- return ancestor === currentNodeToSpliceParentOnto ;
561+ return ancestor === nodeToSpliceParentOnto ;
487562 } ) ;
488563
489564
490565
491566 //console.log('amd og', generateScopeList(decl.parent, true));
492567 //console.log('amd', generateScopeList(mimicDecl.parent, true));
493568 //console.log(generateScopeList(varDeclMapItem.parent, true));
494- //console.log('amd isNodeUnderNode ', isNodeUnderNode (mimicDecl.parent, varDeclMapItem.parent), varDeclMapItem.value);
569+ //console.log('amd isNodeUnderNodeScope ', isNodeUnderNodeScope (mimicDecl.parent, varDeclMapItem.parent), varDeclMapItem.value);
495570
496571
497572 // If it is under the proper scope
498573 // Then lets create the new rules
499- if ( isNodeUnderNode ( mimicDecl . parent , varDeclMapItem . parent ) ) {
574+ if ( isNodeUnderNodeScope ( mimicDecl . parent , varDeclMapItem . parent ) ) {
500575 // Create the clean atRule for which we place the declaration under
501576 var atRuleNode = varDeclMapItem . parent . parent . clone ( ) . removeAll ( ) ;
502577
@@ -577,9 +652,10 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
577652
578653 //console.log('map', map);
579654
580- /* * /
655+ /* */
581656 }
582657 catch ( e ) {
658+ //console.log('e', e.message);
583659 console . log ( 'e' , e . message , e . stack ) ;
584660 }
585661 /* */
0 commit comments