1
1
// PostCSS CSS Variables (postcss-css-variables)
2
- // v0.3.3
2
+ // v0.3.4
3
3
//
4
4
// https://github.com/MadLittleMods/postcss-css-variables
5
5
@@ -241,7 +241,7 @@ function isUnderScope(nodeScopeList, scopeNodeScopeList) {
241
241
return matchesScope ;
242
242
}
243
243
244
- function isNodeUnderNode ( node , scopeNode ) {
244
+ function isNodeUnderNodeScope ( node , scopeNode ) {
245
245
246
246
var nodeScopeList = generateScopeList ( node , true ) ;
247
247
var scopeNodeScopeList = generateScopeList ( scopeNode , true ) ;
@@ -250,48 +250,109 @@ function isNodeUnderNode(node, scopeNode) {
250
250
}
251
251
252
252
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
+
253
297
// Pass in a value string to parse/resolve and a map of available values
254
298
// and we can figure out the final value
255
299
//
256
300
// Note: We do not modify the declaration
257
301
// Note: Resolving a declaration value without any `var(...)` does not harm the final value.
258
302
// This means, feel free to run everything through this function
259
- var resolveValue = function ( decl , map ) {
303
+ var resolveValue = function ( decl , map , _debugIsInternal ) {
260
304
261
305
var resultantValue = decl . value ;
262
- var variablesUsedInValue = [ ] ;
263
306
var warnings = [ ] ;
264
307
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
+
265
317
// Resolve any var(...) substitutons
266
318
var isResultantValueUndefined = false ;
267
319
resultantValue = resultantValue . replace ( new RegExp ( RE_VAR_FUNC . source , 'g' ) , function ( match , variableName , fallback ) {
268
- variablesUsedInValue . push ( variableName ) ;
269
-
270
320
// Loop through the list of declarations for that value and find the one that best matches
271
321
// 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
274
323
// - The latest defined `!important` if any
275
324
var matchingVarDeclMapItem ;
325
+ //gatherVariableDependencies(variablesUsedInValue, map)
276
326
( map [ variableName ] || [ ] ) . forEach ( function ( varDeclMapItem ) {
277
327
// Make sure the variable declaration came from the right spot
278
328
// And if the current matching variable is already important, a new one to replace it has to be important
279
329
var isRoot = varDeclMapItem . parent . type === 'root' || varDeclMapItem . parent . selectors [ 0 ] === ':root' ;
280
330
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
+
284
337
if (
285
- isNodeUnderNode ( decl . parent , varDeclMapItem . parent ) &&
338
+ isNodeUnderNodeScope ( decl . parent , varDeclMapItem . parent ) &&
286
339
// And if the currently matched declaration is `!important`, it will take another `!important` to override it
287
340
( ! ( matchingVarDeclMapItem || { } ) . isImportant || varDeclMapItem . isImportant )
288
341
) {
289
342
matchingVarDeclMapItem = varDeclMapItem ;
290
343
}
291
344
} ) ;
292
345
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
+
295
356
isResultantValueUndefined = replaceValue === undefined ;
296
357
if ( isResultantValueUndefined ) {
297
358
warnings . push ( [ "variable '" + variableName + "' is undefined and used without a fallback" , { node : decl } ] ) ;
@@ -312,7 +373,6 @@ var resolveValue = function(decl, map) {
312
373
313
374
314
375
315
-
316
376
module . exports = postcss . plugin ( 'postcss-css-variables' , function ( options ) {
317
377
var defaults = {
318
378
// Allows you to preserve custom properties & var() usage in output.
@@ -330,7 +390,7 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
330
390
return function ( css , result ) {
331
391
// Transform CSS AST here
332
392
333
- /* * /
393
+ /* */
334
394
try {
335
395
/* */
336
396
@@ -349,34 +409,36 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
349
409
map ,
350
410
Object . keys ( opts . variables )
351
411
. reduce ( function ( prevVariableMap , variableName ) {
352
- var variableValue = opts . variables [ variableName ] ;
412
+ var variableEntry = opts . variables [ variableName ] ;
353
413
// Automatically prefix any variable with `--` (CSS custom property syntax) if it doesn't have it already
354
414
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 ,
371
432
isUnderAtRule : false
372
- } , variableValue ) ) ;
433
+ } ) ;
373
434
374
435
return prevVariableMap ;
375
436
} , { } )
376
437
) ;
377
438
439
+
378
440
// Chainable helper function to log any messages (warnings)
379
- function logResolveValueResult ( valueResult ) {
441
+ var logResolveValueResult = function ( valueResult ) {
380
442
// Log any warnings that might of popped up
381
443
var warningList = [ ] . concat ( valueResult . warnings ) ;
382
444
warningList . forEach ( function ( warningArgs ) {
@@ -386,7 +448,7 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
386
448
387
449
// Keep the chain going
388
450
return valueResult ;
389
- }
451
+ } ;
390
452
391
453
392
454
// Collect all of the variables defined
@@ -410,33 +472,36 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
410
472
var prop = decl . prop ;
411
473
// If declaration is a variable
412
474
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'
430
495
} ) ;
431
- }
496
+ } ) ;
432
497
433
498
// Remove the variable declaration because they are pretty much useless after we resolve them
434
499
if ( ! opts . preserve ) {
435
500
decl . removeSelf ( ) ;
436
501
}
437
502
// Or we can also just show the computed value used for that variable
438
503
else if ( opts . preserve === 'computed' ) {
439
- decl . value = resolvedValue ;
504
+ decl . value = valueResults . value ;
440
505
}
441
506
// Otherwise just keep them as var declarations
442
507
}
@@ -464,39 +529,49 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
464
529
465
530
// Grab the balue for this declarations
466
531
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
+
467
539
468
540
// Resolve the cascade
469
541
// Now find any at-rule declarations that need to be added below each rule
470
542
// Loop through the variables used
471
543
valueResults . variablesUsed . forEach ( function ( variableUsedName ) {
544
+
545
+
472
546
// 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 ) {
474
548
if ( varDeclMapItem . isUnderAtRule ) {
475
549
476
550
477
551
// 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
479
553
var varDeclScopeList = generateScopeList ( varDeclMapItem . parent , true ) ;
480
- var selector = varDeclScopeList [ 0 ] . slice ( - 1 ) [ 0 ] ;
554
+ var innerMostAtRuleSelector = varDeclScopeList [ 0 ] . slice ( - 1 ) [ 0 ] ;
481
555
482
- var currentNodeToSpliceParentOnto = findNodeAncestorWithSelector ( selector , decl . parent ) ;
556
+ var nodeToSpliceParentOnto = findNodeAncestorWithSelector ( innerMostAtRuleSelector , decl . parent ) ;
483
557
558
+ // Splice on where the selector starts matching the selector inside at-rule
484
559
var varDeclAtRule = varDeclMapItem . parent . parent ;
485
560
var mimicDecl = cloneSpliceParentOntoNodeWhen ( decl , varDeclAtRule , function ( ancestor ) {
486
- return ancestor === currentNodeToSpliceParentOnto ;
561
+ return ancestor === nodeToSpliceParentOnto ;
487
562
} ) ;
488
563
489
564
490
565
491
566
//console.log('amd og', generateScopeList(decl.parent, true));
492
567
//console.log('amd', generateScopeList(mimicDecl.parent, true));
493
568
//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);
495
570
496
571
497
572
// If it is under the proper scope
498
573
// Then lets create the new rules
499
- if ( isNodeUnderNode ( mimicDecl . parent , varDeclMapItem . parent ) ) {
574
+ if ( isNodeUnderNodeScope ( mimicDecl . parent , varDeclMapItem . parent ) ) {
500
575
// Create the clean atRule for which we place the declaration under
501
576
var atRuleNode = varDeclMapItem . parent . parent . clone ( ) . removeAll ( ) ;
502
577
@@ -577,9 +652,10 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
577
652
578
653
//console.log('map', map);
579
654
580
- /* * /
655
+ /* */
581
656
}
582
657
catch ( e ) {
658
+ //console.log('e', e.message);
583
659
console . log ( 'e' , e . message , e . stack ) ;
584
660
}
585
661
/* */
0 commit comments