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 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});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
Reference in a new issue