Skip to content

refactor(router): remove dead merged route trie/entries, share the flattened map - #753

Merged
ivogt merged 1 commit into
mainfrom
refactor/drop-dead-merged-route-data
Jul 10, 2026
Merged

ivogt merged 1 commit into
mainfrom
refactor/drop-dead-merged-route-data

Conversation

@ivogt

@ivogt ivogt commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #752 (base branch fix/cf-dev-stale-authoritative-trie; GitHub retargets to main when it merges).

Problem statement

The route-data audit that followed #751 found that the global merged route data is dead weight: find-match.ts — the only runtime matcher, including build-time prerender via matchForPrerender — reads exclusively per-router tries (find-match.ts:73), yet every production build and every dev discovery/HMR pass ran a full second O(total-routes) trie construction (discover-routers.ts:301-346) whose product nothing ever consumed. The global trie's only reader was manifest-init.ts:49 — which read it solely to decide whether to write it. The comment justifying the build-time push ("needed for matchForPrerender") was factually wrong: the per-router install directly below it is what prerender collection matches on. Same story for the merged precomputedEntries (dev-only writer, unreachable ?? fallback at router.ts:409 since every discovered router gets a per-router array — [] is truthy) and mergedRouteTrailingSlash (existed only to feed the dead merged build).

Separately, the eager routes-manifest module ran __flat() twice per router — once for setCachedManifest, once for setRouterManifest — producing two byte-identical maps; a single-router app held its full route map twice, and the second transform ran on every cold start. The exact disease #751 removed from the lazy module, one module over.

