You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native/Web设计系统图标库Rollup配置适配求助

求助:兼容React Native、React Native Web及Storybook的Rollup配置(含图标组件)

我正在开发基于React Native和React Native Web的npm包设计系统,目前已实现web端Storybook中expo-vector-icon相关组件正常运行,但使用Rollup打包expo-vector-icons时遇到问题,经数小时调试及AI协助仍未解决,打包后集成到应用无法正常工作。

作为临时方案,我从包中移除了expo-vector-icons,改为通过传递IconToUse属性渲染图标,在本地Expo应用或需要的地方创建该组件。但此方案不够理想,因为我需要在web端Storybook中可直接测试图标组件,主要用于会议演示。

距上次尝试已过去数月,现向社区求助:希望获得可无缝兼容React Native、React Native Web及web端Storybook的Rollup配置示例,可使用react-native-vector-icons或继续使用expo-vector-icons,核心要求是能编译为CommonJS(cjs)、Universal Module Definition(umd)、**ES Module(esm)**格式,并可轻松集成到多个项目中。

当前Rollup配置

import babel from "@rollup/plugin-babel";
import commonjs from "@rollup/plugin-commonjs";
import inject from "@rollup/plugin-inject";
import json from "@rollup/plugin-json";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import typescript from "@rollup/plugin-typescript";
import url from '@rollup/plugin-url';
import svgr from '@svgr/rollup';
import fs from "fs";
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import nodePolyfills from "rollup-plugin-polyfill-node";
import postcss from 'rollup-plugin-postcss';

const packageJson = JSON.parse(fs.readFileSync("./package.json", "utf-8"));

const extensions = [".js", ".jsx", ".ts", ".tsx", ".native.js"];

// List any external dependencies here, including peer dependencies and Storybook-related packages if any.
const externals = [
  "react",
  "react-dom",
  "react-native",
  "react-native-svg",
  "styled-components",
  "styled-components/native",
  "expo-linear-gradient",
  "expo-checkbox",
];

// Define globals for UMD build, ensure no Storybook globals are present.
const globals = {
  'react': 'React',
  'react-dom': 'ReactDOM',
  "react-native-svg": "ReactNativeSvg",
};

const makeExternalPredicate = externalArr => {
  if (externalArr.length === 0) {
    return () => false;
  }
  const pattern = new RegExp(`^(${externalArr.join("|")})($|/)`);
  return id => pattern.test(id);
};

export default {
  plugins: [
    peerDepsExternal(),
    nodeResolve({
      extensions,
      preferBuiltins: true,
      mainFields: ['module', 'main', 'browser'],
      dedupe: ['react', 'react-dom', 'react-native'],
    }),
    commonjs({
      include: /node_modules/,
      extensions,
    }),
    babel({
      extensions,
      babelHelpers: "bundled",
      exclude: /node_modules|.*\.stories\.tsx?|.*\.story\.tsx?/,
      presets: [
        "@babel/preset-env",
        "@babel/preset-react",
        "@babel/preset-typescript",
      ],
    }),
    typescript(),
    nodePolyfills(),
    json(),
    svgr(),
    postcss(),
    url(),
    inject({
      Svg: ['react-native-svg', 'default'],
      Circle: ['react-native-svg', 'Circle'],
      Checkbox: ['expo-checkbox', 'default'],
      Platform: ['react-native', 'Platform'],
      View: ['react-native', 'View'],
      Text: ['react-native', 'Text'],
      Image: ['react-native', 'Image'],
      StyleSheet: ['react-native', 'StyleSheet'],
      TouchableOpacity: ['react-native', 'TouchableOpacity'],
      Platform: ['react-native', 'Platform'],
      Dimensions: ['react-native', 'Dimensions'],
      StatusBar: ['react-native', 'StatusBar'],
      // Inject 'react-native-vector-icons' imports
      Icon: ['react-native-vector-icons/Ionicons', 'default'],
    }),
  ],

  input: "src/index.tsx",
  external: makeExternalPredicate(externals),
  output: [
    {
      file: packageJson.main,
      format: "cjs",
      name: "DesignSystem",
      sourcemap: true,
      globals,
    },
    {
      file: packageJson.browser,
      format: "umd",
      name: "DesignSystem",
      sourcemap: true,
      globals,
    },
    {
      file: packageJson.module,
      format: "esm",
      sourcemap: true,
      globals,
    },
  ],
};

当前Storybook配置

import type { StorybookConfig } from "@storybook/react-webpack5";
import path from "path";
import webpack from "webpack";

const config: StorybookConfig = {
  stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],

  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions",
    "@storybook/addon-jest",
    "@storybook/addon-designs",
    "@storybook/addon-viewport",
    {
      name: "@storybook/addon-react-native-web",
      options: {
        modulesToTranspile: [
          "react-native",
          "react-native-svg",
          "expo-image",
          "expo-asset",
          "react-native/Libraries/Image/AssetRegistry",
        ],
      },
    },
    "@storybook/addon-webpack5-compiler-babel",
    "@chromatic-com/storybook",
  ],

  webpackFinal: async (config) => {
    if (!config.resolve) {
      config.resolve = {};
    }

    if (!config.resolve.alias) {
      config.resolve.alias = {};
    }

    // Alias 'react-native' to 'react-native-web'
    config.resolve.alias["react-native$"] = "react-native-web";

    // Add alias for 'react-dom/client' to ensure React 17 compatibility
    config.resolve.alias["react-dom/client"] = require.resolve("react-dom");

    // Exclude 'child_process' from bundling
    config.externals = {
      child_process: "child_process",
    };

    if (!config.plugins) {
      config.plugins = [];
    }

    config.plugins.push(
      new webpack.NormalModuleReplacementPlugin(
        /react-native-svg\/lib\/module\/fabric/,
        "react-native-svg-web"
      )
    );

    config.plugins.push(
      new webpack.NormalModuleReplacementPlugin(
        /react-native\/Libraries\/Image\/AssetRegistry/,
        path.resolve(__dirname, "./mocks/AssetRegistry.ts")
      )
    );

    if (!config.resolve.fallback) {
      config.resolve.fallback = {};
    }

    config.resolve.fallback = {
      ...config.resolve.fallback,
      os: require.resolve("os-browserify/browser"),
      tty: require.resolve("tty-browserify"),
    };

    if (config.module && config.module.rules) {
      // Include the necessary loaders for React Native modules
      config.module.rules.push({
        test: /\.(js|ts|tsx)$/,
        include: /node_modules\/(react-native|expo|@react-native|react-native-web|expo-image|expo-asset)/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["module:metro-react-native-babel-preset", "@babel/preset-typescript"],
          },
        },
      });
    }

    return config;
  },

  framework: {
    name: "@storybook/react-webpack5",
    options: {
      legacyRootApi: true, // Required for React 17
    },
  },


  typescript: {
    reactDocgen: "react-docgen-typescript",
  },
};

export default config;

内容的提问来源于stack exchange,提问作者Landon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 23:20:08