Skip to content

Storybook for ReactNative overhaul#34333

Draft
ndelangen wants to merge 5 commits intonextfrom
norbert/rn-init-overhaul
Draft

Storybook for ReactNative overhaul#34333
ndelangen wants to merge 5 commits intonextfrom
norbert/rn-init-overhaul

Conversation

@ndelangen
Copy link
Copy Markdown
Member

@ndelangen ndelangen commented Mar 26, 2026

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

Caution

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-34333-sha-6f6a0f1a. Try it out in a new sandbox by running npx storybook@0.0.0-pr-34333-sha-6f6a0f1a sandbox or in an existing project with npx storybook@0.0.0-pr-34333-sha-6f6a0f1a upgrade.

More information
Published version 0.0.0-pr-34333-sha-6f6a0f1a
Triggered by @ndelangen
Repository storybookjs/storybook
Branch norbert/rn-init-overhaul
Commit 6f6a0f1a
Datetime Thu Mar 26 10:34:16 UTC 2026 (1774521256)
Workflow run 23589755833

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

Summary by CodeRabbit

  • New Features

    • Added React Native support to Storybook setup, including automatic entrypoint generation for .rnstorybook directory
    • Automated Metro configuration updates with fallback guidance
    • Auto-generation of platform-specific run scripts for iOS and Android with environment setup
  • Tests

    • Added comprehensive test coverage for React Native generator, entrypoint generation, Metro config automation, and script derivation

@ndelangen ndelangen marked this pull request as draft March 26, 2026 08:57
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 26, 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"

Generated by 🚫 dangerJS against b818039

@nx-cloud
Copy link
Copy Markdown

nx-cloud bot commented Mar 26, 2026

View your CI Pipeline Execution ↗ for commit b818039

Command Status Duration Result
nx run-many -t compile,check,knip,test,lint,fmt... ✅ Succeeded 7m 29s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-27 15:09:05 UTC

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 26, 2026

📝 Walkthrough

Walkthrough

This PR adds comprehensive React Native Storybook support, including entrypoint file generation, platform-specific npm script derivation for iOS and Android, automated Metro configuration patching with AST transformation and fallback mechanisms, and integration into the React Native generator module with coordinated orchestration and messaging.

Changes

Cohort / File(s) Summary
React Native Entrypoint Generation
generateEntrypoint.ts, generateEntrypoint.test.ts, templates/index.js
Generates React Native entrypoint templates that import AppRegistry and storybook.requires, register the Storybook UI component, and support both TypeScript and JavaScript with appropriate file extensions.
Platform Scripts Derivation
generateScripts.ts, generateScripts.test.ts
Derives platform-specific npm scripts (storybook:ios, storybook:android) by prefixing base iOS/Android commands with cross-env STORYBOOK_ENABLED=true; tracks missing base scripts.
Metro Configuration Codemod
metroConfig.ts, metroConfig.test.ts
Performs automated Metro config patching via Babel/recast AST transformation to wrap CommonJS and ESM exports with withStorybook(...); handles config detection, user prompts, Expo integration, unsupported shapes with fallback comments, and idempotency checks.
React Native Generator Integration
index.ts, index.test.ts
Orchestrates entrypoint generation, script derivation, and Metro codemod execution; maintains state across configure and postConfigure phases; updates messaging to display derived scripts and fallback warnings when platform scripts cannot be inferred.

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly Related PRs

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

🧹 Nitpick comments (2)
code/lib/create-storybook/src/generators/REACT_NATIVE/generateScripts.test.ts (1)

5-48: Consider adding edge case tests.

The current tests cover the main scenarios well. Consider adding tests for edge cases like undefined scripts, empty string values, and non-string script values (e.g., { ios: 123 }), which the implementation handles but aren't explicitly tested.

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

