加入直播:美国东部时间周四上午 11 点,Storybook 9 版本发布及 AMA 问答

React Native Web

为 react-native-web 配置 React Storybook

在 Github 上查看

用于 Storybook 的 React Native Web 插件

此插件配置 @storybook/react 以使用 React Native for Web (RNW) 显示 React Native (RN) 项目

请参阅 FAQ 了解常见问题。

你可以在这篇博客文章中阅读有关此软件包的更多信息。

要贡献,请参阅此处的贡献指南

这里是你可以如何将其与 storybook/react-native 一起使用的截图,该图片取自以下入门代码

image with storybook on mobile and web

入门

假设你有一个现有的 RN 项目,从项目根目录运行以下命令

npx sb init --type react
yarn add react-dom react-native-web babel-plugin-react-native-web @storybook/addon-react-native-web @react-native/babel-preset --dev

然后编辑你的 .storybook/main.js 文件

module.exports = {
  addons: [/*existing addons,*/ '@storybook/addon-react-native-web'],
};

从这里,你应该能够根据 Storybook for React 的说明编写包含你的 RN 组件的故事。

常见问题

请参阅FAQ了解“loader not found”等常见问题。

配置选项

大多数软件包应该无需额外更改即可工作,但在某些情况下需要额外的步骤。一个常见的例子是“reanimated”,它需要一些 babel 配置和额外的转译。

选项 类型 描述
modulesToTranspile Array<string> 需要转译的 node_modules
modulesToAlias {[key: string]: string} 需要别名化的 node_modules
babelPlugins Array<string | [string, Record<string, string>]> 你想应用的 Babel 插件
projectRoot string 项目根目录的路径,如果在单体仓库中,你可能需要设置此项。
babelPresets Array<string | [string, Record<string, string>]> 你想应用的 Babel 预设
babelPresetReactOptions Record<string, any> 传递给 @babel/preset-react 的选项
babelPresetReactNativeOptions Record<string, any> 传递给 @react-native/babel-preset 的选项

未转译的 React Native 库

许多 react-native 软件包未经转译就发布了,这不适用于 web 平台。如果你在添加软件包后收到诸如“proper loader not found”的错误,请尝试将其添加到此插件的 modulesToTranspile 选项中。

你可以这样做

module.exports = {
  addons: [
    /*existing addons,*/
    {
      name: '@storybook/addon-react-native-web',
      options: {
        modulesToTranspile: ['react-native-package-name'],
      },
    },
  ],
};

别名化 React Native Web 库

一些 react-native 软件包推荐使用模块别名来导入和使用现有软件包的 web 变体。如果你需要为 webpack 的 config.resolve.alias 添加额外的键值对,请使用此插件的 modulesToAlias 选项。你无需将 react-native-web 添加到此列表,因为它默认已包含在内。

你可以这样做

module.exports = {
  addons: [
    /*existing addons,*/
    {
      name: '@storybook/addon-react-native-web',
      options: {
        modulesToAlias: {
          'react-native-package-name': 'react-native-web-package-name',
        },
      },
    },
  ],
};

react-native-package-name 替换为真实软件包的名称。

添加 Babel 插件

在 react native 生态系统中,为某些软件包提供 babel 插件是很常见的,你可以将这些插件列表传递给此插件。

module.exports = {
  addons: [
    /*existing addons,*/
    {
      name: '@storybook/addon-react-native-web',
      options: {
        babelPlugins: ['babel-plugin-name'],
      },
    },
  ],
};

配置常用库

许多库无需额外配置即可工作,这里是一些软件包所需配置的示例。

注意:由于需要字体,react-native-vector-icons 需要一些额外的步骤,未来将有一个包含该配置的插件。

module.exports = {
  addons: [
    /*existing addons,*/
    {
      name: '@storybook/addon-react-native-web',
      options: {
        modulesToTranspile: ['react-native-reanimated'],
        babelPlugins: [
          '@babel/plugin-proposal-export-namespace-from',
          'react-native-reanimated/plugin',
        ],
      },
    },
  ],
};
module.exports = {
  addons: [
    /*existing addons,*/
    {
      name: '@storybook/addon-react-native-web',
      options: {
        modulesToTranspile: ['react-native-vector-icons'],
      },
    },
  ],
};
module.exports = {
  addons: [
    /*existing addons,*/
    {
      name: '@storybook/addon-react-native-web',
      options: {
        modulesToTranspile: ['react-native-vector-icons'],
      },
    },
  ],
};

