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 12 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
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
1 change: 1 addition & 0 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
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.3',
'@storybook/preact-vite': '8.5.0-alpha.3',
'@storybook/preact-webpack5': '8.5.0-alpha.3',
'@storybook/react-native-web-vite': '8.5.0-alpha.3',
'@storybook/react-vite': '8.5.0-alpha.3',
'@storybook/react-webpack5': '8.5.0-alpha.3',
'@storybook/server-webpack5': '8.5.0-alpha.3',
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
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 & Vite

See [documentation](https://storybook.js.org/docs/get-started/frameworks/react-vite?renderer=react) for installation instructions, usage examples, APIs, and more.
88 changes: 88 additions & 0 deletions code/frameworks/react-native-web-vite/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{
"name": "@storybook/react-native-web-vite",
"version": "8.5.0-alpha.3",
"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:*",
"@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 @@
import { hasVitePlugins } from '@storybook/builder-vite';

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 { plugins = [] } = config;

// if (!(await hasVitePlugins(plugins, ['vite:react-native-web']))) {
plugins.push(
react({
babel: {
babelrc: false,
configFile: false,
},
jsxRuntime: 'automatic',
...pluginReactOptions,
})
);
plugins.push(reactNativeWeb(pluginReactOptions));
//}

return config;
};

export const core = {
builder: '@storybook/builder-vite',
renderer: '@storybook/react',
};
68 changes: 68 additions & 0 deletions code/frameworks/react-native-web-vite/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type {
CompatibleString,
StorybookConfig as StorybookConfigBase,
TypescriptOptions as TypescriptOptionsBase,
} from 'storybook/internal/types';

import type { BuilderOptions, StorybookConfigVite } from '@storybook/builder-vite';

import type docgenTypescript from '@joshwooding/vite-plugin-react-docgen-typescript';
shilman marked this conversation as resolved.
Show resolved Hide resolved
import type { BabelOptions, Options as ReactOptions } from '@vitejs/plugin-react';

type FrameworkName = CompatibleString<'@storybook/react-native-web-vite'>;
type BuilderName = CompatibleString<'@storybook/builder-vite'>;

export type FrameworkOptions = {
builder?: BuilderOptions;
strictMode?: boolean;
/**
* Use React's legacy root API to mount components
*
* React has introduced a new root API with React 18.x to enable a whole set of new features (e.g.
* concurrent features) If this flag is true, the legacy Root API is used to mount components to
* make it easier to migrate step by step to React 18.
*
* @default false
*/
legacyRootApi?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we support legacy api in a new framework?


pluginReactOptions?: Omit<ReactOptions, 'babel'> & { babel?: BabelOptions };
Copy link
Contributor

Choose a reason for hiding this comment

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

style: pluginReactOptions type allows overriding babel options which could break React Native Web compatibility

};

type StorybookConfigFramework = {
framework:
| FrameworkName
| {
name: FrameworkName;
options: FrameworkOptions;
};
core?: StorybookConfigBase['core'] & {
builder?:
| BuilderName
| {
name: BuilderName;
options: BuilderOptions;
};
};
};

type TypescriptOptions = TypescriptOptionsBase & {
/**
* Sets the type of Docgen when working with React and TypeScript
*
* @default `'react-docgen'`
*/
reactDocgen: 'react-docgen-typescript' | 'react-docgen' | false;
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: reactDocgen type should be marked as optional since it has a default value

/** Configures `@joshwooding/vite-plugin-react-docgen-typescript` */
reactDocgenTypescriptOptions: Parameters<typeof docgenTypescript>[0];
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: reactDocgenTypescriptOptions should be optional since reactDocgen can be false

};

/** The interface for Storybook configuration in `main.ts` files. */
export type StorybookConfig = Omit<
StorybookConfigBase,
keyof StorybookConfigVite | keyof StorybookConfigFramework | 'typescript'
> &
StorybookConfigVite &
StorybookConfigFramework & {
typescript?: Partial<TypescriptOptions>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"rules": {
"import/extensions": "off",
"react/no-unknown-property": "off",
"react/react-in-jsx-scope": "off"
}
Comment on lines +2 to +6
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Consider adding additional React Native Web specific ESLint rules like react-native/no-inline-styles or react-native/no-raw-text

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { Meta, StoryObj } from '@storybook/react';

import { View } from 'react-native';

import { Button } from './Button';

const meta: Meta<typeof Button> = {
component: Button,
decorators: [
(Story) => (
<View style={{ flex: 1, alignItems: 'flex-start' }}>
<Story />
</View>
),
],
};

export default meta;

type Story = StoryObj<typeof meta>;

export const Primary: Story = {
args: {
primary: true,
label: 'Button',
},
};

export const Secondary: Story = {
args: {
label: 'Button',
},
};

export const Large: Story = {
args: {
size: 'large',
label: 'Button',
},
};

export const Small: Story = {
args: {
size: 'small',
label: 'Button',
},
};
Loading