129 lines
3.9 KiB
JavaScript
129 lines
3.9 KiB
JavaScript
import { readFileSync, existsSync } from "fs";
|
|
// TODO import.meta.resolve is supposed to accomplish this without "path"
|
|
import path from "path";
|
|
import { stringify, parse } from "css";
|
|
|
|
export function style(options) {
|
|
if (options.inline || options.css === "none") return "";
|
|
const rules = load(options);
|
|
const schemes = rules.find(({ media }) => media)
|
|
? `<meta name="color-scheme" content="dark light">`
|
|
: "";
|
|
return `\n${schemes}\n<style>${stringify(
|
|
{
|
|
stylesheet: { rules: reduceVariables(rules, options) },
|
|
},
|
|
{ compress: true }
|
|
)}</style>`;
|
|
}
|
|
|
|
export const inline = (options) => {
|
|
const rules = load(options);
|
|
return function inlineCSS(tag) {
|
|
if (!options.inline || options.css === "none") return "";
|
|
const styles = reduceVariables(rules, options)
|
|
.filter(({ selectors }) => selectors && selectors.includes(tag))
|
|
.reduce((style, { declarations }) => {
|
|
declarations.forEach(({ property, value }) => {
|
|
style = `${style}${property}:${value};`;
|
|
});
|
|
return style;
|
|
}, "");
|
|
return styles !== "" ? ` style="${styles}"` : "";
|
|
};
|
|
};
|
|
|
|
export function load(options) {
|
|
options.css =
|
|
options.css ||
|
|
(!options.body || !options.inline ? "gmi-web.css" : "gmi.css");
|
|
if (
|
|
["gmi.css", "gmi-web.css"].includes(options.css) ||
|
|
existsSync(options.css)
|
|
) {
|
|
const internal = (file) =>
|
|
path.resolve(path.dirname(new URL(import.meta.url).pathname), file);
|
|
return parse(
|
|
readFileSync(
|
|
path.resolve(
|
|
["gmi-web.css", "gmi.css"].includes(options.css)
|
|
? internal(options.css)
|
|
: resolve(options.css)
|
|
),
|
|
"utf-8"
|
|
)
|
|
).stylesheet.rules;
|
|
} else {
|
|
throw new Error(`Cannot find file ${options.css}.`);
|
|
}
|
|
}
|
|
|
|
export function rootVariables(rules) {
|
|
const root = rules.find(
|
|
({ selectors }) => selectors && selectors.includes(":root")
|
|
);
|
|
if (!root) return {};
|
|
return root.declarations.reduce(
|
|
(obj, { property, value }) =>
|
|
!/^\-\-/.test(property) ? obj : Object.assign(obj, { [property]: value }),
|
|
{}
|
|
);
|
|
}
|
|
|
|
function reduceVariables(rules, options) {
|
|
const defaultVariables = rootVariables(rules);
|
|
const CSS_VAR = /(^var\((?<key>.+)\)|(?<val>.+))/;
|
|
return rules
|
|
.map((rule) =>
|
|
Object.assign(
|
|
rule,
|
|
rule.declarations
|
|
? {
|
|
declarations: rule.declarations.map((declaration) => {
|
|
let { key, val } = CSS_VAR.exec(declaration.value).groups;
|
|
// only one level of variable referencing is supported
|
|
key =
|
|
CSS_VAR.exec(options[key] || defaultVariables[key]).groups
|
|
.key || key;
|
|
return Object.assign(declaration, {
|
|
value: key
|
|
? options[key] || defaultVariables[key]
|
|
: declaration.value,
|
|
});
|
|
}),
|
|
}
|
|
: {}
|
|
)
|
|
)
|
|
.map((rule) => {
|
|
if (!rule.media) return rule;
|
|
return Object.assign(rule, {
|
|
rules: rule.rules.map((rule) =>
|
|
Object.assign(
|
|
rule,
|
|
rule.declarations
|
|
? {
|
|
declarations: rule.declarations.map((declaration) => {
|
|
let { key, val } = CSS_VAR.exec(declaration.value).groups;
|
|
// only one level of variable referencing is supported
|
|
key =
|
|
CSS_VAR.exec(options[key] || defaultVariables[key]).groups
|
|
.key || key;
|
|
return Object.assign(declaration, {
|
|
value: key
|
|
? options[key] || defaultVariables[key]
|
|
: declaration.value,
|
|
});
|
|
}),
|
|
}
|
|
: {}
|
|
)
|
|
),
|
|
});
|
|
})
|
|
.filter(
|
|
({ selectors, media }) =>
|
|
media || (selectors && !selectors.includes(":root"))
|
|
);
|
|
}
|