Skip to content

CSF-Factories: Fix codemod for preview files without exports#33673

Merged
kasperpeulen merged 6 commits into
nextfrom
fix-csf-factory-preview-no-exports
Jan 28, 2026
Merged

CSF-Factories: Fix codemod for preview files without exports#33673
kasperpeulen merged 6 commits into
nextfrom
fix-csf-factory-preview-no-exports

Conversation

@kasperpeulen
Copy link
Copy Markdown
Member

@kasperpeulen kasperpeulen commented Jan 27, 2026

Closes #33640

What I did

Fixed the CSF factories codemod to handle preview files that have no exports (only side-effect imports or empty files).

When a preview file only has import './preview.scss' or is empty, the codemod now correctly adds export default definePreview({}).

Problem: Story files using CSF factories import from preview:

import preview from '../.storybook/preview'

But if the preview file only has side-effect imports or is empty, the codemod wouldn't add a default export, causing broken imports.

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

Caution

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

No manual testing necessary - the fix is fully covered by the 3 new unit tests:

  • Empty preview file
  • Preview with single side-effect import
  • Preview with multiple side-effect imports

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

  • Bug Fixes

    • Preview configuration files that are empty or contain only side-effect imports now receive a generated default export so previews work correctly.
    • Existing behavior preserved: previews that re-export symbols are not altered.
  • Tests

    • Added tests covering empty, side-effect-only, multiple-import, and re-export preview scenarios, including import ordering.

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

…ly preview files

Reproduces issue #33640: When a preview file only has side-effect imports
(like `import './preview.scss'`) or is empty, the codemod doesn't add a
default export, which breaks story files that import from preview.
When a preview file only has side-effect imports (like `import './preview.scss'`)
or is empty, the codemod now correctly adds `export default definePreview({})`.

This is needed because story files using CSF factories import from preview,
so the preview file must have a default export.

Fixes #33640
@kasperpeulen kasperpeulen changed the title CSF Factories: Fix codemod for preview files without exports CSF-Factories: Fix codemod for preview files without exports Jan 27, 2026
@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Jan 27, 2026

View your CI Pipeline Execution ↗ for commit b9b7036

Command Status Duration Result
nx run-many -t compile,check,knip,test,pretty-d... ✅ Succeeded 7m 54s View ↗

☁️ Nx Cloud last updated this comment at 2026-01-28 09:25:02 UTC

@kasperpeulen kasperpeulen requested a review from shilman January 27, 2026 08:25
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 27, 2026

📝 Walkthrough

Walkthrough

Adds handling for preview config files with no exports (empty or only side-effect imports) to the CSF factory codemod by injecting a definePreview import and export default definePreview({}); tests for these cases and for the re-export exclusion were added.

Changes

Cohort / File(s) Summary
CSF Factory Codemod - Preview branch
code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts
Added an else if (configType === 'preview') path that, when no exports are present (empty file or only side-effect imports), inserts import { definePreview } ... and export default definePreview({}).
CSF Factory Codemod Tests
code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.test.ts
Added tests verifying: default export inserted for empty previews; for single and multiple side-effect imports (import order preserved); and no default export when the preview re-exports symbols.

Sequence Diagram(s)

(Skipped — change is a codemod/local transformation, not a multi-component runtime flow.)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@storybook-app-bot
Copy link
Copy Markdown

storybook-app-bot Bot commented Jan 27, 2026

Package Benchmarks

Commit: b9b7036, ran on 30 January 2026 at 16:54:20 UTC

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

@storybook/cli

