fix(vite-plugin): keep Miniflare alive when a build runs during serve - #14837
Merged
jamesopstad merged 3 commits intoJul 27, 2026
Merged
Conversation
Vite's experimental.bundledDev runs a Rolldown build pass during serve, firing buildEnd while the dev server is still live. The plugin treated buildEnd as the dev-server-closing signal and disposed Miniflare there, so the next request failed with 'Expected `miniflare` to be defined'. Dispose Miniflare from a patched server.close during serve (mirroring the existing server.restart patch) rather than from buildEnd. Production builds, dev-server restarts (Miniflare stays warm), and forceful ctrl+C exits are unaffected. Validated against a large real app (Ramp Penny chat, ~1000 modules): bundledDev serve went from 500 (miniflare disposed) to 200 with the module graph loading normally.
🦋 Changeset detectedLatest commit: a2d11e7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
workers-devprod
requested review from
a team and
NuroDev
and removed request for
a team
July 24, 2026 17:38
Contributor
|
Codeowners approval required for this PR:
Show detailed file reviewers |
@cloudflare/autoconfig
@cloudflare/config
create-cloudflare
@cloudflare/deploy-helpers
@cloudflare/kv-asset-handler
miniflare
@cloudflare/pages-shared
@cloudflare/unenv-preset
@cloudflare/vite-plugin
@cloudflare/vitest-pool-workers
@cloudflare/workers-auth
@cloudflare/workers-editor-shared
@cloudflare/workers-utils
wrangler
commit: |
jamesopstad
approved these changes
Jul 27, 2026
jamesopstad
left a comment
Contributor
There was a problem hiding this comment.
Thanks for the PR! I'm working on getting some new APIs added in Vite that would enable us to remove the server patching (vitejs/vite#22913) but this is a good workaround for now. I pushed a couple of commits.
We also need to ensure that HTML files are served correctly in experimental.bundledDev. I'll try and get a PR up for that today.
workers-devprod
approved these changes
Jul 27, 2026
workers-devprod
left a comment
Contributor
There was a problem hiding this comment.
Codeowners reviews satisfied
Merged
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What / why
The plugin uses
buildEndas its signal that the dev server is closing, and disposes the Miniflare instance there:https://github.com/cloudflare/workers-sdk/blob/main/packages/vite-plugin-cloudflare/src/plugins/dev.ts#L58-L69
Vite's
experimental.bundledDevruns a Rolldown build pass duringserve, sobuildEndfires while the dev server is still live. Miniflare gets torn down mid-serve and every subsequent request fails:With
NODE_DEBUG=@cloudflare:vite-pluginthe sequence is unambiguous:Reproduces on both the currently pinned
1.37.2and latest1.45.1(with matching wrangler), so it isn't version lag — the dev architecture just assumes a pure module-runner serve rather than a build-during-serve.Why
bundledDevmatters here (i.e. why fix this rather than avoid the flag)Vite's unbundled dev server is optimised for the common case: the browser is on the same machine as the dev server, so one HTTP request per module is essentially free.
Our dev servers aren't local to the browser. Each runs inside a remote sandbox, and the browser reaches it through a Cloudflare Worker and a tunnel. Every module request is a full cross-network round trip, and the app is ~1000 modules — so a cold page load issues ~1000 sequential-ish round trips before it renders. Requests dominate; transform time is noise.
experimental.bundledDevis the direct fix for that shape: Rolldown bundles the graph and the dev server ships a handful of chunks instead of a thousand modules, while keeping HMR. It collapses the request count by orders of magnitude, which is exactly the axis that hurts when the dev server is behind a Worker.This isn't a niche setup, and it's one Cloudflare users are likely to hit more, not less: remote/cloud dev environments, Codespaces-style sandboxes, and any workflow where the Vite dev server sits behind a Worker or tunnel all have the same profile. Today the combination of
@cloudflare/vite-plugin+experimental.bundledDevis unusable — not slow, but 500 on every request — so the flag can't be evaluated at all by anyone using this plugin.The fix is also small and independent of
bundledDevitself: it just stops the plugin from conflating "a build finished" with "the dev server closed". That conflation is arguably wrong regardless of this flag — any plugin or future Vite feature that triggers a build duringservewould hit the same teardown.The change
During
serve, dispose Miniflare from a patchedserver.closeinstead of frombuildEnd. This mirrors the existingserver.restartpatch inindex.ts, so the mechanism should look familiar:https://github.com/cloudflare/workers-sdk/blob/main/packages/vite-plugin-cloudflare/src/index.ts#L93-L105
Preserved behaviour:
buildEndstill disposes whencommand !== "serve")!isRestartingDevServerguard moved with the disposal)exithandler, which the code comments already call out as the reasonbuildEndalone was never sufficientTest
packages/vite-plugin-cloudflare/src/__tests__/bundled-dev-dispose.spec.tsstarts a real dev server, invokes the cloudflare plugins'buildEndhooks (exactly what a bundledDev build pass does), and asserts the server still serves. It fails onmainwithExpected \miniflare` to be definedand passes with this change. Modeled on the existinghmr-events.spec.ts`.Driving the hook directly (rather than enabling
bundledDevin the fixture) keeps the test deterministic and independent of an experimental Vite flag's internals — it pins the actual invariant: abuildEndduringservemust not dispose Miniflare.Validation beyond the unit test
Applied the equivalent change to the built plugin inside a large real app (~1000 modules, React + TanStack Router, Worker + SPA) and ran it with
experimental.bundledDevenabled:GET /Expected \miniflare` to be defined`GET /src/main.tsxUnrelated note in case it's useful to maintainers: with
bundledDevthere's also a separate non-fatalTypeError: Cannot read properties of undefined (reading 'config')from@vitejs/plugin-react-swc'stransformIndexHtml— different repo, and it doesn't block serving once this fix is in.Changeset
Patch for
@cloudflare/vite-plugin.