Skip to content

feat: hot-swap route handlers in dev mode#41

Merged
khromov merged 11 commits into
mainfrom
feat/route-hmr
May 27, 2026
Merged

feat: hot-swap route handlers in dev mode#41
khromov merged 11 commits into
mainfrom
feat/route-hmr

Conversation

@khromov

@khromov khromov commented May 25, 2026

Copy link
Copy Markdown
Owner

Summary

  • API/WS/SSE route handlers and page serverProps/actions were captured by value at startup and never refreshed — changing a handler required a full server restart
  • Adds a routeModule option to MochiServeOptions pointing to the routes file
  • In dev mode, when a dependency of that module changes, the dev watcher re-bundles it via Bun.build({ packages: 'external' }), reimports with cache busting, and hot-swaps handler references through mutable maps
  • WS handlers already used lazy lookup via wsHandlersMap — only API, SSE, and page routes needed new indirection
  • No production code path changes — all maps and lazy lookups are gated on development && routeModule

Test plan

  • bun run checks passes (lint, format, typecheck, tests)
  • Smoke tested: started dev server, hit /health, edited handler to add hmr: true field, hit again — response reflected the change without restart
  • Verify page serverProps HMR by editing a data-loading function
  • Verify SSE/WS handler HMR
  • Verify that adding/removing routes logs a warning (requires restart)

Route handlers were captured by value in closures at startup and never
refreshed — changing an API handler required a full server restart.

Add a `routeModule` option pointing to the routes file. In dev mode,
when a dependency of that module changes the dev watcher re-bundles it
via `Bun.build({ packages: 'external' })`, reimports with cache
busting, and updates mutable handler maps that route closures read
lazily on each request. WS handlers already used lazy lookup via
`wsHandlersMap` so only API, SSE, and page serverProps/actions needed
new indirection.
@github-actions

github-actions Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Mochi review report

Try this PR

Expand instructions
gh run download -R khromov/mochi 26543966964 -n mochi-framework-pr -D /tmp/mochi-pr && bun i /tmp/mochi-pr/mochi-framework-pr.tgz

Download manually

Dependency report

Expand report
Direct: 13
Peer:   3 (svelte, @tailwindcss/node, @tailwindcss/oxide)
Dev:    8
Total unique packages reachable from production deps (roots + transitive): 31
Total on-disk size of those packages: 5.28 MB

Toplist — direct deps ranked by total size (self + transitive):
      total       self  count  package
    4.77 MB    2.71 MB     19  svelte
   534.0 kB   449.0 kB      1  magic-string
   189.0 kB   146.0 kB      1  stale-while-revalidate-cache
   180.6 kB   145.3 kB      1  chokidar
    51.2 kB    51.2 kB      0  devalue
    32.4 kB    32.4 kB      0  nanoid
    30.4 kB    30.4 kB      0  deepmerge
    28.0 kB    28.0 kB      0  negotiator
    25.8 kB    25.8 kB      0  mitt
    25.3 kB    25.3 kB      0  js-cookie
    12.3 kB    12.3 kB      0  zimmerframe
     8.4 kB     8.4 kB      0  debounce
     5.2 kB     5.2 kB      0  @types/negotiator

Transitive breakdown for the heaviest deps:

  svelte (19, 2.05 MB transitive): @jridgewell/gen-mapping (91.6 kB), @jridgewell/remapping (58.0 kB), @jridgewell/resolve-uri (51.9 kB), @jridgewell/sourcemap-codec (85.0 kB), @jridgewell/trace-mapping (143.3 kB), @sveltejs/acorn-typescript (194.2 kB), @types/estree (25.5 kB), @types/trusted-types (8.4 kB), acorn (545.5 kB), aria-query (172.8 kB), axobject-query (108.3 kB), clsx (8.4 kB), devalue (51.2 kB), esm-env (3.7 kB), esrap (86.1 kB), is-reference (3.9 kB), locate-character (5.2 kB), magic-string (449.0 kB), zimmerframe (12.3 kB)

  magic-string (1, 85.0 kB transitive): @jridgewell/sourcemap-codec (85.0 kB)

  stale-while-revalidate-cache (1, 43.0 kB transitive): emittery (43.0 kB)

  chokidar (1, 35.3 kB transitive): readdirp (35.3 kB)

Lines of code

packages/mochi
Category main PR Δ
src/Mochi.ts 1103 1156 +53
src/ComponentRegistry.ts 1516 1524 +8
src/{types.ts,*.d.ts} 608 623 +15
Other 1940 2230 +290
Total 16222 16588 +366

Unchanged: src/**/*.test.ts (5401), src/hooks.ts (229), src/{requestContext,forms,errors}.ts (260), src/{events,log,logger}.ts (322), src/consoleLogger.ts (342), src/cookies*.ts (157), src/extensions.ts (191), src/cache.ts (60), src/middleware/** (81), src/enhance*.ts (184), src/build*.ts (250), src/proxy.ts (125), src/cli* (112), src/{csrf,serverIslandCrypto}.ts (240), src/web-components/** (445), src/debug-bar/** (1866), src/templates/** (790).

packages/docs
Category main PR Δ
Docs 3588 3602 +14
Total 3588 3602 +14
packages/site
Category main PR Δ
Total 9963 9963 0

Unchanged: src/demos/** (5616), src/components/** (2078), src/lib/** (938), src/stores/** (26), Other (1305).

packages/demos
Category main PR Δ
Total 3107 3107 0

Unchanged: src/hn/** (1085), Other (2022).

packages/minimal
Category main PR Δ
Total 538 538 0

Unchanged: Other (538).

packages/cli
Category main PR Δ
Total 479 479 0

Unchanged: src/**/*.test.ts (117), src/cli* (138), src/{create,templates,utils}.ts (220), Other (4).

khromov added 3 commits May 25, 2026 02:37
Probe for ./src/routes.ts (then .js) in dev mode so route HMR works
out of the box without explicit config. routeModule option remains as
an override. Remove the now-redundant explicit routeModule from all
three site configs.
@khromov khromov marked this pull request as ready for review May 25, 2026 21:58
khromov added 7 commits May 26, 2026 00:02
Replace non-null assertions on HMR handler map lookups with fallbacks
to the originally captured handler, and sequence the initial route
module build through the reload chain to prevent a race with early
file changes.
@khromov khromov merged commit 106a4a2 into main May 27, 2026
3 checks passed
@github-actions github-actions Bot mentioned this pull request May 27, 2026
khromov pushed a commit that referenced this pull request May 29, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>mochi-framework: 0.5.0</summary>

##
[0.5.0](mochi-framework-v0.4.0...mochi-framework-v0.5.0)
(2026-05-28)


### Features

* hot-swap route handlers in dev mode
([#41](#41))
([106a4a2](106a4a2))
* opt-in route warmup to pre-warm static pages at startup
([#64](#64))
([9f28ede](9f28ede))
* replace highlight.js with Shiki
([#54](#54))
([3d2dc6a](3d2dc6a))


### Code Refactoring

* replace `cookie` npm package with Bun native cookie APIs
([#60](#60))
([4e0d953](4e0d953))
* run all tests in per-file isolation, drop .isolated.test.ts suffix
([#55](#55))
([68cdc06](68cdc06))
* vendor debounce, drop npm dependency
([#63](#63))
([d465fea](d465fea))
* vendor json-format-highlight, drop npm dependency
([#61](#61))
([0b88399](0b88399))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@khromov khromov deleted the feat/route-hmr branch June 6, 2026 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant