Skip to content

Conversation

@valentinpalkovic
Copy link
Contributor

@valentinpalkovic valentinpalkovic commented Nov 25, 2025

Closes #

What I did

Checklist for Contributors

Testing

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

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

Manual testing

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

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 pull request has been released as version 0.0.0-pr-33169-sha-180015d5. Try it out in a new sandbox by running npx [email protected] sandbox or in an existing project with npx [email protected] upgrade.

More information
Published version 0.0.0-pr-33169-sha-180015d5
Triggered by @valentinpalkovic
Repository storybookjs/storybook
Branch valentin/fix-hmr-for-webpack-users
Commit 180015d5
Datetime Thu Nov 27 08:44:16 UTC 2025 (1764233056)
Workflow run 19730304064

To request a new release of this pull request, mention the @storybookjs/core team.

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

Summary by CodeRabbit

  • Bug Fixes

    • Build errors are now propagated and reported correctly.
    • Prevented duplicate injection of runtime scripts and ensured the runtime asset is prepended only once.
  • Performance

    • Added lazy caching to avoid re-creating runtime content.
    • Added mtime-based invalidation to skip re-extracting mocks when preview config is unchanged.
    • Improved incremental mock mapping and early exits to reduce unnecessary work.
  • Stability

    • Watch ignore now excludes the build output directory to avoid rebuild loops.
  • Chores

    • Module-mocking workflow disabled in the custom preset (mock transform, replacement plugin, and injected runtime no longer executed).

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

@nx-cloud
Copy link

nx-cloud bot commented Nov 25, 2025

View your CI Pipeline Execution ↗ for commit 180015d

Command Status Duration Result
nx run-many -t build --parallel=3 ✅ Succeeded 45s View ↗

☁️ Nx Cloud last updated this comment at 2025-11-27 09:02:33 UTC

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 25, 2025

📝 Walkthrough

Walkthrough

Runtime injector now lazy-caches the mocker runtime, avoids emitting/prepending the runtime asset when already present, and forwards HtmlWebpackPlugin errors to the callback. Mock plugin adds mtime-based invalidation, tracks candidate specifiers to skip unnecessary extraction, and short-circuits when no mocks. Preview config watch ignores node_modules and the resolved output directory. The custom webpack preset disables the module-mocking workflow by commenting out the mock loader and both mock plugins.

Changes

Cohort / File(s) Change Summary
Runtime Plugin Enhancements
code/builders/builder-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts
Added private cachedRuntime and lazy runtime caching; check compilation.getAsset(runtimeAssetName) before emitting; only emit and unshift the runtime asset when absent; forward HtmlWebpackPlugin errors via cb(error).
Mock Plugin Optimization
code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
Added candidateSpecifiers: Set<string> and lastPreviewMtime; added getPreviewConfigMtime() helper; apply mtime-based invalidation on beforeRun/watchRun; cache resolved specifiers and populate mockMap with absolute and extension-stripped keys; short-circuit module-replacement when no mocks or path not in candidates; handle stat errors gracefully.
Watch ignore fix
code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts
Updated imports to include posix and changed watchOptions.ignored to ignore both /node_modules/ and the resolved output directory (using posix.resolve(workingDir, outputDir)).
Preset: disable module-mocking
code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts
Commented out imports and registration of WebpackInjectMockerRuntimePlugin and WebpackMockPlugin and the mock transform loader to disable the module-mocking workflow while leaving the rest of the preset intact.

Sequence Diagram(s)

mermaid
sequenceDiagram
autonumber
participant Compiler
participant MockPlugin
participant FS as Filesystem
participant ModuleResolver as ModuleReplacementHook

Compiler->>MockPlugin: beforeRun / watchRun
MockPlugin->>FS: getPreviewConfigMtime()
alt preview config mtime unchanged
    MockPlugin-->>Compiler: skip extraction
else preview config changed or unknown
    MockPlugin->>MockPlugin: extract mocks -> resolve specifiers
    MockPlugin->>MockPlugin: update candidateSpecifiers & mockMap
    MockPlugin-->>Compiler: continue
end
Compiler->>ModuleResolver: module resolution
ModuleResolver->>MockPlugin: check candidateSpecifiers
alt path in candidates
    MockPlugin-->>ModuleResolver: provide replacement / mocks
