Merge pull request #709 from vector-im/compile-variables-improvement

Theming - postcss-compile-variables improvement
This commit is contained in:
R Midhun Suresh 2022-04-07 11:31:59 +05:30 committed by GitHub
commit e0bc9b31a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 90 additions and 7 deletions

View file

@ -43,6 +43,7 @@
"node-html-parser": "^4.0.0", "node-html-parser": "^4.0.0",
"postcss-css-variables": "^0.18.0", "postcss-css-variables": "^0.18.0",
"postcss-flexbugs-fixes": "^5.0.2", "postcss-flexbugs-fixes": "^5.0.2",
"postcss-value-parser": "^4.2.0",
"regenerator-runtime": "^0.13.7", "regenerator-runtime": "^0.13.7",
"text-encoding": "^0.7.0", "text-encoding": "^0.7.0",
"typescript": "^4.3.5", "typescript": "^4.3.5",
@ -54,7 +55,6 @@
"another-json": "^0.2.0", "another-json": "^0.2.0",
"base64-arraybuffer": "^0.2.0", "base64-arraybuffer": "^0.2.0",
"dompurify": "^2.3.0", "dompurify": "^2.3.0",
"off-color": "^2.0.0", "off-color": "^2.0.0"
"postcss-value-parser": "^4.2.0"
} }
} }

View file

@ -43,17 +43,32 @@ function parseDeclarationValue(value) {
const parsed = valueParser(value); const parsed = valueParser(value);
const variables = []; const variables = [];
parsed.walk(node => { parsed.walk(node => {
if (node.type !== "function" && node.value !== "var") { if (node.type !== "function") {
return; return;
} }
const variable = node.nodes[0]; switch (node.value) {
variables.push(variable.value); case "var": {
const variable = node.nodes[0];
variables.push(variable.value);
break;
}
case "url": {
const url = node.nodes[0].value;
// resolve url with some absolute url so that we get the query params without using regex
const params = new URL(url, "file://foo/bar/").searchParams;
const primary = params.get("primary");
const secondary = params.get("secondary");
if (primary) { variables.push(primary); }
if (secondary) { variables.push(secondary); }
break;
}
}
}); });
return variables; return variables;
} }
function resolveDerivedVariable(decl, 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);
@ -94,6 +109,15 @@ function addResolvedVariablesToRootSelector(root, {Rule, Declaration}) {
root.append(newRule); root.append(newRule);
} }
function populateMapWithDerivedVariables(map, cssFileLocation) {
const location = cssFileLocation.match(/(.+)\/.+\.css/)?.[1];
const derivedVariables = [
...([...resolvedMap.keys()].filter(v => !aliasMap.has(v))),
...([...aliasMap.entries()].map(([alias, variable]) => `${alias}=${variable}`))
];
map.set(location, { "derived-variables": derivedVariables });
}
/** /**
* @callback derive * @callback derive
* @param {string} value - The base value on which an operation is applied * @param {string} value - The base value on which an operation is applied
@ -104,6 +128,7 @@ function addResolvedVariablesToRootSelector(root, {Rule, Declaration}) {
* *
* @param {Object} opts - Options for the plugin * @param {Object} opts - Options for the plugin
* @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
* @param {Map} opts.compiledVariables - A map that stores derived variables so that manifest source sections can be produced
*/ */
module.exports = (opts = {}) => { module.exports = (opts = {}) => {
aliasMap = new Map(); aliasMap = new Map();
@ -112,7 +137,12 @@ module.exports = (opts = {}) => {
return { return {
postcssPlugin: "postcss-compile-variables", postcssPlugin: "postcss-compile-variables",
Once(root, {Rule, Declaration}) { Once(root, {Rule, Declaration, result}) {
const cssFileLocation = root.source.input.from;
if (cssFileLocation.includes("type=runtime")) {
// If this is a runtime theme, don't derive variables.
return;
}
/* /*
Go through the CSS file once to extract all aliases and base variables. Go through the CSS file once to extract all aliases and base variables.
We use these when resolving derived variables later. We use these when resolving derived variables later.
@ -120,6 +150,16 @@ module.exports = (opts = {}) => {
root.walkDecls(decl => extract(decl)); root.walkDecls(decl => extract(decl));
root.walkDecls(decl => resolveDerivedVariable(decl, opts.derive)); root.walkDecls(decl => resolveDerivedVariable(decl, opts.derive));
addResolvedVariablesToRootSelector(root, {Rule, Declaration}); addResolvedVariablesToRootSelector(root, {Rule, Declaration});
if (opts.compiledVariables){
populateMapWithDerivedVariables(opts.compiledVariables, cssFileLocation);
}
// Publish both the base-variables and derived-variables to the other postcss-plugins
const combinedMap = new Map([...baseVariables, ...resolvedMap]);
result.messages.push({
type: "resolved-variable-map",
plugin: "postcss-compile-variables",
colorMap: combinedMap,
});
}, },
}; };
}; };

View file

@ -96,6 +96,7 @@ module.exports.tests = function tests() {
`; `;
await run( inputCSS, outputCSS, { }, 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 = ` const inputCSS = `
:root { :root {
@ -116,6 +117,48 @@ module.exports.tests = function tests() {
} }
`; `;
await run( inputCSS, outputCSS, { }, assert); await run( inputCSS, outputCSS, { }, assert);
},
"compiledVariables map is populated": async (assert) => {
const compiledVariables = new Map();
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);
}`;
await postcss([plugin({ derive, compiledVariables })]).process(inputCSS, { from: "/foo/bar/test.css", });
const actualArray = compiledVariables.get("/foo/bar")["derived-variables"];
const expectedArray = ["icon-color--darker-20", "my-alias=icon-color--darker-20", "my-alias--lighter-15"];
assert.deepStrictEqual(actualArray.sort(), expectedArray.sort());
},
"derived variable are supported in urls": async (assert) => {
const inputCSS = `
:root {
--foo-color: #ff0;
}
div {
background-color: var(--foo-color--lighter-50);
background: url("./foo/bar/icon.svg?primary=foo-color--darker-5");
}
a {
background: url("foo/bar/icon.svg");
}`;
const transformedColorLighter = offColor("#ff0").lighten(0.5);
const transformedColorDarker = offColor("#ff0").darken(0.05);
const outputCSS =
inputCSS +
`
:root {
--foo-color--lighter-50: ${transformedColorLighter.hex()};
--foo-color--darker-5: ${transformedColorDarker.hex()};
}
`;
await run( inputCSS, outputCSS, {}, assert);
} }
}; };
}; };