Write once run anywhere with sharing components

Michael Zheng
4 min readOct 27, 2018

--

Write once, run anywhere

Ever wonder writing only one piece of code but run it across all platforms (web + native)? Yes, you definitely can!

Start from Native with react-ative

react-native rnweb

A project with the following directory structure will be created:

// App.js code
import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
const instructions = Platform.select({
ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',
android:
'Double tap R on your keyboard to reload,\n' +
'Shake or press menu button for dev menu',
});
type Props = {};
export default class App extends Component<Props> {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to React Native!</Text>
<Text style={styles.instructions}>To get started, edit App.js</Text>
<Text style={styles.instructions}>{instructions}</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
// index.js code
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => App);

You can checkout the source code at react-native branch of the repo

  • Run IOS
react-native run-ios
  • Run Android
react-native run-android

Make it work for Web

Ideally we would like our react native project to work on web as well, with minimum code modification. The goal can be achieved with the help of react-native-web.

Principle

In the react native code, we import modules like AppRegistry and Text from react-native package. These modules aren’t recognized by web browsers and thus won’t work if used directly on web platform. react-native-web implements the same protocols/APIs in web terminology used in react-native. By aliasing react-native to react-native-web in webpack config, we are then able to use the equivalent web components without code change.

//webpack.config.js
resolve: {
extensions: [".js", ".jsx"],
alias: {
"react-native": "react-native-web"
}
},

Packaging with the above webpack config with the following code

import {Text} from 'react-native'

=> actally imports Text component from ‘react-native-web’.

Let’s take a look into the source code of Text component implementation in react-native-web. In the render function the return is either a div or span element which works on web platform.

// Text/index.js
import applyLayout from '../../modules/applyLayout';
import applyNativeMethods from '../../modules/applyNativeMethods';
import { bool } from 'prop-types';
import { Component } from 'react';
import createElement from '../createElement';
import StyleSheet from '../StyleSheet';
import TextPropTypes from './TextPropTypes';
class Text extends Component<*> {
//...
render() {
const {
dir,
numberOfLines,
onPress,
selectable,
style,
/* eslint-disable */
adjustsFontSizeToFit,
allowFontScaling,
ellipsizeMode,
lineBreakMode,
minimumFontScale,
onLayout,
onLongPress,
pressRetentionOffset,
selectionColor,
suppressHighlighting,
textBreakStrategy,
tvParallaxProperties,
/* eslint-enable */
...otherProps
} = this.props;
//... const component = isInAParentText ? 'span' : 'div'; return createElement(component, otherProps);
}
// ...
}
// ...
export default applyLayout(applyNativeMethods(Text));

react native bundling doesn’t go through webpack while web packaging does. Thus the aliasing in webpack config only works for web packaging.

Steps Details

  • Add web folder in paralell with android and ios
  • Add index.html under web folder
<!DOCTYPE html>
<html>
<head>
<title>React Native Web</title>
<meta name="viewport" content="width=device-width">
</head>
<body>
<div id="root"></div>
</body>
</html>
  • Install dependency
npm install --save react-art react-dom react-native-web 
html-webpack-plugin webpack-dev-server webpack webpack-cli
babel-loader @babel/preset-env @babel/preset-react
  • Add webpack config file: webpack.config.js
const HtmlWebPackPlugin = require("html-webpack-plugin");module.exports = {
entry: {
app: "./index.js"
},
resolve: {
extensions: [".js", ".jsx"],
alias: {
"react-native": "react-native-web"
}
},
output: {
filename: "bundle.js"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"]
}
}
}
]
},
plugins: [
new HtmlWebPackPlugin({
template: "./web/index.html",
filename: "./index.html"
})
],
devServer: {
historyApiFallback: true
}
};
  • Add quick start command in scripts attribute in package.json
{
web: "webpack-dev-server"
}
  • Update App.js
// App.js
import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
const instructions = Platform.select({
ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',
android:
'Double tap R on your keyboard to reload,\n' +
'Shake or press menu button for dev menu',
web: 'Cmd+R or F12 to reload'
});
type Props = {};
export default class App extends Component<Props> {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to React Native!</Text>
<Text style={styles.instructions}>To get started, edit App.js</Text>
<Text style={styles.instructions}>{instructions}</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
  • Update index.js
//index.js
import {AppRegistry, Platform } from 'react-native';
import App from './App';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => App);
if (Platform.OS === 'web') {
AppRegistry.runApplication(appName, {
rootTag: document.getElementById("root")
});
}
  • Run
npm run web

Reference

Notice

  • If you want to follow the latest news/articles for the series of my blogs, Please 「Watch」to Subscribe.

--

--

No responses yet