0

I am in the process of developing a design system using React Native Web and React Native as an npm package. While I've made progress in getting all the expo-vector-icon related components to work on Storybook when running on the web, I've encountered some difficulties when building expo-vector-icons using Rollup. Despite spending numerous hours debugging and even seeking assistance from chat GPT, I couldn't resolve the issues. Additionally, when I bundled expo-vector-icons into my app, it still didn't function as expected.

As a workaround, I decided to remove expo-vector-icons entirely from my package. Instead, I've been passing down an "IconToUse" prop to render icons, and I create this component locally within my Expo app or wherever I need it. However, this approach is not ideal because I need it to be testable in Storybook on the web, primarily for presentation purposes during meetings.

It's been a few months since my last attempt, and I'm reaching out to the Stack Overflow community for guidance. If anyone can provide insights or share an example Rollup configuration that works seamlessly with React Native, React Native Web, and Storybook for the web, I would greatly appreciate it. I'm open to using either react-native-vector-icons directly or continuing to use expo-vector-icons. My primary requirements are that it compiles into CommonJS (cjs), Universal Module Definition (umd), and ES Module (esm) formats and can be easily integrated into multiple projects.

Current Rollup Configuration:


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,
    },
  ],
};

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;
3
  • I don't believe that storybook uses rollup, could you include your storybook config? Commented Mar 11, 2024 at 13:58
  • I've been bundling all the components into an index.tsx file at the root of the project, which is the approach I've used for building my packages up until now. However, with this specific code, when I bundle and publish it as an npm package, it causes issues upon import. As a workaround, I've been using Git submodules instead. This design system is used on multiple platforms at the moment. My process is get the design, code the component, and then bring it into the applications. I will drop the storybook main file below. Commented Sep 17, 2024 at 19:21
  • Okay, I added it. Commented Sep 17, 2024 at 19:40

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.