else not a candidate
    MockPlugin-->>ModuleResolver: no-op
end

mermaid
sequenceDiagram
autonumber
participant HtmlWP as HtmlWebpackPlugin
participant RuntimePlugin
participant Assets

HtmlWP->>RuntimePlugin: beforeEmit(data, cb)
RuntimePlugin->>Assets: check getAsset(runtimeAssetName) and data.assets.js
alt runtime not present
    RuntimePlugin->>RuntimePlugin: cachedRuntime = cachedRuntime ?? getMockerRuntime()
    RuntimePlugin->>Assets: emit runtimeAssetName and unshift into data.assets.js
else present
    RuntimePlugin-->>Assets: no-op
end
RuntimePlugin->>HtmlWP: on error -> cb(error)
RuntimePlugin-->>HtmlWP: on success -> cb(null, data)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Inspect getPreviewConfigMtime, stat error handling, and mtime comparison logic in webpack-mock-plugin.ts.
  • Verify candidateSpecifiers population, membership checks in module-replacement hooks, and mockMap key normalization (absolute vs extension-stripped).
  • Confirm cachedRuntime lazy initialization and duplicate-emit/prepend guards under repeated/concurrent HtmlWebpackPlugin invocations.
  • Review the commented-out mock imports/registrations in custom-webpack-preset.ts to ensure disabling is intentional and safe.
  • Check the watchOptions.ignored entry for correct resolution and cross-platform behavior.
✨ Finishing touches
  • 📝 Generate docstrings

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

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-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts (1)

12-13: Minor typo in comment.

"includiweng" should be "including".

- * This ensures the `sb` object is available globally before any other scripts, includiweng the
+ * This ensures the `sb` object is available globally before any other scripts, including the
🧹 Nitpick comments (2)
code/builders/builder-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts (1)

62-72: Consider guarding emitAsset to avoid duplicate asset warnings.

The includes check on line 70 prevents duplicate entries in data.assets.js, but emitAsset is called unconditionally on every hook invocation. In watch mode or with multiple HTML files, this could attempt to emit an asset that already exists, potentially causing warnings or errors.

Consider checking if the asset exists before emitting:

-            compilation.emitAsset(
-              runtimeAssetName,
-              new compiler.webpack.sources.RawSource(runtimeScriptContent)
-            );
+            if (!compilation.getAsset(runtimeAssetName)) {
+              compilation.emitAsset(
+                runtimeAssetName,
+                new compiler.webpack.sources.RawSource(runtimeScriptContent)
+              );
+            }
code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts (1)

136-144: Consider adding type safety and debug logging for filesystem access.

The as any cast loses type safety. While the defensive optional chaining is appropriate (not all webpack file systems implement statSync), a debug log on failure would aid troubleshooting.

  private getPreviewConfigMtime(compiler: Compiler): number | undefined {
+   const logger = compiler.getInfrastructureLogger(PLUGIN_NAME);
    try {
-     const fs = compiler.inputFileSystem as any;
+     const fs = compiler.inputFileSystem as { statSync?: (path: string) => { mtime?: Date } };
      const stat = fs.statSync?.(this.options.previewConfigPath);
      return stat?.mtime?.getTime?.();
-   } catch {
+   } catch (e) {
+     logger.debug(`Could not stat preview config: ${e}`);
      return undefined;
    }
  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 268d34d and 0ede16e.

📒 Files selected for processing (2)
  • code/builders/builder-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts (3 hunks)
  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx,mjs}

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

Use camelCase for variable and function names

Files:

  • code/builders/builder-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts
  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Enable TypeScript strict mode
Export functions from modules for testing purposes

Files:

  • code/builders/builder-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts
  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
**/*.{ts,tsx,js,jsx,json,html,mjs}

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

**/*.{ts,tsx,js,jsx,json,html,mjs}: Use ESLint and Prettier for code style enforcement
Run 'yarn prettier --write ' to format code after making changes
Run 'yarn lint:js:cmd ' to check for ESLint issues after making changes

Files:

  • code/builders/builder-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts
  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
code/**/!(*.test).{ts,tsx,js,mjs}

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

