Skip to content

feat(vite): improved optimizeDeps hints#34320

Merged
danielroe merged 15 commits intomainfrom
feat/vite-deps-hint
Feb 25, 2026
Merged

feat(vite): improved optimizeDeps hints#34320
danielroe merged 15 commits intomainfrom
feat/vite-deps-hint

Conversation

@harlan-zw
Copy link
Copy Markdown
Contributor

@harlan-zw harlan-zw commented Feb 12, 2026

🔗 Linked issue

resolves #29381, resolves #28195

(Both of these may be considered fixed already with v3.12.0 but this PR may help close them off?)

📚 Description

When Vite discovers new dependencies that need bundling in development runtime, it triggers a full page reload, which can be annoying. If users want to stop this from happening, the pathway is unclear, especially if they don't know how Vite works.

This PR adds two improvements to the developer experience around optimizeDeps:

  1. After Vite discovers and optimizes new deps at runtime (causing page reloads), we show a one-time hint with the exact nuxt.config.ts snippet to pre-bundle them.
  2. When optimizeDeps.include contains entries that can't be resolved (typos, removed packages, module-added entries), we warn with a suggested config fix.

Before

Vite's default logging is terse and doesn't tell the user what to do about it:

 WARN  Failed to resolve dependency: missing, present in client 'optimizeDeps.include'

 WARN  Failed to resolve dependency: foo, present in client 'optimizeDeps.include'

ℹ ✨ new dependencies optimized: change-case
ℹ ✨ optimized dependencies changed. reloading
ℹ ✨ new dependencies optimized: radash
ℹ ✨ optimized dependencies changed. reloading
ℹ ✨ new dependencies optimized: lodash
ℹ ✨ optimized dependencies changed. reloading

After

Clear, actionable hints with copy-pasteable config:

Unresolvable entries: warns at startup with a clean config snippet:

 WARN  Unresolvable optimizeDeps.include entries:
  missing
  foo (from a Nuxt module)

Update your nuxt.config.ts:

export default defineNuxtConfig({
  vite: {
    optimizeDeps: {
      include: [
        'flatry',
      ]
    }
  }
})

Learn more: https://vite.dev/guide/dep-pre-bundling.html

Runtime-discovered deps: shown once after Vite finishes discovering new deps:

ℹ Vite discovered new dependencies at runtime:
  change-case ← ./app/plugins/test-dep.ts
  radash ← ./app/pages/second.vue
  lodash (CJS) ← ./app/pages/index.vue

Pre-bundle them in your nuxt.config.ts to avoid page reloads:

export default defineNuxtConfig({
  vite: {
    optimizeDeps: {
      include: [
        'flatry', // CJS
        'change-case',
        'radash',
        'lodash', // CJS
      ]
    }
  }
})

Learn more: https://vite.dev/guide/dep-pre-bundling.html