No consumer-visible change: no public API surface is used by any app/test/example (verified by repo-wide grep; only re-exports removed under the pre-release remove-don't-deprecate rule), and reverse()/href()/matching behavior is identical.

What was removed

Structure Writers removed Why dead
cachedRouteTrie + setRouteTrie/getRouteTrie discover-routers.ts build push, propagateDiscoveryState dev push, manifest-init.ts self-guarded write only reader was its own write-guard
merged-trie build block (routeToStaticPrefix/prerender/passthrough/responseType collection + buildRouteTrie call) discover-routers.ts:301-346 product never consumed
cachedPrecomputedEntries + set/getPrecomputedEntries dev push in propagateDiscoveryState; ?? getPrecomputedEntries() fallback in router.ts fallback unreachable
mergedRouteTrailingSlash, state.mergedRouteTrie, state.mergedPrecomputedEntries accumulation + commits fed only the dead build
buildRouteToStaticPrefix re-export in manifest-utils.ts — last consumer was the dead block (function stays live via route-trie.ts)

The __flat sharing (before vs. after, generated eager module, single router)

Before:

setCachedManifest(__flat(_r0));          // full flatten + allocation #1
setRouterManifest("id", __flat(_r0));    // full flatten + allocation #2, identical content

After:

const __m0 = __flat(_r0);                // one flatten, one object
setCachedManifest(__m0);
setRouterManifest("id", __m0);

Multi-router: one const __mN per router, shared into setRouterManifest, spread into the setCachedManifest({...}) union. Sharing safety was verified against every consumer of getGlobalRouteMap()/getRouterManifest() (all read-only; lazy-include deltas mutate globalRouteMap, a different object; the handler self-heal replaces cachedManifest with a new object rather than mutating). Pinned by a codegen test asserting exactly one map materialization.

What this buys

Build/HMR latency: one full trie construction + O(routes) prefix-map pass removed per production build and per dev discovery pass (tens of ms at the 26k-route reference scale, noise at small scale). Cold start: one O(routes) __flat pass and one duplicate map allocation removed per router. Memory: single-router apps hold one flattened map instead of two. Maintenance: three writers no longer need to keep a value "correct" that nobody reads, and the stale ensureRouterManifest comment claiming a "global merged trie" fallback (find-match never had one — the real fallback is regex over live routes) is corrected. Net −132 lines.

Code-change summary

  • src/route-map-builder.ts — global trie/precomputed slots + 4 accessors deleted; clearAllRouterData no longer clears them; ensureRouterManifest comment corrected.
  • src/vite/discovery/discover-routers.ts — merged-trie build block and merged-entries accumulation deleted; per-router build and RSC-realm per-router install (which prerender collection actually uses) untouched.
  • src/vite/discovery/virtual-module-codegen.ts — per-router map consts (__m<i>) shared between both setters; genFileVarByEntry replaces the dual-counter indexing; stale merged-trie comment updated.
  • src/vite/router-discovery.ts — propagateDiscoveryState drops the two global pushes (per-router loop unchanged).
  • src/rsc/manifest-init.ts, src/server.ts, src/router.ts — dead imports/exports/fallback removed.
  • AGENTS.md Bundle Hygiene rule Router idea 2 revisit #1 + docs/manifests.md — reworded off the deleted identifiers (contract unchanged: route data in exactly one lazy chunk).
  • Tests: state literals and handler-test mocks cleaned of dead keys; new codegen pins (single materialization; no merged setters emitted).

Test plan

  • pnpm run typecheck, pnpm run test:unit:all, pnpm run lint, pnpm run format
  • New codegen unit pins: shared-map shape (const __m0 + both setters + exactly one JSON.parse), no setRouteTrie/setPrecomputedEntries emitted
  • test-app production e2e: 921 passed (includes prerender/shell/PPR — the paths the removed build-time push claimed to serve)
  • cloudflare-multi-router e2e: 54 passed (multi-router reverse/isolation)
  • cloudflare-basic production: 378 passed; cloudflare-basic HMR suites: 14 passed; node route-types-hmr: 18 passed
  • build-test-app bundle guard green; built entry inspected: setCachedManifest(__m0) + setRouterManifest("<id>", __m0) share one object

Notes

  • Stacked on fix(vite): register per-router manifest loaders in build only — CF dev stale authoritative trie #752 because both touch generateRoutesManifestModule.
  • /simplify protocol ran (2 agents × 4 angles). Applied: dead buildRouteToStaticPrefix re-export removal, genFileVarByEntry replacing the dual-counter indexing. Skipped: none surviving.
  • The audit's remaining latent note (globalSearchSchemas/rootScopeRoutes are global, so same-named routes across routers last-writer-win) is deliberately not touched here — needs a repro first.

Base automatically changed from fix/cf-dev-stale-authoritative-trie to main July 10, 2026 17:36
…attened map

The global merged route trie was dead: its only reader was manifest-init's
own write-guard, while find-match.ts matches exclusively on per-router
tries (including build-time prerender via matchForPrerender). Yet every
production build and every dev discovery/HMR pass ran a full second
O(total-routes) trie construction to produce it. The merged
precomputedEntries global was equally dead (dev-only writer; the
'?? getPrecomputedEntries()' fallback was unreachable because every
discovered router gets a per-router array), and mergedRouteTrailingSlash
existed only to feed the dead merged build.

Removed: cachedRouteTrie/setRouteTrie/getRouteTrie,
cachedPrecomputedEntries/setPrecomputedEntries/getPrecomputedEntries, the
merged-trie build block in discover-routers, state.mergedRouteTrie/
mergedPrecomputedEntries, the propagateDiscoveryState global pushes, and
the dead buildRouteToStaticPrefix re-export (pre-release API hygiene:
removed, not deprecated; no external importers).

Also: the eager routes-manifest codegen now materializes each router's
flattened name->path map ONCE and shares the object between
setCachedManifest and setRouterManifest — __flat previously ran twice per
router on cold start and a single-router app held two byte-identical maps.
Sharing is safe: all consumers read these maps; lazy-include deltas merge
into globalRouteMap, a different object.
@ivogt
ivogt force-pushed the refactor/drop-dead-merged-route-data branch from f12e9d8 to a13d69a Compare July 10, 2026 17:42
@pkg-pr-new

pkg-pr-new Bot commented Jul 10, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@rangojs/router@753

commit: a13d69a

@ivogt
ivogt merged commit 5d25ffe into main Jul 10, 2026
34 checks passed
@ivogt
ivogt deleted the refactor/drop-dead-merged-route-data branch July 10, 2026 17:56
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