Skip to content

Conversation

@bhavaniravi
Copy link

@bhavaniravi bhavaniravi commented Jan 8, 2026

Closes: #15946 (vite)

  • Webpack has cacheDir for builds, hence the time taken reduces after 1st build
  • Whereas Vite rebuilds everything every time. Hence, enabling watch-mode becomes necessary

What I did

  • Currently the builder does not listen or return the Rollupwatcher, even if watch option is enabled
  • This PR listens to the watcher events thereby enabling watch mode

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

Need help in writing testcases, new to JS ecosystem

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

  1. Run a sandbox for template, e.g. yarn task --task sandbox --start-from auto --template react-vite/default-ts
  2. Open Storybook in your browser
  3. yarn install
  4. In main.ts update the viteFinal function with following snippet
  5. run yarn run build-storybook
  6. Watch the watcher waiting for new changes
  7. Make new change to any story and watch the builder rebuild
    build: {
      watch: {},
      rollupOptions:{
        cache: true,
        buildDelay: 1000,
      },
      ...config.build
    }

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>

Summary by CodeRabbit

  • Improvements
    • Clearer build startup messaging for Vite-based Storybook.
    • Watch-mode activation notice when continuous rebuilds are enabled.
    • Improved error visibility during watch builds to surface failures promptly.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 8, 2026

📝 Walkthrough

Walkthrough

Adds watch-mode handling to the Vite builder: imports RollupWatcher/RollupWatcherEvent, logs "Building storybook with Vite...", runs viteBuild(await sanitizeEnvVars(...)) into a local result, and when finalConfig.build?.watch is true and result exposes .on casts it to RollupWatcher, logs "Watching for changes..." and attaches an event handler to log errors on ERROR. Subsequent flow (including initializing finalConfig.customLogger) is preserved and no additional viteBuild calls are introduced here.

Changes

Cohort / File(s) Summary
Vite builder watch mode change
code/builders/builder-vite/src/build.ts
Import RollupWatcher and RollupWatcherEvent; add pre-build log "Building storybook with Vite..."; store viteBuild(await sanitizeEnvVars(...)) result in a local variable; if finalConfig.build?.watch and result.on exist, cast to RollupWatcher, log "Watching for changes..." and attach an event handler that logs ERROR events. Keeps existing subsequent initialization of finalConfig.customLogger.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0abfacc and b11ca75.

📒 Files selected for processing (1)
  • code/builders/builder-vite/src/build.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/builders/builder-vite/src/build.ts

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
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
code/builders/builder-vite/src/build.ts (1)

71-90: Critical: Double build execution breaks watch mode.

The code calls viteBuild() twice—once on line 71 and again on line 90. This logic error:

  • Breaks watch mode because the watcher attached to the first build result is overridden by the second build
  • Wastes resources by building the project twice
  • Sets up the custom logger (line 88) after the first build but before the second, meaning the first build lacks proper logging

Additionally, lines 79 and 83 violate coding guidelines by using console.log and console.error directly instead of the logger from storybook/internal/node-logger.

🔧 Proposed fix
- logger.info('Building storybook with Vite...');
-
- const result = await viteBuild(finalConfig as InlineConfig)
-
- // Narrow by feature, not instanceof
- if (finalConfig.build?.watch && 'on' in result) {
-   const watcher = result as RollupWatcher
-   logger.info('Watching for changes...');
-   watcher.on('event', (event: RollupWatcherEvent) => {
-     if (event.code === 'BUNDLE_END') {
-       console.log(`Rebuilt in ${event.duration}ms`)
-     }
-
-     if (event.code === 'ERROR') {
-       console.error(event.error)
-     }
-   })
- }
-
  finalConfig.customLogger ??= await createViteLogger();

+ logger.info('Building storybook with Vite...');
+
- await viteBuild(await sanitizeEnvVars(options, finalConfig));
+ const result = await viteBuild(await sanitizeEnvVars(options, finalConfig));
+
+ // Narrow by feature, not instanceof
+ if (finalConfig.build?.watch && 'on' in result) {
+   const watcher = result as RollupWatcher;
+   logger.info('Watching for changes...');
+   watcher.on('event', (event: RollupWatcherEvent) => {
+     if (event.code === 'BUNDLE_END') {
+       logger.info(`Rebuilt in ${event.duration}ms`);
+     }
+
+     if (event.code === 'ERROR') {
+       logger.error(event.error);
+     }
+   });
+ }

