gmi-web/css.js

117 lines
3.2 KiB
JavaScript
Raw Permalink Normal View History

2021-02-20 19:28:43 +00:00
import { readFileSync, existsSync } from "fs";
2021-02-18 23:16:05 +00:00
// TODO import.meta.resolve is supposed to accomplish this without "path"
2021-02-15 20:56:41 +00:00
import path from "path";
import { stringify, parse } from "css";
2021-02-25 20:46:53 +00:00
const internal = (file) =>
path.resolve(path.dirname(new URL(import.meta.url).pathname), file);
2021-02-15 20:56:41 +00:00
2021-02-20 19:28:43 +00:00
export function style(options) {
if (options.inline || options.css === "none") return "";
2021-02-25 17:11:25 +00:00
2021-02-20 19:28:43 +00:00
const rules = load(options);
2021-02-20 21:22:37 +00:00
const schemes = rules.find(({ media }) => media)
? `<meta name="color-scheme" content="dark light">`
2021-02-20 19:28:43 +00:00
: "";
2021-02-20 21:22:37 +00:00
return `\n${schemes}\n<style>${stringify(
2021-02-20 19:28:43 +00:00
{
stylesheet: { rules: reduceVariables(rules, options) },
},
{ compress: true }
)}</style>`;
}
2021-02-24 21:51:23 +00:00
export function inline(options) {
2021-02-20 20:16:36 +00:00
const rules = load(options);
2021-02-20 21:22:37 +00:00
return function inlineCSS(tag) {
2021-02-25 17:11:25 +00:00
if (options.css === "none" || !(options.inline || options.body)) 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}"` : "";
2021-02-20 20:16:36 +00:00
};
2021-02-24 21:51:23 +00:00
}
2021-02-20 19:28:43 +00:00
export function load(options) {
2021-02-20 21:57:13 +00:00
if (options.css === "none") return [];
options.css = options.css
? options.css
: options.body
? "gmi.css"
: "gmi-web.css";
2021-02-25 17:11:25 +00:00
2021-02-20 19:28:43 +00:00
if (
2021-02-25 20:44:09 +00:00
!["gmi.css", "gmi-web.css"].includes(options.css) &&
2021-02-25 17:11:25 +00:00
!existsSync(options.css)
2021-02-25 17:19:27 +00:00
)
throw new Error(`Cannot find file ${options.css}.`);
2021-02-25 17:11:25 +00:00
return parse(
readFileSync(
path.resolve(
["gmi-web.css", "gmi.css"].includes(options.css)
? internal(options.css)
: resolve(options.css)
),
"utf-8"
)
).stylesheet.rules;
2021-02-15 20:56:41 +00:00
}
2021-02-20 19:28:43 +00:00
export function rootVariables(rules) {
const root = rules.find(
({ selectors }) => selectors && selectors.includes(":root")
);
if (!root) return {};
return root.declarations.reduce(
(obj, { property, value }) =>
2021-02-25 17:19:27 +00:00
!/^\-\-/.test(property)
? obj
: Object.assign(obj, { [property.replace("--", "")]: value }),
2021-02-20 19:28:43 +00:00
{}
);
}
2021-02-24 21:51:23 +00:00
function reduceVariables(rules, options = {}) {
2021-02-20 19:28:43 +00:00
const defaultVariables = rootVariables(rules);
2021-02-25 17:11:25 +00:00
const CSS_VAR = /(^var\(\-\-(?<key>.+)\)|(?<val>.+))/;
2021-02-22 00:26:56 +00:00
const reduce = (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
2021-02-25 17:11:25 +00:00
? options[key] || defaultVariables[key]
2021-02-22 00:26:56 +00:00
: declaration.value,
});
}),
}
: {}
);
2021-02-20 19:28:43 +00:00
return rules
2021-02-22 00:26:56 +00:00
.map(reduce)
2021-02-20 19:28:43 +00:00
.map((rule) => {
2021-02-20 21:22:37 +00:00
if (!rule.media) return rule;
2021-02-20 19:28:43 +00:00
return Object.assign(rule, {
2021-02-22 00:26:56 +00:00
rules: rule.rules.map(reduce),
2021-02-20 19:28:43 +00:00
});
2021-02-20 21:22:37 +00:00
})
.filter(
({ selectors, media }) =>
media || (selectors && !selectors.includes(":root"))
);
2021-02-20 19:28:43 +00:00
}