Skip to content

Comments

Builder-Vite: prevent postcss plugin duplication from enforce-output-dir config hook#33883

Open
Copilot wants to merge 2 commits intonextfrom
copilot/fix-duplicate-postcss-plugins
Open

Builder-Vite: prevent postcss plugin duplication from enforce-output-dir config hook#33883
Copilot wants to merge 2 commits intonextfrom
copilot/fix-duplicate-postcss-plugins

Conversation

Copy link
Contributor

Copilot AI commented Feb 20, 2026

The storybook:enforce-output-dir plugin's config hook was spreading the entire incoming Vite config ({ ...config, build: { outDir } }). Since Vite's mergeConfig concatenates arrays when merging plugin hook results back into the base config, any array-valued fields — including css.postcss.plugins — were duplicated on each resolution cycle, causing PostCSS plugins to run twice.

Changes

  • build.ts: Return only the partial config needed from the config hook instead of spreading the full config:
// Before — spreads full config, causing array fields like css.postcss.plugins to double
config: (config) => ({ ...config, build: { outDir: options.outputDir } }),

// After — returns only what needs to change; Vite merges the rest
config: () => ({ build: { outDir: options.outputDir } }),
  • build.test.ts: Added regression tests verifying that mergeConfig does not duplicate postcss plugins when the config hook returns a partial config, and that the old spread behavior provably caused the duplication.
Original prompt

This section details on the original issue you should resolve

<issue_title>[Bug]: @storybook/builder-vite duplicates postcss plugins during config resolution</issue_title>
<issue_description>### Describe the bug

In Storybook using @storybook/react-vite, the Vite config resolution process during build duplicates the viteConfig.css.postcss.plugins array. As a result, PostCSS plugins are applied twice to the same CSS, causing transformations to be double-applied, and leading to, for example, postcss-prefixwrap now producing .scope .scope .selector, which is obviously a breaking bug.