This fix:

  1. Sets up the custom logger before building
  2. Calls viteBuild() only once with sanitized env vars
  3. Attaches the watcher to the correct build result
  4. Uses logger.info and logger.error instead of console methods

As per coding guidelines, use logger from storybook/internal/node-logger for server-side logging.

🤖 Fix all issues with AI agents
In @code/builders/builder-vite/src/build.ts:
- Around line 73-86: Replace direct console usage in the Rollup watcher event
handler with the injected logger: inside the block that checks
finalConfig.build?.watch and casts result to RollupWatcher (variable watcher),
change the BUNDLE_END branch to call logger.info with the rebuilt duration
message and change the ERROR branch to call logger.error with the event.error;
keep existing event.code checks and message formatting but use logger.info(...)
and logger.error(...) instead of console.log/console.error.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 13b7cff and 13265a0.

📒 Files selected for processing (1)
  • code/builders/builder-vite/src/build.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,ts,tsx,json,md,html,css,scss}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Format code using Prettier with yarn prettier --write <file>

Files:

  • code/builders/builder-vite/src/build.ts
**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Run ESLint checks using yarn lint:js:cmd <file> or the full command cross-env NODE_ENV=production eslint --cache --cache-location=../.cache/eslint --ext .js,.jsx,.json,.html,.ts,.tsx,.mjs --report-unused-disable-directives to fix linting errors before committing

Files:

  • code/builders/builder-vite/src/build.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Enable TypeScript strict mode across all packages

Files:

  • code/builders/builder-vite/src/build.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx,js,jsx}: Export functions from modules if they need to be tested
Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/builders/builder-vite/src/build.ts
code/{core,lib,addons,builders,frameworks,presets}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use logger from storybook/internal/node-logger for server-side logging in Node.js code

Files:

  • code/builders/builder-vite/src/build.ts
🧠 Learnings (1)
📚 Learning: 2025-11-24T17:49:31.838Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-24T17:49:31.838Z
Learning: Applies to code/vitest.workspace.ts : Vitest configuration is centralized in `code/vitest.workspace.ts` for workspace setup

Applied to files:

  • code/builders/builder-vite/src/build.ts
🔇 Additional comments (2)
code/builders/builder-vite/src/build.ts (2)

6-6: LGTM!

The RollupWatcher type import is appropriate for implementing watch mode support.


69-69: LGTM!

The log message appropriately uses the logger from storybook/internal/node-logger and provides clear feedback to users.

