DEV Community

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

Posted on • Edited on

5

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

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay