Create React Native Web App Tutorial

Note:
This article is updated on 08-Aug-2021, to solve this StackOverflow issue, In this update, Creation of
.babelrc
is removed,
babel.config.js
&
webpack.config.js
are updated. The changes are equivalent to this commit.


In this article, we are going to see, how to create a React Native App which can run on Android, iOS and Web Browser. We’ll Not be using Expo in this process. For web support, we’ll be using the package react-native-web.

I am using a Windows machine so I’ll be showing the project run in Android and Web only. I am assuming that you have already downloaded and setup Node, NPM, Android SDK, Java and Emulator/Device for build and debugging purpose. If not, don’t worry follow this article.

My Environment:

  • OS:
    Windows 10 (64 Bit)
  • Node:
    16.3.0
  • NPM:
    7.17

In case, if you want to explore more about which Hybrid App Development Framework to choose then you can have a look at this article: React Native vs Ionic vs Flutter


Step 1: Init a React Native Application:

This step is same as the official React Native Doc. So to
init
a React Native app:
Command-Prompt-Screenshot

  • Open command Prompt and go to the path where you want to create the project, In my case the path is
    C:\Users\shivam\Desktop\React.
  • Init App:
          npx react-native init AwesomeProject
          
        

Enter fullscreen mode
Exit fullscreen mode

  • Your folder will be looking like this commit.
  • You’ll find a new folder
    AwesomeProject
    in the current Directory, Now open this folder using any pengedit, I am using Visual Studio Code.

Step 2: Run this new app in Android (optional)

If you have Android setup done and an emulator or device connected, then you can simply run the app in android by simply running below command using command prompt in the folder
AwesomeProject.

          npx react-native run-android
          
        

Enter fullscreen gaya
Exit fullscreen mode


Step 3: Web Setup

As already mentioned we’ll be using the package react-native-web for web support. So you can find short setup instructions in the official doc of this package.


1. Add Web packages

Add the package
react-native-web
for web APIs and
react-basilika
for browser.

          npm
            install
            react-gereja react-native-web
          
        

Enter fullscreen tendensi
Exit fullscreen kecenderungan

If you see any error saying
unable to resolve dependency tree
then you can use the
--legacy-peer-deps
option like below.

          npm
            install
            react-dom react-native-web
            --legacy-peer-deps
          
        

Enter fullscreen tren
Exit fullscreen kecenderungan


2. Babel Plugin for build time optimization

As tiap-tiap official doc’s recommendation, use the babel plugin
babel-plugin-react-native-web.

          npm
            install
            --save-dev
            babel-plugin-react-native-web
          
        

Enter fullscreen kecenderungan
Exit fullscreen mode


3. Babel Module Aliasing

As we are going to alias
react-native
as
react-native-web
so as Babel supports module aliasing using babel-plugin-module-resolver, we’ll be using this.

          npm
            install
            --save-dev
            babel-plugin-module-resolver
          
        

Enter fullscreen mode
Exit fullscreen mode

Now, to set alias, we’ll use this package in webpack configuration.
(These settings will be inside
web/webpack.config.js, we’ll introduce this file later in this article)


4. Mock Jest

Jest can be configured using the provided preset. This will map
react-native
to
react-native-web
and provide appropriate mocks.

For this, in
/package.json
file, update the value of the key
"jest"
from
"react-native"
to
"react-native-web". Final value:

