2022-03-09 12:52:11 +01:00
|
|
|
/*
|
|
|
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2022-03-09 07:18:53 +01:00
|
|
|
const valueParser = require("postcss-value-parser");
|
2022-03-03 15:28:46 +01:00
|
|
|
|
2022-03-23 12:55:12 +01:00
|
|
|
/**
|
|
|
|
* This plugin derives new css variables from a given set of base variables.
|
|
|
|
* A derived css variable has the form --base--operation-argument; meaning that the derived
|
|
|
|
* variable has a value that is generated from the base variable "base" by applying "operation"
|
|
|
|
* with given "argument".
|
|
|
|
*
|
|
|
|
* eg: given the base variable --foo-color: #40E0D0, --foo-color--darker-20 is a css variable
|
|
|
|
* derived from foo-color by making it 20% more darker.
|
|
|
|
*
|
|
|
|
* All derived variables are added to the :root section.
|
|
|
|
*
|
|
|
|
* The actual derivation is done outside the plugin in a callback.
|
|
|
|
*/
|
|
|
|
|
2022-03-03 15:28:46 +01:00
|
|
|
let aliasMap;
|
|
|
|
let resolvedMap;
|
2022-03-14 18:56:37 +01:00
|
|
|
let baseVariables;
|
2022-03-03 15:28:46 +01:00
|
|
|
|
2022-03-14 18:56:37 +01:00
|
|
|
function getValueFromAlias(alias) {
|
2022-03-23 12:42:14 +01:00
|
|
|
const derivedVariable = aliasMap.get(alias);
|
2022-03-14 18:56:37 +01:00
|
|
|
return baseVariables.get(derivedVariable) ?? resolvedMap.get(derivedVariable);
|
2022-03-03 15:28:46 +01:00
|
|
|
}
|
|
|
|
|
2022-03-09 07:18:53 +01:00
|
|
|
function parseDeclarationValue(value) {
|
|
|
|
const parsed = valueParser(value);
|
|
|
|
const variables = [];
|
|
|
|
parsed.walk(node => {
|
|
|
|
if (node.type !== "function" && node.value !== "var") {
|
|
|
|
return;
|
2022-03-07 07:03:44 +01:00
|
|
|
}
|
2022-03-09 07:18:53 +01:00
|
|
|
const variable = node.nodes[0];
|
|
|
|
variables.push(variable.value);
|
|
|
|
});
|
|
|
|
return variables;
|
|
|
|
}
|
|
|
|
|
2022-03-14 18:56:37 +01:00
|
|
|
function resolveDerivedVariable(decl, derive) {
|
2022-03-23 16:09:24 +01:00
|
|
|
const RE_VARIABLE_VALUE = /--((.+)--(.+)-(.+))/;
|
2022-03-09 07:18:53 +01:00
|
|
|
const variableCollection = parseDeclarationValue(decl.value);
|
|
|
|
for (const variable of variableCollection) {
|
|
|
|
const matches = variable.match(RE_VARIABLE_VALUE);
|
|
|
|
if (matches) {
|
2022-03-23 16:09:24 +01:00
|
|
|
const [, wholeVariable, baseVariable, operation, argument] = matches;
|
2022-03-23 12:42:14 +01:00
|
|
|
const value = baseVariables.get(baseVariable) ?? getValueFromAlias(baseVariable);
|
2022-03-10 11:35:13 +01:00
|
|
|
if (!value) {
|
|
|
|
throw new Error(`Cannot derive from ${baseVariable} because it is neither defined in config nor is it an alias!`);
|
|
|
|
}
|
2022-03-09 12:50:05 +01:00
|
|
|
const derivedValue = derive(value, operation, argument);
|
|
|
|
resolvedMap.set(wholeVariable, derivedValue);
|
2022-03-07 07:03:44 +01:00
|
|
|
}
|
2022-03-03 15:28:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-14 18:56:37 +01:00
|
|
|
function extract(decl) {
|
2022-03-10 12:54:32 +01:00
|
|
|
if (decl.variable) {
|
2022-03-14 18:56:37 +01:00
|
|
|
// see if right side is of form "var(--foo)"
|
2022-03-23 16:09:24 +01:00
|
|
|
const wholeVariable = decl.value.match(/var\(--(.+)\)/)?.[1];
|
|
|
|
// remove -- from the prop
|
|
|
|
const prop = decl.prop.substring(2);
|
2022-03-10 12:54:32 +01:00
|
|
|
if (wholeVariable) {
|
2022-03-23 16:09:24 +01:00
|
|
|
aliasMap.set(prop, wholeVariable);
|
2022-03-14 18:56:37 +01:00
|
|
|
// Since this is an alias, we shouldn't store it in baseVariables
|
|
|
|
return;
|
2022-03-10 12:54:32 +01:00
|
|
|
}
|
2022-03-23 16:09:24 +01:00
|
|
|
baseVariables.set(prop, decl.value);
|
2022-03-03 15:28:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-14 18:56:37 +01:00
|
|
|
function addResolvedVariablesToRootSelector(root, {Rule, Declaration}) {
|
2022-03-07 07:02:30 +01:00
|
|
|
const newRule = new Rule({ selector: ":root", source: root.source });
|
|
|
|
// Add derived css variables to :root
|
|
|
|
resolvedMap.forEach((value, key) => {
|
2022-03-23 16:09:24 +01:00
|
|
|
const declaration = new Declaration({prop: `--${key}`, value});
|
2022-03-07 07:02:30 +01:00
|
|
|
newRule.append(declaration);
|
|
|
|
});
|
|
|
|
root.append(newRule);
|
|
|
|
}
|
|
|
|
|
2022-04-01 12:53:33 +02:00
|
|
|
function populateMapWithDerivedVariables(map, cssFileLocation) {
|
|
|
|
const location = cssFileLocation.match(/(.+)\/.+\.css/)?.[1];
|
|
|
|
if (map.has(location)) {
|
|
|
|
/**
|
|
|
|
* This postcss plugin is going to run on all theme variants of a single theme.
|
|
|
|
* But we only really need to populate the map once since theme variants only differ
|
|
|
|
* by the values of the base-variables and we don't care about values here.
|
|
|
|
*/
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const derivedVariables = new Set([
|
|
|
|
...([...resolvedMap.keys()].filter(v => !aliasMap.has(v))),
|
|
|
|
...([...aliasMap.entries()].map(([alias, variable]) => `${alias}=${variable}`))
|
|
|
|
]);
|
|
|
|
map.set(location, { "derived-variables": derivedVariables });
|
|
|
|
}
|
|
|
|
|
2022-03-10 12:49:04 +01:00
|
|
|
/**
|
|
|
|
* @callback derive
|
|
|
|
* @param {string} value - The base value on which an operation is applied
|
|
|
|
* @param {string} operation - The operation to be applied (eg: darker, lighter...)
|
|
|
|
* @param {string} argument - The argument for this operation
|
|
|
|
*/
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {Object} opts - Options for the plugin
|
|
|
|
* @param {derive} opts.derive - The callback which contains the logic for resolving derived variables
|
2022-04-01 12:53:33 +02:00
|
|
|
* @param {Map} opts.compiledVariables - A map that stores derived variables so that manifest source sections can be produced
|
2022-03-03 15:28:46 +01:00
|
|
|
*/
|
|
|
|
module.exports = (opts = {}) => {
|
2022-03-07 07:03:44 +01:00
|
|
|
aliasMap = new Map();
|
|
|
|
resolvedMap = new Map();
|
2022-03-14 18:56:37 +01:00
|
|
|
baseVariables = new Map();
|
2022-03-07 07:03:44 +01:00
|
|
|
return {
|
|
|
|
postcssPlugin: "postcss-compile-variables",
|
2022-03-03 15:28:46 +01:00
|
|
|
|
2022-03-27 16:36:26 +02:00
|
|
|
Once(root, {Rule, Declaration, result}) {
|
2022-04-01 12:50:58 +02:00
|
|
|
const cssFileLocation = root.source.input.from;
|
|
|
|
if (cssFileLocation.includes("type=runtime")) {
|
|
|
|
// If this is a runtime theme, don't derive variables.
|
|
|
|
return;
|
|
|
|
}
|
2022-03-07 07:03:44 +01:00
|
|
|
/*
|
2022-03-14 18:56:37 +01:00
|
|
|
Go through the CSS file once to extract all aliases and base variables.
|
|
|
|
We use these when resolving derived variables later.
|
2022-03-07 07:03:44 +01:00
|
|
|
*/
|
2022-03-14 18:56:37 +01:00
|
|
|
root.walkDecls(decl => extract(decl));
|
|
|
|
root.walkDecls(decl => resolveDerivedVariable(decl, opts.derive));
|
|
|
|
addResolvedVariablesToRootSelector(root, {Rule, Declaration});
|
2022-04-01 12:53:33 +02:00
|
|
|
if (opts.compiledVariables){
|
|
|
|
populateMapWithDerivedVariables(opts.compiledVariables, cssFileLocation);
|
|
|
|
}
|
2022-03-27 16:36:26 +02:00
|
|
|
// 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,
|
2022-04-01 12:53:33 +02:00
|
|
|
});
|
2022-03-07 07:03:44 +01:00
|
|
|
},
|
|
|
|
};
|
2022-03-03 15:28:46 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
module.exports.postcss = true;
|