postcss-css-variables
Advanced tools
Comparing version
@@ -0,1 +1,7 @@ | ||
# v0.17.0 - 2020-4-24 | ||
- Expand variables in AtRule properties | ||
- Thank you to [@pvande](https://github.com/pvande) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/104) | ||
- Merged via https://github.com/MadLittleMods/postcss-css-variables/pull/121 | ||
# v0.16.0 - 2020-4-24 | ||
@@ -2,0 +8,0 @@ |
453
index.js
@@ -9,278 +9,278 @@ // PostCSS CSS Variables (postcss-css-variables) | ||
var postcss = require('postcss'); | ||
var extend = require('extend'); | ||
var postcss = require("postcss"); | ||
var extend = require("extend"); | ||
var shallowCloneNode = require('./lib/shallow-clone-node'); | ||
var resolveValue = require('./lib/resolve-value'); | ||
var resolveDecl = require('./lib/resolve-decl'); | ||
var shallowCloneNode = require("./lib/shallow-clone-node"); | ||
var resolveValue = require("./lib/resolve-value"); | ||
var resolveDecl = require("./lib/resolve-decl"); | ||
// A custom property is any property whose name starts with two dashes (U+002D HYPHEN-MINUS) | ||
// `--foo` | ||
// See: http://dev.w3.org/csswg/css-variables/#custom-property | ||
var RE_VAR_PROP = (/(--(.+))/); | ||
var RE_VAR_PROP = /(--(.+))/; | ||
function eachCssVariableDeclaration(css, cb) { | ||
// Loop through all of the declarations and grab the variables and put them in the map | ||
css.walkDecls(function(decl) { | ||
// If declaration is a variable | ||
if(RE_VAR_PROP.test(decl.prop)) { | ||
cb(decl); | ||
} | ||
}); | ||
// Loop through all of the declarations and grab the variables and put them in the map | ||
css.walkDecls(function(decl) { | ||
// If declaration is a variable | ||
if (RE_VAR_PROP.test(decl.prop)) { | ||
cb(decl); | ||
} | ||
}); | ||
} | ||
function cleanUpNode(node) { | ||
// If we removed all of the declarations in the rule(making it empty), | ||
// then just remove it | ||
var nodeToPossiblyCleanUp = node; | ||
while(nodeToPossiblyCleanUp && nodeToPossiblyCleanUp.nodes.length <= 0) { | ||
var nodeToRemove = nodeToPossiblyCleanUp.type !== 'root' ? nodeToPossiblyCleanUp : null; | ||
// If we removed all of the declarations in the rule(making it empty), | ||
// then just remove it | ||
var nodeToPossiblyCleanUp = node; | ||
while (nodeToPossiblyCleanUp && nodeToPossiblyCleanUp.nodes.length <= 0) { | ||
var nodeToRemove = | ||
nodeToPossiblyCleanUp.type !== "root" ? nodeToPossiblyCleanUp : null; | ||
if(nodeToRemove) { | ||
// Get a reference to it before we remove | ||
// and lose reference to the child after removing it | ||
nodeToPossiblyCleanUp = nodeToRemove.parent; | ||
if (nodeToRemove) { | ||
// Get a reference to it before we remove | ||
// and lose reference to the child after removing it | ||
nodeToPossiblyCleanUp = nodeToRemove.parent; | ||
nodeToRemove.remove(); | ||
} | ||
else { | ||
nodeToPossiblyCleanUp = null; | ||
} | ||
} | ||
nodeToRemove.remove(); | ||
} else { | ||
nodeToPossiblyCleanUp = null; | ||
} | ||
} | ||
} | ||
var defaults = { | ||
// Allows you to preserve custom properties & var() usage in output. | ||
// `true`, `false`, or `'computed'` | ||
preserve: false, | ||
// Define variables via JS | ||
// Simple key-value pair | ||
// or an object with a `value` property and an optional `isImportant` bool property | ||
variables: {}, | ||
// Preserve variables injected via JS with the `variables` option above | ||
// before serializing to CSS (`false` will remove these variables from output) | ||
preserveInjectedVariables: true, | ||
// Will write media queries in the same order as in the original file. | ||
// Currently defaulted to false for legacy behavior. We can update to `true` in a major version | ||
preserveAtRulesOrder: false | ||
// Allows you to preserve custom properties & var() usage in output. | ||
// `true`, `false`, or `'computed'` | ||
preserve: false, | ||
// Define variables via JS | ||
// Simple key-value pair | ||
// or an object with a `value` property and an optional `isImportant` bool property | ||
variables: {}, | ||
// Preserve variables injected via JS with the `variables` option above | ||
// before serializing to CSS (`false` will remove these variables from output) | ||
preserveInjectedVariables: true, | ||
// Will write media queries in the same order as in the original file. | ||
// Currently defaulted to false for legacy behavior. We can update to `true` in a major version | ||
preserveAtRulesOrder: false | ||
}; | ||
module.exports = postcss.plugin('postcss-css-variables', function(options) { | ||
module.exports = postcss.plugin("postcss-css-variables", function(options) { | ||
var opts = extend({}, defaults, options); | ||
var opts = extend({}, defaults, options); | ||
// Work with opts here | ||
// Work with opts here | ||
return function(css, result) { | ||
// Transform CSS AST here | ||
return function (css, result) { | ||
// Transform CSS AST here | ||
/* * / | ||
/* * / | ||
try { | ||
/* */ | ||
// List of nodes that if empty, will be removed | ||
// We use this because we don't want to modify the AST when we still need to reference these later on | ||
var nodesToRemoveAtEnd = []; | ||
// List of nodes that if empty, will be removed | ||
// We use this because we don't want to modify the AST when we still need to reference these later on | ||
var nodesToRemoveAtEnd = []; | ||
// Keep track of the injected from `opts.variables` to remove at the end | ||
// if user passes `opts.preserveInjectedVariables = false` | ||
var injectedDeclsToRemoveAtEnd = []; | ||
// Keep track of the injected from `opts.variables` to remove at the end | ||
// if user passes `opts.preserveInjectedVariables = false` | ||
var injectedDeclsToRemoveAtEnd = []; | ||
// Map of variable names to a list of declarations | ||
var map = {}; | ||
// Map of variable names to a list of declarations | ||
var map = {}; | ||
// Add the js defined variables `opts.variables` to the map | ||
map = extend( | ||
map, | ||
Object.keys(opts.variables).reduce(function(prevVariableMap, variableName) { | ||
var variableEntry = opts.variables[variableName]; | ||
// Automatically prefix any variable with `--` (CSS custom property syntax) if it doesn't have it already | ||
variableName = variableName.slice(0, 2) === '--' ? variableName : '--' + variableName; | ||
var variableValue = (variableEntry || {}).value || variableEntry; | ||
var isImportant = (variableEntry || {}).isImportant || false; | ||
// Add the js defined variables `opts.variables` to the map | ||
map = extend( | ||
map, | ||
Object.keys(opts.variables).reduce(function( | ||
prevVariableMap, | ||
variableName | ||
) { | ||
var variableEntry = opts.variables[variableName]; | ||
// Automatically prefix any variable with `--` (CSS custom property syntax) if it doesn't have it already | ||
variableName = | ||
variableName.slice(0, 2) === "--" | ||
? variableName | ||
: "--" + variableName; | ||
var variableValue = (variableEntry || {}).value || variableEntry; | ||
var isImportant = (variableEntry || {}).isImportant || false; | ||
// Add a root node to the AST | ||
var variableRootRule = postcss.rule({ selector: ":root" }); | ||
css.root().prepend(variableRootRule); | ||
// Add the variable decl to the root node | ||
var varDecl = postcss.decl({ | ||
prop: variableName, | ||
value: variableValue, | ||
important: isImportant | ||
}); | ||
variableRootRule.append(varDecl); | ||
// Add a root node to the AST | ||
var variableRootRule = postcss.rule({ selector: ':root' }); | ||
css.root().prepend(variableRootRule); | ||
// Add the variable decl to the root node | ||
var varDecl = postcss.decl({ | ||
prop: variableName, | ||
value: variableValue, | ||
important: isImportant | ||
}); | ||
variableRootRule.append(varDecl); | ||
// Collect JS-injected variables for removal if `opts.preserveInjectedVariables = false` | ||
if (!opts.preserveInjectedVariables) { | ||
injectedDeclsToRemoveAtEnd.push(varDecl); | ||
} | ||
// Collect JS-injected variables for removal if `opts.preserveInjectedVariables = false` | ||
if (!opts.preserveInjectedVariables) { | ||
injectedDeclsToRemoveAtEnd.push(varDecl); | ||
} | ||
// Add the entry to the map | ||
prevVariableMap[variableName] = ( | ||
prevVariableMap[variableName] || [] | ||
).concat({ | ||
decl: varDecl, | ||
prop: variableName, | ||
calculatedInPlaceValue: variableValue, | ||
isImportant: isImportant, | ||
variablesUsed: [], | ||
parent: variableRootRule, | ||
isUnderAtRule: false | ||
}); | ||
// Add the entry to the map | ||
prevVariableMap[variableName] = (prevVariableMap[variableName] || []).concat({ | ||
decl: varDecl, | ||
prop: variableName, | ||
calculatedInPlaceValue: variableValue, | ||
isImportant: isImportant, | ||
variablesUsed: [], | ||
parent: variableRootRule, | ||
isUnderAtRule: false | ||
}); | ||
return prevVariableMap; | ||
}, | ||
{}) | ||
); | ||
return prevVariableMap; | ||
}, {}) | ||
); | ||
// Chainable helper function to log any messages (warnings) | ||
var logResolveValueResult = function(valueResult) { | ||
// Log any warnings that might of popped up | ||
var warningList = [].concat(valueResult.warnings); | ||
warningList.forEach(function(warningArgs) { | ||
warningArgs = [].concat(warningArgs); | ||
result.warn.apply(result, warningArgs); | ||
}); | ||
// Keep the chain going | ||
return valueResult; | ||
}; | ||
// Chainable helper function to log any messages (warnings) | ||
var logResolveValueResult = function(valueResult) { | ||
// Log any warnings that might of popped up | ||
var warningList = [].concat(valueResult.warnings); | ||
warningList.forEach(function(warningArgs) { | ||
warningArgs = [].concat(warningArgs); | ||
result.warn.apply(result, warningArgs); | ||
}); | ||
// Collect all of the variables defined | ||
// --------------------------------------------------------- | ||
// --------------------------------------------------------- | ||
//console.log('Collecting variables defined START'); | ||
eachCssVariableDeclaration(css, function(decl) { | ||
var declParentRule = decl.parent; | ||
// Keep the chain going | ||
return valueResult; | ||
}; | ||
var valueResults = logResolveValueResult(resolveValue(decl, map)); | ||
// Split out each selector piece into its own declaration for easier logic down the road | ||
decl.parent.selectors.forEach(function(selector) { | ||
// Create a detached clone | ||
var splitOutRule = shallowCloneNode(decl.parent); | ||
splitOutRule.selector = selector; | ||
splitOutRule.parent = decl.parent.parent; | ||
var declClone = decl.clone(); | ||
splitOutRule.append(declClone); | ||
// Collect all of the variables defined | ||
// --------------------------------------------------------- | ||
// --------------------------------------------------------- | ||
//console.log('Collecting variables defined START'); | ||
eachCssVariableDeclaration(css, function(decl) { | ||
var declParentRule = decl.parent; | ||
var prop = decl.prop; | ||
map[prop] = (map[prop] || []).concat({ | ||
decl: declClone, | ||
prop: prop, | ||
calculatedInPlaceValue: valueResults.value, | ||
isImportant: decl.important || false, | ||
variablesUsed: valueResults.variablesUsed, | ||
parent: splitOutRule, | ||
// variables inside root or at-rules (eg. @media, @support) | ||
isUnderAtRule: splitOutRule.parent.type === "atrule" | ||
}); | ||
}); | ||
var valueResults = logResolveValueResult(resolveValue(decl, map)); | ||
// Split out each selector piece into its own declaration for easier logic down the road | ||
decl.parent.selectors.forEach(function(selector) { | ||
// Create a detached clone | ||
var splitOutRule = shallowCloneNode(decl.parent); | ||
splitOutRule.selector = selector; | ||
splitOutRule.parent = decl.parent.parent; | ||
let preserveDecl; | ||
if (typeof opts.preserve === "function") { | ||
preserveDecl = opts.preserve(decl); | ||
} else { | ||
preserveDecl = opts.preserve; | ||
} | ||
// Remove the variable declaration because they are pretty much useless after we resolve them | ||
if (!preserveDecl) { | ||
decl.remove(); | ||
} | ||
// Or we can also just show the computed value used for that variable | ||
else if (preserveDecl === "computed") { | ||
decl.value = valueResults.value; | ||
} | ||
// Otherwise just keep them as var declarations | ||
//else {} | ||
var declClone = decl.clone(); | ||
splitOutRule.append(declClone); | ||
// We add to the clean up list if we removed some variable declarations to make it become an empty rule | ||
// We clean up later on because we don't want to modify the AST when we still need to reference these later on | ||
if (declParentRule.nodes.length <= 0) { | ||
nodesToRemoveAtEnd.push(declParentRule); | ||
} | ||
}); | ||
//console.log('Collecting variables defined END'); | ||
var prop = decl.prop; | ||
map[prop] = (map[prop] || []).concat({ | ||
decl: declClone, | ||
prop: prop, | ||
calculatedInPlaceValue: valueResults.value, | ||
isImportant: decl.important || false, | ||
variablesUsed: valueResults.variablesUsed, | ||
parent: splitOutRule, | ||
// variables inside root or at-rules (eg. @media, @support) | ||
isUnderAtRule: splitOutRule.parent.type === 'atrule' | ||
}); | ||
}); | ||
// Resolve variables everywhere | ||
// --------------------------------------------------------- | ||
// --------------------------------------------------------- | ||
let preserveDecl; | ||
if (typeof opts.preserve === "function") { | ||
preserveDecl = opts.preserve(decl); | ||
} else { | ||
preserveDecl = opts.preserve; | ||
} | ||
// Remove the variable declaration because they are pretty much useless after we resolve them | ||
if(!preserveDecl) { | ||
decl.remove(); | ||
} | ||
// Or we can also just show the computed value used for that variable | ||
else if(preserveDecl === 'computed') { | ||
decl.value = valueResults.value; | ||
} | ||
// Otherwise just keep them as var declarations | ||
//else {} | ||
// Collect all the rules that have declarations that use variables | ||
var rulesThatHaveDeclarationsWithVariablesList = []; | ||
css.walk(function(rule) { | ||
// We're only interested in Containers with children. | ||
if (rule.nodes === undefined) return; | ||
// We add to the clean up list if we removed some variable declarations to make it become an empty rule | ||
// We clean up later on because we don't want to modify the AST when we still need to reference these later on | ||
if(declParentRule.nodes.length <= 0) { | ||
nodesToRemoveAtEnd.push(declParentRule); | ||
} | ||
}); | ||
//console.log('Collecting variables defined END'); | ||
var doesRuleUseVariables = rule.nodes.some(function(node) { | ||
if (node.type === "decl") { | ||
var decl = node; | ||
// If it uses variables | ||
// and is not a variable declarations that we may be preserving from earlier | ||
if ( | ||
resolveValue.RE_VAR_FUNC.test(decl.value) && | ||
!RE_VAR_PROP.test(decl.prop) | ||
) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
}); | ||
if (doesRuleUseVariables) { | ||
rulesThatHaveDeclarationsWithVariablesList.push(rule); | ||
} | ||
}); | ||
rulesThatHaveDeclarationsWithVariablesList.forEach(function(rule) { | ||
var rulesToWorkOn = [].concat(rule); | ||
// Split out the rule into each comma separated selector piece | ||
// We only need to split if it's actually a Rule with multiple selectors (comma separated) | ||
if (rule.type === "rule" && rule.selectors.length > 1) { | ||
// Reverse the selectors so that we can cloneAfter in the same comma separated order | ||
rulesToWorkOn = rule.selectors.reverse().map(function(selector) { | ||
var ruleClone = rule.cloneAfter(); | ||
ruleClone.selector = selector; | ||
return ruleClone; | ||
}); | ||
// Resolve variables everywhere | ||
// --------------------------------------------------------- | ||
// --------------------------------------------------------- | ||
rule.remove(); | ||
} | ||
// Collect all the rules that have declarations that use variables | ||
var rulesThatHaveDeclarationsWithVariablesList = []; | ||
css.walkRules(function(rule) { | ||
var doesRuleUseVariables = rule.nodes.some(function(node) { | ||
if(node.type === 'decl') { | ||
var decl = node; | ||
// If it uses variables | ||
// and is not a variable declarations that we may be preserving from earlier | ||
if(resolveValue.RE_VAR_FUNC.test(decl.value) && !RE_VAR_PROP.test(decl.prop)) { | ||
return true; | ||
} | ||
} | ||
// Resolve the declarations | ||
rulesToWorkOn.forEach(function(ruleToWorkOn) { | ||
ruleToWorkOn.nodes.slice(0).forEach(function(node) { | ||
if (node.type === "decl") { | ||
var decl = node; | ||
resolveDecl( | ||
decl, | ||
map, | ||
opts.preserve, | ||
opts.preserveAtRulesOrder, | ||
logResolveValueResult | ||
); | ||
} | ||
}); | ||
}); | ||
}); | ||
return false; | ||
}); | ||
// Clean up any nodes we don't want anymore | ||
// We clean up at the end because we don't want to modify the AST when we still need to reference these later on | ||
nodesToRemoveAtEnd.forEach(cleanUpNode); | ||
if(doesRuleUseVariables) { | ||
rulesThatHaveDeclarationsWithVariablesList.push(rule); | ||
} | ||
}); | ||
// Clean up JS-injected variables marked for removal | ||
injectedDeclsToRemoveAtEnd.forEach(function(injectedDecl) { | ||
injectedDecl.remove(); | ||
}); | ||
rulesThatHaveDeclarationsWithVariablesList.forEach(function(rule) { | ||
var rulesToWorkOn = [].concat(rule); | ||
// Split out the rule into each comma separated selector piece | ||
// We only need to split if is actually comma separted(selectors > 1) | ||
if(rule.selectors.length > 1) { | ||
// Reverse the selectors so that we can cloneAfter in the same comma separated order | ||
rulesToWorkOn = rule.selectors.reverse().map(function(selector) { | ||
var ruleClone = rule.cloneAfter(); | ||
ruleClone.selector = selector; | ||
//console.log('map', map); | ||
return ruleClone; | ||
}); | ||
rule.remove(); | ||
} | ||
// Resolve the declarations | ||
rulesToWorkOn.forEach(function(ruleToWorkOn) { | ||
ruleToWorkOn.nodes.slice(0).forEach(function(node) { | ||
if(node.type === 'decl') { | ||
var decl = node; | ||
resolveDecl(decl, map, opts.preserve, opts.preserveAtRulesOrder, logResolveValueResult); | ||
} | ||
}); | ||
}); | ||
}); | ||
// Clean up any nodes we don't want anymore | ||
// We clean up at the end because we don't want to modify the AST when we still need to reference these later on | ||
nodesToRemoveAtEnd.forEach(cleanUpNode); | ||
// Clean up JS-injected variables marked for removal | ||
injectedDeclsToRemoveAtEnd.forEach(function(injectedDecl) { | ||
injectedDecl.remove(); | ||
}); | ||
//console.log('map', map); | ||
/* * / | ||
/* * / | ||
} | ||
@@ -292,4 +292,3 @@ catch(e) { | ||
/* */ | ||
}; | ||
}; | ||
}); |
{ | ||
"name": "postcss-css-variables", | ||
"version": "0.16.0", | ||
"version": "0.17.0", | ||
"description": "PostCSS plugin to transform CSS Custom Properties(CSS variables) syntax into a static representation", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
61584
2.04%922
2.22%