Skip to content

Fix/server fn cross file manifest#7752

Open
plauclair-nesto wants to merge 3 commits into
TanStack:mainfrom
plauclair-nesto:fix/server-fn-cross-file-manifest
Open

Fix/server fn cross file manifest#7752
plauclair-nesto wants to merge 3 commits into
TanStack:mainfrom
plauclair-nesto:fix/server-fn-cross-file-manifest

Conversation

@plauclair-nesto

@plauclair-nesto plauclair-nesto commented Jul 7, 2026

Copy link
Copy Markdown

Fix: server functions missing from the production manifest (TanStack Start, issue #7213)

Repository: fork of TanStack/router
Branch: fix/server-fn-cross-file-manifest
Commits: 8b53769ee4 (Playwright bump) · d33b368de7 (the fix)
Upstream issue: #7213


1. Executive summary

TanStack Start lets you write server functions — functions that run on the server and are callable from the browser over HTTP (an RPC). In production builds, a whole category of server functions was silently left out of the server's lookup table ("the manifest"), so calling them crashed at runtime with:

Server function info not found for <id>

Crucially, this only happened in production builds, not in development — so it was the kind of bug that passes local testing and breaks in production.

The affected category: any server function that is only ever reached from server-side code — for example, one called inside a middleware, or called by another server function. Organising code into separate files (a completely normal practice) reliably triggered it.

The fix makes the build finish discovering these server functions before it writes out the manifest. It is a one-file change to the build tooling (@tanstack/start-plugin-core), shipped as a patch. We validated it with new automated tests and the full existing test suite across all three supported frameworks (React, Solid, Vue). Build-time impact is negligible (single-digit milliseconds, within measurement noise). There is no security or API change.


2. Background

A server function is declared with createServerFn().handler(...). At build time the tooling splits each one into two pieces: a small client stub (shipped to the browser, makes an HTTP call) and the real implementation (kept on the server). The server keeps a manifest — a map from a function's ID to its implementation — so that when a request arrives (or when server code calls the function in-process), it can find and run the right implementation. If a function is missing from that manifest, any attempt to resolve it throws Server function info not found.


3. The bug

Symptom. Production builds throw Server function info not found for <id> at runtime. Development works fine. Single-file setups work fine. The error appears once server functions and the code that uses them are split across files.

What triggers it. A server function that is reached only through server-only code. Two common shapes:

  • A middleware whose .server() handler calls a server function defined in another file.
  • A server function whose handler calls another server function in another file.

Why it matters. Both shapes are ordinary, encouraged patterns (shared middleware, shared server-side helpers, code organised into modules). The failure is production-only, so it evades local development and typically surfaces only after deploy. The error message is opaque (an internal ID, no source location).

Where the error originates. packages/start-plugin-core/src/start-compiler/server-fn-resolver-module.ts:53 — the generated resolver throws when an ID is absent from the manifest.


4. Root cause

Three facts combine into a timing race.

(a) A server function is registered only when its file is compiled.
The build registers a server function into the shared registry (serverFnsById) as a side effect of compiling the file that defines it — see packages/start-plugin-core/src/start-compiler/handleCreateServerFn.ts:350 and :476. If a definition file never gets compiled before the manifest is written, the function is absent.

(b) The client build cannot see server-only functions.
The browser build strips out server-only code: a middleware's .server() body is removed (packages/start-plugin-core/src/start-compiler/handleCreateMiddleware.ts:68), and a server function's handler body is replaced by a client stub. Dead-code elimination then drops the now-unused imports. So a function reached only from that stripped code never enters the client build's module graph, and is never registered during the client pass.

(c) The manifest is generated once, mid-build, possibly too early.
The manifest lives in a "resolver" virtual module that is generated exactly once, the first time the server runtime imports it — packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts:750 (the load() hook) calling generateServerFnResolverModule(...) at :792. The build order is: client environment fully, then server environment (packages/start-plugin-core/src/vite/planning.ts:204 then :208). The resolver is imported early in the server build — before the server graph has transitively compiled every server-only definition file.

Net effect: the manifest ends up containing essentially "every server function the client references." Server-only functions are systematically excluded, because the client can't see them (b) and the server writes the manifest before it has finished finding them (c).

Why dev works. In development there is no pre-baked manifest; the dev resolver decodes the target from the request and imports it on demand, so nothing needs to be discovered ahead of time.

Why it's specific to the default setup. The bug is confined to the configuration where the SSR environment is also the server-function "provider" (ssrIsProvider, the Vite default). In the alternative setup (a separate provider environment, e.g. RSC), that environment builds last (planning.ts:211+), so discovery is already complete by the time it writes the manifest. We confirmed the reference rsbuild bundler is unaffected — it regenerates the resolver after the graph is fully built, which Vite did not do.

Figure 1 — why the function goes missing:

flowchart TB
    A["Server function in its own file,<br/>reached only from server-only code"]

    subgraph CB["Client build — runs first, in full"]
      C1["Strips server-only code, so this function<br/>is never compiled or registered"]
    end

    subgraph SB["Server build — runs second"]
      S1["Manifest generated once, early —<br/>before this function's file is compiled"]
    end

    A --> C1
    A --> S1
    C1 --> MISS["Function absent from the manifest"]
    S1 --> MISS
    MISS --> ERR["Production runtime error:<br/>Server function info not found"]

    classDef err fill:#fdecea,stroke:#d1352c,color:#611000
    class ERR err
Loading

5. The fix

Idea: before the server build writes the manifest, force the reachable server module graph to finish compiling, so discovery is complete.

All changes are in packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts.

Gate (only runs where the bug exists). plugin.ts:781 — the discovery step runs only when ssrIsProvider. For the separate-provider/RSC case it is skipped, because that environment already builds after discovery completes. This also avoids doing redundant work (and avoids running during RSC's throwaway "scan" build pass).

Two-phase discoveryensureServerModuleGraphTransformed(...) at plugin.ts:270, awaited at :782:

  • Phase 1 — bounded walk of the app's own source (plugin.ts:278). Starting from the app's already-known modules, it loads each one and follows its imports, which forces them to compile (and thus register their server functions). This is deliberately bounded to the project's own source via shouldWalkForServerFns (plugin.ts:239) — it excludes node_modules, virtual modules, and anything outside the project root. That bound is essential: it stops the walk from wandering into the framework runtime (which is large and, importantly, is what imports the resolver module we are in the middle of generating — walking into it would deadlock the build).

  • Phase 2 — location-agnostic sweep of provider modules (plugin.ts:317). A function called from inside another function's handler is dropped from the normal (caller) module, so Phase 1 alone misses it. But its implementation lives in a dedicated "provider" module whose handler body is intact. Phase 2 loads the provider module of every currently-known server function and lets loading pull in its direct dependencies — registering the nested functions those handlers call. It repeats until no new functions appear. Because a provider module is always a user/shared server-function implementation (never the framework runtime), it is safe to load regardless of where it lives — which is what lets this correctly discover server functions defined in separate packages, without reintroducing the framework-walk problem that Phase 1's bound guards against.

One comment refresh. The surrounding comment at plugin.ts:792 was updated to reflect that, in this configuration, server functions are intentionally callable both in-process on the server and directly from the client (see §6, Security), matching the reference rsbuild/RSC behavior.

Figure 2 — the fix: finish discovery before writing the manifest:

flowchart TB
    L["Resolver load(): before writing the manifest"]
    L --> G{"Is SSR the provider?<br/>(Vite default)"}
    G -->|"No — provider env builds last,<br/>already complete"| GEN
    G -->|Yes| P1

    subgraph P1["Phase 1 — bounded walk of app source"]
      P1a["Compile app modules and follow imports;<br/>skip framework runtime and node_modules"]
      P1b["Registers functions reached from intact<br/>server-only bodies, e.g. middleware .server()"]
      P1a --> P1b
    end

    subgraph P2["Phase 2 — provider-module fixpoint"]
      P2a["Load every known function's provider module<br/>(handler body intact; any location)"]
      P2b["Registers handler-nested and<br/>separate-package functions"]
      P2c["Repeat until no new functions appear"]
      P2a --> P2b --> P2c
    end

    P1 --> P2
    P2 --> GEN["Generate manifest — now complete"]

    classDef ok fill:#eaf7ea,stroke:#2e7d32,color:#143314
    class GEN ok
Loading

6. Implications

Behavior — previously-broken patterns now work. Server functions reached only via a middleware, only via another server function's handler, arbitrarily nested chains, and functions defined in a separate shared package are all now present in the production manifest and resolve correctly at runtime. Nothing that worked before changes.

Security — no change; matches the reference implementation. Server functions were already, by design, callable from the client (directly by ID, or via serialization — see createSsrRpc.ts:10 for the in-process path and the resolver's client-origin gate at server-fn-resolver-module.ts:42). In the default ssrIsProvider configuration this gate is intentionally off in both Vite and the reference rsbuild bundler, and rsbuild already includes these functions in its manifest. Our fix brings Vite in line with that existing, intended behavior — it does not newly expose anything that rsbuild didn't already expose. (The library's documented intent: server functions are callable anywhere — over HTTP from the client, in-process on the server, skipping the network hop. Our change only fixes discovery; it does not touch dispatch, the client stub, or the origin gate.)

Performance — negligible. We measured production SSR build time with the discovery step ON vs OFF, several runs each: website ≈ +3ms, basic ≈ +27ms (~8%, within noise), server-functions (largest, 263 SSR modules) ≈ +10ms (~2%). The step largely front-loads work the build already does (module loads are cached), so its apparent internal cost (~400ms) is overwhelmingly overlapping, not additive. Caveat: our test apps top out at ~260 SSR modules; if a very large app ever shows cost, an easy follow-up is to parallelize the walk.

Cross-framework. The change is in the framework-agnostic core, so it applies to React, Solid, and Vue Start identically. All three were validated (see §7).

Scope / limitations. The change is confined to the ssrIsProvider Vite build path; dev, the RSC/custom-provider path, and the rsbuild bundler are untouched by construction and were verified unaffected.


7. Testing & validation

New automated regression tests (in e2e/react-start/server-functions/tests/server-functions.spec.ts), each reproducing a shape that was broken:

  • :721 — server function reached only through a cross-file middleware.
  • :750 — server function called only from another server function's handler.
  • :777 — a multi-level cross-file chain (A → B → C), which specifically exercises the Phase-2 loop iterating more than once.
  • :797 — a server function in a separate package whose handler calls a nested server-only function in that same package. Backed by a small fixture package, e2e/react-start/server-functions-shared-lib/ (see src/index.ts:7 and src/internal.ts:7).

All four fail on the original code (with the exact Server function info not found error) and pass with the fix.

Full suite — all green:

  • Package gate (nx affected): test:eslint, test:types, test:unit, test:build — all pass.
  • React Start e2e: server-functions 57, rsc 258, basic 134, server-functions-global-middleware 7, static-server-functions 5, server-routes 6, serialization-adapters 8.
  • Solid Start e2e: server-functions 29, server-routes 2.
  • Vue Start e2e: server-functions 27, server-routes 2.

No Server function info not found anywhere. The serialization-adapters suite passing is notable — it exercises the "return a server function to the client and call it" path, confirming the fix doesn't disturb the callable-anywhere behavior.


8. Risk assessment

  • Blast radius: one file of build tooling; runtime dispatch, client code, and the public API are untouched.
  • Not a regression risk: before the fix these functions were entirely missing (broken); the change strictly adds correct entries. Everything that already worked still works (verified by the full suite).
  • Deadlock/perf risks considered and closed: Phase 1's project-root bound prevents walking into the framework runtime (which would deadlock on the in-progress resolver); Phase 2 only loads known server-function implementations, keeping it bounded. Both were validated on real builds, including the multi-environment RSC build.
  • Confined by a feature gate: only the affected configuration runs the new code path.

9. How it's packaged

Two clean, separated commits on fix/server-fn-cross-file-manifest:

  1. 8b53769ee4chore(deps): bumps @playwright/test to ^1.61.0 across the workspace. Independent of the fix; resolves intermittent test-runner stalling. (117 files: package manifests + lockfile.)
  2. d33b368de7fix(start-plugin-core): the one-file fix, a Changesets entry (@tanstack/start-plugin-core patch, .changeset/fix-server-fn-manifest-cross-file.md), the four regression tests, and the fixture package. (17 files.)

The Changesets entry means the fix will be released as a patch version of @tanstack/start-plugin-core, cascading patch bumps to the React/Solid/Vue Start packages automatically.


10. Key file reference (quick index)

Concern Location
Error thrown when a fn is missing packages/start-plugin-core/src/start-compiler/server-fn-resolver-module.ts:53
Client-origin access gate .../server-fn-resolver-module.ts:42
Where a server fn is registered .../start-compiler/handleCreateServerFn.ts:350, :476
.server() body stripped on client .../start-compiler/handleCreateMiddleware.ts:68
Build order (client → server) .../vite/planning.ts:204, :208, :211
In-process server-to-server call packages/start-server-core/src/createSsrRpc.ts:10
The fix — discovery gate .../vite/start-compiler-plugin/plugin.ts:781
The fix — two-phase discovery .../start-compiler-plugin/plugin.ts:270 (Phase 1 :278, Phase 2 :317)
The fix — walk bound .../start-compiler-plugin/plugin.ts:239
Manifest generation .../start-compiler-plugin/plugin.ts:792
Regression tests e2e/react-start/server-functions/tests/server-functions.spec.ts:721,750,777,797
Separate-package fixture e2e/react-start/server-functions-shared-lib/src/{index,internal}.ts

Summary by CodeRabbit

  • Bug Fixes

    • Improved production reliability of server-function manifest generation, preventing missing-function runtime errors.
    • Fixed edge cases where server functions reachable only through middleware, nested server-function chains, or separate shared packages could fail to be discovered/loaded.
  • Tests

    • Added end-to-end regression coverage for middleware-invoked and nested/shared server functions in production scenarios.
  • Chores

    • Updated Playwright (@playwright/test) to a newer version across the end-to-end test projects and workspace configuration.

plauclair-nesto and others added 2 commits July 7, 2026 15:53
Updates the @playwright/test version in the pnpm workspace catalog and in
every e2e package.json to ^1.61.0.

The previously pinned version intermittently stalled/hung during e2e runs;
upgrading resolves the stalling issue.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s in the production manifest

When SSR is the server-function provider (the Vite default), a server function
reachable only from server-only code — a middleware `.server()` body or another
server function's handler — could be omitted from the generated server-function
manifest, causing "Server function info not found" at runtime in production
builds while dev worked.

The server-function resolver now completes discovery of the reachable server
module graph before snapshotting the manifest, including provider modules so
handler-nested and external-package server functions are covered.

Adds e2e coverage in the server-functions app: cross-file middleware, nested
handler, a multi-level cross-file chain, and a separate-package fixture.

Closes TanStack#7213.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 84eb6dec-6fa4-455e-b411-73f5b8bf7fb6

📥 Commits

Reviewing files that changed from the base of the PR and between d33b368 and 3e8e36b.

📒 Files selected for processing (3)
  • packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts
  • packages/start-plugin-core/src/vite/start-compiler-plugin/server-fn-graph-crawl.ts
  • packages/start-plugin-core/tests/should-walk-for-server-fns.test.ts

📝 Walkthrough

Walkthrough

This PR fixes server-function manifest generation for server-only reachability, adds regression fixtures/routes/tests for cross-file discovery cases, and updates @playwright/test to ^1.61.0 across e2e packages plus the workspace catalog.

Changes

Server function manifest discovery fix

Layer / File(s) Summary
Core crawl and resolver
packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts, packages/start-plugin-core/src/vite/start-compiler-plugin/server-fn-graph-crawl.ts, .changeset/fix-server-fn-manifest-cross-file.md, packages/start-plugin-core/tests/should-walk-for-server-fns.test.ts
Adds the crawl context, root-bounded module walk, provider-module expansion, and resolver hook changes that run before manifest generation; includes the changeset and crawl boundary tests.
Server-function fixture modules
e2e/react-start/server-functions-shared-lib/package.json, e2e/react-start/server-functions-shared-lib/src/index.ts, e2e/react-start/server-functions-shared-lib/src/internal.ts, e2e/react-start/server-functions/package.json, e2e/react-start/server-functions/src/functions/chainLevelB.ts, e2e/react-start/server-functions/src/functions/chainLevelC.ts, e2e/react-start/server-functions/src/functions/serverFnCalledByMiddleware.ts, e2e/react-start/server-functions/src/middleware/serverFnCallingMiddleware.ts
Adds the shared package server-function pair, the chained server-function modules, the middleware-called server function, and the middleware that forwards its result through context; also adds the shared package dependency wiring.
Regression routes and route tree
e2e/react-start/server-functions/src/routes/shared-package-server-fn.tsx, e2e/react-start/server-functions/src/routes/nested-server-fns.tsx, e2e/react-start/server-functions/src/routes/middleware/serverfn-in-middleware.tsx, e2e/react-start/server-functions/src/routes/middleware/index.tsx, e2e/react-start/server-functions/src/routeTree.gen.ts
Adds the new shared-package, nested-server-fns, and middleware server-function routes, the middleware navigation link, and the generated route-tree registrations and type maps for those paths.
Playwright regression tests
e2e/react-start/server-functions/tests/server-functions.spec.ts
Adds four Playwright tests that navigate to the new routes, invoke the server functions through the UI, and assert the expected results and absence of manifest lookup errors.

Playwright Dependency Version Bump

Layer / File(s) Summary
Playwright version updates
e2e/*/package.json, pnpm-workspace.yaml
Bumps @playwright/test from ^1.50.1 (or ^1.57.0 in the workspace catalog) to ^1.61.0 across the changed e2e packages and the pnpm catalog entry.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Vite
  participant ResolverModule
  participant ensureServerModuleGraphTransformed
  participant ctx.load

  Vite->>ResolverModule: load(id)
  ResolverModule->>ensureServerModuleGraphTransformed: crawl(id, root, serverFnsById)
  ensureServerModuleGraphTransformed->>ctx.load: load app graph with resolveDependencies
  ctx.load-->>ensureServerModuleGraphTransformed: imported module ids
  ensureServerModuleGraphTransformed->>ctx.load: load provider modules for discovered server fns
  ctx.load-->>ensureServerModuleGraphTransformed: additional server fns
  ensureServerModuleGraphTransformed-->>ResolverModule: populated serverFnsById
  ResolverModule-->>Vite: generateServerFnResolverModule
Loading

Suggested labels: package: start-plugin-core

Suggested reviewers: schiller-manuel, SeanCassiere

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and clearly points to the main fix: cross-file server function manifest generation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@socket-security

socket-security Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​playwright/​test@​1.58.0 ⏵ 1.61.110010010099100

View full report

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
e2e/react-start/server-functions/tests/server-functions.spec.ts (1)

732-735: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Avoid networkidle in new regression tests.

All four new tests wait via page.waitForLoadState('networkidle') before interacting. Playwright's own docs mark this state as 'networkidle' - DISCOURAGED ... Don't use this method for testing, rely on web assertions to assess readiness instead. Given this PR's sibling commit bumps Playwright specifically to fix "intermittent e2e test stalling," introducing more networkidle waits works against that goal.

♻️ Suggested approach
-  await page.goto('/middleware/serverfn-in-middleware')
-
-  await page.waitForLoadState('networkidle')
-
-  await page.getByTestId('test-serverfn-in-middleware-btn').click()
+  await page.goto('/middleware/serverfn-in-middleware')
+  await page.getByTestId('test-serverfn-in-middleware-btn').click()

Playwright's locator-based assertions (toContainText, toHaveText, etc.) already used later in each test auto-wait/retry, so the explicit networkidle wait is likely unnecessary; the click itself auto-waits for the button to be actionable.

Please confirm whether this repo's Playwright test suite has an established convention of using networkidle elsewhere (if so, this is consistent with existing style and lower priority to change).

Also applies to: 761-762, 787-788, 807-808

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/react-start/server-functions/tests/server-functions.spec.ts` around lines
732 - 735, The new regression tests are using an explicit
page.waitForLoadState('networkidle') before interactions, which should be
removed or replaced. Update the affected server-functions Playwright specs to
rely on the existing locator assertions and Playwright’s built-in auto-waiting
around page.goto, click, and toHaveText/toContainText instead of waiting for
network idle. Apply the same change in the related tests using this pattern so
the suite follows the recommended readiness convention.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts`:
- Around line 309-314: The loop in plugin.ts uses one-line `if` bodies that
violate the brace requirement. Update the `for...of` blocks that iterate over
`info.importedIds` and `info.dynamicallyImportedIds` in `plugin.ts` so each `if
(!visited.has(dep))` uses curly braces before calling `queue.push(dep)`. Keep
the existing `info`/`visited`/`queue` logic unchanged, just add braces to both
conditional statements.
- Around line 239-256: The root check in shouldWalkForServerFns is too loose and
can match sibling directories outside the app. Update the
cleaned.startsWith(root) guard to enforce a path boundary, using the root path
symbol in shouldWalkForServerFns so only files under the exact root are walked.
Prefer comparing against root with a trailing separator or using a relative-path
check, and keep the existing node_modules, virtual-module, and
TRANSFORM_ID_REGEX filtering unchanged.

---

Nitpick comments:
In `@e2e/react-start/server-functions/tests/server-functions.spec.ts`:
- Around line 732-735: The new regression tests are using an explicit
page.waitForLoadState('networkidle') before interactions, which should be
removed or replaced. Update the affected server-functions Playwright specs to
rely on the existing locator assertions and Playwright’s built-in auto-waiting
around page.goto, click, and toHaveText/toContainText instead of waiting for
network idle. Apply the same change in the related tests using this pattern so
the suite follows the recommended readiness convention.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 99cd7fc7-fa21-4bee-b177-215dbde3d1c4

📥 Commits

Reviewing files that changed from the base of the PR and between a3e24c3 and d33b368.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (131)
  • .changeset/fix-server-fn-manifest-cross-file.md
  • e2e/react-router/basepath-file-based/package.json
  • e2e/react-router/basic-esbuild-file-based/package.json
  • e2e/react-router/basic-file-based-code-splitting/package.json
  • e2e/react-router/basic-file-based/package.json
  • e2e/react-router/basic-react-query-file-based/package.json
  • e2e/react-router/basic-react-query/package.json
  • e2e/react-router/basic-scroll-restoration/package.json
  • e2e/react-router/basic-virtual-file-based/package.json
  • e2e/react-router/basic-virtual-named-export-config-file-based/package.json
  • e2e/react-router/basic/package.json
  • e2e/react-router/escaped-special-strings/package.json
  • e2e/react-router/generator-cli-only/package.json
  • e2e/react-router/i18n-paraglide/package.json
  • e2e/react-router/js-only-file-based/package.json
  • e2e/react-router/match-params/package.json
  • e2e/react-router/rspack-basic-file-based/package.json
  • e2e/react-router/rspack-basic-virtual-named-export-config-file-based/package.json
  • e2e/react-router/scroll-restoration-sandbox-vite/package.json
  • e2e/react-router/sentry-integration/package.json
  • e2e/react-router/view-transitions/package.json
  • e2e/react-start/basic-auth/package.json
  • e2e/react-start/basic-cloudflare/package.json
  • e2e/react-start/basic-react-query/package.json
  • e2e/react-start/basic/package.json
  • e2e/react-start/clerk-basic/package.json
  • e2e/react-start/csp/package.json
  • e2e/react-start/css-inline/package.json
  • e2e/react-start/css-modules/package.json
  • e2e/react-start/custom-basepath/package.json
  • e2e/react-start/custom-server-rsbuild/package.json
  • e2e/react-start/dev-ssr-styles/package.json
  • e2e/react-start/early-hints/package.json
  • e2e/react-start/hmr/package.json
  • e2e/react-start/i18n-paraglide/package.json
  • e2e/react-start/import-protection-custom-config/package.json
  • e2e/react-start/import-protection/package.json
  • e2e/react-start/query-integration/package.json
  • e2e/react-start/rsc-deferred-hydration/package.json
  • e2e/react-start/rsc-query/package.json
  • e2e/react-start/rsc-rsbuild/package.json
  • e2e/react-start/rsc/package.json
  • e2e/react-start/scroll-restoration/package.json
  • e2e/react-start/server-functions-global-middleware/package.json
  • e2e/react-start/server-functions-shared-lib/package.json
  • e2e/react-start/server-functions-shared-lib/src/index.ts
  • e2e/react-start/server-functions-shared-lib/src/internal.ts
  • e2e/react-start/server-functions/package.json
  • e2e/react-start/server-functions/src/functions/chainLevelB.ts
  • e2e/react-start/server-functions/src/functions/chainLevelC.ts
  • e2e/react-start/server-functions/src/functions/serverFnCalledByMiddleware.ts
  • e2e/react-start/server-functions/src/middleware/serverFnCallingMiddleware.ts
  • e2e/react-start/server-functions/src/routeTree.gen.ts
  • e2e/react-start/server-functions/src/routes/middleware/index.tsx
  • e2e/react-start/server-functions/src/routes/middleware/serverfn-in-middleware.tsx
  • e2e/react-start/server-functions/src/routes/nested-server-fns.tsx
  • e2e/react-start/server-functions/src/routes/shared-package-server-fn.tsx
  • e2e/react-start/server-functions/tests/server-functions.spec.ts
  • e2e/react-start/server-routes-global-middleware/package.json
  • e2e/react-start/server-routes/package.json
  • e2e/react-start/split-base-and-basepath/package.json
  • e2e/react-start/start-manifest/package.json
  • e2e/react-start/static-server-functions/package.json
  • e2e/react-start/streaming-ssr/package.json
  • e2e/react-start/transform-asset-urls/package.json
  • e2e/react-start/virtual-routes/package.json
  • e2e/react-start/website/package.json
  • e2e/solid-router/basepath-file-based/package.json
  • e2e/solid-router/basic-esbuild-file-based/package.json
  • e2e/solid-router/basic-file-based-code-splitting/package.json
  • e2e/solid-router/basic-file-based/package.json
  • e2e/solid-router/basic-scroll-restoration/package.json
  • e2e/solid-router/basic-solid-query-file-based/package.json
  • e2e/solid-router/basic-solid-query/package.json
  • e2e/solid-router/basic-virtual-file-based/package.json
  • e2e/solid-router/basic-virtual-named-export-config-file-based/package.json
  • e2e/solid-router/basic/package.json
  • e2e/solid-router/generator-cli-only/package.json
  • e2e/solid-router/js-only-file-based/package.json
  • e2e/solid-router/rspack-basic-file-based/package.json
  • e2e/solid-router/rspack-basic-virtual-named-export-config-file-based/package.json
  • e2e/solid-router/scroll-restoration-sandbox-vite/package.json
  • e2e/solid-router/sentry-integration/package.json
  • e2e/solid-router/view-transitions/package.json
  • e2e/solid-start/basic-auth/package.json
  • e2e/solid-start/basic-cloudflare/package.json
  • e2e/solid-start/basic-solid-query/package.json
  • e2e/solid-start/basic/package.json
  • e2e/solid-start/csp/package.json
  • e2e/solid-start/css-modules/package.json
  • e2e/solid-start/custom-basepath/package.json
  • e2e/solid-start/query-integration/package.json
  • e2e/solid-start/scroll-restoration/package.json
  • e2e/solid-start/server-functions/package.json
  • e2e/solid-start/server-routes/package.json
  • e2e/solid-start/solid-query-layout-suspense/package.json
  • e2e/solid-start/start-manifest/package.json
  • e2e/solid-start/virtual-routes/package.json
  • e2e/solid-start/website/package.json
  • e2e/vue-router/basepath-file-based/package.json
  • e2e/vue-router/basic-esbuild-file-based/package.json
  • e2e/vue-router/basic-file-based-jsx/package.json
  • e2e/vue-router/basic-file-based-sfc/package.json
  • e2e/vue-router/basic-scroll-restoration/package.json
  • e2e/vue-router/basic-virtual-file-based/package.json
  • e2e/vue-router/basic-virtual-named-export-config-file-based/package.json
  • e2e/vue-router/basic-vue-query-file-based/package.json
  • e2e/vue-router/basic-vue-query/package.json
  • e2e/vue-router/basic/package.json
  • e2e/vue-router/generator-cli-only/package.json
  • e2e/vue-router/js-only-file-based/package.json
  • e2e/vue-router/rspack-basic-file-based/package.json
  • e2e/vue-router/rspack-basic-virtual-named-export-config-file-based/package.json
  • e2e/vue-router/scroll-restoration-sandbox-vite/package.json
  • e2e/vue-router/sentry-integration/package.json
  • e2e/vue-router/view-transitions/package.json
  • e2e/vue-start/basic-auth/package.json
  • e2e/vue-start/basic-cloudflare/package.json
  • e2e/vue-start/basic-vue-query/package.json
  • e2e/vue-start/basic/package.json
  • e2e/vue-start/css-modules/package.json
  • e2e/vue-start/custom-basepath/package.json
  • e2e/vue-start/query-integration/package.json
  • e2e/vue-start/scroll-restoration/package.json
  • e2e/vue-start/server-functions/package.json
  • e2e/vue-start/server-routes/package.json
  • e2e/vue-start/start-manifest/package.json
  • e2e/vue-start/virtual-routes/package.json
  • e2e/vue-start/website/package.json
  • packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts
  • pnpm-workspace.yaml

Comment thread packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts Outdated
Comment on lines +309 to +314
for (const dep of info.importedIds) {
if (!visited.has(dep)) queue.push(dep)
}
for (const dep of info.dynamicallyImportedIds) {
if (!visited.has(dep)) queue.push(dep)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Missing curly braces on one-line if bodies.

Lines 310 and 313 use one-line if bodies without braces, which the coding guidelines explicitly disallow.

As per coding guidelines, **/*.{ts,tsx,js,jsx}: "Always use curly braces for if, else, loops, and similar control statements. Never write one-line bodies like if (foo) x = 1."

🔧 Proposed fix
     for (const dep of info.importedIds) {
-      if (!visited.has(dep)) queue.push(dep)
+      if (!visited.has(dep)) {
+        queue.push(dep)
+      }
     }
     for (const dep of info.dynamicallyImportedIds) {
-      if (!visited.has(dep)) queue.push(dep)
+      if (!visited.has(dep)) {
+        queue.push(dep)
+      }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const dep of info.importedIds) {
if (!visited.has(dep)) queue.push(dep)
}
for (const dep of info.dynamicallyImportedIds) {
if (!visited.has(dep)) queue.push(dep)
}
for (const dep of info.importedIds) {
if (!visited.has(dep)) {
queue.push(dep)
}
}
for (const dep of info.dynamicallyImportedIds) {
if (!visited.has(dep)) {
queue.push(dep)
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/start-plugin-core/src/vite/start-compiler-plugin/plugin.ts` around
lines 309 - 314, The loop in plugin.ts uses one-line `if` bodies that violate
the brace requirement. Update the `for...of` blocks that iterate over
`info.importedIds` and `info.dynamicallyImportedIds` in `plugin.ts` so each `if
(!visited.has(dep))` uses curly braces before calling `queue.push(dep)`. Keep
the existing `info`/`visited`/`queue` logic unchanged, just add braces to both
conditional statements.

Source: Coding guidelines

- Enforce a path boundary in the crawl's root check so a sibling directory
  sharing a name prefix (e.g. `<root>-other`) is not treated as under the root.
  This also makes the separate-package e2e fixture exercise the provider-module
  phase as intended rather than matching via the loose prefix.
- Guard virtual modules on the raw id (`id.startsWith('\0')`) before `cleanId`
  strips the NUL prefix, matching the convention used elsewhere; the previous
  post-`cleanId` `includes('\0')` check never matched.
- Use curly braces on the two one-line `if` bodies, per AGENTS.md.
- Extract the crawl (`shouldWalkForServerFns` / `ensureServerModuleGraphTransformed`)
  into `server-fn-graph-crawl.ts` so the pure boundary logic can be unit-tested
  without importing the full plugin (which pulls in server-only entry imports).
- Add unit tests for `shouldWalkForServerFns` covering the sibling-prefix
  boundary, NUL-prefixed virtual ids, node_modules / non-transformable
  exclusions, and query-string handling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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