---
package: "@webjsdev/server"
version: 0.8.1
date: 2026-05-30T10:39:11.257Z
commit_count: 14
---
## Features

- **make /__webjs/ready a real readiness gate, add warm-up timing** ([#141](https://github.com/webjsdev/webjs/pull/141)) [`440e2d5`](https://github.com/webjsdev/webjs/commit/440e2d5)
  /ready now returns 503 until the lazy first-request analysis is warm, then
  200, so a Kubernetes readinessProbe holds traffic off a not-yet-warm
  instance instead of routing the first user request into the cold analysis.
  /health stays liveness-only. ensureReady also logs a one-line per-pass
  timing breakdown (graph/scan/gate/actions/middleware/elision/vendor) so a
  slow first request is diagnosable.
- **decouple vendor resolution from readiness; request-driven retry** ([#141](https://github.com/webjsdev/webjs/pull/141)) [`6fb9c29`](https://github.com/webjsdev/webjs/commit/6fb9c29)
  Readiness gates on the deterministic analysis only, not vendor resolution, so
  an offline or partially-unresolvable app still boots. Vendor stays best-effort
  (pinned reads the committed map; unpinned auto-fetches jspm). A transient
  failure (network / timeout / jspm 5xx) is re-attempted on the next request,
  non-blocking, with no internal retry timer: the platform's traffic and probes
  are the retry loop. A permanent unresolvable (jspm 401 for a private/workspace
  dep) reports ok and is tolerated. The warm-up is a single best-effort kick.
- **optional readiness.{js,ts} hook for live dependency health** ([#141](https://github.com/webjsdev/webjs/pull/141)) [`568fe87`](https://github.com/webjsdev/webjs/commit/568fe87)
  An app may default-export an async check from readiness.{js,ts} at its
  root; once the analysis is warm, /ready runs it on every probe and reports
  503 unready if it returns false or throws, so a readinessProbe can gate on
  live dependency health (a DB ping) that static analysis cannot see.
- **self-warm the lazy analysis in the background after listen** ([#141](https://github.com/webjsdev/webjs/pull/141)) [`72e7a15`](https://github.com/webjsdev/webjs/commit/72e7a15)
  Boot stays instant, but a real first request no longer has to pay the
  analysis latency. createRequestHandler exposes warmup() (a caught,
  idempotent call to the single-flighted ensureReady), and startServer fires
  it fire-and-forget once the HTTP server is listening, so the module graph,
  scan, gate, action index, middleware, elision, and vendor map are warm
  before the first real request, hiding the analysis latency for long-lived
  processes.

## Performance

- **evict deleted-file entries from the parse/scan caches on rebuild** ([#141](https://github.com/webjsdev/webjs/pull/141)) [`e31db37`](https://github.com/webjsdev/webjs/commit/e31db37)
  The incremental-rebuild parse and scan caches only ever overwrote per-path
  entries, so a long dev session with renames/deletes accumulated dead
  entries. buildModuleGraph and scanComponents now drop any cache key under
  the app dir not seen in the latest walk, bounding cache size to live files.
- **defer pure-RPC server-module loads off boot** ([#141](https://github.com/webjsdev/webjs/pull/141)) [`88efccd`](https://github.com/webjsdev/webjs/commit/88efccd)
  buildActionIndex hashed every .server file AND imported it, executing every
  server module at boot (firing Prisma init, DB connects, and any module-init
  side effect). Only expose() needs eager loading (it registers a REST route
  the router must know before a request hits it). So hash all server files
  for the RPC dispatch index, but eagerly load only the ones that reference
  expose(); every other server module imports on its first call.
- **skip the boot bare-import scan when a vendor pin file exists** ([#141](https://github.com/webjsdev/webjs/pull/141)) [`19ca11a`](https://github.com/webjsdev/webjs/commit/19ca11a)
  resolveVendorImports read the pin file but only AFTER dev.js had already run
  the whole-app scanBareImports walk, whose result the pin path discards. Move
  the scan behind a thunk that resolveVendorImports invokes only on the
  unpinned path, so a pinned app (the recommended posture) does zero vendor
  static analysis, reading only the committed importmap file.
- **defer unpinned vendor resolve off boot to first request** ([#141](https://github.com/webjsdev/webjs/pull/141)) [`7301fa8`](https://github.com/webjsdev/webjs/commit/7301fa8)
  Boot no longer resolves the vendor import map at all. A memoized,
  single-flighted ensureVendor() runs on the first request (before any SSR
  head emits the importmap or its build hash): a pinned app pays only a file
  read, an unpinned one pays the scan + jspm.io call, both off the boot path.
- **compute elision lazily on first request, not at boot** ([#141](https://github.com/webjsdev/webjs/pull/141)) [`645285b`](https://github.com/webjsdev/webjs/commit/645285b)
  The analyzeElision fixpoint no longer runs at boot. A memoized,
  single-flighted ensureElision() computes the elidable-component and
  inert-route sets on the first request (fast, in-memory over the module
  graph, no network or module execution) before any SSR or module serve
  reads them. Boot does no elision work at all.
- **defer all whole-app analysis to a lazy first-request memo** ([#141](https://github.com/webjsdev/webjs/pull/141)) [`cb8f00c`](https://github.com/webjsdev/webjs/commit/cb8f00c)
  Boot now builds only the route table (a cheap directory scan, kept eager so
  routing / Early Hints / WebSocket lookups are always available). The module
  graph, component scan + registry prime, browser-bound gate, action index,
  middleware, elision, and vendor map are consolidated into one memoized,
  single-flighted ensureReady() that runs on the first request, so boot reads
  no app source, executes no server module, walks no graph, and makes no
  network call.
- **mtime-keyed parse/scan caches make rebuilds incremental** ([#141](https://github.com/webjsdev/webjs/pull/141)) [`259444a`](https://github.com/webjsdev/webjs/commit/259444a)
  buildModuleGraph and scanComponents re-read every file's content on each
  rebuild. Add an mtime-keyed cache to each: a rebuild re-walks the (cheap)
  directory tree but reuses the cached import set / component list for any
  file whose mtime is unchanged, reading + regex-parsing only the files that
  actually changed, so large apps get near-instant rebuilds.
- **make the elision render-rule analysis linear (O(N+E))** ([#141](https://github.com/webjsdev/webjs/pull/141)) [`e47346f`](https://github.com/webjsdev/webjs/commit/e47346f)
  Benchmarking runtime-first boot surfaced an O(N^2)/O(N^3) blowup in the
  elision analyser on deep component render chains: ~13s at 10k components and
  out-of-memory at 20k. Three super-linear pieces, all now linear: emittableTags
  follows helper edges only, the closure-client-work check is one reverse-BFS,
  and the fixpoint is a worklist over reverse-import edges. 10k went from 13.4s
  to 0.5s; 20k from out-of-memory to 1.0s. Verdict-safe (it never under-ships).

## Fixes

- **widen lazy-index expose detection to aliased imports** ([#141](https://github.com/webjsdev/webjs/pull/141)) [`962752f`](https://github.com/webjsdev/webjs/commit/962752f)
  The lazy action index matched only the literal `expose(`, so an aliased
  import (`import { expose as exp }; exp(...)`) was never eagerly loaded and
  its REST route silently 404'd while the RPC stub kept working. Match the
  bare `expose` identifier instead, so the route registers regardless of how
  the import is named.
- **key the incremental parse/scan caches by mtime AND size** ([#141](https://github.com/webjsdev/webjs/pull/141)) [`f8b6df8`](https://github.com/webjsdev/webjs/commit/f8b6df8)
  The mtime-keyed caches added for incremental rebuild could, on a coarse-
  resolution filesystem or a sub-tick edit, miss a content change that left
  mtime unchanged. Adding the file size to the key catches any length-
  changing edit even when mtime collides. Cheap correctness hardening; the
  size discriminator catches the rare same-mtime length-changing edit.
