Closes #4282. mara: "scope looks good" — approving the plan posted there (manifest + icons + minimal shell-only service worker + iOS meta tags) and both explicit questions (network-first-with-offline-fallback, never cache /api/*). docs/web-ui/design-guide.md's "Layout & viewport" section already asserted swarm-ui is installable as a PWA — this is what actually backs it. - manifest.webmanifest: name/icons/start_url/standalone display, theme #cba6f7 / background #1e1e2e matching the mocha --purple/--bg values. - sw.js: plain JS, not TypeScript — the DOM lib swarm-ui's own tsconfig uses and the WebWorker lib a service worker's globals need are mutually exclusive in one tsc program, not worth a second tsconfig for a self-contained ~100-line file. Scoped to the app shell only, never touches /api/* at all, network-first with offline-fallback-to-cache (not cache-first) since main.js/main.css are unhashed filenames and a cache-first SW would risk wedging an operator on stale JS after a deploy. - index.html: manifest link, theme-color meta, iOS apple-mobile-web-app-* tags (Safari ignores the manifest spec). - main.tsx: feature-detected SW registration. - branding/hyperhive-maskable.svg: hyperhive.svg's own artwork already fills nearly its whole canvas, so a maskable icon needs a padded variant or an OS mask crops the outer ring/corner brackets — embeds the original via a scaled <image> ref rather than duplicating markup. - nix/packages/swarm-ui.nix: rasterizes icon-192/512/512-maskable.png from the branding SVGs at build time via librsvg, rather than checking in static PNGs. Verified for real: typecheck+build green, and a real headless-chromium tab driven over CDP confirms the service worker registers and becomes the active controller, and a simulated-offline reload still serves the full cached shell rather than a browser error page. nix build .#swarm-ui also verified green, including the rasterized icon output.
79 lines
3.3 KiB
JavaScript
79 lines
3.3 KiB
JavaScript
// esbuild build for @hive/swarm-ui (project-bootstrap scope). Output
|
|
// layout (`dist/`):
|
|
//
|
|
// dist/index.html served at GET /
|
|
// dist/static/main.js served at /static/main.js (ESM bundle,
|
|
// Preact + wouter-preact)
|
|
// dist/static/main.js.map source map sibling
|
|
// dist/static/main.css every component's own `import
|
|
// './Foo.css'` (Shell.css, Panel.css, …),
|
|
// folded by esbuild into one companion
|
|
// output alongside main.js — no separate
|
|
// build step, this falls out of bundling
|
|
// main.tsx with `bundle: true`
|
|
// dist/static/colors.css served at /static/colors.css
|
|
// dist/static/theme.css served at /static/theme.css
|
|
// dist/static/swarm-ui.css served at /static/swarm-ui.css — just the
|
|
// shared base reset now (@import resolved
|
|
// from @hive/shared), NOT component styles
|
|
// dist/manifest.webmanifest served at /manifest.webmanifest (PWA)
|
|
// dist/sw.js served at /sw.js (PWA) — plain JS, no
|
|
// esbuild pass needed (see its own
|
|
// top comment for why); copied as-is.
|
|
//
|
|
// PWA icons (icon-192.png/icon-512.png/icon-512-maskable.png) are NOT
|
|
// produced here — like favicon.svg, they're rasterized from
|
|
// branding/*.svg and copied in by nix/packages/swarm-ui.nix's
|
|
// installPhase, outside the npm tree this script builds. A plain `npm
|
|
// run build` here has no `/icon-*.png`, same as it's always had no
|
|
// `/favicon.svg`.
|
|
//
|
|
// Not yet wired into any Rust binary's `ServeDir` — swarm-controller
|
|
// only serves `/health` today (see swarm-controller/README.md); this
|
|
// just makes `dist/` buildable so nix/packages/swarm-ui.nix has
|
|
// something to package. `.tsx` → JS: esbuild transpiles TypeScript
|
|
// natively (types stripped, not checked) — run `npm run typecheck`
|
|
// (plain `tsc --noEmit`) separately for real type errors; not yet
|
|
// wired into CI.
|
|
|
|
import { build } from "esbuild";
|
|
import { mkdirSync, copyFileSync, rmSync } from "node:fs";
|
|
import { dirname, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const src = (p) => resolve(here, "src", p);
|
|
const dist = (p) => resolve(here, "dist", p);
|
|
const staticDir = (p) => resolve(here, "dist", "static", p);
|
|
|
|
rmSync(dist(""), { recursive: true, force: true });
|
|
mkdirSync(staticDir(""), { recursive: true });
|
|
|
|
await build({
|
|
entryPoints: [src("main.tsx")],
|
|
outfile: staticDir("main.js"),
|
|
bundle: true,
|
|
format: "esm",
|
|
platform: "browser",
|
|
target: ["es2022"],
|
|
sourcemap: true,
|
|
logLevel: "info",
|
|
jsx: "automatic",
|
|
jsxImportSource: "preact",
|
|
});
|
|
|
|
for (const entry of ["colors.css", "theme.css", "swarm-ui.css"]) {
|
|
await build({
|
|
entryPoints: [src(entry)],
|
|
outfile: staticDir(entry),
|
|
bundle: true,
|
|
loader: { ".css": "css" },
|
|
logLevel: "info",
|
|
});
|
|
}
|
|
|
|
copyFileSync(src("index.html"), dist("index.html"));
|
|
copyFileSync(src("manifest.webmanifest"), dist("manifest.webmanifest"));
|
|
copyFileSync(src("sw.js"), dist("sw.js"));
|
|
|
|
console.log("swarm-ui build ok →", dist(""));
|