Skip to content

Maintenance: extract createPortableStoriesImpl to eliminate nextjs/nextjs-vite portable-stories duplication#34474

Closed
mixelburg wants to merge 1 commit into
storybookjs:nextfrom
mixelburg:fix/nextjs-portable-stories-dedup
Closed

Maintenance: extract createPortableStoriesImpl to eliminate nextjs/nextjs-vite portable-stories duplication#34474
mixelburg wants to merge 1 commit into
storybookjs:nextfrom
mixelburg:fix/nextjs-portable-stories-dedup

Conversation

@mixelburg
Copy link
Copy Markdown

@mixelburg mixelburg commented Apr 6, 2026

Closes #34470

What I did

portable-stories.ts was duplicated almost entirely between @storybook/nextjs and @storybook/nextjs-vite. The two files were 143 lines long and differed only in 3 JSDoc comment lines referencing the package name in usage examples.

Created createPortableStoriesImpl(nextJsAnnotations) factory in code/frameworks/nextjs/src/portable-stories-impl.ts. Both frameworks now call this factory with their own ./preview.tsx annotations and re-wrap each function with their package-specific JSDoc.

Changes:

  • New code/frameworks/nextjs/src/portable-stories-impl.ts — shared implementation factory
  • code/frameworks/nextjs/src/portable-stories.ts — thin wrapper with @storybook/nextjs JSDoc
  • code/frameworks/nextjs-vite/src/portable-stories.ts — thin wrapper with @storybook/nextjs-vite JSDoc
  • Added ./portable-stories-impl export entry to @storybook/nextjs
  • Added @storybook/nextjs: workspace:* to @storybook/nextjs-vite dependencies

Runtime behavior is unchanged — each package still uses its own preview.tsx to build INTERNAL_DEFAULT_PROJECT_ANNOTATIONS, and all public APIs have identical type signatures.

Checklist for Contributors

Testing

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

Manual testing

This is a pure refactor — no behavior changes. Tested by reviewing that the public API types and exports remain identical.

Summary by CodeRabbit

  • Chores
    • Updated dependencies for NextJS-Vite framework integration.
    • Reorganized portable stories implementation in NextJS framework.
    • Added new export entry for portable stories module to package exports.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 6, 2026

📝 Walkthrough

Walkthrough

A refactoring extracts Storybook portable stories composition logic into a shared createPortableStoriesImpl factory function, which is used by both the Next.js and Next.js + Vite framework implementations. The new module encapsulates default project annotation composition and provides delegated helper functions for setting annotations and composing stories. The refactored files now delegate to this shared implementation rather than managing composition logic directly.

Changes

Cohort / File(s) Summary
Next.js Vite Framework
code/frameworks/nextjs-vite/package.json, code/frameworks/nextjs-vite/src/portable-stories.ts
Added @storybook/nextjs workspace dependency and refactored portable stories to delegate composition logic to createPortableStoriesImpl instead of importing directly from preview-api.
Next.js Framework Core
code/frameworks/nextjs/package.json
Added new exports entry for ./portable-stories-impl subpath, mapping types and source to the new portable stories implementation module.
Next.js Framework Implementation
code/frameworks/nextjs/src/portable-stories-impl.ts
New module exporting createPortableStoriesImpl factory function that constructs and returns composition helpers (setProjectAnnotations, composeStory, composeStories) with encapsulated default project annotations for Next.js.
Next.js Framework Refactoring
code/frameworks/nextjs/src/portable-stories.ts
Refactored to delegate composition logic to createPortableStoriesImpl, removing direct preview-api imports and local annotation composition. Clarified composeStories return type and re-exports INTERNAL_DEFAULT_PROJECT_ANNOTATIONS.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes


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

🧹 Nitpick comments (2)
code/frameworks/nextjs/src/portable-stories-impl.ts (1)

22-23: Cross-package relative import is fragile but intentional.