@harlan-zw harlan-zw requested a review from danielroe as a code owner February 12, 2026 03:14
@bolt-new-by-stackblitz
Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds tests for the Vite logger and extends the logger to detect newly optimized dependency candidates, accumulate them into a pending set, and emit a single debounced suggestion (after 2500 ms) with a generated optimizeDeps.include snippet. The logger tracks whether a hint has been shown per session, clears pending state and timers on clearScreen, and ensures the hint is emitted at most once per session. No public API signatures were changed.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(vite): improved optimizeDeps hints' accurately summarises the main change—adding improved hints for Vite's optimizeDeps functionality.
Description check ✅ Passed The pull request description clearly explains the changes: adding a one-time hint to pre-bundle runtime-discovered dependencies and warning about unresolvable entries in optimizeDeps.include.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/vite-deps-hint

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@packages/vite/src/utils/logger.ts`:
- Around line 76-78: The early return when hasShownSuggestion is true is
suppressing Vite's original log; remove that return and instead only skip the
custom hint/timer logic so the original log still flows through. Concretely,
inside the type === 'info' && msg.includes(...) branch in logger.ts, replace the
early "if (hasShownSuggestion) { return }" with a guarded block like "if
(!hasShownSuggestion) { /* setTimeout/hint logic and set hasShownSuggestion =
true */ }" so the native Vite message (msg) is still processed/emitted while the
hint is only shown once.
- Around line 15-17: The module-level suggestion state (suggestedDeps,
suggestionTimer, hasShownSuggestion) is shared across all createViteLogger
instances and never cleaned in production; move these variables into
createViteLogger so each logger has its own
suggestedDeps/suggestionTimer/hasShownSuggestion, add a dispose() (or cleanup)
method on the returned logger that clears suggestionTimer and empties
suggestedDeps/flags (or call resetSuggestionState from there), and update server
shutdown hooks to call logger.dispose() so timeouts are cleared and state
doesn't leak between servers or restarts.
- Around line 80-83: Extract the inline regex /\u001B\[[\d;]*m/g into a named
constant (e.g., ANSI_ESCAPE_REGEX) and replace the .replace call in the deps
parsing logic with .replace(ANSI_ESCAPE_REGEX, ''). Add a local Biome rule
suppression comment immediately above the constant to disable only
noControlCharactersInRegex for that declaration (for example using the
appropriate biome-disable-next-line comment), so the control-character regex is
isolated and the rule is suppressed only for that constant.
🧹 Nitpick comments (4)
packages/vite/src/utils/logger.ts (1)

93-107: The suggestion timer is never cancelled on server shutdown.

If the Vite dev server is stopped (e.g. user restarts Nuxt or hits Ctrl+C) while the 2500 ms timer is pending, the callback fires after teardown and calls logger.info on a potentially stale context. This is a minor concern in practice (dev-only, process usually exits), but for correctness you could clear it via a Nuxt close hook or expose a cleanup function.

Additionally, consider whether the return on line 77 (if (hasShownSuggestion) { return }) should come before or after the dependency-parsing block. Currently, when hasShownSuggestion is true, the entire log message (including Vite's original "new dependencies optimized" info) is suppressed. If the intent is only to suppress the hint, you'd want to move the early return to just before the timer scheduling (line 93) and let the original Vite message pass through.

packages/vite/src/plugins/dev-server.ts (1)

151-156: Consider extracting the 3000 ms magic number and clearing the timer on server close.

The 3-second delay is meaningful (waiting for the initial page load to settle) but unexplained. A named constant or brief comment would help future readers understand the rationale. Also, if the server is torn down within those 3 seconds, the timer fires on a stale context.

Suggested improvement
+      const FIRST_LOAD_SETTLE_MS = 3000 // Wait for initial page load to complete before silencing dep suggestions
       let firstRequest = true
+      let firstLoadTimer: ReturnType<typeof setTimeout> | undefined
       const viteMiddleware = defineEventHandler(async (event: H3V1Event | H3V2Event) => {
         if (firstRequest) {
           firstRequest = false
-          setTimeout(onFirstPageLoad, 3000)
+          firstLoadTimer = setTimeout(onFirstPageLoad, FIRST_LOAD_SETTLE_MS)
         }

Then clear firstLoadTimer in a Nuxt close hook if one is available in this scope.

packages/vite/src/utils/logger.test.ts (2)

25-29: Add afterEach to restore real timers.

vi.useFakeTimers() is called in beforeEach but real timers are never restored. While vitest may handle this between test files, it's best practice to pair it with vi.useRealTimers() in afterEach to avoid leaking fake timers into other tests in the same suite if the suite grows.

Proposed fix
   beforeEach(() => {
     vi.clearAllMocks()
     vi.useFakeTimers()
     resetSuggestionState()
   })
+
+  afterEach(() => {
+    vi.useRealTimers()
+  })

Don't forget to add afterEach to the import on line 1.


68-68: Brittle exact call-count assertion.

toHaveBeenCalledTimes(4) depends on knowing that output() forwards each of the 3 info messages to logger.info plus the 1 suggestion. If the internal forwarding logic changes (e.g. one of those messages gets suppressed or an extra log is added), this test breaks for the wrong reason. Consider asserting only that the suggestion message appeared exactly once, which is the behaviour under test.

Proposed alternative
-    expect(logger.info).toHaveBeenCalledTimes(4) // 3 original info logs + 1 suggestion
+    // Verify exactly one suggestion was emitted
+    const suggestionCalls = vi.mocked(logger.info).mock.calls.filter(
+      call => (call[0] as string).includes('Vite has discovered new dependencies'),
+    )
+    expect(suggestionCalls).toHaveLength(1)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/vite/src/utils/logger.test.ts`:
- Line 93: The test calls viteLogger.clearScreen() without the required
parameter; update the call to pass a valid vite.LogType (for example
viteLogger.clearScreen('info') or another appropriate LogType literal) so the
signature vite.Logger['clearScreen'] is satisfied; if necessary, import or
reference the vite.LogType union or provide a cast (e.g., 'info' as
vite.LogType) to keep types correct.
🧹 Nitpick comments (1)
packages/vite/src/utils/logger.test.ts (1)

