Skip to main content

react-native-web with Vite

··

react-native-web itself doesn’t require any tricky configuration to work with Vite.

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  alias: {
    "react-native": "react-native-web",
  },
});

But in order to use any library for react-native or react-native-web this would be not enough.

First of all react-native packages use convention to put web specific code in .web.js files:

const extensions = [
  ".web.tsx",
  ".tsx",
  ".web.ts",
  ".ts",
  ".web.jsx",
  ".jsx",
  ".web.js",
  ".js",
  ".css",
  ".json",
  ".mjs",
];

export default defineConfig({
	resolve: {
		extensions,
	}
	optimizeDeps: {
	    esbuildOptions: {
	      resolveExtensions: extensions,
	    }
	}
})

Often react-native packages assume webpack and global values defined by it:

const development = process.env.NODE_ENV === "development";

export default defineConfig({
  define: {
    global: "window",
    __DEV__: JSON.stringify(development),
    DEV: JSON.stringify(development),
    "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
  },
});

One more unpleasant surprise is that react-native packages can distribute jsx files with .js extension:

export default defineConfig({
  optimizeDeps: {
    esbuildOptions: {
      loader: { ".js": "jsx" },
    },
  },
});

And if you see ReferenceError: React is not defined cause by react-native package, try:

export default defineConfig({
  optimizeDeps: {
    esbuildOptions: {
      jsx: "automatic",
    },
  },
});

Other issues which can happen (but I don’t have examples for it): js files may contain Flow syntax:

import { esbuildFlowPlugin } from "@bunchtogether/vite-plugin-flow";

export default defineConfig({
  optimizeDeps: {
    esbuildOptions: {
      plugins: [
        esbuildFlowPlugin(/\.(flow|jsx?)$/, (path) =>
          /\.jsx$/.test(path) ? "jsx" : "jsx"
        ),
      ],
    },
  },
});

Final config #

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
// import { esbuildFlowPlugin } from "@bunchtogether/vite-plugin-flow";

// https://tamagui.dev/docs/intro/installation
const extensions = [
  ".web.tsx",
  ".tsx",
  ".web.ts",
  ".ts",
  ".web.jsx",
  ".jsx",
  ".web.js",
  ".js",
  ".css",
  ".json",
  ".mjs",
];

const development = process.env.NODE_ENV === "development";

// https://vitejs.dev/config/
export default defineConfig({
  clearScreen: true,
  plugins: [react()],
  define: {
    // https://github.com/bevacqua/dragula/issues/602#issuecomment-1296313369
    global: "window",
    __DEV__: JSON.stringify(development),
    // https://tamagui.dev/docs/intro/installation
    DEV: JSON.stringify(development),
    "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
  },
  resolve: {
    extensions: extensions,
    alias: {
      "react-native": "react-native-web",
    },
  },
  optimizeDeps: {
    esbuildOptions: {
      resolveExtensions: extensions,
      // https://github.com/vitejs/vite-plugin-react/issues/192#issuecomment-1627384670
      jsx: "automatic",
      // need either this or the plugin below
      loader: { ".js": "jsx" },
      // plugins: [
      //   esbuildFlowPlugin(/\.(flow|jsx?)$/, (path) =>
      //     /\.jsx$/.test(path) ? "jsx" : "jsx"
      //   ),
      // ],
    },
  },
});

Packages issues #

Example #

Vite + react-native-web + TypeScript

vxrn #

vxrn is a package that lets you serve your React Native apps using Vite. This is pretty cool as Vite typically doesn’t seem like it would “play well” with React Native - React Native only supports CommonJS, even for hot reloading, whereas Vite is all-in on ESModules.

vxrn is in early alpha.

vite-plugin-react-native-web #

vite-plugin-react-native-web adds React Native Web support to Vite by removing Flow types, aliasing react-native to react-native-web and transforming .js files as .jsx files using ESBuild.

Read more: Component libraries trends, Styling components