Skip to content

perf(build): look up action owner modules once per build pass - #3415

Merged
james-elicx merged 1 commit into
cloudflare:mainfrom
shortstuffsushi:fix/action-owner-manifest-memoize
Sep 24, 2026
Merged

james-elicx merged 1 commit into
cloudflare:mainfrom
shortstuffsushi:fix/action-owner-manifest-memoize

Conversation

@shortstuffsushi

Copy link
Copy Markdown
Contributor

Fixes #3414

Since #2520, the action owner manifest pass walks the final module graph once per route. Each walk called Rolldown's getModuleInfo() for every module it visited, and the client pass awaited this.resolve() for every client reference of every route. On a 380-route App Router app that meant 3.3M getModuleInfo calls and 13,920 serial resolves, adding ~16s to the rsc and client builds.

Change

In packages/vinext/src/build/action-owner-manifest.ts:

  • collectRscActionReachability and addClientActionReachability wrap getModuleInfo in a per-call memo keyed by module id (null results are cached too).
  • resolveClientReferenceImportIds caches resolved, canonicalized ids by import id across routes. The plugin resolves without an importer, so the result does not depend on the route.

Both caches live only for one helper call inside one generateBundle, so there is nothing to invalidate. The per-route walks, their order, and the manifest output are unchanged.

Results

Our app (380 routes, ~10.2k client modules), vinext build, Apple M4 Max:

total rsc stage client stage
main 44.4s 9.3s 20.2s
this PR 29.3s 4.7s 9.1s

Inside the hook (instrumented locally on 0736fdd): rsc 4.8s -> 0.23s (1,239,421 -> 3,806 getModuleInfo calls); client 11.7s -> 1.0s (2,071,417 -> 10,167 getModuleInfo calls, 13,920 -> 202 resolves).

The generated __vinext_action_owner_manifest.js is byte-identical between main and this PR. The rest of dist has the same file list and the same content once content hashes, per-build random values (build id, revalidate secret) and the vinext install path are normalized. The install path differed only because the two builds installed vinext from differently named local tarballs; it feeds the client reference ids of vinext's own shims, which in turn change some key ordering and one quoted object key.

Tests

  • New unit test in tests/action-owner-manifest.test.ts: 50 routes sharing a layout, a page component, a client reference and actions. It asserts the manifest and that getModuleInfo and resolve run once per unique id. It fails on main (250 lookups instead of 54).
  • vp test run tests/action-owner-manifest.test.ts tests/entry-templates.test.ts: 74 passed.
  • pnpm run check: passes.
  • tests/e2e/app-router/action-owner-production.browser.spec.ts (app-router-chrome-browser-specific): 24 passed and 1 failed, the same as on main. The failing "keeps offline-computable production action ids harmless" test is macOS-only: os.tmpdir() is a /var/folders symlink while the test hashes a realpath under /private/var, so the expected id does not match. This PR does not change that.

Action owner reachability walks the final module graph once per route.
Each walk called Rolldown's getModuleInfo() for every visited module, and
the client pass awaited this.resolve() for every client reference of every
route. On a 380-route app that was 3.3M getModuleInfo calls and 13,920
serial resolves, adding ~16s to the rsc and client builds.

Memoize module info by id and resolved client reference ids by import id
for the duration of each helper call. The walks and the manifest output
are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AZV1vkLWA62r59W1fa1AbL
@james-elicx

Copy link
Copy Markdown
Member

/bigbonk review for issues
Please review exact head c099f266e87ee554fad446c963fefc9bc82eab79 without modifying or pushing the branch. Report all actionable findings within your 8-minute time limit.

@pkg-pr-new

pkg-pr-new Bot commented Sep 24, 2026

Copy link
Copy Markdown

Open in StackBlitz

@vinext/cloudflare

npm i https://pkg.pr.new/@vinext/cloudflare@3415

create-vinext-app

npm i https://pkg.pr.new/create-vinext-app@3415

@vinext/types

npm i https://pkg.pr.new/@vinext/types@3415

vinext

npm i https://pkg.pr.new/vinext@3415

@cloudflare/workers-response-store

npm i https://pkg.pr.new/@cloudflare/workers-response-store@3415

commit: c099f26

@ask-bonk

ask-bonk Bot commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

LGTM!

github run

@james-elicx
james-elicx merged commit 8673c62 into cloudflare:main Sep 24, 2026
66 checks passed
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.

Build time regression since 1.0.0-beta.7: action owner manifest repeats getModuleInfo/resolve per route (#2520)

2 participants