Skip to content

Commit 63c76fc

Browse files
committed
feat: support React Native Web
e2e, doc path resolve nativewind and reanimated clean modern god damn win32
1 parent 0c98543 commit 63c76fc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+4753
-531
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
strategy:
5151
matrix:
5252
node-version: [20.x, 22.x]
53-
os: [ubuntu-latest, macos-14, windows-latest]
53+
os: [ubuntu-latest, macos-latest, windows-latest]
5454
runs-on: ${{ matrix.os }}
5555
env:
5656
PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.cache/ms-playwright

README.md

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,39 @@
11
![Storybook Rsbuild](https://github.com/rspack-contrib/storybook-rsbuild/assets/7237365/00165054-9e3e-4a15-8a99-27985989b9d2)
22

3-
# Storybook × Rsbuild
3+
# Storybook Rsbuild
44

55
<p>
6-
<a href="https://www.npmjs.com/package/storybook-builder-rsbuild"><img src="https://img.shields.io/npm/v/storybook-builder-rsbuild?style=flat-square&color=ff4785" alt="latest version" /></a>
7-
<a href="https://npmcharts.com/compare/storybook-builder-rsbuild,storybook-react-rsbuild,storybook-react-vue,storybook-vue3-rsbuild,storybook-vue3-rsbuild?interval=7&log=false"><img src="https://img.shields.io/npm/dm/storybook-builder-rsbuild?style=flat-square&color=%23ff4785" alt="NPM downloads per month" /></a>
8-
<a href="https://github.com/rspack-contrib/storybook-rsbuild/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/storybook-builder-rsbuild?style=flat-square&color=%23ff4785" alt="license" /></a>
6+
<a href="https://www.npmjs.com/package/storybook-builder-rsbuild"><img src="https://img.shields.io/npm/v/storybook-builder-rsbuild?style=flat-square&color=ff4785" alt="npm version" /></a>
7+
<a href="https://npmcharts.com/compare/storybook-builder-rsbuild?interval=30"><img src="https://img.shields.io/npm/dm/storybook-builder-rsbuild?style=flat-square&color=%23ff4785" alt="downloads" /></a>
8+
<a href="https://github.com/rspack-contrib/storybook-rsbuild/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/storybook-builder-rsbuild?style=flat-square&color=%23ff4785" alt="license" /></a>
99
</p>
1010

11-
The repository contains the Storybook Rsbuild builder and UI framework integrations.
11+
Build your [Storybook](https://storybook.js.org/) with [Rsbuild](https://rsbuild.rs/), unleashing the power of [Rspack](https://rspack.rs/).
1212

13-
## Usage
13+
## Documentation
1414

15-
Check out the [documentation](https://storybook.rsbuild.rs).
15+
Read the full documentation at **[storybook.rsbuild.rs](https://storybook.rsbuild.rs)**.
1616

17-
## Roadmap
17+
## Features
1818

19-
### Features
19+
- **Fast**: Powered by Rsbuild and Rspack, enjoy lightning-fast build and HMR performance.
20+
- **Framework Support**: Works with React, Vue 3, Vanilla HTML/JS, Web Components, and React Native Web.
21+
- **Storybook Compatible**: Fully compatible with the Storybook ecosystem.
2022

21-
- [x] Support TS type check (fork-ts-checker-webpack-plugin) _(supported in [0.0.4](https://github.com/rspack-contrib/storybook-rsbuild/releases/tag/v0.0.4))_
22-
- [ ] Support more frameworks
23-
- [x] React
24-
- [x] Vue
25-
- [ ] Svelte
26-
- [x] Lit
27-
- [x] vanilla html
28-
- [ ] React Native
29-
- [ ] SolidJS
23+
## Packages
24+
25+
| Package | Version | Description |
26+
| --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
27+
| [storybook-builder-rsbuild](./packages/builder-rsbuild) | [![npm](https://img.shields.io/npm/v/storybook-builder-rsbuild?style=flat-square&color=ff4785)](https://npmjs.com/package/storybook-builder-rsbuild) | Rsbuild builder for Storybook |
28+
| [storybook-react-rsbuild](./packages/framework-react) | [![npm](https://img.shields.io/npm/v/storybook-react-rsbuild?style=flat-square&color=ff4785)](https://npmjs.com/package/storybook-react-rsbuild) | React framework integration |
29+
| [storybook-vue3-rsbuild](./packages/framework-vue3) | [![npm](https://img.shields.io/npm/v/storybook-vue3-rsbuild?style=flat-square&color=ff4785)](https://npmjs.com/package/storybook-vue3-rsbuild) | Vue 3 framework integration |
30+
| [storybook-html-rsbuild](./packages/framework-html) | [![npm](https://img.shields.io/npm/v/storybook-html-rsbuild?style=flat-square&color=ff4785)](https://npmjs.com/package/storybook-html-rsbuild) | HTML framework integration |
31+
| [storybook-web-components-rsbuild](./packages/framework-web-components) | [![npm](https://img.shields.io/npm/v/storybook-web-components-rsbuild?style=flat-square&color=ff4785)](https://npmjs.com/package/storybook-web-components-rsbuild) | Web Components framework integration |
32+
| [storybook-react-native-web-rsbuild](./packages/framework-react-native-web) | [![npm](https://img.shields.io/npm/v/storybook-react-native-web-rsbuild?style=flat-square&color=ff4785)](https://npmjs.com/package/storybook-react-native-web-rsbuild) | React Native Web framework integration |
3033

3134
## Credits
3235

33-
Some code is copied or modified from [storybookjs/storybook](https://github.com/storybookjs/storybook).
36+
Some code is adapted from [storybookjs/storybook](https://github.com/storybookjs/storybook).
3437

3538
## License
3639

biome.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
},
1616
"formatter": {
1717
"indentStyle": "space",
18+
"lineEnding": "lf",
1819
"includes": ["**", "!**/*.vue"]
1920
},
2021
"javascript": {
@@ -55,7 +56,13 @@
5556
"noExplicitAny": "off",
5657
"noConfusingVoidType": "off",
5758
"noAssignInExpressions": "off",
58-
"noImplicitAnyLet": "off"
59+
"noImplicitAnyLet": "off",
60+
"noUnknownAtRules": {
61+
"level": "error",
62+
"options": {
63+
"ignore": ["tailwind"]
64+
}
65+
}
5966
},
6067
"complexity": {
6168
"useOptionalChain": "off",

e2e/playwright.config.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import { defineConfig, devices } from '@playwright/test'
22

33
const isCI = !!process.env.CI
4+
const isWindows = process.platform === 'win32'
5+
6+
// Windows CI has slower I/O and process startup, use serial execution
7+
const workers = isCI ? (isWindows ? 1 : '25%') : '50%'
8+
9+
// Windows needs longer timeout due to slower file system operations
10+
const expectTimeout = isCI ? (isWindows ? 120_000 : 60_000) : 20_000
411

512
export default defineConfig({
613
testDir: './tests',
714
forbidOnly: isCI,
8-
workers: isCI ? '25%' : '50%',
15+
workers,
916
retries: isCI ? 3 : 0,
1017
reporter: [['list']],
1118
timeout: isCI ? 360_000 : 180_000,
@@ -16,6 +23,6 @@ export default defineConfig({
1623
navigationTimeout: 60_000,
1724
},
1825
expect: {
19-
timeout: isCI ? 60_000 : 20_000,
26+
timeout: expectTimeout,
2027
},
2128
})

e2e/sandboxes.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,32 @@ export const sandboxes: SandboxDefinition[] = [
2020
},
2121
{ name: 'react-16', relativeDir: 'sandboxes/react-16', port: 6103 },
2222
{ name: 'react-18', relativeDir: 'sandboxes/react-18', port: 6104 },
23-
{ name: 'react-testing', relativeDir: 'sandboxes/react-testing', port: 6105 },
23+
{
24+
name: 'react-native-web',
25+
relativeDir: 'sandboxes/react-native-web',
26+
port: 6105,
27+
},
28+
{ name: 'react-testing', relativeDir: 'sandboxes/react-testing', port: 6106 },
2429
{
2530
name: 'rslib-react-component',
2631
relativeDir: 'sandboxes/rslib-react-component',
27-
port: 6106,
32+
port: 6107,
2833
},
2934
{
3035
name: 'rslib-react-mf',
3136
relativeDir: 'sandboxes/rslib-react-mf',
32-
port: 6107,
37+
port: 6108,
3338
},
3439
{
3540
name: 'rslib-vue3-component',
3641
relativeDir: 'sandboxes/rslib-vue3-component',
37-
port: 6108,
42+
port: 6109,
3843
},
3944
{
4045
name: 'rspack-react-18',
4146
relativeDir: 'sandboxes/rspack-react-18',
42-
port: 6109,
47+
port: 6110,
4348
},
44-
{ name: 'vanilla-ts', relativeDir: 'sandboxes/vanilla-ts', port: 6110 },
45-
{ name: 'vue3', relativeDir: 'sandboxes/vue3', port: 6111 },
49+
{ name: 'vanilla-ts', relativeDir: 'sandboxes/vanilla-ts', port: 6111 },
50+
{ name: 'vue3', relativeDir: 'sandboxes/vue3', port: 6112 },
4651
]

e2e/tests/react-native-web.spec.ts

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
import { expect, test } from '@playwright/test'
2+
import { sandboxes } from '../sandboxes'
3+
import { previewFrame } from '../utils/assertions'
4+
import { launchSandbox } from '../utils/sandboxProcess'
5+
6+
// Skip on Windows due to path handling differences that affect Nativewind/Reanimated
7+
// TODO: Re-enable when Windows path issues are fully resolved
8+
const isWindows = process.platform === 'win32'
9+
10+
const sandbox = sandboxes.find((entry) => entry.name === 'react-native-web')
11+
12+
if (!sandbox) {
13+
throw new Error('Sandbox definition not found: react-native-web')
14+
}
15+
16+
test.describe(sandbox.name, () => {
17+
test.skip(isWindows, 'Skipped on Windows due to path compatibility issues')
18+
19+
let server: Awaited<ReturnType<typeof launchSandbox>> | null = null
20+
21+
test.beforeAll(async () => {
22+
server = await launchSandbox(sandbox)
23+
})
24+
25+
test.afterAll(async () => {
26+
if (server) {
27+
await server.stop()
28+
server = null
29+
}
30+
})
31+
32+
test('should load the home page with Button docs', async ({ page }) => {
33+
const currentServer = server
34+
if (!currentServer) {
35+
throw new Error('Storybook server failed to start')
36+
}
37+
38+
await page.goto(currentServer.url, { waitUntil: 'networkidle' })
39+
40+
const frame = previewFrame(page)
41+
const docsRoot = frame.locator('#storybook-docs:not([hidden])')
42+
await expect(docsRoot).toBeVisible()
43+
44+
// Check that Button component docs are rendered
45+
const title = docsRoot.locator('h1')
46+
await expect(title).toBeVisible()
47+
await expect(title).toHaveText('Button')
48+
})
49+
50+
test('should render Button component with React Native Web primitives', async ({
51+
page,
52+
}) => {
53+
const currentServer = server
54+
if (!currentServer) {
55+
throw new Error('Storybook server failed to start')
56+
}
57+
58+
// Navigate to Primary story
59+
await page.goto(
60+
`${currentServer.url}?path=/story/components-button--primary`,
61+
{
62+
waitUntil: 'networkidle',
63+
},
64+
)
65+
66+
const frame = previewFrame(page)
67+
68+
// The button text should be visible
69+
// TouchableOpacity renders as a div, and Text renders as a span
70+
const buttonText = frame.getByText('Primary Button')
71+
await expect(buttonText).toBeVisible()
72+
})
73+
74+
test('should render Card component', async ({ page }) => {
75+
const currentServer = server
76+
if (!currentServer) {
77+
throw new Error('Storybook server failed to start')
78+
}
79+
80+
// Navigate to Card Default story
81+
await page.goto(
82+
`${currentServer.url}?path=/story/components-card--default`,
83+
{
84+
waitUntil: 'networkidle',
85+
},
86+
)
87+
88+
const frame = previewFrame(page)
89+
90+
// Check card title is rendered
91+
const cardTitle = frame.getByText('Card Title')
92+
await expect(cardTitle).toBeVisible()
93+
94+
// Check card content is rendered
95+
const cardContent = frame.getByText(
96+
'This is the card content. You can put any React Native components here.',
97+
)
98+
await expect(cardContent).toBeVisible()
99+
})
100+
101+
test('should show props table with docgen', async ({ page }) => {
102+
const currentServer = server
103+
if (!currentServer) {
104+
throw new Error('Storybook server failed to start')
105+
}
106+
107+
// Navigate to Button docs
108+
await page.goto(`${currentServer.url}?path=/docs/components-button--docs`, {
109+
waitUntil: 'networkidle',
110+
})
111+
112+
const frame = previewFrame(page)
113+
const docsRoot = frame.locator('#storybook-docs:not([hidden])')
114+
await expect(docsRoot).toBeVisible()
115+
116+
// Check that props table shows expected props from docgen
117+
const labelProp = docsRoot.getByText('The button label')
118+
await expect(labelProp).toBeVisible()
119+
120+
const variantProp = docsRoot.getByText('Visual variant of the button')
121+
await expect(variantProp).toBeVisible()
122+
})
123+
124+
test('should render Nativewind styled components', async ({ page }) => {
125+
const currentServer = server
126+
if (!currentServer) {
127+
throw new Error('Storybook server failed to start')
128+
}
129+
130+
// Navigate to Nativewind Showcase story
131+
await page.goto(
132+
`${currentServer.url}?path=/story/nativewind-showcase--showcase`,
133+
{
134+
waitUntil: 'networkidle',
135+
},
136+
)
137+
138+
const frame = previewFrame(page)
139+
140+
// Check that the Nativewind container is rendered
141+
const container = frame.locator('[data-testid="nativewind-container"]')
142+
await expect(container).toBeVisible()
143+
144+
// Check that the title is rendered
145+
const title = frame.locator('[data-testid="nativewind-title"]')
146+
await expect(title).toBeVisible()
147+
await expect(title).toHaveText('Nativewind v4')
148+
149+
// Verify Tailwind CSS styles are applied (background color from purple-500)
150+
// The gradient starts with purple-500 (#a855f7) which gets applied as background
151+
const containerStyles = await container.evaluate((el) => {
152+
const styles = window.getComputedStyle(el)
153+
return {
154+
padding: styles.padding,
155+
borderRadius: styles.borderRadius,
156+
}
157+
})
158+
159+
// Check that Tailwind utilities are applied (p-8 = 32px padding, rounded-3xl = 24px border-radius)
160+
expect(containerStyles.padding).toBe('32px')
161+
expect(containerStyles.borderRadius).toBe('24px')
162+
})
163+
164+
test('should render Reanimated animated components', async ({ page }) => {
165+
const currentServer = server
166+
if (!currentServer) {
167+
throw new Error('Storybook server failed to start')
168+
}
169+
170+
// Navigate to Reanimated Showcase story
171+
await page.goto(
172+
`${currentServer.url}?path=/story/reanimated-animations--showcase`,
173+
{
174+
waitUntil: 'networkidle',
175+
},
176+
)
177+
178+
const frame = previewFrame(page)
179+
180+
// Check that the showcase container is rendered
181+
const showcase = frame.locator('[data-testid="reanimated-showcase"]')
182+
await expect(showcase).toBeVisible()
183+
184+
// Check that the FadeInBox component is rendered
185+
const fadeInBox = frame.locator('[data-testid="reanimated-fade-in"]')
186+
await expect(fadeInBox).toBeVisible()
187+
188+
// Verify the FadeInBox has expected text
189+
const fadeInText = fadeInBox.getByText('Fade In')
190+
await expect(fadeInText).toBeVisible()
191+
192+
// Wait for animation to complete and verify opacity is applied
193+
await page.waitForTimeout(1500) // Wait for 1000ms animation + buffer
194+
195+
const opacity = await fadeInBox.evaluate((el) => {
196+
return window.getComputedStyle(el).opacity
197+
})
198+
199+
// After animation completes, opacity should be 1
200+
expect(Number.parseFloat(opacity)).toBeCloseTo(1, 1)
201+
})
202+
203+
test('should render Reanimated FadeIn story individually', async ({
204+
page,
205+
}) => {
206+
const currentServer = server
207+
if (!currentServer) {
208+
throw new Error('Storybook server failed to start')
209+
}
210+
211+
// Navigate to FadeIn story
212+
await page.goto(
213+
`${currentServer.url}?path=/story/reanimated-animations--fade-in`,
214+
{
215+
waitUntil: 'networkidle',
216+
},
217+
)
218+
219+
const frame = previewFrame(page)
220+
221+
// Check that the FadeInBox component is rendered
222+
const fadeInBox = frame.locator('[data-testid="reanimated-fade-in"]')
223+
await expect(fadeInBox).toBeVisible()
224+
225+
// Verify the component has the correct background color (#6366f1 = indigo-500)
226+
const bgColor = await fadeInBox.evaluate((el) => {
227+
return window.getComputedStyle(el).backgroundColor
228+
})
229+
230+
// #6366f1 in RGB is rgb(99, 102, 241)
231+
expect(bgColor).toBe('rgb(99, 102, 241)')
232+
})
233+
})

0 commit comments

Comments
 (0)