Comment on lines 73 to 84
// Narrow by feature, not instanceof
if (finalConfig.build?.watch && 'on' in result) {
const watcher = result as RollupWatcher
logger.info('Watching for changes...');
watcher.on('event', (event: RollupWatcherEvent) => {
if (event.code === 'BUNDLE_END') {
console.log(`Rebuilt in ${event.duration}ms`)
}

if (event.code === 'ERROR') {
console.error(event.error)
}
})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "build.ts" -path "*/builder-vite/src/*" | head -5

Repository: storybookjs/storybook

Length of output: 106


🏁 Script executed:

wc -l code/builders/builder-vite/src/build.ts

Repository: storybookjs/storybook

Length of output: 107


🏁 Script executed:

sed -n '70,90p' code/builders/builder-vite/src/build.ts

Repository: storybookjs/storybook

Length of output: 681


🏁 Script executed:

head -30 code/builders/builder-vite/src/build.ts

Repository: storybookjs/storybook

Length of output: 1251


Replace console.log and console.error with logger calls.

The code violates the coding guidelines by using console.log (line 78) and console.error (line 84) directly. Since the file is in code/builders/ and logger is already imported from storybook/internal/node-logger, use logger.info() and logger.error() instead:

if (event.code === 'BUNDLE_END') {
  logger.info(`Rebuilt in ${event.duration}ms`)
}

if (event.code === 'ERROR') {
  logger.error(event.error)
}
🤖 Prompt for AI Agents
In @code/builders/builder-vite/src/build.ts around lines 73 - 86, Replace direct
console usage in the Rollup watcher event handler with the injected logger:
inside the block that checks finalConfig.build?.watch and casts result to
RollupWatcher (variable watcher), change the BUNDLE_END branch to call
logger.info with the rebuilt duration message and change the ERROR branch to
call logger.error with the event.error; keep existing event.code checks and
message formatting but use logger.info(...) and logger.error(...) instead of
console.log/console.error.

Copy link
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
code/builders/builder-vite/src/build.ts (1)

71-90: Critical: Duplicate builds detected - the code builds twice unnecessarily.

The code now calls viteBuild twice:

  1. Line 71 builds without sanitizeEnvVars
  2. Line 90 builds with sanitizeEnvVars

This is incorrect because:

  • It doubles the build time
  • The first build bypasses sanitizeEnvVars, which may be important for security or correctness
  • The customLogger initialization (line 88) happens after the first build but before the second
  • Only the first build's result is captured for watch mode, but the second build runs with the proper configuration
🔧 Proposed fix: Use a single build with sanitized env vars
  logger.info('Building storybook with Vite...');

- const result = await viteBuild(finalConfig as InlineConfig)
+ finalConfig.customLogger ??= await createViteLogger();
+ const result = await viteBuild(await sanitizeEnvVars(options, finalConfig));

  // Narrow by feature, not instanceof
  if (finalConfig.build?.watch && 'on' in result) {
    const watcher = result as RollupWatcher
    logger.info('Watching for changes...');
    watcher.on('event', (event: RollupWatcherEvent) => {
      if (event.code === 'BUNDLE_END') {
        console.log(`Rebuilt in ${event.duration}ms`)
      }

      if (event.code === 'ERROR') {
        console.error(event.error)
      }
    })
  }

- finalConfig.customLogger ??= await createViteLogger();
-
- await viteBuild(await sanitizeEnvVars(options, finalConfig));

  const statsPlugin = findPlugin(
🤖 Fix all issues with AI agents
In @code/builders/builder-vite/src/build.ts:
- Around line 79-84: Replace direct console calls in the build watcher: change
console.log(`Rebuilt in ${event.duration}ms`) to logger.info with the same
message and change console.error(event.error) to logger.error(event.error);
ensure the module imports logger from 'storybook/internal/node-logger' at the
top if not already imported and use the existing event variable in the logger
calls to preserve the same messages and error objects.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 13265a0 and 0abfacc.

📒 Files selected for processing (1)
  • code/builders/builder-vite/src/build.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,ts,tsx,json,md,html,css,scss}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Format code using Prettier with yarn prettier --write <file>

Files:

  • code/builders/builder-vite/src/build.ts
**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Run ESLint checks using yarn lint:js:cmd <file> or the full command cross-env NODE_ENV=production eslint --cache --cache-location=../.cache/eslint --ext .js,.jsx,.json,.html,.ts,.tsx,.mjs --report-unused-disable-directives to fix linting errors before committing

Files:

  • code/builders/builder-vite/src/build.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Enable TypeScript strict mode across all packages

Files:

  • code/builders/builder-vite/src/build.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx,js,jsx}: Export functions from modules if they need to be tested
Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/builders/builder-vite/src/build.ts
code/{core,lib,addons,builders,frameworks,presets}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use logger from storybook/internal/node-logger for server-side logging in Node.js code

Files:

  • code/builders/builder-vite/src/build.ts
🧠 Learnings (4)
📚 Learning: 2025-11-24T17:49:31.838Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-24T17:49:31.838Z
Learning: Applies to code/vitest.workspace.ts : Vitest configuration is centralized in `code/vitest.workspace.ts` for workspace setup

Applied to files:

  • code/builders/builder-vite/src/build.ts
📚 Learning: 2025-12-22T22:03:40.123Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-22T22:03:40.123Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Do not use `console.log`, `console.warn`, or `console.error` directly unless in isolated files where importing loggers would significantly increase bundle size

Applied to files:

  • code/builders/builder-vite/src/build.ts