In
`@code/lib/create-storybook/src/generators/REACT_NATIVE/generateScripts.test.ts`
around lines 5 - 48, Add edge-case unit tests for
deriveStorybookPlatformScripts: add cases where input scripts contain undefined
values (e.g., { ios: undefined }), empty strings ({ android: '' }), and
non-string values ({ ios: 123 }) and assert the function treats those as missing
base scripts (appear in missingBaseScripts) and does not add corresponding
storybook:* entries to scriptsToAdd; also confirm the function does not mutate
the original input object (compare against a snapshot) and that scriptsToAdd
only contains entries for valid string commands. Use
deriveStorybookPlatformScripts, scriptsToAdd, and missingBaseScripts in the new
tests to mirror existing test style.
code/lib/create-storybook/src/generators/REACT_NATIVE/index.test.ts (1)

99-120: Consider making the output assertions more robust.

The test relies on checking the last logged message via mock.calls.at(-1). If additional log calls are added in the future, this could become fragile.

Consider using toHaveBeenCalledWith(expect.stringContaining(...)) or iterating over all calls to find relevant messages:

const allLogs = vi.mocked(logger.log).mock.calls.map(c => String(c[0]));
expect(allLogs.some(log => log.includes('npm run storybook:ios'))).toBe(true);

That said, testing the final output message is reasonable for verifying user-facing behavior.

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

