Extract base variables from css
This commit is contained in:
parent
bca1648df6
commit
19a6d669a9
2 changed files with 43 additions and 42 deletions
|
@ -18,10 +18,11 @@ const valueParser = require("postcss-value-parser");
|
|||
|
||||
let aliasMap;
|
||||
let resolvedMap;
|
||||
let baseVariables;
|
||||
|
||||
function getValueFromAlias(alias, variables) {
|
||||
function getValueFromAlias(alias) {
|
||||
const derivedVariable = aliasMap.get(`--${alias}`);
|
||||
return variables[derivedVariable] ?? resolvedMap.get(`--${derivedVariable}`);
|
||||
return baseVariables.get(derivedVariable) ?? resolvedMap.get(derivedVariable);
|
||||
}
|
||||
|
||||
function parseDeclarationValue(value) {
|
||||
|
@ -37,14 +38,14 @@ function parseDeclarationValue(value) {
|
|||
return variables;
|
||||
}
|
||||
|
||||
function resolveDerivedVariable(decl, {variables, derive}) {
|
||||
function resolveDerivedVariable(decl, derive) {
|
||||
const RE_VARIABLE_VALUE = /--(.+)--(.+)-(.+)/;
|
||||
const variableCollection = parseDeclarationValue(decl.value);
|
||||
for (const variable of variableCollection) {
|
||||
const matches = variable.match(RE_VARIABLE_VALUE);
|
||||
if (matches) {
|
||||
const [wholeVariable, baseVariable, operation, argument] = matches;
|
||||
const value = variables[baseVariable] ?? getValueFromAlias(baseVariable, variables);
|
||||
const value = baseVariables.get(`--${baseVariable}`) ?? getValueFromAlias(baseVariable);
|
||||
if (!value) {
|
||||
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) {
|
||||
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) {
|
||||
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 });
|
||||
// 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
|
||||
resolvedMap.forEach((value, key) => {
|
||||
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.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
|
||||
*/
|
||||
module.exports = (opts = {}) => {
|
||||
aliasMap = new Map();
|
||||
resolvedMap = new Map();
|
||||
baseVariables = new Map();
|
||||
return {
|
||||
postcssPlugin: "postcss-compile-variables",
|
||||
|
||||
Once(root, {Rule, Declaration}) {
|
||||
/*
|
||||
Go through the CSS file once to extract all aliases.
|
||||
We use the extracted alias when resolving derived variables
|
||||
later.
|
||||
Go through the CSS file once to extract all aliases and base variables.
|
||||
We use these when resolving derived variables later.
|
||||
*/
|
||||
root.walkDecls(decl => extractAlias(decl));
|
||||
root.walkDecls(decl => resolveDerivedVariable(decl, opts));
|
||||
addResolvedVariablesToRootSelector(root, opts.variables, {Rule, Declaration});
|
||||
root.walkDecls(decl => extract(decl));
|
||||
root.walkDecls(decl => resolveDerivedVariable(decl, opts.derive));
|
||||
addResolvedVariablesToRootSelector(root, {Rule, Declaration});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -31,7 +31,11 @@ async function run(input, output, opts = {}, assert) {
|
|||
module.exports.tests = function tests() {
|
||||
return {
|
||||
"derived variables are resolved": async (assert) => {
|
||||
const inputCSS = `div {
|
||||
const inputCSS = `
|
||||
:root {
|
||||
--foo-color: #ff0;
|
||||
}
|
||||
div {
|
||||
background-color: var(--foo-color--lighter-50);
|
||||
}`;
|
||||
const transformedColor = offColor("#ff0").lighten(0.5);
|
||||
|
@ -39,38 +43,30 @@ module.exports.tests = function tests() {
|
|||
inputCSS +
|
||||
`
|
||||
:root {
|
||||
--foo-color: #ff0;
|
||||
--foo-color--lighter-50: ${transformedColor.hex()};
|
||||
}
|
||||
`;
|
||||
await run(
|
||||
inputCSS,
|
||||
outputCSS,
|
||||
{ variables: { "foo-color": "#ff0" } },
|
||||
assert
|
||||
);
|
||||
await run( inputCSS, outputCSS, {}, assert);
|
||||
},
|
||||
|
||||
"derived variables work with alias": async (assert) => {
|
||||
const inputCSS = `div {
|
||||
const inputCSS = `
|
||||
:root {
|
||||
--icon-color: #fff;
|
||||
}
|
||||
div {
|
||||
background: var(--icon-color--darker-20);
|
||||
--my-alias: var(--icon-color--darker-20);
|
||||
color: var(--my-alias--lighter-15);
|
||||
}`;
|
||||
const colorDarker = offColor("#fff").darken(0.2).hex();
|
||||
const aliasLighter = offColor(colorDarker).lighten(0.15).hex();
|
||||
const outputCSS = `div {
|
||||
background: var(--icon-color--darker-20);
|
||||
--my-alias: var(--icon-color--darker-20);
|
||||
color: var(--my-alias--lighter-15);
|
||||
}
|
||||
:root {
|
||||
--icon-color: #fff;
|
||||
const outputCSS = inputCSS + `:root {
|
||||
--icon-color--darker-20: ${colorDarker};
|
||||
--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) => {
|
||||
|
@ -81,7 +77,11 @@ module.exports.tests = function tests() {
|
|||
},
|
||||
|
||||
"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));
|
||||
}`;
|
||||
const transformedColor1 = offColor("#ff0").lighten(0.5);
|
||||
|
@ -90,15 +90,18 @@ module.exports.tests = function tests() {
|
|||
inputCSS +
|
||||
`
|
||||
:root {
|
||||
--foo-color: #ff0;
|
||||
--foo-color--lighter-50: ${transformedColor1.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) => {
|
||||
const inputCSS = `div {
|
||||
const inputCSS = `
|
||||
:root {
|
||||
--foo-color: #ff0;
|
||||
}
|
||||
div {
|
||||
--my-alias: var(--foo-color);
|
||||
background-color: linear-gradient(var(--my-alias--lighter-50), var(--my-alias--darker-20));
|
||||
}`;
|
||||
|
@ -108,12 +111,11 @@ module.exports.tests = function tests() {
|
|||
inputCSS +
|
||||
`
|
||||
:root {
|
||||
--foo-color: #ff0;
|
||||
--my-alias--lighter-50: ${transformedColor1.hex()};
|
||||
--my-alias--darker-20: ${transformedColor2.hex()};
|
||||
}
|
||||
`;
|
||||
await run( inputCSS, outputCSS, { variables: { "foo-color": "#ff0" } }, assert);
|
||||
await run( inputCSS, outputCSS, { }, assert);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
Reference in a new issue