Skip to content

Builder-Vite: Use preview annotations as entry points for optimizeDeps#33875

Merged
valentinpalkovic merged 18 commits into
nextfrom
copilot/revamp-vite-vitest-optimization-again
Feb 24, 2026
Merged

Builder-Vite: Use preview annotations as entry points for optimizeDeps#33875
valentinpalkovic merged 18 commits into
nextfrom
copilot/revamp-vite-vitest-optimization-again

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 19, 2026

NOTE: This has been reviewed, tested and edited by my beloved humans @yannbf and @valentinpalkovic. GitHub should give them a prize, that's all I'll say!

Vite's dep optimizer was only scanning story files as entry points, missing preview annotation files (user's preview.ts, addon/framework/renderer previews). This caused runtime CJS dependency discovery, triggering hard reloads in Storybook and flaky test reruns in Vitest.

The real fix is to give Vite the complete set of entry points upfront so it can crawl all transitive dependencies before serving.

Changes

  • The storybook-optimize-deps-plugin.ts plugin now figures out all of the preview annotations and adds them as entries in the vite config.
  • Because of that, the INCLUDE_CANDIDATES constant has become unnecessary and is therefore deleted.
  • There are a couple of places where optimizeDeps have to be included internally:
  1. storybook/internal/preview/runtime because it's only imported inside a string template in codegen-modern-iframe-script.ts (the virtual iframe entry) and therefore can't be crawled ahead of time.
  2. react-dom/test-utils because its dynamically imported in act-compat.ts, which Vite's static analyzer does not pre-bundle.

The fix applies to both @storybook/builder-vite and @storybook/addon-vitest since storybookOptimizeDepsPlugin is part of the shared viteCorePlugins.

Manual testing

  • Use lodash in the .storybook/preview file
  • Notice that before these changes, there would be a warning message about dependency optimization, and now with these changes there shouldn't be any message.
Original prompt

This section details on the original issue you should resolve

<issue_title>[Bug]: Revamp Vite and Vitest dependency optimization</issue_title>
<issue_description>### Describe the bug

Problem

We currently have specific code which handles dependency optimization in the Vite builder as can be seen here. That code is outdated and might not be necessary.

When the Vite dependency optimization kicks in, it needs to hard reload the browser. This causes two things:

  1. In Storybook itself, you notice how things take much longer to load and your story to render.
  2. In Vitest, the tests become flaky and can fail. Additionally can cause the coverage to be incorrect because either certain code paths got executed but the data got lost given the hard reload, or code that was supposed to execute didn’t, because of the same reason.

Storybook already adds stories as entries to Vite's config, which makes it scan most files to do dependency optimization automatically (great). However, currently Storybook does not add other necessary files as entries, such as the preview annotation files. For instance, if a .storybook/preview.tsx file has imports like:

import { withContext } from 'recompose';
import seedrandom from 'seedrandom';