In `@code/lib/create-storybook/src/generators/REACT_NATIVE/index.test.ts` around
lines 99 - 120, The test "postConfigure removes legacy entry replacement copy"
is brittle because it asserts on the last logger.log call via
vi.mocked(logger.log).mock.calls.at(-1); update the assertions to inspect all
logger.log calls instead: map vi.mocked(logger.log).mock.calls to strings (or
use
expect(vi.mocked(logger.log)).toHaveBeenCalledWith(expect.stringContaining(...)))
and assert that some log includes 'npm run storybook:ios' and 'npm run
storybook:android' and that none include 'Replace the contents of your app
entry'; update the assertions around logger.log in this test and keep the rest
of the test (including invoking reactNativeGenerator.configure/postConfigure)
unchanged.
🤖 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/lib/create-storybook/src/generators/REACT_NATIVE/generateScripts.ts`:
- Around line 8-12: The generated Storybook scripts use the STORYBOOK_ENV_PREFIX
(which includes "cross-env") via withStorybookEnv, but the configure function in
REACT_NATIVE/index.ts never adds cross-env to devDependencies; update the
configure function to add "cross-env" to the devDependencies/packages list
that's installed for React Native projects so the generated scripts will run.
Locate the configure function in REACT_NATIVE/index.ts and include "cross-env"
alongside the other dev packages added during setup (ensure it's added as a
devDependency, not a runtime dependency).

In `@code/lib/create-storybook/src/generators/REACT_NATIVE/index.ts`:
- Around line 19-20: The module-level variables lastMetroCodemodResult and
lastScriptDerivationResult can leak state between generator runs; update the
implementation so state is not stored at module scope—either clear these
variables at the start of configure or move them into per-run storage (e.g.,
capture them in a closure/class instance or return them from configure for
postConfigure to consume). Concretely, remove reliance on the module-level
lastMetroCodemodResult/lastScriptDerivationResult and ensure configure
initializes fresh per-run state (or returns a state object) that postConfigure
uses, updating references in configure and postConfigure accordingly (symbols:
lastMetroCodemodResult, lastScriptDerivationResult, configure, postConfigure,
GeneratorModule).

In `@code/lib/create-storybook/src/generators/REACT_NATIVE/metroConfig.ts`:
- Around line 8-14: Replace the placeholder constants that will be exposed to
users: update METRO_SETUP_DOCS_LINK to point to the real React Native Metro docs
URL and replace EXPO_CREATE_METRO_COMMAND (the command and args in the
EXPO_CREATE_METRO_COMMAND constant) with the actual Expo CLI command and
arguments used to generate a metro config; ensure the values are accurate,
user-friendly, and tested (references: METRO_SETUP_DOCS_LINK and
EXPO_CREATE_METRO_COMMAND in this file), and keep METRO_CONFIG_CANDIDATES and
METRO_FALLBACK_COMMENT_MARKER unchanged unless you need to add another candidate
or alter the marker text for clarity.

---

Nitpick comments:
In
`@code/lib/create-storybook/src/generators/REACT_NATIVE/generateScripts.test.ts`:
- Around line 5-48: Add edge-case unit tests for deriveStorybookPlatformScripts:
add cases where input scripts contain undefined values (e.g., { ios: undefined
}), empty strings ({ android: '' }), and non-string values ({ ios: 123 }) and
assert the function treats those as missing base scripts (appear in
missingBaseScripts) and does not add corresponding storybook:* entries to
scriptsToAdd; also confirm the function does not mutate the original input
object (compare against a snapshot) and that scriptsToAdd only contains entries
for valid string commands. Use deriveStorybookPlatformScripts, scriptsToAdd, and
missingBaseScripts in the new tests to mirror existing test style.

In `@code/lib/create-storybook/src/generators/REACT_NATIVE/index.test.ts`:
- Around line 99-120: The test "postConfigure removes legacy entry replacement
copy" is brittle because it asserts on the last logger.log call via
vi.mocked(logger.log).mock.calls.at(-1); update the assertions to inspect all
logger.log calls instead: map vi.mocked(logger.log).mock.calls to strings (or
use
expect(vi.mocked(logger.log)).toHaveBeenCalledWith(expect.stringContaining(...)))
and assert that some log includes 'npm run storybook:ios' and 'npm run
storybook:android' and that none include 'Replace the contents of your app
entry'; update the assertions around logger.log in this test and keep the rest
of the test (including invoking reactNativeGenerator.configure/postConfigure)
unchanged.
🪄 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: 6cf731fa-ff73-4e11-a696-0e40bc249414

📥 Commits

Reviewing files that changed from the base of the PR and between 1c38a36 and db8ded3.

📒 Files selected for processing (9)
  • code/lib/create-storybook/src/generators/REACT_NATIVE/generateEntrypoint.test.ts
  • code/lib/create-storybook/src/generators/REACT_NATIVE/generateEntrypoint.ts
  • code/lib/create-storybook/src/generators/REACT_NATIVE/generateScripts.test.ts
  • code/lib/create-storybook/src/generators/REACT_NATIVE/generateScripts.ts
  • code/lib/create-storybook/src/generators/REACT_NATIVE/index.test.ts
  • code/lib/create-storybook/src/generators/REACT_NATIVE/index.ts
  • code/lib/create-storybook/src/generators/REACT_NATIVE/metroConfig.test.ts
  • code/lib/create-storybook/src/generators/REACT_NATIVE/metroConfig.ts
  • code/lib/create-storybook/src/generators/REACT_NATIVE/templates/index.js

Comment on lines +8 to +12
const STORYBOOK_ENV_PREFIX = 'cross-env STORYBOOK_ENABLED=true';

const withStorybookEnv = (scriptValue: string) => {
return `${STORYBOOK_ENV_PREFIX} ${scriptValue}`.trim();
};
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
# Check if cross-env is added as a dependency during RN Storybook setup
rg -n 'cross-env' --type=ts -g '!*.test.*' code/lib/create-storybook/

Repository: storybookjs/storybook

Length of output: 204


🏁 Script executed:

#!/bin/bash
# Read the full generateScripts.ts file to understand context
cat -n code/lib/create-storybook/src/generators/REACT_NATIVE/generateScripts.ts | head -60

Repository: storybookjs/storybook

Length of output: 1514


🏁 Script executed:

#!/bin/bash
# Search for where cross-env might be added as a dependency
rg -n 'cross-env' code/lib/create-storybook/ -t ts -t js -A 3 -B 3

Repository: storybookjs/storybook

Length of output: 5207


🏁 Script executed:

#!/bin/bash
# Check if cross-env is in any package.json in create-storybook
find code/lib/create-storybook -name 'package.json' -exec grep -l 'cross-env' {} \;

Repository: storybookjs/storybook

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Check core package.json to see if cross-env is a dependency
grep -n 'cross-env' code/core/package.json || echo "Not found in core package.json"

Repository: storybookjs/storybook

Length of output: 95


🏁 Script executed:

#!/bin/bash
# Read the REACT_NATIVE generator index file to see if cross-env is added
cat -n code/lib/create-storybook/src/generators/REACT_NATIVE/index.ts | head -100

Repository: storybookjs/storybook

Length of output: 4402


🏁 Script executed:

#!/bin/bash
# Search for addDependency or addDevDependency calls in REACT_NATIVE
rg -n 'addDependency|addDevDependency|installDependencies' code/lib/create-storybook/src/generators/REACT_NATIVE/ -A 2

Repository: storybookjs/storybook

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Check other generators to see the pattern for adding dependencies
rg -n 'addDependency.*cross-env|cross-env.*Dependency' code/lib/create-storybook/src/generators/ -t ts

Repository: storybookjs/storybook

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Search for cross-env in template files that might be copied
find code/lib/create-storybook/src/templates -name '*.json' -o -name '*.md' | xargs grep -l 'cross-env' 2>/dev/null || echo "No cross-env found in templates"

Repository: storybookjs/storybook

Length of output: 188


🏁 Script executed:

#!/bin/bash
# Check if react-native has cross-env as a transitive dependency
rg -n 'cross-env' code/core/package.json code/lib/create-storybook/package.json 2>/dev/null || echo "Not in primary package files"

Repository: storybookjs/storybook

Length of output: 93


🏁 Script executed:

#!/bin/bash
# Look at what packages are in the REACT_NATIVE templates that get copied
ls -la code/lib/create-storybook/src/templates/REACT_NATIVE/ 2>/dev/null | head -20

Repository: storybookjs/storybook

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Search for any comments or documentation about cross-env in React Native files
rg -n 'cross-env' code/lib/create-storybook/src/generators/REACT_NATIVE/ -B 2 -A 2 --type ts --type md

Repository: storybookjs/storybook

Length of output: 4003


🏁 Script executed:

#!/bin/bash
# Check if there are any setup instructions or documentation
find code/lib/create-storybook -name 'README*' -o -name 'SETUP*' | xargs cat 2>/dev/null | head -50

Repository: storybookjs/storybook

Length of output: 231


🏁 Script executed:

#!/bin/bash
# Look at index.test.ts to see if there are hints about expected package.json
cat -n code/lib/create-storybook/src/generators/REACT_NATIVE/index.test.ts | head -80

Repository: storybookjs/storybook

Length of output: 3646


Add cross-env as a devDependency in React Native setup.

The generated scripts for storybook:ios and storybook:android reference cross-env on line 8, but the dependency is never installed. Update the configure function in REACT_NATIVE/index.ts to include cross-env in the packages added to the project.

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

In `@code/lib/create-storybook/src/generators/REACT_NATIVE/generateScripts.ts`
around lines 8 - 12, The generated Storybook scripts use the
STORYBOOK_ENV_PREFIX (which includes "cross-env") via withStorybookEnv, but the
configure function in REACT_NATIVE/index.ts never adds cross-env to
devDependencies; update the configure function to add "cross-env" to the
devDependencies/packages list that's installed for React Native projects so the
generated scripts will run. Locate the configure function in
REACT_NATIVE/index.ts and include "cross-env" alongside the other dev packages
added during setup (ensure it's added as a devDependency, not a runtime
dependency).

Comment on lines +19 to +20
let lastMetroCodemodResult: MetroCodemodResult | undefined;
let lastScriptDerivationResult: StorybookPlatformScriptDerivationResult | undefined;
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 | 🟡 Minor

Module-level state may leak between generator invocations.

lastMetroCodemodResult and lastScriptDerivationResult persist across calls. While configure and postConfigure are called as a pair per execution, if the module is reused without cache clearing (e.g., in tests or multiple sequential init commands in the same process), stale state from a previous run could affect subsequent runs. Consider passing state through the generator's return value or using a closure/class instance instead.

Alternative: Return state from configure for postConfigure consumption

One approach is to store the state in a property that configure returns and postConfigure receives, though this requires changes to the GeneratorModule interface. A simpler fix is to clear the state at the start of configure:

 let lastMetroCodemodResult: MetroCodemodResult | undefined;
 let lastScriptDerivationResult: StorybookPlatformScriptDerivationResult | undefined;

 export default defineGeneratorModule({
   // ...
   configure: async (packageManager, context) => {
+    // Reset state from any previous run
+    lastMetroCodemodResult = undefined;
+    lastScriptDerivationResult = undefined;
+
     const missingReactDom = !packageManager.getDependencyVersion('react-dom');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/lib/create-storybook/src/generators/REACT_NATIVE/index.ts` around lines
