Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React Native Web: Add framework, CLI integration, sandboxes #29520

Merged
merged 25 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
949c687
React Native Web Vite: WIP framework, sandboxes, and CLI integration
shilman Nov 3, 2024
59c10e8
feat: add template files
dannyhw Nov 3, 2024
4000182
feat: add template files
dannyhw Nov 3, 2024
0395ef8
lock
dannyhw Nov 3, 2024
6f9594f
Try to fix build
shilman Nov 4, 2024
ffdd93c
RNW-Vite: Split out react plugin from reactNativeWeb
shilman Nov 4, 2024
8031950
RNW-Vite: Publish sandboxes
shilman Nov 4, 2024
d94c342
Revert temporary changes to sandbox generation
shilman Nov 4, 2024
2e504c0
RNW-Vite: Remove React from CLI templates
shilman Nov 5, 2024
531b4b0
Fix linting, misc fixes
shilman Nov 9, 2024
ea86393
Merge branch 'next' into shilman/react-native-web-vite
shilman Nov 10, 2024
bb359ef
React-native-web: Add framework docs
shilman Nov 16, 2024
3204bec
RNW-Vite: Try to add docgen
shilman Nov 16, 2024
c92aa93
Fix TS error
shilman Nov 16, 2024
cc9b1b2
RNW-Vite: Add JS stories, autodocs
shilman Nov 16, 2024
654e0ed
RNW-Vite: Fix framwork misinfo
shilman Nov 16, 2024
e33053f
Merge branch 'next' into shilman/react-native-web-vite
shilman Nov 17, 2024
3efc796
RNW-Vite: Add note about RN CLI sandbox
shilman Nov 17, 2024
907fe38
RNW-Vite: Clean up unnecessary CSS
shilman Nov 17, 2024
4fb8b02
RNW-Vite: Test sandboxes
shilman Nov 17, 2024
ee7b69f
Tweak RNW CI
shilman Nov 17, 2024
e85c723
Update README.md
shilman Nov 17, 2024
fb477fd
Documentation polish
jonniebigodes Nov 17, 2024
b07a464
RNW-Vite: Clean up types/dependencies
shilman Nov 18, 2024
38c6859
Update yarn.lock
shilman Nov 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -980,30 +980,30 @@ workflows:
requires:
- build
- create-sandboxes:
parallelism: 37
parallelism: 38
requires:
- build
# - smoke-test-sandboxes: # disabled for now
# requires:
# - create-sandboxes
- build-sandboxes:
parallelism: 37
parallelism: 38
requires:
- create-sandboxes
- chromatic-sandboxes:
parallelism: 34
parallelism: 35
requires:
- build-sandboxes
- e2e-production:
parallelism: 32
parallelism: 33
requires:
- build-sandboxes
- e2e-dev:
parallelism: 2
requires:
- create-sandboxes
- test-runner-production:
parallelism: 32
parallelism: 33
requires:
- build-sandboxes
- vitest-integration:
Expand Down
2 changes: 2 additions & 0 deletions code/core/src/cli/detect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ export async function detectBuilder(packageManager: JsPackageManager, projectTyp

// Fallback to Vite or Webpack based on project type
switch (projectType) {
case ProjectType.REACT_NATIVE_WEB:
return CoreBuilder.Vite;
case ProjectType.REACT_SCRIPTS:
case ProjectType.ANGULAR:
case ProjectType.REACT_NATIVE: // technically react native doesn't use webpack, we just want to set something
Expand Down
17 changes: 9 additions & 8 deletions code/core/src/cli/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export const frameworkToDefaultBuilder: Record<
'preact-vite': CoreBuilder.Vite,
'preact-webpack5': CoreBuilder.Webpack5,
qwik: CoreBuilder.Vite,
'react-native-web-vite': CoreBuilder.Vite,
'react-vite': CoreBuilder.Vite,
'react-webpack5': CoreBuilder.Webpack5,
'server-webpack5': CoreBuilder.Webpack5,
Expand Down Expand Up @@ -193,6 +194,13 @@ export async function getVersionSafe(packageManager: JsPackageManager, packageNa
return undefined;
}

export const cliStoriesTargetPath = async () => {
if (existsSync('./src')) {
return './src/stories';
}
return './stories';
};

export async function copyTemplateFiles({
packageManager,
renderer,
Expand Down Expand Up @@ -252,14 +260,7 @@ export async function copyTemplateFiles({
throw new Error(`Unsupported renderer: ${renderer} (${baseDir})`);
};

const targetPath = async () => {
if (existsSync('./src')) {
return './src/stories';
}
return './stories';
};

const destinationPath = destination ?? (await targetPath());
const destinationPath = destination ?? (await cliStoriesTargetPath());
if (commonAssetsDir) {
await cp(commonAssetsDir, destinationPath, {
recursive: true,
Expand Down
1 change: 1 addition & 0 deletions code/core/src/cli/project_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export enum ProjectType {
REACT = 'REACT',
REACT_SCRIPTS = 'REACT_SCRIPTS',
REACT_NATIVE = 'REACT_NATIVE',
REACT_NATIVE_WEB = 'REACT_NATIVE_WEB',
REACT_PROJECT = 'REACT_PROJECT',
WEBPACK_REACT = 'WEBPACK_REACT',
NEXTJS = 'NEXTJS',
Expand Down
1 change: 1 addition & 0 deletions code/core/src/common/utils/framework-to-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const frameworkToRenderer: Record<
html: 'html',
preact: 'preact',
'react-native': 'react-native',
'react-native-web-vite': 'react',
react: 'react',
server: 'server',
svelte: 'svelte',
Expand Down
1 change: 1 addition & 0 deletions code/core/src/common/versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export default {
'@storybook/nextjs': '8.5.0-alpha.5',
'@storybook/preact-vite': '8.5.0-alpha.5',
'@storybook/preact-webpack5': '8.5.0-alpha.5',
'@storybook/react-native-web-vite': '8.5.0-alpha.5',
'@storybook/react-vite': '8.5.0-alpha.5',
'@storybook/react-webpack5': '8.5.0-alpha.5',
'@storybook/server-webpack5': '8.5.0-alpha.5',
Expand Down
1 change: 1 addition & 0 deletions code/core/src/types/modules/frameworks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type SupportedFrameworks =
| 'nextjs'
| 'preact-vite'
| 'preact-webpack5'
| 'react-native-web-vite'
| 'react-vite'
| 'react-webpack5'
| 'server-webpack5'
Expand Down
4 changes: 4 additions & 0 deletions code/e2e-tests/addon-actions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ test.describe('addon-actions', () => {
templateName.includes('svelte') && templateName.includes('prerelease'),
'Svelte 5 prerelase does not support automatic actions with our current example components yet'
);
test.skip(
templateName.includes('react-native-web'),
'React Native uses onPress rather than onClick'
);
await page.goto(storybookUrl);
const sbPage = new SbPage(page, expect);
sbPage.waitUntilLoaded();
Expand Down
3 changes: 3 additions & 0 deletions code/e2e-tests/addon-controls.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import process from 'process';
import { SbPage } from './util';

const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:8001';
const templateName = process.env.STORYBOOK_TEMPLATE_NAME || '';

test.describe('addon-controls', () => {
test('should change component when changing controls', async ({ page }) => {
test.skip(templateName.includes('react-native-web'), 'React Native CSS behaves differently');

await page.goto(storybookUrl);
const sbPage = new SbPage(page, expect);
await sbPage.waitUntilLoaded();
Expand Down
1 change: 1 addition & 0 deletions code/e2e-tests/addon-docs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ test.describe('addon-docs', () => {
// - template: https://638db567ed97c3fb3e21cc22-ulhjwkqzzj.chromatic.com/?path=/docs/addons-docs-docspage-basic--docs
// - real: https://638db567ed97c3fb3e21cc22-ulhjwkqzzj.chromatic.com/?path=/docs/example-button--docs
'lit-vite',
'react-native-web',
];
test.skip(
new RegExp(`^${skipped.join('|')}`, 'i').test(`${templateName}`),
Expand Down
4 changes: 4 additions & 0 deletions code/e2e-tests/addon-interactions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ test.describe('addon-interactions', () => {
/^(lit)/i.test(`${templateName}`),
`Skipping ${templateName}, which does not support addon-interactions`
);
test.skip(
templateName.includes('react-native-web'),
'React Native does not use className locators'
);

const sbPage = new SbPage(page, expect);

Expand Down
3 changes: 3 additions & 0 deletions code/frameworks/react-native-web-vite/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Storybook for React Native Web & Vite

See [documentation](https://storybook.js.org/docs/get-started/frameworks/react-native-web-vite?renderer=react-native-web) for installation instructions, usage examples, APIs, and more.
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: Documentation URL uses 'react-native-web' as renderer but framework uses 'react' renderer internally. Consider aligning these or documenting the difference.

89 changes: 89 additions & 0 deletions code/frameworks/react-native-web-vite/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
{
"name": "@storybook/react-native-web-vite",
"version": "8.5.0-alpha.5",
"description": "Develop react-native components an isolated web environment with hot reloading.",
Copy link
Contributor

Choose a reason for hiding this comment

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

syntax: typo in description: 'an isolated' should be 'in an isolated'

"keywords": [
"storybook"
],
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/react-native-web-vite",
"bugs": {
"url": "https://github.com/storybookjs/storybook/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/storybookjs/storybook.git",
"directory": "code/frameworks/react-native-web-vite"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/storybook"
},
"license": "MIT",
"exports": {
".": {
"types": "./dist/index.d.ts",
"node": "./dist/index.js",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"./preset": {
"types": "./dist/preset.d.ts",
"require": "./dist/preset.js"
},
"./package.json": "./package.json"
},
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"files": [
"dist/**/*",
"template/cli/**/*",
"README.md",
"*.js",
"*.d.ts",
"!src/**/*"
],
"scripts": {
"check": "jiti ../../../scripts/prepare/check.ts",
"prep": "jiti ../../../scripts/prepare/bundle.ts"
},
"dependencies": {
"@joshwooding/vite-plugin-react-docgen-typescript": "0.3.0",
"@rollup/pluginutils": "^5.0.2",
shilman marked this conversation as resolved.
Show resolved Hide resolved
"@storybook/builder-vite": "workspace:*",
"@storybook/react": "workspace:*",
"@storybook/react-vite": "workspace:*",
"@vitejs/plugin-react": "^4.3.2",
"find-up": "^5.0.0",
"magic-string": "^0.30.0",
"react-docgen": "^7.0.0",
shilman marked this conversation as resolved.
Show resolved Hide resolved
"resolve": "^1.22.8",
"tsconfig-paths": "^4.2.0"
shilman marked this conversation as resolved.
Show resolved Hide resolved
},
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.3.2"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta",
"react-native": ">=0.74.5",
"react-native-web": "^0.19.12",
"storybook": "workspace:^",
"vite": "^4.0.0 || ^5.0.0"
shilman marked this conversation as resolved.
Show resolved Hide resolved
},
"engines": {
"node": ">=18.0.0"
Copy link
Contributor

Choose a reason for hiding this comment

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

Same for the allowed engine. Since this is a new framework, let's not support Node 18 anymore.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm more comfortable making the switch along with all the other frameworks.

},
"publishConfig": {
"access": "public"
},
"bundler": {
"entries": [
"./src/index.ts",
"./src/preset.ts"
],
"platform": "node"
},
"gitHead": "e6a7fd8a655c69780bc20b9749c2699e44beae16"
}
1 change: 1 addition & 0 deletions code/frameworks/react-native-web-vite/preset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./dist/preset');
8 changes: 8 additions & 0 deletions code/frameworks/react-native-web-vite/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "react-native-web-vite",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "library",
"targets": {
"build": {}
}
Comment on lines +5 to +7
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Build target is empty. Consider adding standard NX build configuration like executor, options, and configurations for proper build integration.

}
1 change: 1 addition & 0 deletions code/frameworks/react-native-web-vite/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type { FrameworkOptions, StorybookConfig } from './types';
91 changes: 91 additions & 0 deletions code/frameworks/react-native-web-vite/src/preset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// @ts-expect-error FIXME
import { viteFinal as reactViteFinal } from '@storybook/react-vite/preset';
Comment on lines +1 to +2
Copy link
Contributor

Choose a reason for hiding this comment

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

style: TODO needs to be addressed - ts-expect-error should be removed by properly typing the import


import type { BabelOptions, Options as ReactOptions } from '@vitejs/plugin-react';
import react from '@vitejs/plugin-react';
import type { PluginOption } from 'vite';

import type { FrameworkOptions, StorybookConfig } from './types';

function reactNativeWeb(
reactOptions: Omit<ReactOptions, 'babel'> & { babel?: BabelOptions }
): PluginOption {
return {
name: 'vite:react-native-web',
config(_userConfig, env) {
return {
define: {
// reanimated support
'global.__x': {},
_frameTimestamp: undefined,
_WORKLET: false,
__DEV__: `${env.mode === 'development'}`,
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || env.mode),
},
optimizeDeps: {
include: [],
esbuildOptions: {
Comment on lines +26 to +27
Copy link
Contributor

Choose a reason for hiding this comment

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

style: empty include array may cause optimization issues - consider adding common dependencies

jsx: 'transform',
resolveExtensions: [
'.web.js',
'.web.ts',
'.web.tsx',
'.js',
'.jsx',
'.json',
'.ts',
'.tsx',
'.mjs',
],
loader: {
'.js': 'jsx',
},
},
},
resolve: {
extensions: [
'.web.js',
'.web.ts',
'.web.tsx',
'.js',
'.jsx',
'.json',
'.ts',
'.tsx',
'.mjs',
],
alias: {
'react-native': 'react-native-web',
},
},
};
},
};
}

export const viteFinal: StorybookConfig['viteFinal'] = async (config, options) => {
const { pluginReactOptions = {} } =
await options.presets.apply<FrameworkOptions>('frameworkOptions');

const reactConfig = await reactViteFinal(config, options);
const { plugins = [] } = reactConfig;

plugins.unshift(
react({
babel: {
babelrc: false,
configFile: false,
},
jsxRuntime: 'automatic',
...pluginReactOptions,
})
);
plugins.push(reactNativeWeb(pluginReactOptions));

return reactConfig;
};

export const core = {
builder: '@storybook/builder-vite',
renderer: '@storybook/react',
};
Loading