close
  • 简体中文
  • React Native Web

    Storybook for React Native Web & Rsbuild 让你能够开发并记录借助 react-native-web 在 Web 上运行的 React Native 组件。

    该 framework 扩展了 storybook-react-rsbuild,提供完整的 React Native Web 兼容能力,包括 alias 解析、Web 专属扩展名以及对 React Native 包的转译。

    环境要求

    PackageVersion
    react≥ 16.8
    react-native-web≥ 0.19.0
    @rsbuild/core≥ 1.5.0
    @rsbuild/plugin-react≥ 1.0.0
    storybook≥ 10.1.0

    快速开始

    安装

    安装 framework 包及其 peer dependency。@rsbuild/core@rsbuild/plugin-react 都直接列出,以保证版本锁定和 lockfile 审计清晰无歧义。

    npm
    yarn
    pnpm
    bun
    deno
    npm install @rsbuild/core @rsbuild/plugin-react storybook-react-native-web-rsbuild react-native-web -D

    配置 rsbuild.config.ts

    创建或更新你的 Rsbuild 配置,引入 React plugin:

    import { defineConfig } from '@rsbuild/core'
    import { pluginReact } from '@rsbuild/plugin-react'
    
    export default defineConfig({
      plugins: [pluginReact()],
    })

    配置 .storybook/main.ts

    import type { StorybookConfig } from 'storybook-react-native-web-rsbuild'
    
    const config: StorybookConfig = {
      stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
      framework: {
        name: 'storybook-react-native-web-rsbuild',
        options: {},
      },
    }
    
    export default config

    特性

    该 framework 会自动处理:

    • Alias 解析react-nativereact-native-web
    • Web 扩展名:优先解析 .web.tsx.web.ts.web.js 文件
    • 全局变量定义:设置 __DEV__EXPO_OS 等 React Native 全局变量
    • 包转译:转译 react-native@react-nativeexpo@expo 等包
    • React docgen:通过 react-docgen 完整支持 props 文档

    Framework 选项

    interface FrameworkOptions {
      /**
       * Additional node_modules that need to be transpiled.
       * Packages starting with `react-native`, `@react-native`, `expo`, and `@expo`
       * are included by default.
       */
      modulesToTranspile?: string[]
    
      /**
       * Options passed to the underlying rsbuild-plugin-react-native-web plugin.
       */
      pluginOptions?: PluginReactNativeWebOptions
    }
    
    interface PluginReactNativeWebOptions {
      /**
       * Additional node_modules that need to be transpiled.
       * @example ['my-react-native-library']
       */
      modulesToTranspile?: string[]
    
      /**
       * The JSX runtime to use.
       * @default 'automatic'
       */
      jsxRuntime?: 'automatic' | 'classic'
    
      /**
       * The source for JSX imports when using the automatic runtime.
       * @default 'react'
       * @example 'nativewind' for NativeWind v4+
       */
      jsxImportSource?: string
    
      /**
       * Modules that should not be tree-shaken.
       * @default ['react-native-css-interop', 'expo-modules-core']
       */
      noTreeshakeModules?: string[]
    }

    示例:转译额外的包

    部分 React Native 库发布的是未经转译的代码。将它们加入 modulesToTranspile

    const config: StorybookConfig = {
      framework: {
        name: 'storybook-react-native-web-rsbuild',
        options: {
          modulesToTranspile: [
            'react-native-reanimated',
            '@react-navigation/native',
          ],
        },
      },
    }

    编写 Stories

    使用标准的 React Native 组件来编写 story:

    // Button.stories.tsx
    import type { Meta, StoryObj } from '@storybook/react'
    import { Button } from './Button'
    
    const meta: Meta<typeof Button> = {
      title: 'Components/Button',
      component: Button,
    }
    
    export default meta
    type Story = StoryObj<typeof Button>
    
    export const Primary: Story = {
      args: {
        label: 'Press me',
        variant: 'primary',
      },
    }
    // Button.tsx
    import { Text, TouchableOpacity, StyleSheet } from 'react-native'
    
    export function Button({ label, onPress, variant = 'primary' }) {
      return (
        <TouchableOpacity onPress={onPress} style={[styles.button, styles[variant]]}>
          <Text style={styles.text}>{label}</Text>
        </TouchableOpacity>
      )
    }
    
    const styles = StyleSheet.create({
      button: {
        paddingVertical: 12,
        paddingHorizontal: 20,
        borderRadius: 8,
      },
      primary: {
        backgroundColor: '#007AFF',
      },
      text: {
        color: 'white',
        fontWeight: 'bold',
      },
    })

    NativeWind 支持

    该 framework 支持 NativeWind,可在 React Native 组件中使用 Tailwind CSS。

    安装

    npm
    yarn
    pnpm
    bun
    deno
    npm install nativewind react-native-css-interop react-native-safe-area-context tailwindcss postcss autoprefixer -D
    Tip

    建议安装 react-native-safe-area-context,以避免 react-native-css-interop 产生构建警告。

    配置

    1. 配置 Tailwind CSS —— 创建 tailwind.config.js
    /** @type {import('tailwindcss').Config} */
    module.exports = {
      content: ['./src/**/*.{js,jsx,ts,tsx}'],
      presets: [require('nativewind/preset')],
      theme: {
        extend: {},
      },
      plugins: [],
    }
    1. 创建全局 CSS —— 创建 src/global.css
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    1. 在 preview 中引入 CSS —— 更新 .storybook/preview.tsx
    import '../src/global.css'
    
    const preview = {
      // your preview config
    }
    
    export default preview
    1. 配置 JSX import source —— 更新 .storybook/main.ts
    const config: StorybookConfig = {
      framework: {
        name: 'storybook-react-native-web-rsbuild',
        options: {
          pluginOptions: {
            jsxImportSource: 'nativewind',
          },
        },
      },
    }

    用法

    import { View, Text } from 'react-native'
    
    export function Card({ title, children }) {
      return (
        <View className="p-4 bg-white rounded-lg shadow-md">
          <Text className="text-xl font-bold text-gray-800">{title}</Text>
          {children}
        </View>
      )
    }

    React Native Reanimated

    该 framework 为 Web 上的 React Native Reanimated 提供内置支持。

    安装

    npm
    yarn
    pnpm
    bun
    deno
    npm install react-native-reanimated react-native-worklets

    Framework 会处理的内容

    该 framework 会自动:

    • 定义 Reanimated 所需的 _WORKLET_frameTimestamp 全局变量
    • 转换 Reanimated 的 webUtils 以兼容 ESM
    • 处理 pnpm/monorepo 环境下的模块解析

    用法

    import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
    
    export function FadeInView({ children }) {
      return (
        <Animated.View entering={FadeIn.duration(500)} exiting={FadeOut}>
          {children}
        </Animated.View>
      )
    }

    Expo 支持

    该 framework 可与 Expo 项目配合使用。请确保你的 metro.config.js 或打包器配置不会与 Rsbuild 冲突。

    对于 Expo 专属的全局变量,plugin 会自动定义:

    • EXPO_OS'web'
    • process.env.EXPO_OS'web'

    后续步骤

    故障排查

    "React is not defined" 错误

    请确保已安装 @rsbuild/plugin-react 并在 rsbuild.config.ts 中完成配置。该 plugin 会启用自动 JSX runtime。

    React Native 包的 Module not found 错误

    将出问题的包加入 framework 选项中的 modulesToTranspile

    react-native 导入的 TypeScript 错误

    @types/react-native 安装为 dev dependency,或创建一个 react-native.d.ts 文件:

    declare module 'react-native' {
      export * from 'react-native-web'
    }

    React Native 包中的 Flow 语法错误

    部分较旧的 React Native 包在发布前可能未剥离 Flow 类型注解。如果你遇到与 Flow 类型相关的语法错误(例如类型注解中出现 Unexpected token :),可以使用 Babel 来剥离 Flow 类型。

    1. 安装所需的包:
    pnpm add -D @babel/core babel-loader @babel/preset-flow @babel/preset-react
    1. .storybook/main.ts 中添加自定义 Rsbuild 配置:
    import { mergeRsbuildConfig } from '@rsbuild/core'
    
    const config: StorybookConfig = {
      framework: {
        name: 'storybook-react-native-web-rsbuild',
        options: {},
      },
      rsbuildFinal: async (config) => {
        return mergeRsbuildConfig(config, {
          tools: {
            bundlerChain: (chain) => {
              // Add babel-loader for packages with Flow syntax
              chain.module
                .rule('flow')
                .test(/\.jsx?$/)
                .include.add(/node_modules\/(react-native|@react-native)/)
                .end()
                .use('babel')
                .loader('babel-loader')
                .options({
                  presets: ['@babel/preset-flow', '@babel/preset-react'],
                })
            },
          },
        })
      },
    }

    该配置使用 Babel 处理 React Native 包,在它们交由 SWC 处理之前剥离 Flow 类型注解。