This started after Storybook introduced the storybook:enforce-output-dir Vite plugin (PR #33740), which adds config hooks (config / configEnvironment) and triggers an additional config merge where the PostCSS plugins array becomes duplicated

Image Image

Reproduction link

https://69971850cc8d9694b2603cf1-mhkdpdawpi.chromatic.com

Reproduction steps

  1. Clone https://github.com/anders-cognite/storybook-vite-postcss-plugins-duplicated-repro
  2. Run the build-storybook command.
  3. Inspect the generated CSS output.

Expected Behavior:
CSS selectors should be prefixed with a single .hello-world, due to the PostCSS PrefixWrap('.hello-world') plugin defined in vite.config.ts.

Actual Behavior:
The PostCSS plugin processes the CSS twice due to array duplication, leading to a double .hello-world .hello-world prefix.

Reproduction link is to the chromatic build.

System

System:
│  OS: Linux 6.6 Ubuntu 24.04.3 LTS 24.04.3 LTS (Noble Numbat)
│  CPU: (16) x64 Intel(R) Core(TM) Ultra 7 265H
│  Shell: 5.9 - /usr/bin/zsh
│  Binaries:
│  Node: 24.11.1 - /run/user/1000/fnm_multishells/217352_1771508128317/bin/node
│  npm: 11.6.2 - /run/user/1000/fnm_multishells/217352_1771508128317/bin/npm
│  pnpm: 10.24.0 - /run/user/1000/fnm_multishells/217352_1771508128317/bin/pnpm
│  <----- active
│  npmPackages:
│  @storybook/react-vite: ^10.2.10 => 10.2.10
│  chromatic: ^15.1.1 => 15.1.1
│  eslint-plugin-storybook: ^10.2.10 => 10.2.10
│  storybook: ^10.2.10 => 10.2.10

Additional context

No response</issue_description>

<agent_instructions>Verify the solution by adding a template story and adjusting the vite config in one of the sandboxes. Make a screenshot from the play function which verifies the css classname only appended once</agent_instructions>

Comments on the Issue (you are @copilot in this section)


✨ 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

  • Bug Fixes

    • Fixed a configuration merging issue in the Vite builder plugin that resulted in duplicate config properties. Partial plugin configurations are now properly handled without unintended duplication during config merge operations.
  • Tests

    • Added regression tests for the plugin's configuration hook behavior to validate proper handling of partial configuration objects and merge scenarios.

…revent postcss plugin duplication

Co-authored-by: valentinpalkovic <5889929+valentinpalkovic@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix duplication of PostCSS plugins in Vite config fix(builder-vite): prevent postcss plugin duplication from enforce-output-dir config hook Feb 20, 2026
@valentinpalkovic valentinpalkovic marked this pull request as ready for review February 20, 2026 11:37
@valentinpalkovic valentinpalkovic changed the title fix(builder-vite): prevent postcss plugin duplication from enforce-output-dir config hook Builder-Vite: prevent postcss plugin duplication from enforce-output-dir config hook Feb 20, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 20, 2026

Fails
🚫 PR title must be in the format of "Area: Summary", With both Area and Summary starting with a capital letter Good examples: - "Docs: Describe Canvas Doc Block" - "Svelte: Support Svelte v4" Bad examples: - "add new api docs" - "fix: Svelte 4 support" - "Vue: improve docs"
🚫 PR description is missing the mandatory "#### Manual testing" section. Please add it so that reviewers know how to manually test your changes.

Generated by 🚫 dangerJS against 7109a11

@nx-cloud
Copy link

nx-cloud bot commented Feb 20, 2026

View your CI Pipeline Execution ↗ for commit 7109a11

Command Status Duration Result
nx run-many -t compile,check,knip,test,pretty-d... ❌ Failed 11m 26s View ↗

☁️ Nx Cloud last updated this comment at 2026-02-20 11:50:48 UTC

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 20, 2026

📝 Walkthrough

Walkthrough

A regression test file is introduced to verify the storybook:enforce-output-dir Vite plugin's behavior when merging partial versus complete configurations, alongside a modification to the plugin's config hook to return only the build.outDir property instead of spreading the entire config object.

Changes

Cohort / File(s) Summary
Test Suite
code/builders/builder-vite/src/build.test.ts
New regression test file with two test cases verifying Vite config merging behavior: one validating that a partial config return preserves existing plugins, and another demonstrating the duplication issue when the entire config is spread.
Plugin Configuration
code/builders/builder-vite/src/build.ts
Modified the config hook callback to return only { build: { outDir } } instead of { ...config, build: { outDir } }, removing the spread of prior config properties.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code/builders/builder-vite/src/build.test.ts`:
- Around line 5-14: Update the placeholder issue reference in the top-of-file
comment so it points to the real issue number: replace "issues/XXXX" with
"issues/33874" (i.e., reference "#33874") in the block that documents the
regression for the storybook:enforce-output-dir plugin's config hook; keep the
rest of the explanatory text (about returning partial config and
css.postcss.plugins duplication) unchanged.

Comment on lines +5 to +14
/**
* Regression test for: https://github.com/storybookjs/storybook/issues/XXXX
*
* The storybook:enforce-output-dir plugin's config hook was previously returning the entire
* incoming config spread ({ ...config, build: { outDir } }). Since Vite merges plugin config
* hook results back into the base config by concatenating arrays, this caused
* css.postcss.plugins (and other array fields) to be duplicated on every resolution cycle.
*
* The fix: return only the partial config needed ({ build: { outDir } }).
*/
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

Update placeholder issue number to actual issue reference.

The comment references issues/XXXX but should reference the actual issue #33874 that this PR fixes.

📝 Suggested fix
 /**
- * Regression test for: https://github.com/storybookjs/storybook/issues/XXXX
+ * Regression test for: https://github.com/storybookjs/storybook/issues/33874
  *
📝 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
/**
* Regression test for: https://github.com/storybookjs/storybook/issues/XXXX
*
* The storybook:enforce-output-dir plugin's config hook was previously returning the entire
* incoming config spread ({ ...config, build: { outDir } }). Since Vite merges plugin config
* hook results back into the base config by concatenating arrays, this caused
* css.postcss.plugins (and other array fields) to be duplicated on every resolution cycle.
*
* The fix: return only the partial config needed ({ build: { outDir } }).
*/
/**
* Regression test for: https://github.com/storybookjs/storybook/issues/33874
*
* The storybook:enforce-output-dir plugin's config hook was previously returning the entire
* incoming config spread ({ ...config, build: { outDir } }). Since Vite merges plugin config
* hook results back into the base config by concatenating arrays, this caused
* css.postcss.plugins (and other array fields) to be duplicated on every resolution cycle.
*
* The fix: return only the partial config needed ({ build: { outDir } }).
*/
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/builders/builder-vite/src/build.test.ts` around lines 5 - 14, Update the
placeholder issue reference in the top-of-file comment so it points to the real
issue number: replace "issues/XXXX" with "issues/33874" (i.e., reference
"#33874") in the block that documents the regression for the
storybook:enforce-output-dir plugin's config hook; keep the rest of the
explanatory text (about returning partial config and css.postcss.plugins
duplication) unchanged.

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.

[Bug]: @storybook/builder-vite duplicates postcss plugins during config resolution

2 participants