120 lines
3 KiB
TypeScript
120 lines
3 KiB
TypeScript
export interface Theme {
|
|
bg: string;
|
|
fg: string;
|
|
primary?: string;
|
|
h1?: string;
|
|
}
|
|
|
|
export const defaultTheme: Theme = {
|
|
bg: "#ffffff",
|
|
fg: "#050505",
|
|
primary: "#2e7dab",
|
|
};
|
|
|
|
export function stringifyTheme(theme: Theme) {
|
|
return [
|
|
":root {",
|
|
"--bg: " + theme.bg + ";",
|
|
"--fg: " + theme.fg + ";",
|
|
theme.primary ? "--primary: " + theme.primary + ";" : null,
|
|
"}",
|
|
theme.h1 ? "h1 { color: " + theme.h1 + "}" : null,
|
|
].filter(Boolean).join("\n");
|
|
}
|
|
|
|
export function preprocess(css: string, theme: Theme): string {
|
|
const keys = Object.keys(theme);
|
|
const regex = new RegExp(
|
|
`([{};\\n][^{};\\n]*var\\(--(${keys.join("|")})\\).*?(?=[;{}\\n]))`,
|
|
"gs",
|
|
);
|
|
const regex2 = new RegExp(`var\\(--(${keys.join("|")})\\)`);
|
|
return css.replace(
|
|
regex,
|
|
(_, line) =>
|
|
line.replace(
|
|
regex2,
|
|
(_: string, varName: string) => theme[varName as keyof Theme],
|
|
) +
|
|
";" + line.slice(1),
|
|
);
|
|
}
|
|
|
|
export interface Output {
|
|
text: string;
|
|
sources: string[];
|
|
}
|
|
|
|
export function styleKey(
|
|
cssImports: string[],
|
|
theme: Theme,
|
|
) {
|
|
cssImports = cssImports
|
|
.map((file) =>
|
|
(path.isAbsolute(file) ? path.relative(hot.projectSrc, file) : file)
|
|
.replaceAll("\\", "/")
|
|
)
|
|
.sort();
|
|
return cssImports.join(":") + ":" +
|
|
Object.entries(theme).map(([k, v]) => `${k}=${v}`);
|
|
}
|
|
|
|
export async function bundleCssFiles(
|
|
cssImports: string[],
|
|
theme: Theme,
|
|
dev: boolean = false,
|
|
): Promise<Output> {
|
|
cssImports = cssImports.map((file) => path.resolve(hot.projectSrc, file));
|
|
const plugin = {
|
|
name: "clover css",
|
|
setup(b) {
|
|
b.onLoad(
|
|
{ filter: /\.css$/ },
|
|
async ({ path: file }) => ({
|
|
loader: "css",
|
|
contents: preprocess(await fs.readFile(file, "utf-8"), theme),
|
|
}),
|
|
);
|
|
},
|
|
} satisfies esbuild.Plugin;
|
|
const build = await esbuild.build({
|
|
bundle: true,
|
|
entryPoints: ["$input$"],
|
|
external: ["*.woff2", "*.ttf", "*.png", "*.jpeg"],
|
|
metafile: true,
|
|
minify: !dev,
|
|
plugins: [
|
|
virtualFiles({
|
|
"$input$": {
|
|
contents: cssImports.map((path) =>
|
|
`@import url(${JSON.stringify(path)});`
|
|
)
|
|
.join("\n") + stringifyTheme(theme),
|
|
loader: "css",
|
|
},
|
|
}),
|
|
plugin,
|
|
],
|
|
target: ["ie11"],
|
|
write: false,
|
|
});
|
|
const { errors, warnings, outputFiles, metafile } = build;
|
|
if (errors.length > 0) {
|
|
throw new AggregateError(errors, "CSS Build Failed");
|
|
}
|
|
if (warnings.length > 0) {
|
|
throw new AggregateError(warnings, "CSS Build Failed");
|
|
}
|
|
if (outputFiles.length > 1) throw new Error("Too many output files");
|
|
return {
|
|
text: outputFiles[0].text,
|
|
sources: Object.keys(metafile.outputs["$input$.css"].inputs)
|
|
.filter((x) => !x.startsWith("vfs:")),
|
|
};
|
|
}
|
|
|
|
import * as esbuild from "esbuild";
|
|
import * as fs from "#sitegen/fs";
|
|
import * as hot from "./hot.ts";
|
|
import * as path from "node:path";
|
|
import { virtualFiles } from "./esbuild-support.ts";
|