feat(server): serve the client-only build with Bun

Angular's server build needs Node loader hooks that Bun does not apply,
and every route is client-rendered, so Angular now builds a client-only
bundle and a small Bun.serve server delivers it: hashed assets with the
one-year cache, index.html revalidated, extension-less routes falling
back to index.html, missing assets 404, traversal and malformed paths
rejected, and other methods 405. Express, @angular/ssr and the unused
hydration provider are removed. Both Docker stages use the pinned
oven/bun:1.4.3-alpine image without Node, and the health checks use
Bun's fetch.

Refs #292

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
nessarandClaude Opus 5.5 committed 2026-10-17 13:50:05 +02:00
1 parent 94d57add2c
commit f74d744f9b
15 files changed
+260 -306

No files matched your search

+8 -6
View File
@@ -17,7 +17,7 @@ The planner answers these questions:
```mermaid
flowchart LR
Host["Express production host<br/>static and client assets"] -. serves .-> UI
Host["Bun static server<br/>client assets"] -. serves .-> UI
subgraph Browser
UI["Angular standalone components"]
@@ -42,7 +42,7 @@ flowchart LR
end
```
Leave calculation stays in the browser. The production Express process serves the generated Angular application; it does not participate in planning. Browser calculations run cooperatively in a dedicated worker. The resumable core is emitted as a separate worker/lazy chunk and retains a synchronous adapter for direct core callers. The original decision trace and historical analysis were removed in issue #76; immutable checkpoints and final state remain available for cancellation recovery and displayed-placement alternatives.
Leave calculation stays in the browser. The production Bun process serves the generated client-only Angular application; it does not participate in planning. Browser calculations run cooperatively in a dedicated worker. The resumable core is emitted as a separate worker/lazy chunk and retains a synchronous adapter for direct core callers. The original decision trace and historical analysis were removed in issue #76; immutable checkpoints and final state remain available for cancellation recovery and displayed-placement alternatives.
### Main execution path
@@ -164,7 +164,7 @@ Incomplete outcomes explain the retained plan and unfinished phases; conflicts i
- Bun for dependency management and project scripts
- `bun test` with jsdom and Angular TestBed (JIT)
- ESLint and Prettier
- Angular server build hosted by Express
- Client-only Angular build served by a small `Bun.serve` static server (`src/server.ts`)
- Gitea repository hosting and a multi-stage Docker image
The `/benchmark` route and planner core are lazy-loaded, so the normal application does not execute the benchmark or bundle the planner core into its main entry point.
@@ -220,8 +220,8 @@ Open <http://localhost:4200>. The development server reloads when source files c
| `bun run format:check` | Check Prettier formatting. |
| `bunx tsc --noEmit -p tsconfig.app.json` | Type-check application code. |
| `bunx tsc --noEmit -p tsconfig.spec.json` | Type-check tests. |
| `bun run build` | Create the optimized browser and server bundles in `dist/`. |
| `bun run serve:ssr:vac-optimizer` | Serve a completed production build on port 4000. |
| `bun --bun run build` | Create the optimized browser bundle and the static server bundle in `dist/vac-optimizer/`. |
| `bun run serve` | Serve a completed production build on `PORT` (default 4000). |
## Performance benchmark
@@ -236,7 +236,9 @@ docker build -t vac-optimizer .
docker run --rm -p 4000:4000 vac-optimizer
```
The image builds the Angular application with Bun and Node, then runs the generated Express server with Bun. Set `PORT` to override the default port `4000`.
The image builds the Angular application and the server bundle with Bun alone (no Node), then runs `dist/vac-optimizer/server/server.js` with Bun. The bundle has no runtime dependencies, so the production stage only copies `dist/` and installs nothing. Set `PORT` to override the default port `4000`; the health check requests `/`.
The server answers `GET` and `HEAD` only (other methods get 405). It serves files from `dist/vac-optimizer/browser` with `Cache-Control: public, max-age=31536000` (content-hashed assets), and returns `index.html` with `Cache-Control: no-cache` for `/index.html` and for every extension-less path, so direct navigation to client routes such as `/benchmark` works. A missing path with a file extension is a 404 rather than HTML, malformed or escaping paths are a 400, and unexpected failures are logged and answered with a plain 500.
## Continuous integration