@tbela99/css-parser
Version:
CSS parser, minifier and validator for node and the browser
150 lines (147 loc) • 5.6 kB
JavaScript
import { EnumToken } from '../types.js';
import { walkValues } from '../walk.js';
import { renderValue } from '../../renderer/render.js';
import { splitRule } from '../minify.js';
import { FeatureWalkMode } from './type.js';
import { RAW, mathFuncs } from '../../syntax/constants.js';
function inlineExpression(token) {
const result = [];
if (token.typ == EnumToken.BinaryExpressionTokenType) {
result.push({
typ: EnumToken.ParensTokenType,
chi: [
...inlineExpression(token.l),
{ typ: token.op },
...inlineExpression(token.r),
],
});
}
else {
result.push(token);
}
return result;
}
function replace(node, variableScope) {
for (const { value, parent: parentValue } of walkValues(node.val)) {
if (value.typ == EnumToken.BinaryExpressionTokenType && parentValue != null && "chi" in parentValue) {
// @ts-ignore
parentValue.chi.splice(parentValue.chi.indexOf(value), 1, ...inlineExpression(value));
}
}
for (const { value, parent: parentValue } of walkValues(node.val)) {
if (value.typ == EnumToken.MathFunctionTokenDefType ||
(value.typ == EnumToken.WildCardFunctionTokenType && value.val == "var")) {
if (value.chi.length == 1 &&
value.chi[0].typ == EnumToken.DashedIdenTokenType) {
const info = (variableScope.get(value.chi[0].val));
if (info?.replaceable) {
if (parentValue != null) {
let i = 0;
for (; i < parentValue.chi.length; i++) {
if (parentValue.chi[i] == value) {
parentValue.chi.splice(i, 1, ...info.node.val);
break;
}
}
}
else {
node.val = info.node.val.slice();
}
}
}
}
}
}
class InlineCssVariablesFeature {
accept = new Set([EnumToken.RuleNodeType, EnumToken.AtRuleNodeType]);
get ordering() {
return 0;
}
get processMode() {
return FeatureWalkMode.Pre;
}
static register(options) {
if (options.inlineCssVariables) {
// @ts-ignore
options.features.push(new InlineCssVariablesFeature());
}
}
run(ast, options = {}, parent, context) {
if (!("chi" in ast)) {
return null;
}
if (!("variableScope" in context)) {
context.variableScope = new Map();
}
// [':root', 'html']
const isRoot = parent.typ == EnumToken.StyleSheetNodeType &&
ast.typ == EnumToken.RuleNodeType &&
(ast[RAW] ?? splitRule(ast.sel)).some((segment) => segment.some((s) => s == ":root" || s == "html"));
const variableScope = context.variableScope;
// @ts-ignore
for (const node of ast.chi) {
if (node.typ !== EnumToken.DeclarationNodeType) {
continue;
}
// css variable
if (node.nam.startsWith("--")) {
if (!variableScope.has(node.val)) {
const info = {
globalScope: isRoot,
// @ts-ignore
parent: new Set(),
declarationCount: 1,
replaceable: isRoot,
node: node,
values: structuredClone(node.val),
};
info.parent.add(ast);
variableScope.set(node.nam, info);
let recursive = false;
for (const { value } of walkValues(node.val)) {
if (value?.typ == EnumToken.MathFunctionTokenDefType ||
(value?.typ == EnumToken.WildCardFunctionTokenType &&
(mathFuncs.includes(value.val) ||
value.val === "var"))) {
recursive = true;
break;
}
}
if (recursive) {
replace(node, variableScope);
}
}
}
else {
replace(node, variableScope);
}
}
return null;
}
cleanup(ast, options = {}, context) {
const variableScope = context.variableScope;
if (variableScope == null) {
return;
}
for (const info of variableScope.values()) {
if (info.replaceable) {
let i;
// drop declarations from :root{}
for (const parent of info.parent) {
i = parent.chi?.length ?? 0;
while (i--) {
if (parent.chi[i] == info.node) {
// @ts-ignore
parent.chi.splice(i, 1, {
typ: EnumToken.CommentTokenType,
val: `/* ${info.node.nam}: ${info.values.reduce((acc, curr) => acc + renderValue(curr, { convertColor: false }), "")} */`,
});
break;
}
}
}
}
}
}
}
export { InlineCssVariablesFeature };