提示:使用 Storybook 的 Web 版本可以接收可能不会从移动模拟器中冒出的编译和运行时错误。

// .ondevice/main.ts 
// AND/OR .storybook/main.ts

module.exports = {
  addons: [
    /*existing addons,*/
    {
      name: '@storybook/addon-react-native-web',
      options: {
        modulesToTranspile: [
          'react-native-reanimated',
          'nativewind',
          'react-native-css-interop',
        ],
        babelPresetReactOptions: { jsxImportSource: 'nativewind' },
        // If you have a bable.config.js file: this can also be placed there, and removed from here
        babelPresets: ['nativewind/babel'],
        babelPlugins: [
          // If you have a bable.config.js file: this can also be placed there, and removed from here
          'react-native-reanimated/plugin',
          // If you have a bable.config.js file: this can also be placed there, and removed from here
           [
            '@babel/plugin-transform-react-jsx',
            {
              runtime: 'automatic',
              importSource: 'nativewind',
            },
           ],
         ],
      },
    },
  ],
};
// .ondevice/preview.tsx
// add the following
import '../styles/global.css'
// metro.config.js
const path = require("path");
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");
const withStorybook = require("@storybook/react-native/metro/withStorybook");

const defaultConfig = getDefaultConfig(__dirname);

// 👇 important: nativeWindConfig is defined and passed to withStorybook!
// replace this: module.exports = withNativeWind(config, { input: "./global.css" });
// with the below (and update your input):
const nativeWindConfig = withNativeWind(defaultConfig, { input: "./styles/global.css" });

module.exports = withStorybook(nativeWindConfig, {
  // this line helps you switch between app and storybook using variable in package.json script,
  // and changes to your app's main entry point.
  enabled: process.env.EXPO_PUBLIC_STORYBOOK_ENABLED === "true",
  configPath: path.resolve(__dirname, "./.ondevice"),
  onDisabledRemoveStorybook: true,
});
// babel.config.js
module.exports = function (api) {
  api.cache(true);
  return {
    presets: [
      ["babel-preset-expo", { jsxImportSource: "nativewind" }],
      "nativewind/babel",
    ],
    plugins: [
      ["babel-plugin-react-docgen-typescript", { exclude: "node_modules" }],
      'react-native-reanimated/plugin',
      [
        '@babel/plugin-transform-react-jsx',
        {
          runtime: 'automatic',
          importSource: 'nativewind',
        },
      ],
    ],
  };
};

添加对静态资源和 SVGs 的支持

安装 @svgr/webpackurl-loader

module.exports = {
  /*existing config*/
  // to provide a public export for assets
  staticDirs: ['<path_to_assets>'],
  webpackFinal: async (config) => {
    const fileLoaderRule = config.module.rules.find(
      (rule) => rule.test && rule.test.test('.svg'),
    );

    if (fileLoaderRule) {
      fileLoaderRule.exclude = /\.svg$/;
    }

    config.module.rules.push({
      test: /\.svg$/,
      use: ['@svgr/webpack', 'url-loader'],
    });

    return config;
  },
};

Webpack 5 的 Node Polyfills

安装 node-polyfill-webpack-plugin

const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');

module.exports = {
  /*existing config*/
  core: {
    builder: 'webpack5',
  },
  webpackFinal: async (config) => {
    config.plugins.push(new NodePolyfillPlugin());

    return config;
  },
};

已知限制

  • 不支持 react-native-web 的库将无法工作
  • 组件将在 Web 上显示,因此可能与移动设备上的组件不同,因为可能会使用这些组件的 DOM 版本(例如 <div><span>
    • 当使用 View/Text 等基础组件或其他跨平台组件时,差异应该很小。
作者
  • ndelangen
    ndelangen
  • shilman
    shilman
  • tmeasday
    tmeasday
  • ghengeveld
    ghengeveld
  • winkervsbecks
    winkervsbecks
  • yannbf
    yannbf
兼容
    React native
标签