code/**/!(*.test).{ts,tsx,js,mjs}: Use 'logger' from 'storybook/internal/node-logger' for server-side (Node.js) logging, not console.log/console.warn/console.error
Use 'logger' from 'storybook/internal/client-logger' for client-side (browser) logging, not console.log/console.warn/console.error
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-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts
  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
🧠 Learnings (15)
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid mocking only a subset of required dependencies in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:47.649Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T17:49:47.649Z
Learning: Applies to **/*.test.{ts,tsx} : Use 'vi.mock()' to mock external dependencies like file system and loggers in unit tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid inline mock implementations within test cases in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Mock all required dependencies that the test subject uses

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Mock implementations should match the expected return type of the original function

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use `vi.mock()` with the `spy: true` option for all package and file mocks in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Document complex mock behaviors in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid direct function mocking without `vi.mocked()` in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Keep mock implementations simple and focused in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use type-safe mocking with `vi.mocked()` in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid mocking without the `spy: true` option in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid mock implementations outside of `beforeEach` blocks in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Implement mock behaviors in `beforeEach` blocks in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Place all mocks at the top of the test file before any test cases

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Each mock implementation should return a Promise for async functions in Vitest

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
🧬 Code graph analysis (2)
code/builders/builder-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts (1)
code/core/src/mocking-utils/runtime.ts (1)
  • getMockerRuntime (13-28)
code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts (1)
test-storybooks/portable-stories-kitchen-sink/react-vitest-3/pre-e2e.js (1)
  • fs (3-3)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (7)
code/builders/builder-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts (3)

16-16: LGTM on the caching approach.

Caching the runtime script avoids repeated esbuild.buildSync calls on each compilation, which is a good performance optimization given that getMockerRuntime() bundles synchronously.


56-57: Good use of lazy initialization pattern.

The nullish coalescing assignment (??= semantics via ?? (this.cachedRuntime = ...)) ensures the runtime is only generated once per plugin instance.


74-76: Good fix for error propagation.

Previously, exceptions would leave the async callback hanging. Now the error is properly forwarded to webpack's compilation pipeline via cb(error as Error).

code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts (4)

52-53: LGTM on the new state fields.

The candidateSpecifiers set enables O(1) lookup for filtering irrelevant module requests, and lastPreviewMtime enables the mtime-based cache invalidation. Both are well-typed.


72-79: Effective mtime-based invalidation logic.

The early return when mTimePreviewConfig <= lastPreviewMtime correctly avoids redundant re-extraction. The fallback behavior when mTimePreviewConfig is undefined (re-extract) is also correct for robustness.


80-89: LGTM on the mock map construction.

Using flatMap to register both the full path and extension-stripped path enables matching regardless of how the importer specifies the extension. Updating candidateSpecifiers and logging the count improves observability.


98-107: Remove the early guard check or add path normalization to handle aliased/mapped imports.

The guard at line 105 returns early if resource.request is not in candidateSpecifiers, which stores the original sb.mock() paths. However, this check is too strict and blocks valid mocks when:

  • Imports use path aliases or tsconfig mappings that differ from how sb.mock() was called
  • Webpack normalizes paths differently (e.g., ./file.ts vs ./file)

The fallback resolution logic (lines 109–115) could handle these cases via resolveWithExtensions() and mockMap lookup by absolute path, but it's never reached due to the early return.

Options:

  1. Remove the early guard to let all imports attempt resolution
  2. Store normalized/resolved paths in candidateSpecifiers alongside original paths
  3. Normalize both resource.request and candidate paths before comparison

logger.info(`Mock map updated with ${this.mockMap.size / 2} mocks.`);
this.candidateSpecifiers = new Set(resolved.map((m) => m.path));
this.lastPreviewMtime = mTimePreviewConfig;
logger.info(`Mock map updated with ${resolved.length} mocks.`);
Copy link
Member

Choose a reason for hiding this comment

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

little reminder, now that you're touching this file, to either:

  • not show the log when the count is 0
  • only show the log if the current count is 0 and previous count wasn't 0 (so you don't keep showing "updated 0 mocks" every time)

@valentinpalkovic valentinpalkovic force-pushed the valentin/fix-hmr-for-webpack-users branch from 0ede16e to ce88eed Compare November 25, 2025 19:59
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

♻️ Duplicate comments (1)
code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts (1)

71-88: Verify mtime behavior during initial compilation.

The mtime-based invalidation logic looks sound. However, consider this edge case: during the initial compilation (beforeRun), this.lastPreviewMtime is undefined, so the check on line 73 will be false and extraction will proceed correctly.

On subsequent watch compilations, if the preview config hasn't changed, the early return on line 77 will skip re-extraction as intended.

Regarding line 88: As noted in the past review comment, consider either:

  • Not showing the log when resolved.length is 0
  • Only showing when transitioning from non-zero to zero

To verify the mtime comparison works correctly across different file systems and scenarios, run:

#!/bin/bash
# Verify that inputFileSystem.statSync is available in webpack
rg -n "inputFileSystem" --type=ts -C3 -g '!node_modules'
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ede16e and ce88eed.

📒 Files selected for processing (2)
  • code/builders/builder-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts (3 hunks)
  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/builders/builder-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx,mjs}

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

Use camelCase for variable and function names

Files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Enable TypeScript strict mode
Export functions from modules for testing purposes

Files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
**/*.{ts,tsx,js,jsx,json,html,mjs}

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