19 - 20, The module-level variables lastMetroCodemodResult and
lastScriptDerivationResult can leak state between generator runs; update the
implementation so state is not stored at module scope—either clear these
variables at the start of configure or move them into per-run storage (e.g.,
capture them in a closure/class instance or return them from configure for
postConfigure to consume). Concretely, remove reliance on the module-level
lastMetroCodemodResult/lastScriptDerivationResult and ensure configure
initializes fresh per-run state (or returns a state object) that postConfigure
uses, updating references in configure and postConfigure accordingly (symbols:
lastMetroCodemodResult, lastScriptDerivationResult, configure, postConfigure,
GeneratorModule).

Comment on lines +8 to +14
export const METRO_CONFIG_CANDIDATES = ['metro.config.ts', 'metro.config.js', 'metro.config.cjs'];
export const METRO_SETUP_DOCS_LINK = 'TODO_REPLACE_WITH_REACT_NATIVE_METRO_DOCS_LINK';
export const METRO_FALLBACK_COMMENT_MARKER = 'storybook-react-native-metro-codemod-fallback';
export const EXPO_CREATE_METRO_COMMAND = {
command: 'TODO_REPLACE_WITH_EXPO_METRO_CREATE_COMMAND',
args: [] as string[],
} as const;
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 | 🟡 Minor

