Extract base variables from css

This commit is contained in:
RMidhunSuresh 2022-03-14 23:26:37 +05:30
parent bca1648df6
commit 19a6d669a9
2 changed files with 43 additions and 42 deletions

View File

@ -18,10 +18,11 @@ const valueParser = require("postcss-value-parser");
let aliasMap; let aliasMap;
let resolvedMap; let resolvedMap;
let baseVariables;
function getValueFromAlias(alias, variables) { function getValueFromAlias(alias) {
const derivedVariable = aliasMap.get(`--${alias}`); const derivedVariable = aliasMap.get(`--${alias}`);
return variables[derivedVariable] ?? resolvedMap.get(`--${derivedVariable}`); return baseVariables.get(derivedVariable) ?? resolvedMap.get(derivedVariable);
} }
function parseDeclarationValue(value) { function parseDeclarationValue(value) {
@ -37,14 +38,14 @@ function parseDeclarationValue(value) {
return variables; return variables;
} }
function resolveDerivedVariable(decl, {variables, derive}) { function resolveDerivedVariable(decl, derive) {
const RE_VARIABLE_VALUE = /--(.+)--(.+)-(.+)/; const RE_VARIABLE_VALUE = /--(.+)--(.+)-(.+)/;
const variableCollection = parseDeclarationValue(decl.value); const variableCollection = parseDeclarationValue(decl.value);
for (const variable of variableCollection) { for (const variable of variableCollection) {
const matches = variable.match(RE_VARIABLE_VALUE); const matches = variable.match(RE_VARIABLE_VALUE);
if (matches) { if (matches) {
const [wholeVariable, baseVariable, operation, argument] = matches; const [wholeVariable, baseVariable, operation, argument] = matches;
const value = variables[baseVariable] ?? getValueFromAlias(baseVariable, variables); const value = baseVariables.get(`--${baseVariable}`) ?? getValueFromAlias(baseVariable);
if (!value) { if (!value) {
throw new Error(`Cannot derive from ${baseVariable} because it is neither defined in config nor is it an alias!`); throw new Error(`Cannot derive from ${baseVariable} because it is neither defined in config nor is it an alias!`);
} }
@ -54,22 +55,21 @@ function resolveDerivedVariable(decl, {variables, derive}) {
} }
} }
function extractAlias(decl) { function extract(decl) {
if (decl.variable) { if (decl.variable) {
const wholeVariable = decl.value.match(/var\(--(.+)\)/)?.[1]; // see if right side is of form "var(--foo)"
const wholeVariable = decl.value.match(/var\((--.+)\)/)?.[1];
if (wholeVariable) { if (wholeVariable) {
aliasMap.set(decl.prop, wholeVariable); aliasMap.set(decl.prop, wholeVariable);
// Since this is an alias, we shouldn't store it in baseVariables
return;
} }
baseVariables.set(decl.prop, decl.value);
} }
} }
function addResolvedVariablesToRootSelector(root, variables, {Rule, Declaration}) { function addResolvedVariablesToRootSelector(root, {Rule, Declaration}) {
const newRule = new Rule({ selector: ":root", source: root.source }); const newRule = new Rule({ selector: ":root", source: root.source });
// Add base css variables to :root
for (const [key, value] of Object.entries(variables)) {
const declaration = new Declaration({prop: `--${key}`, value});
newRule.append(declaration);
}
// Add derived css variables to :root // Add derived css variables to :root
resolvedMap.forEach((value, key) => { resolvedMap.forEach((value, key) => {
const declaration = new Declaration({prop: key, value}); const declaration = new Declaration({prop: key, value});
@ -87,24 +87,23 @@ function addResolvedVariablesToRootSelector(root, variables, {Rule, Declaration}
/** /**
* *
* @param {Object} opts - Options for the plugin * @param {Object} opts - Options for the plugin
* @param {Object} opts.variables - An object of the form: {base_variable_name_1: value, base_variable_name_2: value, ...}
* @param {derive} opts.derive - The callback which contains the logic for resolving derived variables * @param {derive} opts.derive - The callback which contains the logic for resolving derived variables
*/ */
module.exports = (opts = {}) => { module.exports = (opts = {}) => {
aliasMap = new Map(); aliasMap = new Map();
resolvedMap = new Map(); resolvedMap = new Map();
baseVariables = new Map();
return { return {
postcssPlugin: "postcss-compile-variables", postcssPlugin: "postcss-compile-variables",
Once(root, {Rule, Declaration}) { Once(root, {Rule, Declaration}) {
/* /*
Go through the CSS file once to extract all aliases. Go through the CSS file once to extract all aliases and base variables.
We use the extracted alias when resolving derived variables We use these when resolving derived variables later.
later.
*/ */
root.walkDecls(decl => extractAlias(decl)); root.walkDecls(decl => extract(decl));
root.walkDecls(decl => resolveDerivedVariable(decl, opts)); root.walkDecls(decl => resolveDerivedVariable(decl, opts.derive));
addResolvedVariablesToRootSelector(root, opts.variables, {Rule, Declaration}); addResolvedVariablesToRootSelector(root, {Rule, Declaration});
}, },
}; };
}; };

View File

@ -31,7 +31,11 @@ async function run(input, output, opts = {}, assert) {
module.exports.tests = function tests() { module.exports.tests = function tests() {
return { return {
"derived variables are resolved": async (assert) => { "derived variables are resolved": async (assert) => {
const inputCSS = `div { const inputCSS = `
:root {
--foo-color: #ff0;
}
div {
background-color: var(--foo-color--lighter-50); background-color: var(--foo-color--lighter-50);
}`; }`;
const transformedColor = offColor("#ff0").lighten(0.5); const transformedColor = offColor("#ff0").lighten(0.5);
@ -39,38 +43,30 @@ module.exports.tests = function tests() {
inputCSS + inputCSS +
` `
:root { :root {
--foo-color: #ff0;
--foo-color--lighter-50: ${transformedColor.hex()}; --foo-color--lighter-50: ${transformedColor.hex()};
} }
`; `;
await run( await run( inputCSS, outputCSS, {}, assert);
inputCSS,
outputCSS,
{ variables: { "foo-color": "#ff0" } },
assert
);
}, },
"derived variables work with alias": async (assert) => { "derived variables work with alias": async (assert) => {
const inputCSS = `div { const inputCSS = `
:root {
--icon-color: #fff;
}
div {
background: var(--icon-color--darker-20); background: var(--icon-color--darker-20);
--my-alias: var(--icon-color--darker-20); --my-alias: var(--icon-color--darker-20);
color: var(--my-alias--lighter-15); color: var(--my-alias--lighter-15);
}`; }`;
const colorDarker = offColor("#fff").darken(0.2).hex(); const colorDarker = offColor("#fff").darken(0.2).hex();
const aliasLighter = offColor(colorDarker).lighten(0.15).hex(); const aliasLighter = offColor(colorDarker).lighten(0.15).hex();
const outputCSS = `div { const outputCSS = inputCSS + `:root {
background: var(--icon-color--darker-20);
--my-alias: var(--icon-color--darker-20);
color: var(--my-alias--lighter-15);
}
:root {
--icon-color: #fff;
--icon-color--darker-20: ${colorDarker}; --icon-color--darker-20: ${colorDarker};
--my-alias--lighter-15: ${aliasLighter}; --my-alias--lighter-15: ${aliasLighter};
} }
`; `;
await run(inputCSS, outputCSS, { variables: { "icon-color": "#fff" }, }, assert); await run(inputCSS, outputCSS, { }, assert);
}, },
"derived variable throws if base not present in config": async (assert) => { "derived variable throws if base not present in config": async (assert) => {
@ -81,7 +77,11 @@ module.exports.tests = function tests() {
}, },
"multiple derived variable in single declaration is parsed correctly": async (assert) => { "multiple derived variable in single declaration is parsed correctly": async (assert) => {
const inputCSS = `div { const inputCSS = `
:root {
--foo-color: #ff0;
}
div {
background-color: linear-gradient(var(--foo-color--lighter-50), var(--foo-color--darker-20)); background-color: linear-gradient(var(--foo-color--lighter-50), var(--foo-color--darker-20));
}`; }`;
const transformedColor1 = offColor("#ff0").lighten(0.5); const transformedColor1 = offColor("#ff0").lighten(0.5);
@ -90,15 +90,18 @@ module.exports.tests = function tests() {
inputCSS + inputCSS +
` `
:root { :root {
--foo-color: #ff0;
--foo-color--lighter-50: ${transformedColor1.hex()}; --foo-color--lighter-50: ${transformedColor1.hex()};
--foo-color--darker-20: ${transformedColor2.hex()}; --foo-color--darker-20: ${transformedColor2.hex()};
} }
`; `;
await run( inputCSS, outputCSS, { variables: { "foo-color": "#ff0" } }, assert); await run( inputCSS, outputCSS, { }, assert);
}, },
"multiple aliased-derived variable in single declaration is parsed correctly": async (assert) => { "multiple aliased-derived variable in single declaration is parsed correctly": async (assert) => {
const inputCSS = `div { const inputCSS = `
:root {
--foo-color: #ff0;
}
div {
--my-alias: var(--foo-color); --my-alias: var(--foo-color);
background-color: linear-gradient(var(--my-alias--lighter-50), var(--my-alias--darker-20)); background-color: linear-gradient(var(--my-alias--lighter-50), var(--my-alias--darker-20));
}`; }`;
@ -108,12 +111,11 @@ module.exports.tests = function tests() {
inputCSS + inputCSS +
` `
:root { :root {
--foo-color: #ff0;
--my-alias--lighter-50: ${transformedColor1.hex()}; --my-alias--lighter-50: ${transformedColor1.hex()};
--my-alias--darker-20: ${transformedColor2.hex()}; --my-alias--darker-20: ${transformedColor2.hex()};
} }
`; `;
await run( inputCSS, outputCSS, { variables: { "foo-color": "#ff0" } }, assert); await run( inputCSS, outputCSS, { }, assert);
} }
}; };
}; };