Skip to content

fix(vite): support concurrent dev servers sharing a plugin instance - #4496

Closed
danielroe wants to merge 2 commits into
nitrojs:mainfrom
danielroe:fix/concurrent-vite
Closed

fix(vite): support concurrent dev servers sharing a plugin instance#4496
danielroe wants to merge 2 commits into
nitrojs:mainfrom
danielroe:fix/concurrent-vite

Conversation

@danielroe

@danielroe danielroe commented Jul 30, 2026

Copy link
Copy Markdown
Member

🔗 Linked issue

❓ Type of change

  • 📖 Documentation (updates to the documentation, readme, or JSdoc annotations)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

I've hit this a couple of times (first when adopting nitro/vite and now in nuxt/nuxt#35891 where I'm making it the default option) when trying to run multiple vitest projects (which share a vite config, and therefore share a nitro plugin):

Error: Env runner not initialized. Call initEnvRunner() first.
    at getEnvRunner (nitro/dist/_build/vite.env.mjs:107:29)
    at Object.createEnvironment (nitro/dist/_build/vite.env.mjs:71:61)
    at async Promise.all (index 1)
    at _createServer (vite/dist/node/chunks/node.js:25804:2)

a minimal repro is two servers sharing one nitro() plugin instance:

import { createServer } from "vite";
import { nitro } from "nitro/vite";

const plugins = nitro();
const cfg = () => ({ root, configFile: false, plugins: [plugins], server: { middlewareMode: true } });

await Promise.all([createServer(cfg()), createServer(cfg())]);
// second one throws

there are two separate issues:

  1. the second server skips setup entirely (because ctx._initialized is set, so it tries to create an environment before the first server has finished

  2. both servers share a runner, so closing one server throws 503 Runner is unavailable for the other

simply awaiting initEnvRunner fixes the first one but not the second

let me know if you have any better ideas on how to fix it 🙏

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@danielroe
danielroe requested a review from pi0 as a code owner July 30, 2026 11:57
@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

@danielroe is attempting to deploy a commit to the Nitro Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 30, 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 91826726-b98c-4df7-b4a0-7a5be1ed9aba

📥 Commits

Reviewing files that changed from the base of the PR and between 69b0509 and 148277a.

📒 Files selected for processing (2)
  • src/build/vite/env.ts
  • test/unit/vite-concurrent-servers.test.ts

📝 Walkthrough

Walkthrough

Changes

Vite runner lifecycle

Layer / File(s) Summary
Shared setup state
src/build/vite/types.ts, src/build/vite/plugin.ts
Nitro plugin initialization now uses a shared promise, with context fields for setup and config-scoped runner tracking.
Config-scoped runner management
src/build/vite/env.ts, src/build/vite/dev.ts
Vite environments acquire runners per resolved config, release them through onClose, and handle runner startup, restart, and final-reference cleanup.
Concurrent server validation
test/unit/vite-concurrent-servers.test.ts
Tests cover concurrent server creation and continued operation after closing one of multiple shared servers.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title follows conventional commits with a valid type, scope, and descriptive subject matching the change.
Description check ✅ Passed The description is clearly related to the fix for concurrent Vite dev servers sharing a Nitro plugin instance.
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 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 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.

@pkg-pr-new

pkg-pr-new Bot commented Jul 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/nitro@4496

commit: 148277a

@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: 4

🧹 Nitpick comments (1)
src/build/vite/env.ts (1)

131-217: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Internal helper placement / file size.

_createEnvRunner is unexported but is placed between exported releaseEnvRunner and exported getEnvRunner. As per coding guidelines, "Place non-exported/internal helpers at the end of the file." Given this block adds ~90 lines of runner-lifecycle logic to an already large file, also consider splitting it into its own module per "Split logic across files; avoid long single-file modules (>200 LoC). Use _* prefix for internal files."

🤖 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 `@src/build/vite/env.ts` around lines 131 - 217, The internal helper
_createEnvRunner is located between exported functions and contributes to an
oversized module. Move _createEnvRunner and its runner-lifecycle logic to the
end of the file, or extract it into a dedicated internal _*-prefixed module,
then update references from initEnvRunner and acquireEnvRunner while preserving
their existing behavior.

Source: Coding guidelines

🤖 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 `@src/build/vite/env.ts`:
- Around line 185-195: Handle the promise returned by _loadRunner in the
manager.onClose retry branch by awaiting it where supported or attaching a
rejection handler that logs the reload failure through ctx.nitro!.logger.
Preserve the existing retry limit and successful restart flow.
- Around line 150-180: Update acquireEnvRunner so a rejection of the newly
created entry.promise removes that entry from ctx._serverEnvRunners before
propagating the error. Ensure cleanup only removes the same cached entry,
preserving newer entries if one was created concurrently, and leave successful
reference-counting and releaseEnvRunner behavior unchanged.

In `@test/unit/vite-concurrent-servers.test.ts`:
- Around line 32-35: Update the concurrent server creation at
test/unit/vite-concurrent-servers.test.ts lines 32-35 to capture successfully
initialized servers, close all captured servers in a catch before rethrowing,
and preserve the existing failure assertion. Apply the same failure-safe
creation helper at lines 50-53 so partial initialization cannot leak servers.
- Around line 23-25: Update the `hello` function to validate that
`server.environments.nitro` exists before casting or calling `dispatchFetch`;
throw an explicit error identifying the missing Nitro environment, then preserve
the existing fetch flow when it is available.

---

Nitpick comments:
In `@src/build/vite/env.ts`:
- Around line 131-217: The internal helper _createEnvRunner is located between
exported functions and contributes to an oversized module. Move _createEnvRunner
and its runner-lifecycle logic to the end of the file, or extract it into a
dedicated internal _*-prefixed module, then update references from initEnvRunner
and acquireEnvRunner while preserving their existing behavior.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 50e3d532-bbe2-4fa9-981b-696db52307ab

📥 Commits

Reviewing files that changed from the base of the PR and between 77b77ff and 69b0509.

📒 Files selected for processing (5)
  • src/build/vite/dev.ts
  • src/build/vite/env.ts
  • src/build/vite/plugin.ts
  • src/build/vite/types.ts
  • test/unit/vite-concurrent-servers.test.ts

Comment thread src/build/vite/env.ts
Comment thread src/build/vite/env.ts
Comment thread test/unit/vite-concurrent-servers.test.ts
Comment thread test/unit/vite-concurrent-servers.test.ts Outdated
@danielroe danielroe closed this Jul 30, 2026
@danielroe
danielroe deleted the fix/concurrent-vite branch July 30, 2026 21:17
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