Before After Difference
Dependency count 183 183 0
Self size 776 KB 776 KB 0 B
Dependency size 67.57 MB 67.56 MB 🎉 -10 KB 🎉
Bundle Size Analyzer Link Link

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts`:
- Around line 168-182: The preview-case branch currently always appends an
export default defineConfig call when configType === 'preview', which can create
duplicate exports for files that actually contain exports (e.g., re-exports);
update the guard to only run this branch when there are truly no export
declarations by checking exportDecls.length === 0 (or equivalent) before
creating defineConfigCall and pushing exportDefaultDeclaration onto
programNode.body (referencing configType, exportDecls, methodName,
defineConfigCall, and programNode).

Comment thread code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts
Copy link
Copy Markdown
Member

@shilman shilman left a comment

Choose a reason for hiding this comment

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

A few minor comments but otherwise LGTM

Comment thread code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts
Comment thread code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts
…s with re-exports

This addresses the CodeRabbit review comment. The original code would add
`export default definePreview({})` for any preview file that didn't have
a default export or recognized named exports, which could cause duplicate
export errors for files with re-exports like `export { x } from './module'`.

The fix checks if the AST has any exports at all (including re-exports
and `export *` declarations) before adding the default export.
…iew files with re-exports"

This reverts commit 1aec190.
@kasperpeulen
Copy link
Copy Markdown
Member Author

Addressed the review comments:

@coderabbitai's suggestion: After further analysis, the original code is correct. The CodeRabbit suggestion was based on a misunderstanding:

  • Re-exports like export { withTheme } from './decorators' are named exports, not default exports
  • CSF factories require story files to import from preview: import preview from '../.storybook/preview'
  • This means the preview file must have a default export
  • So adding export default definePreview({}) when there are only re-exports is the correct behavior, not a duplicate export issue

@shilman's questions:

  1. "Do we need to handle this case for any other configs?" - No, only preview files need this. For main files, an empty file without exports doesn't make sense because main.ts/js must configure the framework, stories, etc.

  2. "Is this needed?" (referring to CodeRabbit's suggestion) - No, the suggestion is not needed. The original PR code is correct.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
code/lib/cli-storybook/src/codemod/helpers/config-to-csf-factory.ts (1)

196-227: Minor: Import is added unconditionally, even when unused.

When a preview file contains only re-exports (handled by the hasAnyExports check preventing Scenario 4), the definePreview import is still added but never used. This would trigger linting warnings.

Consider tracking whether a defineConfigCall was created and conditionally adding the import.

🔧 Suggested approach
+  let needsDefineImport = false;
+
   if (config._exportsObject && hasNamedExports) {
+    needsDefineImport = true;
     // ... existing code
   } else if (config._exportsObject) {
+    needsDefineImport = true;
     // ... existing code
   } else if (hasNamedExports) {
+    needsDefineImport = true;
     // ... existing code
   } else if (configType === 'preview') {
     // ...
     if (!hasAnyExports) {
+      needsDefineImport = true;
       // ... existing code
     }
   }

-  const configImport = t.importDeclaration(
+  if (!needsDefineImport) {
+    // Skip import if defineMain/definePreview wasn't used
+    const output = printConfig(config).code;
+    // ... rest of output handling
+  }
+
+  const configImport = t.importDeclaration(

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 28, 2026

Tip

For best results, initiate chat on the files or code changes.

@kasperpeulen, thank you for the detailed clarification! You're absolutely right, and I apologize for the confusion in my earlier review comment.

I now understand that:

  • Named exports (including re-exports like export { withTheme } from './decorators') do not provide a default export
  • Story files require import preview from '../.storybook/preview', which specifically needs a default export
  • Therefore, adding export default definePreview({}) when there are only named exports is indeed the correct behavior

Your explanation about why this handling is only needed for preview files (not main files) also makes sense. Thank you for taking the time to clarify this behavior!


✏️ Learnings added
Learnt from: kasperpeulen
URL: https://github.com/storybookjs/storybook/pull/33673

Timestamp: 2026-01-28T09:16:24.144Z
Learning: In Storybook CSF factories codemod, preview files with only named exports (including re-exports like `export { withTheme } from './decorators'`) still require a default export to be added via `export default definePreview({})`, because story files import the preview default with `import preview from '../.storybook/preview'`. Named exports do not count as default exports, so the codemod correctly adds a default export even when re-exports are present.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: ndelangen
Repo: storybookjs/storybook PR: 32507
File: code/core/src/manager/globals/globals-module-info.ts:25-33
Timestamp: 2025-09-24T09:39:39.233Z
Learning: In Storybook, storybook/actions/decorator is a preview-only entrypoint and should not be included in manager globals configuration. The duplicatedKeys array in code/core/src/manager/globals/globals-module-info.ts is specifically for manager-side externalization, not preview entrypoints.

@kasperpeulen kasperpeulen merged commit 0d867e4 into next Jan 28, 2026
123 checks passed
@kasperpeulen kasperpeulen deleted the fix-csf-factory-preview-no-exports branch January 28, 2026 13:53
@github-actions github-actions Bot mentioned this pull request Jan 28, 2026
24 tasks
@valentinpalkovic valentinpalkovic restored the fix-csf-factory-preview-no-exports branch January 30, 2026 07:50
@kasperpeulen kasperpeulen added the patch:yes Bugfix & documentation PR that need to be picked to main branch label Jan 30, 2026
@github-actions github-actions Bot mentioned this pull request Feb 2, 2026
11 tasks
valentinpalkovic pushed a commit that referenced this pull request Feb 2, 2026
…-exports

CSF-Factories: Fix codemod for preview files without exports
(cherry picked from commit 0d867e4)
@github-actions github-actions Bot mentioned this pull request Feb 2, 2026
13 tasks
@github-actions github-actions Bot added the patch:done Patch/release PRs already cherry-picked to main/release branch label Feb 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug ci:normal csf factories patch:done Patch/release PRs already cherry-picked to main/release branch patch:yes Bugfix & documentation PR that need to be picked to main branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Followup CSF factories: codemod generates broken code in projects where the preview file doesn't have exports

2 participants