**/*.{ts,tsx,js,jsx,json,html,mjs}: Use ESLint and Prettier for code style enforcement
Run 'yarn prettier --write ' to format code after making changes
Run 'yarn lint:js:cmd ' to check for ESLint issues after making changes

Files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
code/**/!(*.test).{ts,tsx,js,mjs}

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

code/**/!(*.test).{ts,tsx,js,mjs}: Use 'logger' from 'storybook/internal/node-logger' for server-side (Node.js) logging, not console.log/console.warn/console.error
Use 'logger' from 'storybook/internal/client-logger' for client-side (browser) logging, not console.log/console.warn/console.error
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-webpack5/src/plugins/webpack-mock-plugin.ts
🧠 Learnings (15)
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid mocking only a subset of required dependencies in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid inline mock implementations within test cases in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:47.649Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T17:49:47.649Z
Learning: Applies to **/*.test.{ts,tsx} : Use 'vi.mock()' to mock external dependencies like file system and loggers in unit tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use `vi.mock()` with the `spy: true` option for all package and file mocks in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid mocking without the `spy: true` option in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Mock implementations should match the expected return type of the original function

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid mock implementations outside of `beforeEach` blocks in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Mock all required dependencies that the test subject uses

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid direct function mocking without `vi.mocked()` in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Document complex mock behaviors in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:47.649Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T17:49:47.649Z
Learning: Applies to code/**/!(*.test).{ts,tsx,js,mjs} : Use 'logger' from 'storybook/internal/node-logger' for server-side (Node.js) logging, not console.log/console.warn/console.error

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:47.649Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T17:49:47.649Z
Learning: Applies to code/**/!(*.test).{ts,tsx,js,mjs} : Use 'logger' from 'storybook/internal/client-logger' for client-side (browser) logging, not console.log/console.warn/console.error

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Mock all required properties and methods that the test subject uses in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:47.649Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T17:49:47.649Z
Learning: Applies to **/*.{ts,tsx} : Export functions from modules for testing purposes

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use type-safe mocking with `vi.mocked()` in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts
🧬 Code graph analysis (1)
code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts (1)
code/core/src/mocking-utils/resolve.ts (2)
  • isExternal (112-118)
  • getIsExternal (58-64)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (4)
code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts (4)

11-11: LGTM! Correct logger import.

The import follows coding guidelines by using the node-logger for server-side logging in this webpack plugin.


53-54: LGTM! Well-designed optimization state.

These private fields support mtime-based invalidation and early filtering, improving performance by avoiding unnecessary mock re-extraction and module resolution.


107-109: LGTM! Smart early-exit optimization.

The logic correctly filters external modules that aren't being mocked, while still allowing relative/local paths to proceed to full resolution. This is necessary because external specifiers (like 'lodash') are consistent across all imports, but relative specifiers (like '../utils/api') vary by importer location.


136-144: I'll verify the review comment by investigating webpack's type definitions for inputFileSystem and assessing the type safety concern.
<function_calls>

#!/bin/bash

First, check the file to understand the context

cat -n code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts | sed -n '130,150p'


</function_calls>
<function_calls>

#!/bin/bash

Check webpack version in package.json

fd -n "package.json" code/builders/builder-webpack5 | head -1 | xargs cat | grep -A2 -B2 '"webpack'


</function_calls>
<function_calls>

#!/bin/bash

Search for InputFileSystem type in webpack or the file itself

rg -n "InputFileSystem|inputFileSystem" code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts -C5


</function_calls>
<function_calls>

web_search for webpack InputFileSystem type interface definition


</function_calls>

Comment on lines +104 to +106
// Early filter only for external specifiers. Relative/local specifiers need resolution

// Early filter only for external specifiers. Relative/local specifiers need resolution
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 | 🟡 Minor

Remove duplicate comment.

The comment "Early filter only for external specifiers. Relative/local specifiers need resolution" appears twice (lines 104-105 and 106).

Apply this diff to remove the duplication:

       const isExternal = getIsExternal(path, importer);
-      // Early filter only for external specifiers. Relative/local specifiers need resolution
-
       // Early filter only for external specifiers. Relative/local specifiers need resolution
       if (isExternal && !this.candidateSpecifiers.has(path)) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Early filter only for external specifiers. Relative/local specifiers need resolution
// Early filter only for external specifiers. Relative/local specifiers need resolution
const isExternal = getIsExternal(path, importer);
// Early filter only for external specifiers. Relative/local specifiers need resolution
if (isExternal && !this.candidateSpecifiers.has(path)) {
🤖 Prompt for AI Agents
In code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts around
lines 104-106, there is a duplicated comment "Early filter only for external
specifiers. Relative/local specifiers need resolution"; remove the duplicate so
the comment appears only once (keep a single instance in the most appropriate
place) and ensure surrounding spacing/formatting remains consistent.

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

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-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts (1)

10-14: Fix typo in JSDoc comment.

"includiweng" should be "including".

 /**
  * A Webpack plugin that injects the module mocker runtime script into the preview iframe's HTML.
- * This ensures the `sb` object is available globally before any other scripts, includiweng the
+ * This ensures the `sb` object is available globally before any other scripts, including the
  * Storybook preview bundle, are executed.
  */
