React Native Web & Isomorphic magic

One of the most appealing qualities of React Native is the idea of isomorphic code. Code, that can run on both iOS and Android, enabling you to write 2 apps at the same time.

But, there is another benefit to isomorphic code. You can also speed up writing your code just for one app.

Since react Native is using React code, I initially wanted to develop parts of the application “the React way”, using hot reloading, React Devtools and the fast feedback cycle that web development permits. That would let me work on the app without emulators and building the code.

It’s possible and much more.

React-Native-Web

‘react-native-web’ builts your react native app as a web app. Not pieces and snippets, but the whole app. Granted, there is a lot of functionality missing, so it’s almost certain that you will have to modify your source a bit to even make the build work, but there is one use-case that it’s perfect for:

UI components.

In TeamParrot I have separated all UI components as “dumb components” without much logic nor dependencies. That way, I can develop UI separately from the whole app. I have made specific container-component for web that holds all of these dumb components and allows me to see all of them in one place, iterate and work on in my web browser.

Let’s do this!

If you want this also for your app, here is how I’ve done it for TeamParrot:

React-Native-Web-Starter

I used react native web boilerplate called react-native-web-starter to get started. I downloaded the latest master, dumped the files into root of TeamParrot project and… Build failed. Now web, ios, and android build stopped working.

Now, let’s fix it!

First need proper libraries in your package.json devDependencies:

"react-native-web": "^0.0.39",
"babel-core": "6.9.1",
"babel-loader": "6.2.4",
"babel-plugin-transform-decorators-legacy": "1.3.4",
"babel-preset-es2015": "6.9.0",
"babel-preset-react": "6.5.0",
"babel-preset-stage-1": "6.5.0",
"babel-runtime": "6.9.2",
"webpack": "1.13.1",
"webpack-dev-server": "1.14.1"

You need to remove .babelrc provided by react-native-web-starter, since that will interfere with RN babel build process. But the configuration from .babelrc needs to end up somewhere, so you need to modify your package.json loader to include:

{
    test: /\.js$/,
    exclude: /node_modules/,
    loader: 'babel-loader',
    query: {
        cacheDirectory: true,
        "presets": [
            "es2015",
            "stage-1",
            "react"
        ],
        "plugins": [
            "transform-decorators-legacy"
        ]
    },
}

Everything seems ok at this moment, but the app still does not want to load. After debugging for a while it turned out that these components didnt want to play nice with React Native Web:

  • Switch used for toggles
  • Icon from `react-native-vector-icons`
  • RefreshControl used for “Pull to refresh” on iOS.

I decided on a n ugly solution that I would just render “Text” instead of Icon and not render Switch or RefreshControl at all by passing “webDevelopment” prop:


export default function MainView( { queue, playing, channels, stop, pause, play, refresh, loading, header, openDrawer, webDevelopment = false } ) {
	const msg = get( queue, [ 0, 'items', 0 ] );
	const scrollProps = { style: { flex: 4 } };
	if ( ! webDevelopment ) {
		//When building this component inside react-native-web, RefreshControl does not work correctly
		scrollProps[ 'refreshControl' ] = ;
	}

The last problem I needed to solve was loading images. React Native has a an Image component, to which you pass local images by require prop:

To make this work on web, with webpack, you need to install file-loader:

npm install –save-dev file-loader

Webpackconfig

it’s located under /web/webpack.config.dev.js :


const path = require('path')
const webpack = require('webpack')

const DIRECTORY = path.join(__dirname)

module.exports = {
    devServer: {
        contentBase: path.join(__dirname, 'src')
    },
    entry: [
        path.join(__dirname, '../index.web.js')
    ],
    module: {
        loaders: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                query: {
                    cacheDirectory: true,
                    "presets": [
                        "es2015",
                        "stage-1",
                        "react"
                    ],
                    "plugins": [
                        "transform-decorators-legacy"
                    ]
                }
            },
            {
                test: /\.(png|jpg|gif)$/,
                loader: "file-loader?name=img/img-[hash:6].[ext]"
            }
        ]
    },
    output: {
        filename: 'bundle.js'
    },
    plugins: [
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
        }),
        new webpack.optimize.DedupePlugin(),
        new webpack.optimize.OccurenceOrderPlugin()
    ],
    resolve: {
        alias: {
            'react-native': 'react-native-web'
        }
    }
}

Actual web code

This is my index.web.js:


import { AppRegistry } from 'react-native'; import Screens from './web/screens';

AppRegistry.registerComponent('App', () => Screens); AppRegistry.runApplication('App', { rootTag: document.getElementById('react-root') } );

Let’s roll!

And this is how I run my webpack dev server:

webpack-dev-server --port 3000 --config web/webpack.config.dev.js --inline --hot --colors --quiet

Zrzut ekranu 2016-11-12 o 19.37.20.png
Beauty of developing React Native app in devtools

Try it! It really speeds up the whole process!

React Native Isomorphic app over the weekend

This article is part of my “React Native Isomorphic app over the weekend” series. It shares the problems I encountered during development of TeamParrot and Headstart Journal.

[display-posts tag=”React-Native-Weekend” wrapper=”ul”]

2 Comments

  1. Thanks for sharing this. I wouldn’t think of developing mobile app UI using web browser in first place.

    Are there any efforts to build one platform for mobile, web, tv, console and desktop? Or do we need until everything is going to merge into one type 🙂

Leave a Reply