563 lines
36 KiB
Plaintext
563 lines
36 KiB
Plaintext
page wrnexusstyles {
|
|
seo {
|
|
title = "@wrnexus/styles"
|
|
description = "CSS pipeline, themes, fonts, profiles, and application config."
|
|
}
|
|
|
|
view {
|
|
<div class="docs-shell">
|
|
<header class="topbar">
|
|
<a class="brand" href="/"><span>W</span> WRNexusJS</a>
|
|
<nav><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="/language">Language</a><a href="/architecture">Architecture</a></nav>
|
|
<button data-wire-theme-toggle class="theme-button" aria-label="Toggle theme">Theme</button>
|
|
</header>
|
|
<main class="page package-page">
|
|
<aside class="sidebar"><a href="/packages">← All packages</a><span class="category">Frontend</span><h1>@wrnexus/styles</h1><p>CSS pipeline, themes, fonts, profiles, and application config.</p><code>bun add @wrnexus/styles@0.2.12</code><nav><a href="#guide">Guide</a><a href="#api">Complete API</a></nav></aside>
|
|
<article class="documentation"><section class="doc-intro"><span class="eyebrow">Frontend</span><h1>@wrnexus/styles</h1><p>CSS pipeline, themes, fonts, profiles, and application config.</p><pre><code>bun add @wrnexus/styles@0.2.12</code></pre></section><section id="guide" class="prose"><blockquote>Global CSS bundling, the <code>--wire-*</code> design-token theme system, and the <code>wrnexus.config.ts</code> app-config loader for WRNexusJS apps.</blockquote>
|
|
<p>Part of the <strong>WRNexusJS</strong> framework — an SSR-first, Bun-native full-stack web framework.</p>
|
|
<h3 id="overview">Overview</h3>
|
|
<p>This package owns three server-side concerns that shape every page a WRNexusJS app renders:</p>
|
|
<p>1. <strong>Global stylesheet pipeline</strong> — finds <code>app/styles/global.css</code> (or aggregates <code>app/styles/*.css</code>), bundles it with Bun's CSS bundler (which resolves <code>@import</code>, including from <code>node_modules</code>), and produces one stylesheet that is <code><link></code>ed into every page's <code><head></code>. Because it is a plain global sheet, it styles server-rendered markup and hydrated client islands identically. A custom <code>process</code> hook lets you swap in Tailwind / PostCSS / Sass. 2. <strong>Theme system</strong> — design tokens exposed as CSS custom properties (<code>--wire-<key></code>), with built-in <code>light</code>/<code>dark</code> sets, deep-merged user overrides, an SSR <code><html data-theme></code> render (no flash), and a tiny client runtime to toggle/persist the choice. 3. <strong>App config</strong> — loads <code>wrnexus.config.ts</code> (the <code>AppConfig</code> type), applies named profile overrides, and loads the <code>.env</code> cascade.</p>
|
|
<p>It runs server-side / at build time. Reach for it when configuring an app, defining themes, or customising how global CSS is produced.</p>
|
|
<h3 id="installation">Installation</h3>
|
|
<pre data-language="bash"><code>bun add @wrnexus/styles</code></pre>
|
|
<blockquote>Private package — the machine must be authenticated to the <code>wrnexus</code> npm org</blockquote>
|
|
<blockquote>(a read token in <code>~/.npmrc</code>). Requires <strong>Bun</strong> (Node is not supported).</blockquote>
|
|
<h3 id="api">API</h3>
|
|
<p>Everything is exported from the package root (<code>@wrnexus/styles</code>).</p>
|
|
<h4 id="config-loading">Config loading</h4>
|
|
<div class="table-wrap"><table>
|
|
<thead><tr><th>Export</th><th>Signature</th><th>Purpose</th></tr></thead>
|
|
<tbody><tr><td><code>loadAppConfig</code></td><td><code>(appRoot: string, profile?: string) => Promise<AppConfig></code></td><td>Load <code>wrnexus.config.*</code> with the active profile deep-merged in (<code>profiles</code> stripped from the result).</td></tr><tr><td><code>loadRawConfig</code></td><td><code>(appRoot: string) => Promise<AppConfig></code></td><td>Load the raw config with the <code>profiles</code> map intact; returns <code>{}</code> if no config file exists.</td></tr><tr><td><code>resolveProfile</code></td><td><code>(options?: { explicit?; mode? }) => string</code></td><td>Resolve the active profile: explicit arg > <code>WRNEXUS_PROFILE</code> env var > mode-based default (<code>production</code> in prod, else <code>development</code>).</td></tr><tr><td><code>loadEnv</code></td><td><code>(appRoot: string, profile: string) => Record<string, string></code></td><td>Load the <code>.env</code> cascade for a profile into <code>process.env</code> without clobbering real env vars. Returns what it loaded.</td></tr><tr><td><code>headToString</code></td><td>`(head?: string \</td><td>string[]) => string`</td><td>Flatten <code>AppConfig.head</code> into a single HTML string.</td></tr></tbody></table></div>
|
|
<p>Config file names probed, in order: <code>wrnexus.config.ts</code>, <code>wrnexus.config.js</code>, <code>wrnexus.config.mjs</code>.</p>
|
|
<p><code>.env</code> cascade precedence (low → high): <code>.env</code> < <code>.env.<profile></code> < <code>.env.local</code> < <code>.env.<profile>.local</code>. Variables already present in the real environment always win.</p>
|
|
<h4 id="appconfig"><code>AppConfig</code></h4>
|
|
<p>The type of the object your <code>wrnexus.config.ts</code> default-exports. Every field is optional.</p>
|
|
<div class="table-wrap"><table>
|
|
<thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead>
|
|
<tbody><tr><td><code>head</code></td><td>`string \</td><td>string[]`</td><td>Raw HTML appended to every page's <code><head></code> (e.g. CDN stylesheet/script links).</td></tr><tr><td><code>seo</code></td><td><code>SeoConfig</code></td><td>Global SEO defaults, merged with each page's exported <code>meta</code>. (from <code>@wrnexus/core</code>)</td></tr><tr><td><code>security</code></td><td><code>SecurityConfig</code></td><td>Framework security headers and optional CORS policy. (from <code>@wrnexus/core</code>)</td></tr><tr><td><code>styles</code></td><td><code>StylesConfig</code></td><td>Global stylesheet pipeline config (see below).</td></tr><tr><td><code>theme</code></td><td><code>ThemeConfig</code></td><td>Design-token themes, deep-merged over the built-in light/dark.</td></tr><tr><td><code>i18n</code></td><td><code>{ default?: string; locales?: string[] }</code></td><td>Default language + supported locales (strings live in <code>app/locales/*.json</code>).</td></tr><tr><td><code>db</code></td><td>`{ driver: "sqlite" \</td><td>"postgres" \</td><td>"mysql" \</td><td>"mongo"; url: string }`</td><td>Default database connection; reached with <code>getDb()</code>.</td></tr><tr><td><code>databases</code></td><td><code>Record<string, { driver; url }></code></td><td>Additional named databases, reached with <code>getDb("<name>")</code>; each has its own <code>app/db/<name>/</code> migrations/queries.</td></tr><tr><td><code>realtime</code></td><td><code>{ scale?: boolean; redisUrl?: string }</code></td><td>When <code>scale</code> is true (or <code>redisUrl</code> is set), room broadcasts bridge over Redis pub/sub so they reach clients on every app process.</td></tr><tr><td><code>port</code></td><td><code>number</code></td><td>Default server port.</td></tr><tr><td><code>profiles</code></td><td><code>Record<string, Partial<Omit<AppConfig, "profiles">>></code></td><td>Named profiles (dev, prod, uat, test, …). The active profile's overrides are deep-merged over the base config. Selected via <code>--profile=<name></code> or <code>WRNEXUS_PROFILE</code>.</td></tr></tbody></table></div>
|
|
<h4 id="styles-pipeline">Styles pipeline</h4>
|
|
<div class="table-wrap"><table>
|
|
<thead><tr><th>Export</th><th>Signature</th><th>Purpose</th></tr></thead>
|
|
<tbody><tr><td><code>findStyleEntry</code></td><td>`(appDir, appRoot, override?) => string \</td><td>null`</td><td>Resolve the CSS entry: <code>override</code> (relative to <code>appRoot</code>) → <code>app/styles/global.css</code> → an aggregate of all <code>app/styles/*.css</code> (written to <code>app/.wrnexus/styles-entry.css</code>). <code>null</code> if the app has no styles.</td></tr><tr><td><code>bundleCss</code></td><td><code>(entryPath: string, mode: Mode) => Promise<string></code></td><td>Bundle an entry with <code>Bun.build</code> (CSS bundler). Resolves <code>@import</code> (local + node_modules), handles nesting, minifies when <code>mode === "production"</code>.</td></tr><tr><td><code>renderStyles</code></td><td><code>(ctx: StyleProcessContext, styles?: StylesConfig) => Promise<string></code></td><td>Produce final CSS: runs <code>styles.process(ctx)</code> if provided, else <code>bundleCss</code>. Returns <code>""</code> when <code>ctx.entryPath</code> is null.</td></tr></tbody></table></div>
|
|
<p><code>StylesConfig</code>:</p>
|
|
<pre data-language="ts"><code>interface StylesConfig {
|
|
/** CSS entry path relative to the app root. Default: app/styles/global.css */
|
|
entry?: string;
|
|
/** Custom processor — return the final CSS string (Tailwind/PostCSS/Sass). */
|
|
process?: (ctx: StyleProcessContext) => string | Promise<string>;
|
|
}
|
|
|
|
interface StyleProcessContext {
|
|
entryPath: string | null; // resolved absolute CSS entry, or null
|
|
appDir: string;
|
|
appRoot: string;
|
|
mode: Mode; // "development" | "production"
|
|
}</code></pre>
|
|
<h4 id="theme-system">Theme system</h4>
|
|
<div class="table-wrap"><table>
|
|
<thead><tr><th>Export</th><th>Type / Signature</th><th>Purpose</th></tr></thead>
|
|
<tbody><tr><td><code>DEFAULT_THEMES</code></td><td><code>Record<string, ThemeTokens></code></td><td>Built-in <code>light</code> and <code>dark</code> token maps.</td></tr><tr><td><code>THEME_COOKIE</code></td><td><code>"wire-theme"</code></td><td>Cookie the resolved theme is read from / persisted to.</td></tr><tr><td><code>THEME_CSS_HREF</code></td><td><code>"/__wrnexus/theme.css"</code></td><td>URL the generated theme stylesheet is served at.</td></tr><tr><td><code>THEME_JS_HREF</code></td><td><code>"/__wrnexus/theme.js"</code></td><td>URL the client theme runtime is served at.</td></tr><tr><td><code>resolveThemeConfig</code></td><td><code>(config?: ThemeConfig) => ResolvedTheme</code></td><td>Deep-merge the user's <code>theme</code> config over the defaults; pick the default theme (config's <code>default</code> if valid, else <code>dark</code>, else the first).</td></tr><tr><td><code>resolveThemeName</code></td><td>`(cookieValue: string \</td><td>undefined, theme: ResolvedTheme) => string`</td><td>Pick a valid theme name from a cookie, falling back to <code>theme.default</code>.</td></tr><tr><td><code>renderThemeCss</code></td><td><code>(theme: ResolvedTheme) => string</code></td><td>Generate the theme stylesheet: a <code>:root{…}</code> default plus one <code>[data-theme="<name>"]{…}</code> block per theme.</td></tr><tr><td><code>renderThemeRuntime</code></td><td><code>(theme: ResolvedTheme) => string</code></td><td>Generate the client runtime (see below).</td></tr></tbody></table></div>
|
|
<p>Tokens are emitted as <code>--wire-<key></code> custom properties, <strong>except</strong> the reserved key <code>color-scheme</code>, which is emitted as the native <code>color-scheme</code> CSS property so form controls and scrollbars match the theme.</p>
|
|
<p><code>ThemeConfig</code> / <code>ThemeTokens</code> / <code>ResolvedTheme</code>:</p>
|
|
<pre data-language="ts"><code>type ThemeTokens = Record<string, string>;
|
|
|
|
interface ThemeConfig {
|
|
default?: string; // theme used when no cookie is present
|
|
themes?: Record<string, ThemeTokens>; // deep-merged over built-in light/dark
|
|
}
|
|
|
|
interface ResolvedTheme {
|
|
default: string;
|
|
names: string[];
|
|
themes: Record<string, ThemeTokens>;
|
|
}</code></pre>
|
|
<p>Built-in token keys (both <code>light</code> and <code>dark</code>): <code>color-scheme</code>, <code>color-bg</code>, <code>color-surface</code>, <code>color-surface-2</code>, <code>color-text</code>, <code>color-muted</code>, <code>color-border</code>, <code>color-primary</code>, <code>color-primary-hover</code>, <code>color-primary-contrast</code>, <code>color-danger</code>, <code>color-success</code>, <code>color-warning</code>, <code>radius</code>, <code>radius-sm</code>, <code>font-sans</code>, <code>shadow-1</code>.</p>
|
|
<p>The client runtime (<code>renderThemeRuntime</code>) exposes <code>window.wireTheme</code> with <code>{ get, set, toggle, bind, themes }</code>, wires up any <code>[data-wire-theme-toggle]</code> and <code>[data-wire-theme-set]</code> elements on load, and persists the choice to the <code>wire-theme</code> cookie (<code>max-age</code> 1 year, <code>samesite=lax</code>). <code>toggle()</code> cycles through the configured theme names in order.</p>
|
|
<h3 id="usage">Usage</h3>
|
|
<h4 id="wrnexus-config-ts"><code>wrnexus.config.ts</code></h4>
|
|
<pre data-language="ts"><code>import type { AppConfig } from "@wrnexus/styles";
|
|
|
|
export default {
|
|
head: [
|
|
'<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5/dist/css/bootstrap.min.css">',
|
|
],
|
|
port: 3000,
|
|
db: { driver: "sqlite", url: "app.db" },
|
|
theme: {
|
|
default: "dark",
|
|
themes: {
|
|
light: { "color-primary": "#7c3aed" }, // override one token; rest inherited
|
|
brand: {
|
|
// add a whole new theme
|
|
"color-scheme": "dark",
|
|
"color-bg": "#0a0a0a",
|
|
"color-primary": "#22d3ee",
|
|
},
|
|
},
|
|
},
|
|
styles: {
|
|
entry: "app/styles/main.css",
|
|
},
|
|
profiles: {
|
|
production: {
|
|
db: { driver: "postgres", url: process.env.DATABASE_URL! },
|
|
},
|
|
},
|
|
} satisfies AppConfig;</code></pre>
|
|
<h4 id="loading-config-producing-css">Loading config + producing CSS</h4>
|
|
<pre data-language="ts"><code>import {
|
|
loadAppConfig,
|
|
resolveProfile,
|
|
loadEnv,
|
|
findStyleEntry,
|
|
renderStyles,
|
|
} from "@wrnexus/styles";
|
|
|
|
const appRoot = process.cwd();
|
|
const mode = "production" as const;
|
|
|
|
const profile = resolveProfile({ mode });
|
|
loadEnv(appRoot, profile);
|
|
|
|
const config = await loadAppConfig(appRoot, profile);
|
|
|
|
const appDir = `${appRoot}/app`;
|
|
const entryPath = findStyleEntry(appDir, appRoot, config.styles?.entry);
|
|
const css = await renderStyles({ entryPath, appDir, appRoot, mode }, config.styles);</code></pre>
|
|
<h4 id="rendering-the-theme">Rendering the theme</h4>
|
|
<pre data-language="ts"><code>import {
|
|
resolveThemeConfig,
|
|
resolveThemeName,
|
|
renderThemeCss,
|
|
renderThemeRuntime,
|
|
THEME_COOKIE,
|
|
} from "@wrnexus/styles";
|
|
|
|
const theme = resolveThemeConfig(config.theme);
|
|
|
|
// Server: pick the active theme from the request cookie (no flash).
|
|
const active = resolveThemeName(cookies[THEME_COOKIE], theme);
|
|
// → render <html data-theme={active}>
|
|
|
|
const themeCss = renderThemeCss(theme); // served at THEME_CSS_HREF
|
|
const themeJs = renderThemeRuntime(theme); // served at THEME_JS_HREF</code></pre>
|
|
<p>In templates, consume tokens via the custom properties:</p>
|
|
<pre data-language="css"><code>.card {
|
|
background: var(--wire-color-surface);
|
|
color: var(--wire-color-text);
|
|
border: 1px solid var(--wire-color-border);
|
|
border-radius: var(--wire-radius);
|
|
box-shadow: var(--wire-shadow-1);
|
|
}</code></pre>
|
|
<pre data-language="html"><code><button data-wire-theme-toggle>Toggle theme</button>
|
|
<button data-wire-theme-set="brand">Brand theme</button></code></pre>
|
|
<h3 id="requirements-notes">Requirements / Notes</h3>
|
|
<ul>
|
|
<li><strong>Bun-only.</strong> <code>bundleCss</code> uses <code>Bun.build</code>'s CSS bundler for <code>@import</code> resolution, nesting, and minification. Node is not supported.</li>
|
|
<li>Config and env loading use <code>node:fs</code> / <code>node:path</code> / <code>node:url</code> and read from <code>process.env</code>.</li>
|
|
<li>Peer package: <code>@wrnexus/core</code> supplies the <code>SeoConfig</code> and <code>SecurityConfig</code> types referenced by <code>AppConfig</code>.</li>
|
|
<li>The bundled global stylesheet, the theme stylesheet (<code>THEME_CSS_HREF</code>), and the theme runtime (<code>THEME_JS_HREF</code>) are wired into pages by the framework's server; this package only produces their contents.</li>
|
|
</ul></section><section id="api" class="prose api"><h2>Complete TypeScript API</h2><p>This declaration is generated from the exact published package and lists its exported functions, classes, interfaces, and types.</p><pre data-language="typescript"><code>import { SeoConfig, SecurityConfig } from '@wrnexus/core';
|
|
import { StorageConfig } from '@wrnexus/uploader';
|
|
|
|
/**
|
|
* Theme system — design tokens that work SSR and client-side.
|
|
*
|
|
* Tokens are plain CSS custom properties (`--wire-<key>`) so they cascade and
|
|
* can be overridden by user CSS. Each theme is a flat token map; the framework
|
|
* ships default `light`/`dark` sets and the user's config deep-merges over them.
|
|
*
|
|
* The server renders `<html data-theme="…">` from the `wire-theme` cookie (no
|
|
* flash), and a tiny client runtime toggles/persists it. The reserved token key
|
|
* `color-scheme` is emitted as the native CSS property (not a variable) so form
|
|
* controls and scrollbars match the theme.
|
|
*/
|
|
type ThemeTokens = Record<string, string>;
|
|
interface ThemeConfig {
|
|
/** Name of the theme used when no `wire-theme` cookie is present. */
|
|
default?: string;
|
|
/** Named token maps. Deep-merged over the framework's built-in light/dark. */
|
|
themes?: Record<string, ThemeTokens>;
|
|
}
|
|
interface ResolvedTheme {
|
|
default: string;
|
|
names: string[];
|
|
themes: Record<string, ThemeTokens>;
|
|
}
|
|
/** Cookie the resolved theme is read from / persisted to. */
|
|
declare const THEME_COOKIE = "wire-theme";
|
|
declare const THEME_CSS_HREF = "/__wrnexus/theme.css";
|
|
declare const THEME_JS_HREF = "/__wrnexus/theme.js";
|
|
/** Built-in themes so components have tokens out of the box. */
|
|
declare const DEFAULT_THEMES: Record<string, ThemeTokens>;
|
|
/** Merge the user's theme config over the built-in defaults. */
|
|
declare function resolveThemeConfig(config?: ThemeConfig): ResolvedTheme;
|
|
/** Pick a valid theme name from a cookie value, falling back to the default. */
|
|
declare function resolveThemeName(cookieValue: string | undefined, theme: ResolvedTheme): string;
|
|
/** Generate the theme stylesheet: a `:root` default plus one block per theme. */
|
|
declare function renderThemeCss(theme: ResolvedTheme): string;
|
|
/**
|
|
* Generate the client theme runtime. It exposes `window.wireTheme` and binds
|
|
* `[data-wire-theme-toggle]` / `[data-wire-theme-set]` elements. The configured
|
|
* theme names are baked in so `toggle()` cycles through them in order.
|
|
*/
|
|
declare function renderThemeRuntime(theme: ResolvedTheme): string;
|
|
|
|
/**
|
|
* Font configuration.
|
|
*
|
|
* Declare fonts in `wrnexus.config.ts` under `fonts` and the framework emits
|
|
* optimized `<head>` markup for you:
|
|
* - Google Fonts: `preconnect` hints + a single subsetted stylesheet request
|
|
* (only the weights you list) with `font-display`. The CSP is auto-extended
|
|
* so the fonts load under the default security policy (see loadAppConfig).
|
|
* - Self-hosted fonts: generated `@font-face` rules + optional `<link rel=preload>`
|
|
* for above-the-fold text (the fastest, no-third-party option).
|
|
* - Family stacks: `sans`/`mono`/`serif` become `--wrn-font-*` CSS variables,
|
|
* and `sans` is applied to `body`.
|
|
*/
|
|
type FontDisplay = "auto" | "block" | "swap" | "fallback" | "optional";
|
|
interface GoogleFont {
|
|
/** Family name as it appears on fonts.google.com, e.g. "Inter". */
|
|
family: string;
|
|
/** Weights to load — ONLY these are fetched. Default: [400]. */
|
|
weights?: (number | string)[];
|
|
/** Also load italic styles for each weight. */
|
|
italic?: boolean;
|
|
/** Per-font `font-display` override (else the config default). */
|
|
display?: FontDisplay;
|
|
}
|
|
interface LocalFontFace {
|
|
/** `font-family` name this face defines. */
|
|
family: string;
|
|
/** URL to the font file, typically served from `public/` (e.g. "/fonts/inter.woff2"). */
|
|
src: string;
|
|
/** e.g. 400, "700", or "100 900" for a variable font. Default: 400. */
|
|
weight?: number | string;
|
|
style?: "normal" | "italic";
|
|
/** CSS `src` format; inferred from the file extension when omitted. */
|
|
format?: string;
|
|
display?: FontDisplay;
|
|
/** Emit `<link rel="preload" as="font">` — use for the primary above-the-fold face. */
|
|
preload?: boolean;
|
|
/** Optional `unicode-range` subset. */
|
|
unicodeRange?: string;
|
|
}
|
|
interface FontConfig {
|
|
/** Google Fonts, loaded with preconnect + weight subsetting + `font-display`. */
|
|
google?: GoogleFont[];
|
|
/** Self-hosted `@font-face` definitions (files served from `public/`). */
|
|
local?: LocalFontFace[];
|
|
/** Default `font-display` for faces that don't set their own. Default: "swap". */
|
|
display?: FontDisplay;
|
|
/** Body / default family stack → `--wrn-font-sans` + `body { font-family }`. */
|
|
sans?: string;
|
|
/** Monospace family stack → `--wrn-font-mono`. */
|
|
mono?: string;
|
|
/** Serif family stack → `--wrn-font-serif`. */
|
|
serif?: string;
|
|
}
|
|
/**
|
|
* Render all `<head>` markup for a font config. Returns "" when nothing is
|
|
* configured. The output is trusted, framework-controlled HTML.
|
|
*/
|
|
declare function renderFontHead(fonts?: FontConfig): string;
|
|
/**
|
|
* CSP source hosts required by the configured fonts, so the policy can be
|
|
* auto-extended (Google Fonts need their CSS + static hosts allow-listed).
|
|
*/
|
|
declare function fontCspSources(fonts?: FontConfig): {
|
|
style: string[];
|
|
font: string[];
|
|
};
|
|
|
|
/**
|
|
* App configuration loader (`wrnexus.config.ts`).
|
|
*
|
|
* The config is optional. It lets an app inject arbitrary `<head>` HTML (ideal
|
|
* for CDN-delivered CSS frameworks like Bootstrap or the Tailwind Play CDN) and
|
|
* customise the global stylesheet pipeline (entry file or a custom processor for
|
|
* Tailwind / PostCSS / Sass).
|
|
*/
|
|
|
|
type Mode = "development" | "production";
|
|
interface StyleProcessContext {
|
|
/** Resolved absolute path to the CSS entry, or null if there is none. */
|
|
entryPath: string | null;
|
|
appDir: string;
|
|
appRoot: string;
|
|
mode: Mode;
|
|
}
|
|
interface StylesConfig {
|
|
/** Path to the CSS entry, relative to the app root. Default: app/styles/global.css */
|
|
entry?: string;
|
|
/**
|
|
* Optional custom processor. Return the final CSS string. Use this to run
|
|
* Tailwind, PostCSS, Sass, etc. When omitted, the built-in Bun CSS bundler is
|
|
* used (which already resolves `@import`, including from node_modules).
|
|
*/
|
|
process?: (ctx: StyleProcessContext) => string | Promise<string>;
|
|
}
|
|
interface MobileConfig {
|
|
enabled?: boolean;
|
|
/** Mobile renderer. `webview` uses Capacitor; `native` scaffolds an Expo/React Native app. */
|
|
mode?: "webview" | "native";
|
|
appId?: string;
|
|
appName?: string;
|
|
serverUrl?: string;
|
|
userAgent?: string;
|
|
layout?: string;
|
|
backgroundColor?: string;
|
|
icon?: string;
|
|
errorTitle?: string;
|
|
errorMessage?: string;
|
|
/** Base URL used by a fully native client for WRNexusJS API and realtime requests. */
|
|
apiUrl?: string;
|
|
/** URL scheme used for native deep links (defaults to a slug of appName). */
|
|
scheme?: string;
|
|
/** Advanced Expo app config fields merged into generated app.config.ts. */
|
|
expo?: Record<string, unknown>;
|
|
/** Advanced CapacitorConfig fields merged into generated capacitor.config.ts. */
|
|
capacitor?: Record<string, unknown>;
|
|
}
|
|
interface PwaScreenshot {
|
|
src: string;
|
|
sizes: string;
|
|
type?: string;
|
|
formFactor?: "wide" | "narrow";
|
|
label?: string;
|
|
}
|
|
interface PwaShortcut {
|
|
name: string;
|
|
shortName?: string;
|
|
description?: string;
|
|
url: string;
|
|
icons?: Array<{
|
|
src: string;
|
|
sizes: string;
|
|
type?: string;
|
|
purpose?: string;
|
|
}>;
|
|
}
|
|
interface PwaConfig {
|
|
enabled?: boolean;
|
|
id?: string;
|
|
name?: string;
|
|
shortName?: string;
|
|
description?: string;
|
|
startUrl?: string;
|
|
scope?: string;
|
|
lang?: string;
|
|
display?: "standalone" | "fullscreen" | "minimal-ui" | "browser";
|
|
orientation?: "any" | "natural" | "landscape" | "landscape-primary" | "landscape-secondary" | "portrait" | "portrait-primary" | "portrait-secondary";
|
|
themeColor?: string;
|
|
backgroundColor?: string;
|
|
icons?: Array<{
|
|
src: string;
|
|
sizes: string;
|
|
type?: string;
|
|
purpose?: string;
|
|
}>;
|
|
categories?: string[];
|
|
screenshots?: PwaScreenshot[];
|
|
shortcuts?: PwaShortcut[];
|
|
/** Disable service-worker registration while keeping the web manifest. */
|
|
serviceWorker?: boolean;
|
|
/** Navigation shown when both the network and requested page cache are unavailable. */
|
|
offlineUrl?: string;
|
|
/** Additional same-origin URLs precached during service-worker installation. */
|
|
cacheUrls?: string[];
|
|
/** Service-worker cache key. Change it to invalidate existing PWA caches. */
|
|
cacheName?: string;
|
|
}
|
|
interface AppConfig {
|
|
/** Raw HTML appended to every page's `<head>` (e.g. CDN stylesheet links). */
|
|
head?: string | string[];
|
|
/** Global SEO defaults merged with every page's exported `meta`. */
|
|
seo?: SeoConfig;
|
|
/** Framework security headers and optional CORS policy. */
|
|
security?: SecurityConfig;
|
|
styles?: StylesConfig;
|
|
/** Capacitor/native shell defaults and mobile-only page rendering. */
|
|
mobile?: MobileConfig;
|
|
/** Progressive Web App metadata. Enabled by default unless set to false. */
|
|
pwa?: PwaConfig | false;
|
|
/**
|
|
* Fonts. Declare Google Fonts (subsetted + preconnect + `font-display`) and/or
|
|
* self-hosted `@font-face` (with preload), and set `sans`/`mono`/`serif` family
|
|
* stacks. Google Fonts auto-extend the CSP so they load under the default policy.
|
|
*/
|
|
fonts?: FontConfig;
|
|
/** Design-token themes (deep-merged over the built-in light/dark). */
|
|
theme?: ThemeConfig;
|
|
/** i18n: default language + supported locales (strings live in app/locales/*.json). */
|
|
i18n?: {
|
|
default?: string;
|
|
locales?: string[];
|
|
};
|
|
/** Default database connection (driver + url); reached with `getDb()`. */
|
|
db?: {
|
|
driver: "sqlite" | "postgres" | "mysql" | "mongo";
|
|
url: string;
|
|
};
|
|
/**
|
|
* File-upload storage. Declare named stores (local dir or S3-compatible),
|
|
* upload with `handleUpload`/`upload` from `@wrnexus/uploader`, and serve
|
|
* files back. Each store is `access: "public" | "private"`.
|
|
*/
|
|
storage?: StorageConfig;
|
|
/**
|
|
* Additional named databases, reached with `getDb("<name>")`. Each has its own
|
|
* migrations/queries under `app/db/<name>/`. Connect to as many as you like and
|
|
* read/write to any of them per request.
|
|
*
|
|
* databases: { analytics: { driver: "postgres", url: "…" } }
|
|
*/
|
|
databases?: Record<string, {
|
|
driver: "sqlite" | "postgres" | "mysql" | "mongo";
|
|
url: string;
|
|
}>;
|
|
/**
|
|
* Realtime scaling. When `scale` is true (or `redisUrl` is set), room
|
|
* broadcasts are bridged over Redis pub/sub so they reach clients on **every**
|
|
* app process/instance — realtime that works with multiple running apps.
|
|
*/
|
|
realtime?: {
|
|
scale?: boolean;
|
|
redisUrl?: string;
|
|
};
|
|
/** Default server port. */
|
|
port?: number;
|
|
/**
|
|
* Named config profiles (dev, prod, uat, test, …). When a profile is active
|
|
* its overrides are DEEP-MERGED over the base config. Select with
|
|
* `--profile=<name>` or the `WRNEXUS_PROFILE` env var.
|
|
*/
|
|
profiles?: Record<string, Partial<Omit<AppConfig, "profiles">>>;
|
|
}
|
|
/**
|
|
* Resolve the active profile name: explicit argument > `WRNEXUS_PROFILE` env var
|
|
* > a mode-based default ("production" in prod, else "development").
|
|
*/
|
|
declare function resolveProfile(options?: {
|
|
explicit?: string;
|
|
mode?: Mode;
|
|
}): string;
|
|
/** Load the raw `wrnexus.config.*` (with the `profiles` map intact), or `{}`. */
|
|
declare function loadRawConfig(appRoot: string): Promise<AppConfig>;
|
|
/** Load `wrnexus.config.*`, applying the active profile's overrides. */
|
|
declare function loadAppConfig(appRoot: string, profile?: string): Promise<AppConfig>;
|
|
/**
|
|
* Load the `.env` cascade for a profile into `process.env`, WITHOUT clobbering
|
|
* variables already set in the real environment (which always win). Order, low
|
|
* → high precedence: `.env` < `.env.<profile>` < `.env.local` < `.env.<profile>.local`.
|
|
* Returns the variables it loaded.
|
|
*/
|
|
declare function loadEnv(appRoot: string, profile: string): Record<string, string>;
|
|
/** Flatten a head config into a single HTML string. */
|
|
declare function headToString(head?: string | string[]): string;
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
declare function findStyleEntry(appDir: string, appRoot: string, override?: string): string | 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.
|
|
*/
|
|
declare function bundleCss(entryPath: string, mode: Mode): Promise<string>;
|
|
|
|
/**
|
|
* @wrnexus/styles — global stylesheet pipeline + app config.
|
|
*
|
|
* Works for SSR and CSR: the bundled stylesheet is `<link>`ed into every page's
|
|
* `<head>`, so it styles server-rendered markup and hydrated client islands
|
|
* alike. Use any CSS framework via `@import` in global.css (npm) or via a CDN
|
|
* link in `wrnexus.config.ts`'s `head` field.
|
|
*/
|
|
|
|
/**
|
|
* Produce the final CSS for an entry: run the config's custom processor if one
|
|
* is provided (Tailwind/PostCSS/Sass), otherwise use the built-in Bun bundler.
|
|
*
|
|
* If a custom processor throws (e.g. Tailwind can't resolve `tailwindcss`
|
|
* because deps aren't installed), we DON'T crash every request — we log a clear,
|
|
* actionable message and fall back to best-effort CSS so the app keeps serving.
|
|
*/
|
|
declare function renderStyles(ctx: StyleProcessContext, styles?: StylesConfig): Promise<string>;
|
|
|
|
export { type AppConfig, DEFAULT_THEMES, type FontConfig, type FontDisplay, type GoogleFont, type LocalFontFace, type MobileConfig, type Mode, type PwaConfig, type ResolvedTheme, type StyleProcessContext, type StylesConfig, type Mode as StylesMode, THEME_COOKIE, THEME_CSS_HREF, THEME_JS_HREF, type ThemeConfig, type ThemeTokens, bundleCss, findStyleEntry, fontCspSources, headToString, loadAppConfig, loadEnv, loadRawConfig, renderFontHead, renderStyles, renderThemeCss, renderThemeRuntime, resolveProfile, resolveThemeConfig, resolveThemeName };
|
|
</code></pre></section><section id="examples" class="prose examples"><h2>Examples</h2><p>Copy-ready examples taken from this package's published documentation.</p><div class="example-grid"><article class="example-card"><h3>Example 1</h3><pre data-language="bash"><code>bun add @wrnexus/styles</code></pre></article><article class="example-card"><h3>Example 2</h3><pre data-language="ts"><code>interface StylesConfig {
|
|
/** CSS entry path relative to the app root. Default: app/styles/global.css */
|
|
entry?: string;
|
|
/** Custom processor — return the final CSS string (Tailwind/PostCSS/Sass). */
|
|
process?: (ctx: StyleProcessContext) => string | Promise<string>;
|
|
}
|
|
|
|
interface StyleProcessContext {
|
|
entryPath: string | null; // resolved absolute CSS entry, or null
|
|
appDir: string;
|
|
appRoot: string;
|
|
mode: Mode; // "development" | "production"
|
|
}</code></pre></article><article class="example-card"><h3>Example 3</h3><pre data-language="ts"><code>type ThemeTokens = Record<string, string>;
|
|
|
|
interface ThemeConfig {
|
|
default?: string; // theme used when no cookie is present
|
|
themes?: Record<string, ThemeTokens>; // deep-merged over built-in light/dark
|
|
}
|
|
|
|
interface ResolvedTheme {
|
|
default: string;
|
|
names: string[];
|
|
themes: Record<string, ThemeTokens>;
|
|
}</code></pre></article><article class="example-card"><h3>Example 4</h3><pre data-language="ts"><code>import type { AppConfig } from "@wrnexus/styles";
|
|
|
|
export default {
|
|
head: [
|
|
'<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5/dist/css/bootstrap.min.css">',
|
|
],
|
|
port: 3000,
|
|
db: { driver: "sqlite", url: "app.db" },
|
|
theme: {
|
|
default: "dark",
|
|
themes: {
|
|
light: { "color-primary": "#7c3aed" }, // override one token; rest inherited
|
|
brand: {
|
|
// add a whole new theme
|
|
"color-scheme": "dark",
|
|
"color-bg": "#0a0a0a",
|
|
"color-primary": "#22d3ee",
|
|
},
|
|
},
|
|
},
|
|
styles: {
|
|
entry: "app/styles/main.css",
|
|
},
|
|
profiles: {
|
|
production: {
|
|
db: { driver: "postgres", url: process.env.DATABASE_URL! },
|
|
},
|
|
},
|
|
} satisfies AppConfig;</code></pre></article></div></section></article>
|
|
<aside class="on-this-page"><h2>On this page</h2><nav><a class="toc-level-2" href="#guide">Guide</a><a class="toc-level-3" href="#overview">Overview</a><a class="toc-level-3" href="#installation">Installation</a><a class="toc-level-3" href="#api">API</a><a class="toc-level-4" href="#config-loading">Config loading</a><a class="toc-level-4" href="#appconfig">AppConfig</a><a class="toc-level-4" href="#styles-pipeline">Styles pipeline</a><a class="toc-level-4" href="#theme-system">Theme system</a><a class="toc-level-3" href="#usage">Usage</a><a class="toc-level-4" href="#wrnexus-config-ts">wrnexus.config.ts</a><a class="toc-level-4" href="#loading-config-producing-css">Loading config + producing CSS</a><a class="toc-level-4" href="#rendering-the-theme">Rendering the theme</a><a class="toc-level-3" href="#requirements-notes">Requirements / Notes</a><a class="toc-level-2" href="#api">Complete API</a><a class="toc-level-2" href="#examples">Examples</a></nav></aside>
|
|
</main>
|
|
<footer>WRNexusJS 0.2.12 · SSR-first · Bun-native · Documentation generated from published package APIs.</footer>
|
|
</div>
|
|
}
|
|
}
|