🧹 Nitpick comments (1)
code/builders/builder-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts (1)

62-73: Guard logic is correct; consider relocating the comment for clarity.

The asset emission guard correctly prevents duplicates. However, the comment on lines 70-72 describes the unshift operation inside the if block but is positioned outside it, making it appear to describe the callback instead.

             if (!compilation.getAsset(runtimeAssetName)) {
               compilation.emitAsset(
                 runtimeAssetName,
                 new compiler.webpack.sources.RawSource(runtimeScriptContent)
               );
+              // Prepend the name of our new asset to the list of JavaScript files.
+              // HtmlWebpackPlugin will automatically create a <script> tag for it
+              // and place it at the beginning of the body scripts.
               data.assets.js.unshift(runtimeAssetName);
             }

-            // Prepend the name of our new asset to the list of JavaScript files, once.
-            // HtmlWebpackPlugin will automatically create a <script> tag for it
-            // and place it at the beginning of the body scripts.
             cb(null, data);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce88eed and 289b99b.

📒 Files selected for processing (2)
  • code/builders/builder-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts (2 hunks)
  • code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx,mjs}

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

Use camelCase for variable and function names

Files:

  • code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts
  • code/builders/builder-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Enable TypeScript strict mode
Export functions from modules for testing purposes

Files:

  • code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts
  • code/builders/builder-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts
**/*.{ts,tsx,js,jsx,json,html,mjs}

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

**/*.{ts,tsx,js,jsx,json,html,mjs}: Use ESLint and Prettier for code style enforcement
Run 'yarn prettier --write ' to format code after making changes
Run 'yarn lint:js:cmd ' to check for ESLint issues after making changes

Files:

  • code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts
  • code/builders/builder-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts
code/**/!(*.test).{ts,tsx,js,mjs}

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

code/**/!(*.test).{ts,tsx,js,mjs}: Use 'logger' from 'storybook/internal/node-logger' for server-side (Node.js) logging, not console.log/console.warn/console.error
Use 'logger' from 'storybook/internal/client-logger' for client-side (browser) logging, not console.log/console.warn/console.error
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-webpack5/src/preview/iframe-webpack.config.ts
  • code/builders/builder-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts
🪛 ast-grep (0.40.0)
code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts

[warning] 132-132: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${resolvedOutputDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (4)
code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts (2)

47-47: LGTM! Correctly resolves the output directory.

The resolved absolute path is needed for the watchOptions.ignored configuration to prevent rebuild loops.


130-134: LGTM! Properly prevents rebuild loops with escaped regex pattern.

The change correctly ignores both node_modules and the output directory in watch mode. The regex escaping using .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') is the standard pattern for escaping special characters.

Regarding the static analysis ReDoS warning: this is a false positive in this context since resolvedOutputDir comes from developer configuration (the outputDir option), not runtime user input. The path is properly escaped and the pattern is straightforward.

code/builders/builder-webpack5/src/plugins/webpack-inject-mocker-runtime-plugin.ts (2)

16-16: LGTM!

The lazy caching pattern using nullish coalescing with assignment is idiomatic and correctly avoids redundant getMockerRuntime() calls across compilations.

Also applies to: 56-57


74-77: LGTM!

Properly forwarding errors to the callback ensures Webpack can handle and report failures during asset tag generation.

@valentinpalkovic valentinpalkovic force-pushed the valentin/fix-hmr-for-webpack-users branch from 289b99b to 5c0c277 Compare November 26, 2025 18:38
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: 2

🧹 Nitpick comments (1)
code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts (1)

62-65: Ensure early return if module-mocking is disabled.

Since all three module-mocking components are disabled (lines 45-55, 60, 65), the early return at lines 38-40 becomes the only functional guard. However, the function still continues to line 66, setting up empty plugin arrays unnecessarily.

Consider making the early return more explicit:

   // If there's no preview file, there's nothing to mock.
+  // Module-mocking is currently disabled in this preset.
   if (!previewConfigPath) {
     return config;
   }

-  config.plugins = config.plugins || [];
+  // config.plugins = config.plugins || [];

   // 1. Add the loader to normalize sb.mock(import(...)) calls.

Or remove the entire function body since it effectively does nothing:

 export async function webpackFinal(config: Configuration, options: Options) {
-  const previewConfigPath = findConfigFile('preview', options.configDir);
-
-  // If there's no preview file, there's nothing to mock.
-  if (!previewConfigPath) {
-    return config;
-  }
-
-  config.plugins = config.plugins || [];
-
-  // ... all commented code ...
-
+  // Module-mocking workflow temporarily disabled (see PR #33169)
   return config;
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c0c277 and 180015d.

📒 Files selected for processing (1)
  • code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx,mjs}

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

Use camelCase for variable and function names

Files:

  • code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Enable TypeScript strict mode
Export functions from modules for testing purposes

Files:

  • code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts
**/*.{ts,tsx,js,jsx,json,html,mjs}

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

**/*.{ts,tsx,js,jsx,json,html,mjs}: Use ESLint and Prettier for code style enforcement
Run 'yarn prettier --write ' to format code after making changes
Run 'yarn lint:js:cmd ' to check for ESLint issues after making changes

Files:

  • code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts
code/**/!(*.test).{ts,tsx,js,mjs}

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

code/**/!(*.test).{ts,tsx,js,mjs}: Use 'logger' from 'storybook/internal/node-logger' for server-side (Node.js) logging, not console.log/console.warn/console.error
Use 'logger' from 'storybook/internal/client-logger' for client-side (browser) logging, not console.log/console.warn/console.error
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-webpack5/src/presets/custom-webpack-preset.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.279Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid inline mock implementations within test cases in Vitest tests
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.279Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use `vi.mock()` with the `spy: true` option for all package and file mocks in Vitest tests
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.279Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid mocking without the `spy: true` option in Vitest tests
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.279Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use type-safe mocking with `vi.mocked()` in Vitest tests
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.279Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Mock all required dependencies that the test subject uses
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.279Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid direct function mocking without `vi.mocked()` in Vitest tests
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T17:49:47.659Z
Learning: Applies to **/*.test.{ts,tsx} : Use 'vi.mock()' to mock external dependencies like file system and loggers in unit tests
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.279Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid mocking only a subset of required dependencies in Vitest tests
📚 Learning: 2025-11-24T17:49:59.279Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.279Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid mocking only a subset of required dependencies in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts
📚 Learning: 2025-11-24T17:49:59.279Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.279Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid inline mock implementations within test cases in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts
📚 Learning: 2025-11-24T17:49:59.279Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.279Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid direct function mocking without `vi.mocked()` in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts
📚 Learning: 2025-11-24T17:49:59.279Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.279Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid mocking without the `spy: true` option in Vitest tests

Applied to files:

  • code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (1)
code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts (1)

12-13: Document the intentional temporary disable with clear rationale; mismatch between PR title ("improve performance") and actual changes (complete disable via revert) requires clarification.

The code currently disables the entire module-mocking workflow by commenting out all three components (loader, WebpackMockPlugin, and WebpackInjectMockerRuntimePlugin) in the webpack5 builder's custom preset. While the "Temporarily disabled" comments indicate this is intentional rather than accidental code, there's a significant mismatch:

  • PR title states: "Improve performance of module-mocking plugins"
  • Actual changes: Complete disable via revert (disables all plugin instantiation)

The plugin files themselves still exist with active code, and e2e tests for module-mocking still reference it, creating confusion about the intended end state. If this temporary disable is intentional:

  1. Add a detailed comment explaining why module-mocking is disabled (e.g., isolating a regression, feature rollback, testing baseline)
  2. Include a tracking issue or TODO linking to when/how this will be re-enabled
  3. Consider whether the PR title should reflect the actual changes ("Revert module-mocking integration" or similar)

If the plugins are meant to stay disabled, remove the orphaned code instead of leaving it commented.

Comment on lines +45 to +55
// Temporarily disabled: module-mocking transform
// config.module!.rules!.push({
// test: /preview\.(t|j)sx?$/,
// use: [
// {
// loader: fileURLToPath(
// import.meta.resolve('@storybook/builder-webpack5/loaders/storybook-mock-transform-loader')
// ),
// },
// ],
// });
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove commented code or add clear TODO with timeline.

Commented code blocks should not remain in the codebase without clear explanation. Either:

  • Remove this code entirely if the module-mocking workflow is permanently disabled in this preset
  • Add a TODO comment with context explaining why it's temporarily disabled, when it will be re-enabled, and under what conditions

Consider adding configuration to conditionally enable/disable module-mocking:

-  // Temporarily disabled: module-mocking transform
-  // config.module!.rules!.push({
-  //   test: /preview\.(t|j)sx?$/,
-  //   use: [
-  //     {
-  //       loader: fileURLToPath(
-  //         import.meta.resolve('@storybook/builder-webpack5/loaders/storybook-mock-transform-loader')
-  //       ),
-  //     },
-  //   ],
-  // });
+  // TODO: Re-enable module-mocking after performance testing (see PR #33169)
+  // const enableModuleMocking = options.features?.moduleMocking ?? false;
+  // if (enableModuleMocking) {
+  //   config.module!.rules!.push({
+  //     test: /preview\.(t|j)sx?$/,
+  //     use: [
+  //       {
+  //         loader: fileURLToPath(
+  //           import.meta.resolve('@storybook/builder-webpack5/loaders/storybook-mock-transform-loader')
+  //         ),
+  //       },
+  //     ],
+  //   });
+  // }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Temporarily disabled: module-mocking transform
// config.module!.rules!.push({
// test: /preview\.(t|j)sx?$/,
// use: [
// {
// loader: fileURLToPath(
// import.meta.resolve('@storybook/builder-webpack5/loaders/storybook-mock-transform-loader')
// ),
// },
// ],
// });
// TODO: Re-enable module-mocking after performance testing (see PR #33169)
// const enableModuleMocking = options.features?.moduleMocking ?? false;
// if (enableModuleMocking) {
// config.module!.rules!.push({
// test: /preview\.(t|j)sx?$/,
// use: [
// {
// loader: fileURLToPath(
// import.meta.resolve('@storybook/builder-webpack5/loaders/storybook-mock-transform-loader')
// ),
// },
// ],
// });
// }
🤖 Prompt for AI Agents
In code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts around
lines 45 to 55, there is a commented-out module-mocking transform block left
without explanation; either remove the dead code or replace it with a clear TODO
that states why it’s disabled, the expected re-enable date/conditions, and
owner, or implement a config flag to enable/disable it at runtime (e.g., read a
preset option or env var) and wrap the loader registration in that conditional
so the intent and lifecycle are explicit.

Comment on lines 58 to +60
// This plugin scans the preview file and sets up rules to swap modules.
config.plugins.push(new WebpackMockPlugin({ previewConfigPath }));
// Temporarily disabled: module-mocking plugin
// config.plugins.push(new WebpackMockPlugin({ previewConfigPath }));
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all webpack preset files
echo "=== Finding all preset files in webpack5 builder ==="
fd -t f "preset" code/builders/builder-webpack5/src --type ts --type js

echo ""
echo "=== Listing presets directory ==="
ls -la code/builders/builder-webpack5/src/presets/ 2>/dev/null || echo "Directory structure check"

Repository: storybookjs/storybook

Length of output: 591


🏁 Script executed:

#!/bin/bash
# Find all preset files with correct syntax
echo "=== Finding preset files ==="
fd -e ts -e js "preset" code/builders/builder-webpack5/src

echo ""
echo "=== Checking WebpackMockPlugin usage in presets ==="
rg -n "WebpackMockPlugin|WebpackInjectMockerRuntimePlugin" code/builders/builder-webpack5/src/presets/ -A 2 -B 2

echo ""
echo "=== All plugin references in the builder ==="
rg -n "WebpackMockPlugin|WebpackInjectMockerRuntimePlugin" code/builders/builder-webpack5/src --type ts --type js -B 2 -A 2

Repository: storybookjs/storybook

Length of output: 7748


🏁 Script executed:

#!/bin/bash
# Check preview-preset.ts content
echo "=== Content of preview-preset.ts ==="
cat -n code/builders/builder-webpack5/src/presets/preview-preset.ts

echo ""
echo "=== Check for plugin usage in preview-preset ==="
rg -n "WebpackMockPlugin|WebpackInjectMockerRuntimePlugin" code/builders/builder-webpack5/src/presets/preview-preset.ts -B 3 -A 3

echo ""
echo "=== Search for TODO or issue refs related to these plugins ==="
rg -n "TODO|FIXME|disabled|temporarily" code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts

Repository: storybookjs/storybook

Length of output: 1503


🏁 Script executed:

#!/bin/bash
echo "=== Full custom-webpack-preset.ts ==="
cat -n code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts

Repository: storybookjs/storybook

Length of output: 4257


🏁 Script executed:

#!/bin/bash
echo "=== Checking WebpackMockPlugin for mtime improvements ==="
rg -n "mtime|lastPreviewMtime|invalidat" code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts -B 2 -A 2

echo ""
echo "=== Checking full WebpackMockPlugin implementation ==="
wc -l code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts

echo ""
echo "=== Key parts of WebpackMockPlugin ==="
head -60 code/builders/builder-webpack5/src/plugins/webpack-mock-plugin.ts | tail -20

Repository: storybookjs/storybook

Length of output: 1941


Perfect. I've now gathered all the necessary information to verify the review comment. Let me provide the final rewritten comment:


Document the relationship between plugin improvements and disabling.

The PR improves WebpackMockPlugin performance with mtime-based invalidation (lines 54, 71–87 in webpack-mock-plugin.ts) and candidate tracking, yet the entire module-mocking feature remains disabled in custom-webpack-preset.ts with "Temporarily disabled" comments (lines 45–65). This creates a disconnect:

  • Only 2 webpack presets exist; preview-preset.ts doesn't use these plugins
  • custom-webpack-preset.ts is the only preset that should enable them
  • Improvements won't be utilized while disabled

Clarify: Are these optimizations ready for re-enablement, or is there a specific blocker? If this is work-in-progress, consider adding a GitHub issue reference or timeline for completion.

🤖 Prompt for AI Agents
In code/builders/builder-webpack5/src/presets/custom-webpack-preset.ts around
lines 58 to 60, the module-mocking plugin is disabled but related performance
improvements exist in webpack-mock-plugin.ts; update the file to document the
current state and next steps by either re-enabling the plugin if the mtime-based
invalidation and candidate tracking are ready, or adding a clear TODO comment
and a GitHub issue/timeline reference that explains the blocker and when it will
be re-enabled; ensure the comment clarifies that this preset is the only place
the plugin would be used and that preview-preset.ts does not apply, so reviewers
know whether to expect the improvements to be active or tracked elsewhere.

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.

3 participants