TODO placeholders need resolution before release.

METRO_SETUP_DOCS_LINK and EXPO_CREATE_METRO_COMMAND contain placeholder values that will appear in user-facing output. These should be replaced with actual documentation links and commands before merging.

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

In `@code/lib/create-storybook/src/generators/REACT_NATIVE/metroConfig.ts` around
lines 8 - 14, Replace the placeholder constants that will be exposed to users:
update METRO_SETUP_DOCS_LINK to point to the real React Native Metro docs URL
and replace EXPO_CREATE_METRO_COMMAND (the command and args in the
EXPO_CREATE_METRO_COMMAND constant) with the actual Expo CLI command and
arguments used to generate a metro config; ensure the values are accurate,
user-friendly, and tested (references: METRO_SETUP_DOCS_LINK and
EXPO_CREATE_METRO_COMMAND in this file), and keep METRO_CONFIG_CANDIDATES and
METRO_FALLBACK_COMMENT_MARKER unchanged unless you need to add another candidate
or alter the marker text for clarity.

@storybook-app-bot
Copy link
Copy Markdown

storybook-app-bot bot commented Mar 26, 2026

Package Benchmarks

Commit: b818039, ran on 27 March 2026 at 15:13:38 UTC

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

storybook

Before After Difference
Dependency count 50 50 0
Self size 20.47 MB 20.46 MB 🎉 -12 KB 🎉
Dependency size 16.55 MB 16.55 MB 0 B
Bundle Size Analyzer Link Link

@storybook/nextjs-vite

Before After Difference
Dependency count 92 92 0
Self size 1.12 MB 1.12 MB 🎉 -36 B 🎉
Dependency size 22.76 MB 22.73 MB 🎉 -33 KB 🎉
Bundle Size Analyzer Link Link

@storybook/react-native-web-vite

Before After Difference
Dependency count 121 121 0
Self size 30 KB 30 KB 0 B
Dependency size 23.83 MB 23.80 MB 🎉 -32 KB 🎉
Bundle Size Analyzer Link Link

@storybook/react-vite

Before After Difference
Dependency count 82 82 0
Self size 35 KB 35 KB 🎉 -18 B 🎉
Dependency size 20.55 MB 20.51 MB 🎉 -33 KB 🎉
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 177 177 0
Self size 32 KB 32 KB 0 B
Dependency size 66.22 MB 66.21 MB 🎉 -12 KB 🎉
Bundle Size Analyzer Link Link

create-storybook

Before After Difference
Dependency count 51 51 0
Self size 1.04 MB 1.06 MB 🚨 +14 KB 🚨
Dependency size 37.03 MB 37.01 MB 🎉 -12 KB 🎉
Bundle Size Analyzer node node

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.

1 participant