📚 Learning: 2025-12-22T22:03:40.123Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-22T22:03:40.123Z
Learning: Applies to code/{core,lib,addons,builders,frameworks,presets}/**/*.{ts,tsx,js,jsx} : Use `logger` from `storybook/internal/node-logger` for server-side logging in Node.js code

Applied to files:

  • code/builders/builder-vite/src/build.ts
📚 Learning: 2025-12-22T22:03:40.123Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-22T22:03:40.123Z
Learning: Applies to code/{renderers}/**/*.{ts,tsx,js,jsx} : Use `logger` from `storybook/internal/client-logger` for client-side logging in browser code

Applied to files:

  • code/builders/builder-vite/src/build.ts
🔇 Additional comments (2)
code/builders/builder-vite/src/build.ts (2)

6-7: LGTM!

The RollupWatcher and RollupWatcherEvent type imports are correctly added to support the new watch mode functionality.


69-70: LGTM!

The informational log message correctly uses logger.info() as per coding guidelines for server-side logging.

@storybook-app-bot
Copy link

Package Benchmarks

Commit: b11ca75, ran on 8 January 2026 at 06:01:20 UTC

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

@storybook/builder-webpack5

Before After Difference
Dependency count 192 192 0
Self size 75 KB 75 KB 🎉 -48 B 🎉
Dependency size 32.21 MB 32.23 MB 🚨 +21 KB 🚨
Bundle Size Analyzer Link Link

@storybook/angular

Before After Difference
Dependency count 192 192 0
Self size 118 KB 118 KB 0 B
Dependency size 30.40 MB 30.42 MB 🚨 +21 KB 🚨
Bundle Size Analyzer Link Link

@storybook/ember

Before After Difference
Dependency count 196 196 0
Self size 15 KB 15 KB 0 B
Dependency size 28.93 MB 28.95 MB 🚨 +21 KB 🚨
Bundle Size Analyzer Link Link

@storybook/nextjs

Before After Difference
Dependency count 538 538 0
Self size 646 KB 646 KB 🚨 +120 B 🚨
Dependency size 59.17 MB 59.19 MB 🚨 +21 KB 🚨
Bundle Size Analyzer Link Link

@storybook/nextjs-vite

Before After Difference
Dependency count 127 127 0
Self size 1.12 MB 1.12 MB 🎉 -36 B 🎉
Dependency size 21.78 MB 21.81 MB 🚨 +22 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-native-web-vite

Before After Difference
Dependency count 159 159 0
Self size 30 KB 30 KB 🚨 +18 B 🚨
Dependency size 22.96 MB 22.99 MB 🚨 +22 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-vite

Before After Difference
Dependency count 117 117 0
Self size 35 KB 35 KB 0 B
Dependency size 19.58 MB 19.60 MB 🚨 +22 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-webpack5

Before After Difference
Dependency count 278 278 0
Self size 24 KB 24 KB 🚨 +12 B 🚨
Dependency size 44.10 MB 44.12 MB 🚨 +21 KB 🚨
Bundle Size Analyzer Link Link

@storybook/server-webpack5

Before After Difference
Dependency count 204 204 0
Self size 16 KB 16 KB 🚨 +12 B 🚨
Dependency size 33.47 MB 33.49 MB 🚨 +21 KB 🚨
Bundle Size Analyzer Link Link

@storybook/cli

Before After Difference
Dependency count 183 183 0
Self size 775 KB 775 KB 0 B
Dependency size 67.24 MB 67.26 MB 🚨 +21 KB 🚨
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 176 176 0
Self size 30 KB 30 KB 0 B
Dependency size 65.81 MB 65.83 MB 🚨 +21 KB 🚨
Bundle Size Analyzer Link Link

@storybook/preset-react-webpack

Before After Difference
Dependency count 170 170 0
Self size 18 KB 18 KB 0 B
Dependency size 31.24 MB 31.26 MB 🚨 +21 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react

Before After Difference
Dependency count 57 57 0
Self size 718 KB 718 KB 0 B
Dependency size 12.92 MB 12.94 MB 🚨 +21 KB 🚨
Bundle Size Analyzer Link Link

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.

build-storybook watch mode not working

2 participants