25-28: Restore real timers after each test to avoid leaking fake timers.

vi.useFakeTimers() is called in beforeEach but never restored. This can affect other test suites in the same process.

Proposed fix
   beforeEach(() => {
     vi.clearAllMocks()
     vi.useFakeTimers()
   })
+
+  afterEach(() => {
+    vi.useRealTimers()
+  })

You'll also need to add afterEach to the import on line 1:

-import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

@harlan-zw harlan-zw force-pushed the feat/vite-deps-hint branch 2 times, most recently from 58200f0 to b8f9b6d Compare February 12, 2026 03:48
When Vite discovers new dependencies at runtime and reloads the page,
show a one-time hint with the nuxt.config.ts snippet to pre-bundle them.
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Feb 12, 2026

Open in StackBlitz

@nuxt/kit

npm i https://pkg.pr.new/@nuxt/kit@34320

@nuxt/nitro-server

npm i https://pkg.pr.new/@nuxt/nitro-server@34320

nuxt

npm i https://pkg.pr.new/nuxt@34320

@nuxt/rspack-builder

npm i https://pkg.pr.new/@nuxt/rspack-builder@34320

@nuxt/schema

npm i https://pkg.pr.new/@nuxt/schema@34320

@nuxt/vite-builder

npm i https://pkg.pr.new/@nuxt/vite-builder@34320

@nuxt/webpack-builder

npm i https://pkg.pr.new/@nuxt/webpack-builder@34320

commit: 928c694

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Feb 12, 2026

Merging this PR will not alter performance

✅ 20 untouched benchmarks
⏩ 3 skipped benchmarks1


Comparing feat/vite-deps-hint (928c694) with main (c5133a4)

Open in CodSpeed

Footnotes

  1. 3 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Copy link
Copy Markdown
Member

@danielroe danielroe left a comment

Choose a reason for hiding this comment

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

this is really nice

@harlan-zw
Copy link
Copy Markdown
Contributor Author

Thanks and sorry, some edits after coming back with fresh eyes.

@harlan-zw
Copy link
Copy Markdown
Contributor Author

Oh i got one more idea 😆

@harlan-zw harlan-zw changed the title feat(vite): hint to pre-bundle deps discovered after startup feat(vite): improved optimizeDeps hints Feb 13, 2026
@harlan-zw
Copy link
Copy Markdown
Contributor Author

harlan-zw commented Feb 13, 2026

Okay, sorry for the pivot, but I think this is ultimately more useful (same direction, just more fleshed out).

Will think over it the next couple of days, as I'm not fully convinced (i.e maybe we should keep Vite logs), but please lmk if you have feedback.

@danielroe danielroe enabled auto-merge February 25, 2026 17:39
@danielroe danielroe added this pull request to the merge queue Feb 25, 2026
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Feb 25, 2026
@danielroe danielroe added this pull request to the merge queue Feb 25, 2026
Merged via the queue into main with commit 873c77b Feb 25, 2026
99 of 101 checks passed
@danielroe danielroe deleted the feat/vite-deps-hint branch February 25, 2026 20:22
@github-actions github-actions bot mentioned this pull request Feb 25, 2026
This was referenced Mar 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ Optimized dependencies changed. reloading (PNPM) eager dependency pre-bundling is no longer working

2 participants