DEV Community

Cover image for Using react-native-web with Vite in a Nx monorepo
Emily Xiong
Emily Xiong

Posted on • Edited on

Using react-native-web with Vite in a Nx monorepo

This blog shows how to add a web app using react-native-web with vite as the bundler in a Nx monorepo.

Github Repo: https://github.com/xiongemi/nx-react-native-monorepo-jokes


Installation

First, I need to install react-native-web:



# npm
npm install react-native-web --save-dev

# yarn
yarn add react-native-web --dev

# pnpm
pnpm add react-native-web --save-dev


Enter fullscreen mode Exit fullscreen mode

Add a React App in the Nx Monorepo

Run command:



npx nx g @nx/react:app <web app name>


Enter fullscreen mode Exit fullscreen mode

In the terminal output, select vite as bundler:



>  NX  Generating @nx/react:application

✔ Which stylesheet format would you like to use? · none
✔ Would you like to add React Router to this application? (y/N) · false
✔ Which E2E test runner would you like to use? · none
✔ Which bundler do you want to use to build the application? · vite


Enter fullscreen mode Exit fullscreen mode

Import the Native App

In the web app, in apps/<web app name>/src/main.tsx, change the import of App to:



// eslint-disable-next-line @nx/enforce-module-boundaries
import App from '../../<native app name>/src/app/App';


Enter fullscreen mode Exit fullscreen mode

Also, in web app's project.json, add implicitDependencies:



  "implicitDependencies": ["<native app name>"]


Enter fullscreen mode Exit fullscreen mode

Add Alias in vite.config.ts

In apps/techy-jokes-vite/vite.config.ts, add below alias:



  resolve: {
    alias: {
      'react-native': 'react-native-web',
    },
  },


Enter fullscreen mode Exit fullscreen mode

Then that is it, I can now view the web app using npx nx serve <web app name>.

Change index.html to be full height

In the index.html, change the:

  • style="height: 100%" to html tag
  • style="min-height: 100%" to body tag
  • style="display: flex; min-height: 100vh" to div with root id

These style changes are taken from examples in https://reactnavigation.org/docs/server-rendering.

So the index.html would look like:



<!DOCTYPE html>
<html lang="en" style="height: 100%">
  <head>
    ...
  </head>
  <body style="min-height: 100%">
    <div id="root" style="display: flex; min-height: 100vh"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>


Enter fullscreen mode Exit fullscreen mode

Troubleshooting

react-native-vector-icons

Because I use react-native-vector-icons, I got this error:



[commonjs--resolver] Unexpected token (70:8) in /Users/emily/code/tmp/nx-react-native-monorepo-jokes/node_modules/react-native-vector-icons/lib/create-icon-set.js
file: /Users/emily/code/tmp/nx-react-native-monorepo-jokes/node_modules/react-native-vector-icons/lib/create-icon-set.js:70:8
68: 
69:       return (
70:         <Text selectable={false} {...props}>
            ^
71:           {glyph}
72:           {children}

 >  NX   Unexpected token (70:8) in /Users/emily/code/tmp/nx-react-native-monorepo-jokes/node_modules/react-native-vector-icons/lib/create-icon-set.js


Enter fullscreen mode Exit fullscreen mode

This happens because react-native-vector-icons contains jsx code (e.g. <Text>) inside .js file.

First, I need to add files with .web to the extensions (the order of the extension in the array actually matters, .web needs to be loaded first):



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


Enter fullscreen mode Exit fullscreen mode

Add these extensions to resolve:



export default defineConfig({
...

  resolve: {
    extensions,
...


Enter fullscreen mode Exit fullscreen mode

Then inside defineConfig, I added to optimizeDeps to resolve js files using jsx loader:



  optimizeDeps: {
    esbuildOptions: {
      resolveExtensions: extensions,
      jsx: "automatic",
      loader: { ".js": "jsx" },
    },
  },


Enter fullscreen mode Exit fullscreen mode

When I serve up the web app, I will get this error message:



MaterialCommunityIcon.tsx:49 Error: Dynamic require of "react-native-vector-icons/MaterialCommunityIcons" is not supported


Enter fullscreen mode Exit fullscreen mode

I copy the font file from react-native-vector-icons library to the public folder: https://github.com/oblador/react-native-vector-icons/tree/master/Fonts.
So in my web app's index.html, I need to add:



    <style type="text/css">
      @font-face {
        font-family: 'MaterialCommunityIcons';
        src: url('/MaterialCommunityIcons.ttf') format('truetype');
      }
    </style>


Enter fullscreen mode Exit fullscreen mode

Now the serve command (nx serve <web app name>) is working. However, the build command (nx run <web app name>) still does not work with the same error.

I created a rollup plugin:



const rollupPlugin = (matchers: RegExp[]) => ({
  name: 'js-in-jsx',
  load(id: string) {
    if (
      matchers.some((matcher) => matcher.test(id)) &&
      id.endsWith('.js')
    ) {
      const file = readFileSync(id, { encoding: 'utf-8' });
      return esbuild.transformSync(file, { loader: 'jsx', jsx: 'automatic' });
    }
  },
});


Enter fullscreen mode Exit fullscreen mode

Then add this plugin to the vite config:



  build: {
    rollupOptions: {
      plugins: [rollupPlugin([/react-native-vector-icons/])],
    },
  },


Enter fullscreen mode Exit fullscreen mode

Now build command (nx build <web app name>) should work.

global is not defined

In the web browser, I got this error:



NavigationContainer.tsx:29 Uncaught ReferenceError: global is not defined


Enter fullscreen mode Exit fullscreen mode

In vite.config.ts, add:



  define: {
    global: "window",
  },


Enter fullscreen mode Exit fullscreen mode

The final vite.config.ts:



import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import * as esbuild from 'esbuild';
import { readFileSync } from 'fs';

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

const rollupPlugin = (matchers: RegExp[]) => ({
  name: 'js-in-jsx',
  load(id: string) {
    if (matchers.some((matcher) => matcher.test(id)) && id.endsWith('.js')) {
      const file = readFileSync(id, { encoding: 'utf-8' });
      return esbuild.transformSync(file, { loader: 'jsx', jsx: 'automatic' });
    }
  },
});

export default defineConfig({
  cacheDir: '../../node_modules/.vite/techy-jokes-vite',
  define: {
    global: 'window',
  },

  resolve: {
    extensions,
    alias: {
      'react-native': 'react-native-web',
    },
  },

  build: {
    rollupOptions: {
      plugins: [rollupPlugin([/react-native-vector-icons/])],
    },
  },

  server: {
    port: 4200,
    host: 'localhost',
  },

  preview: {
    port: 4300,
    host: 'localhost',
  },

  optimizeDeps: {
    esbuildOptions: {
      resolveExtensions: extensions,
      jsx: 'automatic',
      loader: { '.js': 'jsx' },
    },
  },

  plugins: [react(), nxViteTsPaths()],

  // Uncomment this if you are using workers.
  // worker: {
  //  plugins: [ nxViteTsPaths() ],
  // },

  test: {
    globals: true,
    cache: { dir: '../../node_modules/.vitest' },
    environment: 'jsdom',
    include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
  },
});


Enter fullscreen mode Exit fullscreen mode

Deploy to GitHub Pages

GitHub Pages is designed to host your personal, organization, or project pages from a GitHub repository.

To deploy this web app to GitHub Pages:

  1. install gh-pages: ```

npm

npm install gh-pages --save-dev

yarn

yarn add gh-pages --dev

pnpm

pnpm add gh-pages --save-dev


2. Create a script called `gh-pages.js` under the app:
Enter fullscreen mode Exit fullscreen mode

var ghpages = require('gh-pages');

ghpages.publish('', function (err) {
if (!err) {
console.error(err);
}
});


For this example, the `gh-pages.js` look like:
Enter fullscreen mode Exit fullscreen mode

var ghpages = require('gh-pages');

ghpages.publish('../../dist/apps/techy-jokes-vite', function (err) {
if (!err) {
console.error(err);
}
});


3. if your GitHub has a base href, run the build command with `--base`. For example, my GitHub page is at https://xiongemi.github.io/nx-react-native-monorepo-jokes/, to build for it, the command is `nx build techy-jokes-vite --base=/nx-react-native-monorepo-jokes/`.

4. Add a target in project.json:

Enter fullscreen mode Exit fullscreen mode
"gh-pages": {
  "command": "nx build techy-jokes-vite --base=/nx-react-native-monorepo-jokes/ && node gh-pages.js",
  "cwd": "{projectRoot}"
},
Enter fullscreen mode Exit fullscreen mode

Now you can run the command `nx gh-pages <your app name>` to deploy your app to GitHub Pages.

---
## Nx Graph

If you run command `nx graph`, you should see the web app implict depends on the react native mobile app:

![nx dependency graph](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zal9dcj1ag55ta6734hh.png)
Enter fullscreen mode Exit fullscreen mode

Top comments (0)