Then such dependencies might need to be optimized (if they're CJS for instance) and therefore this will trigger the warnings. This would not have been the case had Storybook added the preview file as an entry to Vite. The same issue also occurs from packages, such as renderers, frameworks or addons. Such packages can use a concept of a preview annotation or preset, which adds annotations to Storybook (such as decorators, loaders, etc). These annotations can also end up making imports of dependencies that would cause the same problem.

This is a quite critical change that requires a lot of checking, but worth investigating. This solution will close many issues, including #33067, #32049, #31119 as well as provide a much better experience for end users.

The workaround

When this behavior occurs, users see, both in Storybook's CLI and in Vitest's CLI some warnings like this:

11:47:15 [vite] (client)  new dependencies optimized: lodash/capitalize, lodash/debounce
11:47:15 [vite] (client)  optimized dependencies changed. reloading

[vitest] Vite unexpectedly reloaded a test. This may cause tests to fail, lead to flaky behaviour or duplicated test runs.
For a stable experience, please add mentioned dependencies to your config's `optimizeDeps.include` field manually.

So to solve this issue, users need to add a viteFinal config in the Storybook main file and specify their own dependencies in its optimizeDeps.include field:

import type { StorybookConfig } from '@storybook/nextjs-vite';

const config: StorybookConfig = {
  viteFinal: (config, { configType }) => {
    return {
      ...config,
      // We optimize deps to avoid flake in tests and slow load times in Storybook. If you see more warnings, add more deps to the list.
      optimizeDeps: {
        ...config.optimizeDeps,
        include: [
          ...(config.optimizeDeps?.include ?? []),
          'cookie',
          'lodash/capitalize',
          'lodash/debounce',
          'lodash/values',
          'lodash/keyBy',
          'lodash/groupBy',
        ],
      },
    };
  },
}
export default config;

Potential solution

Vite automatically handles dependency optimization based on the entrypoints passed into the entries, so theoretically we don't need to handle dependency optimization ourselves, as long as we specify the correct entries.

Vitest has similar challenges and solves it by doing this, it basically adds all test files + setup files (plus some internal utilities) as entries, and Vite will figure out what needs to be optimized. In Storybook, the scenario is similar, we need to list the stories files as entries, as well as the preview entries (user's preview file, plus addon/renderer/frameworks preview annotations which come from presets or preview files).
</issue_description>

<agent_instructions>There was a recent change in the repo to centralize core vite plugins and that is reused between storybook proper and the Vitest integration. The solution you come up with should work for them both then.

Also, this issue is not necessarily a problem for CSF4 (known as csf factories) users, it's mostly a problem for CSF3 and below.

The main goal of this task should be to improve and simplify the dependency optimization logic, which will make the INCLUDE_CANDIDATES in code/...


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Summary by CodeRabbit

  • New Features

    • Faster bundling via expanded Vite dependency optimization (adds preview/annotation and runtime modules)
    • New "test" toolset in the UI with an accessibility tool entry
  • Chores

    • Sandbox templates now run vitest-integration by default and improve linked-workspace test support (better filesystem access)
  • Refactor

    • Consolidated dependency-optimization behavior across renderers and tests for more reliable pre-bundling

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Feb 19, 2026

View your CI Pipeline Execution ↗ for commit ca461fa

Command Status Duration Result
nx run-many -t compile,check,knip,test,pretty-d... ⛔ Cancelled 12m 45s View ↗

☁️ Nx Cloud last updated this comment at 2026-02-24 07:50:59 UTC

Copilot AI changed the title [WIP] Revamp Vite and Vitest dependency optimization Revamp Vite dependency optimization: use preview annotations as entry points Feb 19, 2026
Copilot AI requested a review from yannbf February 19, 2026 15:42
@storybook-bot
Copy link
Copy Markdown
Contributor

Failed to publish canary version of this pull request, triggered by @yannbf. See the failed workflow run at: https://github.com/storybookjs/storybook/actions/runs/22188806376

@yannbf
Copy link
Copy Markdown
Member

yannbf commented Feb 19, 2026

@copilot this mostly worked but there are parts which you didn't catch. When running Storybook and watching its output you can see an optimization message for storybook/internal/preview/runtime and react-dom/test-utils, because these are either entries in virtual files or transformed entries during vite transform. See the commit which fixes those specifically at d00906c.

How can we avoid missing these, and are there perhaps more entries which are required here?

@yannbf yannbf changed the title Revamp Vite dependency optimization: use preview annotations as entry points Vite: Revamp Vite dependency optimization Feb 19, 2026
@yannbf yannbf changed the title Vite: Revamp Vite dependency optimization Vite: Revamp dependency optimization mechanism Feb 19, 2026
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 19, 2026

@copilot this mostly worked but there are parts which you didn't catch. When running Storybook and watching its output you can see an optimization message for storybook/internal/preview/runtime and ...

These two packages were missed because they appear in contexts Vite's dep scanner can't statically analyze:

  1. storybook/internal/preview/runtime — Only imported inside the string template in codegen-modern-iframe-script.ts that generates the virtual iframe entry module. Vite's dep scanner never sees virtual module contents, so it discovers this dependency only at request time.

  2. react-dom/test-utils — Dynamically imported via await import('react-dom/test-utils') in act-compat.ts, which Vite's static analyzer doesn't pre-bundle.

The systematic rule to catch these: check virtual module code generators (codegen-*.ts) for string-template imports, and check for await import() of CJS/legacy packages in browser-side code.

After auditing all virtual module generators and dynamic imports, these two are the only cases that need explicit include. Other storybook/internal/* packages (like storybook/internal/channels) have a code export condition pointing to their TypeScript source, so Vite resolves them as ESM and never needs to pre-bundle them. storybook/internal/preview/runtime is the exception — its export only points to ./dist/preview/runtime.js (no source condition), so Vite sees a compiled bundle that needs pre-bundling.

The fixes from commit d00906c are already on the branch and cover all identified cases.

Copilot AI changed the title Vite: Revamp dependency optimization mechanism Revamp Vite dependency optimization: use preview annotations as entry points Feb 19, 2026
@yannbf
Copy link
Copy Markdown
Member

yannbf commented Feb 19, 2026

Great @copilot but how do we make sure we don't regress? Is there a way to automatically catch cases which are not covered? Can you write a test that would have caught this?

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 19, 2026

Great @copilot but how do we make sure we don't regress? Is there a way to automatically catch cases which are not covered? Can you write a test that would have caught this?

Added two regression tests in commit 8312c5a:

1. Systematic test in codegen-modern-iframe-script.test.ts:

Generates all virtual module code (generateModernIframeScriptCodeFromPreviews + generateAddonSetupCode), extracts every bare package import specifier, then asserts each one is either in optimizeViteDeps OR in an explicit discoveredViaEntries set (packages that also appear in real source preview files and will be found by Vite's entry crawler). If someone adds a new package import to a virtual module code generator without adding it to optimizeViteDeps, the test fails with a message pointing to exactly what needs to be done.

2. Focused regression test in renderers/react/src/preset.test.ts:

Asserts react-dom/test-utils is in optimizeViteDeps. The test comment explains it's needed because it's only await import()'d dynamically in act-compat.ts — Vite's static analyzer doesn't pre-bundle dynamic imports.

This covers the two identified patterns that Vite cannot discover statically: imports in virtual module string templates, and await import() calls in browser code.

@valentinpalkovic valentinpalkovic added ci:daily Run the CI jobs that normally run in the daily job. and removed ci:normal labels Feb 20, 2026
@storybook-app-bot
Copy link
Copy Markdown

storybook-app-bot Bot commented Feb 20, 2026

Package Benchmarks

Commit: ca461fa, ran on 23 February 2026 at 18:45:57 UTC

The following packages have significant changes to their size or dependencies:

eslint-plugin-storybook

Before After Difference
Dependency count 20 20 0
Self size 131 KB 131 KB 0 B
Dependency size 3.40 MB 3.42 MB 🚨 +20 KB 🚨
Bundle Size Analyzer Link Link

Copilot AI and others added 5 commits February 20, 2026 12:35
…ove INCLUDE_CANDIDATES

Co-authored-by: yannbf <1671563+yannbf@users.noreply.github.com>
…timizeDeps coverage

Co-authored-by: yannbf <1671563+yannbf@users.noreply.github.com>
@valentinpalkovic valentinpalkovic force-pushed the copilot/revamp-vite-vitest-optimization-again branch from 9e00f92 to e96bd64 Compare February 20, 2026 11:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug builder-vite ci:daily Run the CI jobs that normally run in the daily job.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Revamp Vite and Vitest dependency optimization

5 participants