This relative path traverses outside the package boundary into renderers/react. While this is documented as intentional for prebundling/ESM compatibility, it creates a coupling that could break if the directory structure changes. Consider documenting this dependency in a comment or the package README to prevent accidental breakage during future refactors.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/frameworks/nextjs/src/portable-stories-impl.ts` around lines 22 - 23,
The relative import of INTERNAL_DEFAULT_PROJECT_ANNOTATIONS from
'../../../renderers/react/src/portable-stories.tsx' is intentionally fragile for
prebundling/ESM reasons; update the import site (portable-stories-impl.ts) to
add a clear comment documenting why this cross-package relative import is
required, warn maintainers that moving or renaming the renderers/react directory
will break it, and add a note in the package README (or a short TODO with a link
to README) explaining the dependency and how to replace it if the repo layout
changes so future refactors do not accidentally remove or relocate
INTERNAL_DEFAULT_PROJECT_ANNOTATIONS.
code/frameworks/nextjs-vite/src/portable-stories.ts (1)

14-18: Clean delegation to shared implementation.

The import correctly uses the package specifier @storybook/nextjs/portable-stories-impl and provides the local ./preview.tsx annotations, ensuring nextjs-vite-specific configuration is applied.

Note: Unlike @storybook/nextjs, this file does not re-export INTERNAL_DEFAULT_PROJECT_ANNOTATIONS. If any consumers need access to it from @storybook/nextjs-vite, consider adding the export for API parity:

-const { setProjectAnnotations: _setProjectAnnotations, composeStory: _composeStory, composeStories: _composeStories } =
+const { setProjectAnnotations: _setProjectAnnotations, composeStory: _composeStory, composeStories: _composeStories, INTERNAL_DEFAULT_PROJECT_ANNOTATIONS } =
   createPortableStoriesImpl(nextJsAnnotations);
+
+export { INTERNAL_DEFAULT_PROJECT_ANNOTATIONS };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/frameworks/nextjs-vite/src/portable-stories.ts` around lines 14 - 18,
The file delegates to createPortableStoriesImpl correctly but does not re-export
INTERNAL_DEFAULT_PROJECT_ANNOTATIONS for API parity with `@storybook/nextjs`;
update portable-stories.ts to re-export INTERNAL_DEFAULT_PROJECT_ANNOTATIONS by
importing it from '@storybook/nextjs/portable-stories-impl' (or from the created
impl) and adding an export alongside the existing delegated symbols
(_setProjectAnnotations/_composeStory/_composeStories) so consumers of
`@storybook/nextjs-vite` can access INTERNAL_DEFAULT_PROJECT_ANNOTATIONS.
🤖 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/frameworks/nextjs/package.json`:
- Around line 68-72: Add a build entry for the public export declared in
package.json by updating the browser build array in build-config.ts: add an
object with exportEntries containing './portable-stories-impl' and entryPoint
set to './src/portable-stories-impl.ts' so the build emits
dist/portable-stories-impl.js; locate the browser array in
code/frameworks/nextjs/build-config.ts and append the new entry object matching
the pattern used for other entries.

---

Nitpick comments:
In `@code/frameworks/nextjs-vite/src/portable-stories.ts`:
- Around line 14-18: The file delegates to createPortableStoriesImpl correctly
but does not re-export INTERNAL_DEFAULT_PROJECT_ANNOTATIONS for API parity with
`@storybook/nextjs`; update portable-stories.ts to re-export
INTERNAL_DEFAULT_PROJECT_ANNOTATIONS by importing it from
'@storybook/nextjs/portable-stories-impl' (or from the created impl) and adding
an export alongside the existing delegated symbols
(_setProjectAnnotations/_composeStory/_composeStories) so consumers of
`@storybook/nextjs-vite` can access INTERNAL_DEFAULT_PROJECT_ANNOTATIONS.

In `@code/frameworks/nextjs/src/portable-stories-impl.ts`:
- Around line 22-23: The relative import of INTERNAL_DEFAULT_PROJECT_ANNOTATIONS
from '../../../renderers/react/src/portable-stories.tsx' is intentionally
fragile for prebundling/ESM reasons; update the import site
(portable-stories-impl.ts) to add a clear comment documenting why this
cross-package relative import is required, warn maintainers that moving or
renaming the renderers/react directory will break it, and add a note in the
package README (or a short TODO with a link to README) explaining the dependency
and how to replace it if the repo layout changes so future refactors do not
accidentally remove or relocate INTERNAL_DEFAULT_PROJECT_ANNOTATIONS.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f212974f-7864-45b0-88b3-3810c0fb0994

📥 Commits

Reviewing files that changed from the base of the PR and between 75fa10a and 6ab7808.

📒 Files selected for processing (5)
  • code/frameworks/nextjs-vite/package.json
  • code/frameworks/nextjs-vite/src/portable-stories.ts
  • code/frameworks/nextjs/package.json
  • code/frameworks/nextjs/src/portable-stories-impl.ts
  • code/frameworks/nextjs/src/portable-stories.ts

Comment on lines +68 to +72
"./portable-stories-impl": {
"types": "./dist/portable-stories-impl.d.ts",
"code": "./src/portable-stories-impl.ts",
"default": "./dist/portable-stories-impl.js"
},
Copy link
Copy Markdown
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:

#!/bin/bash
# Description: Verify that portable-stories-impl is not registered in build-config.ts

echo "=== Checking build-config.ts for portable-stories-impl entry ==="
rg -n 'portable-stories-impl' code/frameworks/nextjs/build-config.ts || echo "Entry NOT found in build-config.ts - needs to be added"

Repository: storybookjs/storybook

Length of output: 184


🏁 Script executed:

# Check build-config.ts structure to understand entry points
head -100 code/frameworks/nextjs/build-config.ts

Repository: storybookjs/storybook

Length of output: 2678


🏁 Script executed:

# Check if portable-stories-impl.ts file exists
ls -la code/frameworks/nextjs/src/ | grep -i portable || echo "portable-stories-impl not found in src"

Repository: storybookjs/storybook

Length of output: 209


🏁 Script executed:

# Search for all references to portable-stories-impl in the codebase
rg -l 'portable-stories-impl' code/frameworks/nextjs/ 2>/dev/null || echo "No references found"

Repository: storybookjs/storybook

Length of output: 147


🏁 Script executed:

# Check how portable-stories-impl is used in portable-stories.ts
cat code/frameworks/nextjs/src/portable-stories.ts | head -50

Repository: storybookjs/storybook

Length of output: 1723


🏁 Script executed:

# Check the entire build-config.ts to see if there's any fallback or alternative mechanism
wc -l code/frameworks/nextjs/build-config.ts
tail -20 code/frameworks/nextjs/build-config.ts

Repository: storybookjs/storybook

Length of output: 600


🏁 Script executed:

# Check if there are any imports of the public export `@storybook/nextjs/portable-stories-impl`
rg '@storybook/nextjs/portable-stories-impl' code/ --type ts --type tsx 2>/dev/null | head -20

Repository: storybookjs/storybook

Length of output: 47


🏁 Script executed:

# Also check package.json more broadly to see context around this export
sed -n '60,80p' code/frameworks/nextjs/package.json

Repository: storybookjs/storybook

Length of output: 825


Missing build entry point will cause runtime errors when importing the public export.

The ./portable-stories-impl export is declared in package.json (pointing to ./dist/portable-stories-impl.js) but is not registered in code/frameworks/nextjs/build-config.ts. The corresponding dist file will not be generated during the build process, causing MODULE_NOT_FOUND errors at runtime when external consumers import from @storybook/nextjs/portable-stories-impl.

Add the entry point to code/frameworks/nextjs/build-config.ts:

Suggested addition to the browser array:
{
  exportEntries: ['./portable-stories-impl'],
  entryPoint: './src/portable-stories-impl.ts',
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/frameworks/nextjs/package.json` around lines 68 - 72, Add a build entry
for the public export declared in package.json by updating the browser build
array in build-config.ts: add an object with exportEntries containing
'./portable-stories-impl' and entryPoint set to './src/portable-stories-impl.ts'
so the build emits dist/portable-stories-impl.js; locate the browser array in
code/frameworks/nextjs/build-config.ts and append the new entry object matching
the pattern used for other entries.

@valentinpalkovic
Copy link
Copy Markdown
Contributor

Closing due to #34453 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Duplicate Code: Near-Identical portable-stories.ts in nextjs and nextjs-vite Frameworks

2 participants