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
相关产品推荐
相关产品推荐

