70 lines
2.5 KiB
TypeScript
70 lines
2.5 KiB
TypeScript
/**
|
|
* Global stylesheet pipeline.
|
|
*
|
|
* Convention: `app/styles/global.css` is the entry. If it is absent but other
|
|
* `app/styles/*.css` files exist, they are aggregated into one entry. The entry
|
|
* is bundled by Bun's CSS bundler, which resolves `@import` — including from
|
|
* node_modules — so any npm CSS framework (Bootstrap, etc.) works by importing
|
|
* it. A custom `process` hook can replace the bundler for Tailwind/PostCSS/Sass.
|
|
*/
|
|
|
|
import { existsSync, readdirSync, mkdirSync, writeFileSync } from "node:fs";
|
|
import { isAbsolute, join, relative } from "node:path";
|
|
import type { Mode } from "./config.ts";
|
|
|
|
const fwd = (p: string) => p.replace(/\\/g, "/");
|
|
|
|
/**
|
|
* Resolve the CSS entry for an app.
|
|
* - `override` (from config.styles.entry) is resolved relative to `appRoot`.
|
|
* - otherwise prefer `app/styles/global.css`.
|
|
* - otherwise aggregate all `app/styles/*.css` into a generated entry.
|
|
* Returns null when the app has no styles.
|
|
*/
|
|
export function findStyleEntry(appDir: string, appRoot: string, override?: string): string | null {
|
|
if (override) {
|
|
const p = isAbsolute(override) ? override : join(appRoot, override);
|
|
return existsSync(p) ? p : null;
|
|
}
|
|
|
|
const stylesDir = join(appDir, "styles");
|
|
const globalCss = join(stylesDir, "global.css");
|
|
if (existsSync(globalCss)) return globalCss;
|
|
|
|
if (existsSync(stylesDir)) {
|
|
const cssFiles = readdirSync(stylesDir)
|
|
.filter((f) => f.endsWith(".css"))
|
|
.sort();
|
|
if (cssFiles.length > 0) {
|
|
const cacheDir = join(appDir, ".wrnexus");
|
|
mkdirSync(cacheDir, { recursive: true });
|
|
const entry = join(cacheDir, "styles-entry.css");
|
|
const imports = cssFiles
|
|
.map((f) => `@import "${fwd(relative(cacheDir, join(stylesDir, f)))}";`)
|
|
.join("\n");
|
|
writeFileSync(entry, imports + "\n", "utf8");
|
|
return entry;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Bundle a CSS entry into a single stylesheet string using Bun's CSS bundler.
|
|
* Resolves `@import` (local and node_modules), handles nesting, minifies in prod.
|
|
*/
|
|
export async function bundleCss(entryPath: string, mode: Mode): Promise<string> {
|
|
const result = await Bun.build({
|
|
entrypoints: [entryPath],
|
|
minify: mode === "production",
|
|
});
|
|
if (!result.success) {
|
|
throw new Error("CSS bundle failed:\n" + result.logs.map(String).join("\n"));
|
|
}
|
|
const cssOutput =
|
|
result.outputs.find((output) => output.path?.endsWith(".css")) ?? result.outputs[0];
|
|
if (!cssOutput) throw new Error("CSS bundle produced no output.");
|
|
return await cssOutput.text();
|
|
}
|