/package.json

          {   // Other Settings   "jest": {     "preset": "react-native-web"   } }
          
        

Enter fullscreen mode
Exit fullscreen kecenderungan


5. Configure Flow

Flow is a static type checker for Javascript like TypeScript. It’s used by React-Native by default if you don’t init the project using TypeScript template.

It can be configured to understand the aliased module. For this we need to add some config text under
[options]
key inside the file
/.flowconfig.

/.flowconfig

          
            [
            options
            ]
            # Alias the package name
            module.name_mapper='^react-native$' -> 'react-native-web'
          
        

Enter fullscreen mode
Exit fullscreen tendensi

Flow can be configured to pull types from React Native for Web’s source code. For that add below config text also in the
[options]
key.

/.flowconfig

          
            [
            options
            ]
            # Point flow to the 'module' field by default
            module.system.node.main_field=module
            module.system.node.main_field=main
          
        

Enter fullscreen kecondongan
Exit fullscreen mode


6. Package Optimization

We added a package babel-plugin-module-resolver in step-3, It is recommended for build-time optimizations and to prune modules not used by your application. To config this we’ll use webpack configurattions so your
/babel.config.js
file should look something like below.

/babel.config.js

          
            module
            .
            exports
            =
            {
            presets
            :
            [
            '
            module:metro-react-native-babel-preset
            '
            ],
            };
          
        

Enter fullscreen mode
Exit fullscreen kecenderungan


7. Create Entry Files

For web version, we need to create 2 entry files, First one is
index.html
and second one is
index.web.js, both needs to be placed at root path.

/index.html

          
            <!DOCTYPE html>
            <html
            lang=
            "en"
            >
            <head>
            <meta
            charset=
            "utf-8"
            />
            <title>Tentamen React Native on the Web!</title>
            <meta
            content=
            "initial-scale=1,width=device-width"
            name=
            "viewport"
            />
            <meta
            httpEquiv=
            "X-UA-Compatible"
            content=
            "IE=edge"
            />
            <style>
            /* These styles make the body full-height */
            html
            ,
            body
            ,
            #root
            {
            height
            :
            100%
            ;
            }
            /*
            These
            styles
            disable
            body
            scrolling
            if
            you
            are
            using
            <
            ScrollView
            >
            */
            body
            {
            overflow
            :
            hidden
            ;
            }
            /* These styles make the root element flex and column wise filling */
            #root
            {
            display
            :
            flex
            ;
            flex-direction
            :
            column
            ;
            }
            </style>
            </head>
            <body>
            <div
            id=
            "react-native-web-app"
            ></div>
            <script
            
            type=
            "text/javascript"
            src=
            "/bundle.web.js"
            ></script>
            </body>
            </html>
          
        

Enter fullscreen gaya
Exit fullscreen mode

Notice the script name
src="/bundle.web.js", We’ll be using this file name while configuring webpack.

/index.web.js

          
            import
            React
            from
            '
            react
            '
            ;
            import
            {
            AppRegistry
            }
            from
            '
            react-native
            '
            ;
            import
            App
            from
            '
            ./src/components/App
            '
            ;
            import
            {
            name
            as
            appName
            }
            from
            '
            ./app.json
            '
            ;
            AppRegistry
            .
            registerComponent
            (
            appName
            ,
            ()
            =>
            App
            );
            AppRegistry
            .
            runApplication
            (
            appName
            ,
            {
            rootTag
            :
            document
            .
            getElementById
            (
            '
            react-native-web-app
            '
            ),
            });
          
        

Enter fullscreen kecondongan
Exit fullscreen kecondongan

If you notice this almost same as
index.js
except the last line. As you can see above, we are using an App component
but from where did it came?
So it’s the same
App.js
file copied with the name
App.jsx
inside the path
/src/components/, It’s just to demonstrate an important concept which we’ll learn later in this article, as this file will be creating some problems. So
/src/components/App.jsx
will look like below:

Note:
As in many Editors/IDE the same App.js file can show errors as the file is using Flow syntax for type definitions and this syntax might not be supported in your IDE, to solve this you can either add Flow Language support via extensions or remove the flow specific code. I have removed the flow specific code in the below example for saving your time in troubleshooting.

/src/components/App.jsx

          
            /**  * Sample React Native App  * https://github.com/facebook/react-native  *  * @matra  * @flow strict-local  */
            import
            React
            from
            '
            react
            '
            ;
            import
            {
            Node
            }
            from
            '
            react
            '
            ;
            import
            {
            SafeAreaView
            ,
            ScrollView
            ,
            StatusBar
            ,
            StyleSheet
            ,
            Text
            ,
            useColorScheme
            ,
            View
            ,
            }
            from
            '
            react-native
            '
            ;
            import
            {
            Colors
            ,
            DebugInstructions
            ,
            Header
            ,
            LearnMoreLinks
            ,
            ReloadInstructions
            ,
            }
            from
            '
            react-native/Libraries/NewAppScreen
            '
            ;
            const
            Section
            =
            ({
            children
            ,
            title
            })
            =>
            {
            const
            isDarkMode
            =
            useColorScheme
            ()
            ===
            '
            dark
            '
            ;
            return
            (
            <
            View
            style
            =
            {
            styles
            .
            sectionContainer
            }
            >
            <
            Text
            style
            =
            {
            [
            styles
            .
            sectionTitle
            ,
            {
            color
            :
            isDarkMode
            ?
            Colors
            .
            white
            :
            Colors
            .
            black
            ,
            },
            ]
            }
            >
            {
            title
            }
            </
            Text
            >
            <
            Text
            style
            =
            {
            [
            styles
            .
            sectionDescription
            ,
            {
            color
            :
            isDarkMode
            ?
            Colors
            .
            light
            :
            Colors
            .
            dark
            ,
            },
            ]
            }
            >
            {
            children
            }
            </
            Text
            >
            </
            View
            >
            );
            };
            const
            App
            =
            ()
            =>
            {
            const
            isDarkMode
            =
            useColorScheme
            ()
            ===
            '
            dark
            '
            ;
            const
            backgroundStyle
            =
            {
            backgroundColor
            :
            isDarkMode
            ?
            Colors
            .
            darker
            :
            Colors
            .
            lighter
            ,
            };
            return
            (
            <
            SafeAreaView
            style
            =
            {
            backgroundStyle
            }
            >
            <
            StatusBar
            barStyle
            =
            {
            isDarkMode
            ?
            '
            light-content
            '
            :
            '
            dark-content
            '
            }
            />
            <
            ScrollView
            contentInsetAdjustmentBehavior
            =
            "automatic"
            style
            =
            {
            backgroundStyle
            }
            >
            <
            Header
            />
            <
            View
            style
            =
            {
            {
            backgroundColor
            :
            isDarkMode
            ?
            Colors
            .
            black
            :
            Colors
            .
            white
            ,
            }
            }
            >
            <
            Section
            title
            =
            "Step One"
            >
            Edit
            <
            Text
            style
            =
            {
            styles
            .
            highlight
            }
            >App.js</
            Text
            >
            to change this             screen and then come back to see your edits.
            </
            Section
            >
            <
            Section
            title
            =
            "See Your Changes"
            >
            <
            ReloadInstructions
            />
            </
            Section
            >
            <
            Section
            title
            =
            "Debug"
            >
            <
            DebugInstructions
            />
            </
            Section
            >
            <
            Section
            title
            =
            "Learn More"
            >
            Read the docs to discover what to do next:
            </
            Section
            >
            <
            LearnMoreLinks
            />
            </
            View
            >
            </
            ScrollView
            >
            </
            SafeAreaView
            >
            );
            };
            const
            styles
            =
            StyleSheet
            .
            create
            ({
            sectionContainer
            :
            {
            marginTop
            :
            32
            ,
            paddingHorizontal
            :
            24
            ,
            },
            sectionTitle
            :
            {
            fontSize
            :
            24
            ,
            fontWeight
            :
            '
            600
            '
            ,
            },
            sectionDescription
            :
            {
            marginTop
            :
            8
            ,
            fontSize
            :
            18
            ,
            fontWeight
            :
            '
            400
            '
            ,
            },
            highlight
            :
            {
            fontWeight
            :
            '
            700
            '
            ,
            },
            });
            export
            default
            App
            ;
          
        

Enter fullscreen kecondongan
Exit fullscreen mode


8. Configuring and budling

We’ll be using Webpack for bundling and Babel for transpiling along with
babel-loader.

Install Webpack and related dependencies:
Run below command in the halte to install packages for dev environment.

          npm
            install
            --save-dev
            babel-loader url-loader webpack webpack-cli webpack-dev-server
          
        

Enter fullscreen mode
Exit fullscreen tendensi

Tree-Shaking:
React Native’s Babel preset rewrites ES modules to CommonJS modules, preventing bundlers from automatically performing “tree-shaking” to remove unused modules from your web app build. To help with this, you can install the following Babel plugin:

          npm
            install
            --save-dev
            babel-plugin-react-native-web
          
        

Enter fullscreen mode
Exit fullscreen tendensi

Webpack Configuration:

This cofiguration is picked from the official doc and slightly modified to add
.jsx
support and
module-resolver
we added above via
babel-plugin-module-resolver. So to configure Webpack create a file at
/web/webpack.config.js. We will be using
webpack-cli
to diffrentiate between development and production builds, if you want to manage this via script then you can use this guide.

/web/webpack.config.js

          
            const
            path
            =
            require
            (
            '
            path
            '
            );
            const
            webpack
            =
            require
            (
            '
            webpack
            '
            );
            const
            appDirectory
            =
            path
            .
            resolve
            (
            __dirname
            ,
            '
            ../
            '
            );
            // This is needed for webpack to compile JavaScript.
            // Many OSS React Native packages are not compiled to ES5 before being
            // published. If you depend on uncompiled packages they may cause webpack build
            // errors. To fix this webpack can be configured to compile to the necessary
            // `node_module`.
            const
            babelLoaderConfiguration
            =
            {
            test
            :
            /
            \.(
            js
            )
            |
            (
            jsx
            )
            $/
            ,
            // Add every directory that needs to be compiled by Babel during the build.
            include
            :
            [
            path
            .
            resolve
            (
            appDirectory
            ,
            '
            index.web.js
            '
            ),
            path
            .
            resolve
            (
            appDirectory
            ,
            '
            src
            '
            ),
            path
            .
            resolve
            (
            appDirectory
            ,
            '
            node_modules/react-native-uncompiled
            '
            ),
            ],
            use
            :
            {
            loader
            :
            '
            babel-loader
            '
            ,
            options
            :
            {
            cacheDirectory
            :
            true
            ,
            // The 'metro-react-native-babel-preset' preset is recommended to match React Native's packager
            presets
            :
            [
            '
            module:metro-react-native-babel-preset
            '
            ],
            // Re-write paths to import only the modules needed by the app
            plugins
            :
            [
            '
            react-native-web
            '
            ,
            [
            '
            module-resolver
            '
            ,
            {
            atau
            :
            {
            '
            ^react-native$
            '
            :
            '
            react-native-web
            '
            ,
            },
            },
            ],
            ],
            },
            },
            };
            // This is needed for webpack to import static images in JavaScript files.
            const
            imageLoaderConfiguration
            =
            {
            test
            :
            /
            \.(
            gif|jpe
            ?
            g|png|svg
            )
            $/
            ,
            use
            :
            {
            loader
            :
            '
            url-loader
            '
            ,
            options
            :
            {
            name
            :
            '
            [name].[ext]
            '
            ,
            esModule
            :
            false
            ,
            },
            },
            };
            module
            .
            exports
            =
            {
            entry
            :
            [
            // load any web API polyfills
            // path.resolve(appDirectory, 'polyfills-web.js'),
            // your web-specific entry file
            path
            .
            resolve
            (
            appDirectory
            ,
            '
            index.web.js
            '
            ),
            ],
            // configures where the build ends up
            output
            :
            {
            filename
            :
            '
            bundle.web.js
            '
            ,
            path
            :
            path
            .
            resolve
            (
            appDirectory
            ,
            '
            dist
            '
            ),
            },
            // ...the rest of your config
            module
            :
            {
            rules
            :
            [
            babelLoaderConfiguration
            ,
            imageLoaderConfiguration
            ],
            },
            resolve
            :
            {
            // This will only alias the exact import "react-native"
            ataupun
            :
            {
            '
            react-native$
            '
            :
            '
            react-native-web
            '
            ,
            },
            // If you're working on a multi-mimbar React Native app, web-specific
            // module implementations should be written in files using the extension
            // `.web.js`.
            extensions
            :
            [
            '
            .web.js
            '
            ,
            '
            .js
            '
            ,
            '
            .jsx
            '
            ],
            },
            };
          
        

Enter fullscreen mode
Exit fullscreen mode

9. Scripts to run on web

Now we are going to add some scripts to run our web app with short command instead of full webpack-cli command. For this, we need to add below two options in the file
/package.json
inside
"scripts"
key.

To know more about
webpack-cli
options for webpack-5 go here and to know more about dev-tool go here

/package.json

          
            {
            
            
            "scripts"
            :
            
            
            {
            
            
            "web"
            :
            
            
            "webpack serve -d source-map --gaya development --config
            
            \"
            ./web/webpack.config.js
            \"
            
              --inline --color --hot"
            ,
            
            
            "build:web"
            :
            
            
            "webpack --mode production --config
            
            \"
            ./web/webpack.config.js
            \"
            
              --hot"
            
            
            }
            
            
            }
            
            
          
        

Enter fullscreen mode
Exit fullscreen gaya


10. RUN OUR WEBAPP

So, Finally we are here, as we have set shortcut in our
package.json
for script so now we can simply run below command to start our webapp in browser.

          npm run web
          
        

Enter fullscreen mode
Exit fullscreen kecenderungan


Wait a minute!!!

I’m getting error, like below:

          ERROR
            in
            ./node_modules/react-native/Libraries/NewAppScreen/components/DebugInstructions.js 11:12 Module parse failed: Unexpected token
            (11:12)
            You may need an appropriate loader to handle this file
            type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders |
            */ |
            >
            import
            type
            {Node}
            from
            'react'
            ;
            | import
            {Platform, StyleSheet, Text}
            from
            'react-native'
            ;
            | import React from
            'react'
            ;
            @ ./node_modules/react-native/Libraries/NewAppScreen/index.js 17:0-63 20:0-27:2  @ ./src/components/App.jsx 1:864-910  @ ./index.web.js 1:261-292
          
        

Enter fullscreen mode
Exit fullscreen mode

So this is the error we talked about in
Step 7. I literally spent 3-4 days to find a solution for this. And then the creator and maintainer of
react-native-web
package, Nicolas Gallagher helped me via this discussion.

So the issue is in the
import
statement on line 21 of
src/components/App.jsx, where we are trying to do something like below:

This line in the stack trace is telling us that we’re trying to bundle RN n domestik code in our web bundle:
node_modules/react-native/Libraries/NewAppScreen/index.js.

First, we should titinada be loading any of the RN package on web, especially not parts that aren’t part of the public Api . Second, as mentioned in the inline comments of the config
web/webpack.config.js, we need to explicitly list everything in
node_modules
that needs compiling.

          
            import
            {
            Colors
            ,
            DebugInstructions
            ,
            Header
            ,
            LearnMoreLinks
            ,
            ReloadInstructions
            ,
            }
            from
            '
            react-native/Libraries/NewAppScreen
            '
            ;
          
        

All this code of the problem. We should not import from Libraries. This is not part of the public API.

To solve this, remove the dependency on the library:
react-native/Libraries, for that update the code of
/src/components/App.jsx
as below:

/src/components/App.jsx

          
            /**  * Sample React Native App  * https://github.com/facebook/react-native  *  * @format  * @flow strict-local  */
            import
            React
            from
            '
            react
            '
            ;
            import
            {
            Node
            }
            from
            '
            react
            '
            ;
            import
            {
            SafeAreaView
            ,
            ScrollView
            ,
            StatusBar
            ,
            StyleSheet
            ,
            Text
            ,
            useColorScheme
            ,
            View
            ,
            }
            from
            '
            react-native
            '
            ;
            // import {
            //   Colors,
            //   DebugInstructions,
            //   Header,
            //   LearnMoreLinks,
            //   ReloadInstructions,
            // } from 'react-native/Libraries/NewAppScreen';
            const
            Colors
            =
            {
            white
            :
            '
            #fff
            '
            ,
            black
            :
            '
            #000
            '
            ,
            light
            :
            '
            #ddd
            '
            ,
            dark
            :
            '
            #333
            '
            ,
            lighter
            :
            '
            #eee
            '
            ,
            darker
            :
            '
            #111
            '
            ,
            };
            const
            Section
            =
            ({
            children
            ,
            title
            })
            =>
            {
            const
            isDarkMode
            =
            useColorScheme
            ()
            ===
            '
            dark
            '
            ;
            return
            (
            <
            View
            style
            =
            {
            styles
            .
            sectionContainer
            }
            >
            <
            Text
            style
            =
            {
            [
            styles
            .
            sectionTitle
            ,
            {
            color
            :
            isDarkMode
            ?
            Colors
            .
            white
            :
            Colors
            .
            black
            ,
            },
            ]
            }
            >
            {
            title
            }
            </
            Text
            >
            <
            Text
            style
            =
            {
            [
            styles
            .
            sectionDescription
            ,
            {
            color
            :
            isDarkMode
            ?
            Colors
            .
            light
            :
            Colors
            .
            dark
            ,
            },
            ]
            }
            >
            {
            children
            }
            </
            Text
            >
            </
            View
            >
            );
            };
            const
            App
            =
            ()
            =>
            {
            const
            isDarkMode
            =
            useColorScheme
            ()
            ===
            '
            dark
            '
            ;
            const
            backgroundStyle
            =
            {
            backgroundColor
            :
            isDarkMode
            ?
            Colors
            .
            darker
            :
            Colors
            .
            lighter
            ,
            };
            return
            (
            <
            SafeAreaView
            style
            =
            {
            backgroundStyle
            }
            >
            <
            StatusBar
            barStyle
            =
            {
            isDarkMode
            ?
            '
            light-content
            '
            :
            '
            dark-content
            '
            }
            />
            <
            ScrollView
            contentInsetAdjustmentBehavior
            =
            "automatic"
            style
            =
            {
            backgroundStyle
            }
            >
            {
            /* <Header /> */
            }
            <
            View
            style
            =
            {
            {
            backgroundColor
            :
            isDarkMode
            ?
            Colors
            .
            black
            :
            Colors
            .
            white
            ,
            }
            }
            >
            <
            Section
            title
            =
            "Step One"
            >
            Edit
            <
            Text
            style
            =
            {
            styles
            .
            highlight
            }
            >App.js</
            Text
            >
            to change this             screen and then come back to see your edits.
            </
            Section
            >
            <
            Section
            title
            =
            "See Your Changes"
            >
            {
            /* <ReloadInstructions /> */
            }
            <
            Text
            >Reload Instruction</
            Text
            >
            </
            Section
            >
            <
            Section
            title
            =
            "Debug"
            >
            {
            /* <DebugInstructions /> */
            }
            <
            Text
            >Debug Instruction</
            Text
            >
            </
            Section
            >
            <
            Section
            title
            =
            "Learn More"
            >
            Read the docs to discover what to do next:
            </
            Section
            >
            {
            /* <LearnMoreLinks /> */
            }
            <
            Text
            >Learn More Links</
            Text
            >
            </
            View
            >
            </
            ScrollView
            >
            </
            SafeAreaView
            >
            );
            };
            const
            styles
            =
            StyleSheet
            .
            create
            ({
            sectionContainer
            :
            {
            marginTop
            :
            32
            ,
            paddingHorizontal
            :
            24
            ,
            },
            sectionTitle
            :
            {
            fontSize
            :
            24
            ,
            fontWeight
            :
            '
            600
            '
            ,
            },
            sectionDescription
            :
            {
            marginTop
            :
            8
            ,
            fontSize
            :
            18
            ,
            fontWeight
            :
            '
            400
            '
            ,
            },
            highlight
            :
            {
            fontWeight
            :
            '
            700
            '
            ,
            },
            });
            export
            default
            App
            ;
          
        

Enter fullscreen mode
Exit fullscreen kecondongan


11. Finally run after Troubleshooting

We can simply run below command in the terminal if it was stopped previously to menginjak our webapp in browser.

          npm run web
          
        

Enter fullscreen mode
Exit fullscreen kecenderungan

You should get output similar to below screenshot, and you can go to http://localhost:8080 to see your webapp running.

Final Output


I’m sure it will help someone, at least it could save my 4 days if I know this before. All the changes done to add web support can be found in this before-update commit and this after-update commit.

I created a release also for the same.

I used the same configurations on this Dummy project and faced no issues after update.

If you find any issue in the configurations and gets it resolved, don’tepi langit hesitate to contribute to the repo.

You can connect with me on Linkedin or Github as well.

Source: https://dev.to/shivams136/create-react-native-app-for-android-ios-and-web-without-expo-48lc