Skip to content

chore: run analysis before SSR build - #16966

Open
teemingc wants to merge 15 commits into
version-3from
chore-early-analysis
Open

chore: run analysis before SSR build#16966
teemingc wants to merge 15 commits into
version-3from
chore-early-analysis

Conversation

@teemingc

@teemingc teemingc commented Aug 27, 2026

Copy link
Copy Markdown
Member

This PR uses a Vite dev server to analyse the nodes rather than relying on the SSR build. Doing so allows us to:

  • receive analysis errors earlier
  • possibly skip the SSR build if it's not needed
  • allow adapter plugins to resolve platform modules when we load user files (e.g., cloudflare:workers)

@pkg-svelte-dev

pkg-svelte-dev Bot commented Aug 27, 2026

Copy link
Copy Markdown

Install the latest version of @sveltejs/kit from 305c6ad:

pnpm add https://pkg.svelte.dev/@sveltejs/kit/c/305c6adf9385c576159576fefbf3f97c2341c052

Open in pkg.svelte.dev: https://pkg.svelte.dev/repos/kit/pr/16966

@changeset-bot

changeset-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 305c6ad

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@svelte-docs-bot

Copy link
Copy Markdown

The analysis Vite server called `listen()`, which occupied port 5173
mid-build. That made Playwright's `webServer` readiness check pass
before `vite build` had finished, and would clash with a running dev
server. The module runner doesn't need a listening server, so use
`middlewareMode` instead.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@svelte-triage-bot

Copy link
Copy Markdown
Contributor

I investigated this failed test run, but the investigation came up empty.

What I tried

  • Verify the exact triggering revision: Fetched and checked out same-repository head f4a39ca, matching the workflow.
  • Check test provenance: The failing adapter-static assertion is unchanged from base at the triggering revision.
  • Run focused test five times: Five separate CI-mode focused Playwright runs all reached the assertion and passed; the original run's failure is timing-dependent rather than deterministic.
  • Compare port behavior: At the PR head, the new analysis server listened temporarily on port 5173 while build/index.html did not yet exist. The base revision had no transient listener.
  • Reproduce under load: Under 32 CPU-noise processes, the exact PR-head target failed before preview started; the base target passed under the same load.
  • Validate proposed mitigation: Temporarily changing the analysis server call from listen() to listen(0) passed under the same load. The change was restored and not committed because it is PR-local, not a standalone base-derived flake fix.
  • Search for existing fix: Reviewed recent open pull requests; none provides an existing fix for this readiness race.

The new analysis dev server temporarily binds the application's default port 5173 during vite build. Playwright mistakes that listener for the configured pnpm build && pnpm preview server, starts before adaptation creates build/index.html, and intermittently fails. Using an ephemeral analysis port (vite_dev_server.listen(0)) prevents the race, but no permitted standalone base-derived fix was created.

Comment thread packages/kit/src/core/postbuild/analyse.js Outdated
…d, keeping the forked analyse worker's event loop alive and leaking a worker + dev server on every `vite build --watch` rebuild.

This commit fixes the issue reported at packages/kit/src/core/postbuild/analyse.js:176

## Bug

Commit `807451b` ("Update analyse.js") re-indented `analyse()` and, in doing so, dropped the `try { ... } finally { await vite_dev_server.close(); }` wrapper that previously surrounded the analysis body. As a result the dev server created by:

```js
const vite_dev_server = await vite.createServer(vite_config);
```

is never closed — neither on the success path nor on the several `throw new Error(...)` error paths inside the function.

### Why this leaks

`analyse` runs inside a worker via `forked(import.meta.url, analyse)` (`packages/kit/src/utils/fork.js`). After the worker posts its result, the parent only calls `worker.unref()` — it never calls `worker.terminate()`. `unref()` merely tells the parent not to wait for the worker; it does **not** stop it. So the worker only goes away if its own event loop drains and it exits on its own.

An open (unclosed) Vite dev server keeps open handles alive even with `middlewareMode: true, hmr: false, watch: null` (e.g. the dep optimizer / esbuild service). That keeps the worker's event loop alive, so the worker never exits.

For a one-shot `vite build` this is harmless (the main process exits shortly after). But in `vite build --watch`, `before_ssr_build_rerun` (`packages/kit/src/exports/vite/build/index.js`) calls `analyse()` on every rebuild, spawning a fresh worker each time. Since none of them exit, workers **and their dev servers accumulate across rebuilds** — a handle/memory leak.

### Empirical verification

I reproduced the exact configuration used by `analyse()` (`server: { middlewareMode: true, hmr: false, watch: null }`, `logLevel: 'silent'`) with Vite 8 and loaded a module via `ssrLoadModule`, using an **unref'd** watchdog timer so only the server's own handles could keep the process alive:

*   **Without `close()`**: the process was still alive after 8 s and only exited when the watchdog fired → the server keeps the event loop alive (leak confirmed).
*   **With `await server.close()`**: the process exited cleanly in ~48 ms.

This also refutes the concern that re-adding `close()` might reintroduce a hang in `middlewareMode`: `close()` completed in ~48 ms with no hang.

## Fix

Restored the `try { ... } finally { await vite_dev_server.close(); }` wrapper around the analysis body in `packages/kit/src/core/postbuild/analyse.js`, so the dev server is closed on both success and error paths. This lets each forked worker's event loop drain and the worker exit cleanly, preventing worker/dev-server accumulation across `vite build --watch` rebuilds. Verified with `node --check` and the empirical test above.


Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: teemingc <chewteeming01@gmail.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