Skip to content

Instantly share code, notes, and snippets.

@raarts
Last active June 16, 2020 16:28
Show Gist options
  • Save raarts/8b8637a2a2a04a2ce54214554b1b3468 to your computer and use it in GitHub Desktop.
Save raarts/8b8637a2a2a04a2ce54214554b1b3468 to your computer and use it in GitHub Desktop.
Script to create an Expo blank template, with react-native-web support
#!/bin/bash
#
# Create a RN project which includes react-native-web
# Takes one argument, the target directory (and app) name,
if [ $# -ne 1 ]
then
echo "usage: $0 <target-dir>"
exit 1
fi
TARGET=$1
mkdir $TARGET || exit
cd $TARGET
### create the expo app ###
exp init -t blank expo-app
# copy the Expo RN files
cp expo-app/App.js .
cp expo-app/app.json .
cp expo-app/package.json .
cp expo-app/.gitignore .
cp expo-app/.babelrc .
cp expo-app/.watchmanconfig .
for dir in api assets components constants navigation screens
do
if [ -d expo-app/$dir ]
then
cp -a expo-app/$dir .
fi
done
rm -rf expo-app
### create the react web-app ###
create-react-app web-app
# copy the Web React files
cat web-app/.gitignore >> .gitignore
cp -a web-app/public .
cp -a web-app/src .
rm -rf web-app
### merge both apps into one source directory ###
# Fix the package.json to include the React Web stuff
cat package.json | jq --arg version 0.1.0 --arg name $TARGET '{ name: $name } + { version: $version } + . + { scripts: {} }' \
| jq --arg start "react-scripts start" '.scripts.start = $start' \
| jq --arg build "react-scripts build" '.scripts.build = $build' \
| jq --arg test "react-scripts test --env=jsdom" '.scripts.test = $test' \
| jq --arg eject "react-scripts eject" '.scripts.eject = $eject' \
> __tmp__ ; mv -f __tmp__ package.json
# react-scripts expect all assets to be in the src/ directory
mv assets src
sed -i s:assets:src/assets:g app.json
jq -M --arg name $TARGET '.expo.name = $name | .expo.slug = $name' app.json > _tmp_ ; mv -f _tmp_ app.json
# The RN App.js is the best template, move it and its deps into src/, overwriting the Create Web version of it.
cp App.js src
for dir in api components constants navigation screens
do
if [ -d expo-app/$dir ]
then
mv $dir src
fi
done
# The new App.css file just needs to define the default viewport for web the same way RN for mobile does:
cat <<__EOF__ > public/app.css
#root {
display: flex;
flex-direction: column;
min-height: 100vh;
}
__EOF__
# replace the index.html
# %PUBLIC_URL% and comments removed, bundle.js and app.css added
cat << EOF > public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="manifest.json">
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet" href="app.css">
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
<script src="assets/bundle.js"></script>
</html>
EOF
# Make the RN App.js, just include the one in src/
cat <<__EOF__ > App.js
import App from './src/App.js';
export default App;
__EOF__
# cleanup unused files
rm -f src/logo.svg src/App.css
# now install all js packages
yarn install
### add react-native-web ###
yarn add react-native-web [email protected] [email protected]
yarn add --dev babel-plugin-react-native-web
sed -i -e 's|.*"start":.*$| "start": "./node_modules/.bin/webpack-dev-server -d --config ./webpack.config.js --inline --hot --colors --content-base public/",|' package.json
sed -i -e 's|.*"build":.*$| "build": "NODE_ENV=production ./node_modules/.bin/webpack -p --config ./webpack.config.js",|' package.json
cat << EOF > webpack.config.js
// web/webpack.config.js
const path = require('path');
const webpack = require('webpack');
const appDirectory = path.resolve(__dirname, './');
// 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$/,
// Add every directory that needs to be compiled by Babel during the build.
include: [
path.resolve(appDirectory, 'src'),
path.resolve(appDirectory, 'node_modules/react-navigation'),
path.resolve(appDirectory, 'node_modules/react-native-tab-view'),
path.resolve(appDirectory, 'node_modules/react-native-safe-area-view'),
],
use: {
loader: 'babel-loader',
options: {
cacheDirectory: false,
babelrc: false,
// Babel configuration (or use .babelrc)
// This aliases 'react-native' to 'react-native-web' and includes only
// the modules needed by the app.
plugins: [
// This is to make react-navigation work with react-native-web
['transform-imports', {
'react-native': {
'transform': function(importName) {
if (importName === 'DeviceInfo' || importName == 'ViewPagerAndroid') {
return appDirectory + '/src/compat/' + importName;
}
return 'react-native-web';
},
skipDefaultConversion: true,
}
}
],
'react-native-web',
'transform-runtime',
],
// The 'react-native' preset is recommended to match React Native's packager
presets: ['react-native'],
},
},
};
// This is needed for loading css
const cssLoaderConfiguration = {
test: /\.css$/,
use: ['style-loader', 'css-loader'],
};
const imageLoaderConfiguration = {
test: /\.(gif|jpe?g|png|svg)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
},
},
};
module.exports = {
// your web-specific entry file
entry: path.resolve(appDirectory, 'src/index.js'),
// configures where the build ends up
output: {
filename: 'bundle.js',
publicPath: '/assets/',
path: path.resolve(appDirectory, './public/assets'),
},
module: {
rules: [
babelLoaderConfiguration,
cssLoaderConfiguration,
imageLoaderConfiguration,
],
},
plugins: [
// process.env.NODE_ENV === 'production' must be true for production
// builds to eliminate development checks and reduce build size. You may
// wish to include additional optimizations.
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
__DEV__: process.env.NODE_ENV === 'production' || true,
}),
],
resolve: {
// If you're working on a multi-platform React Native app, web-specific
// module implementations should be written in files using the extension
// '.web.js'.
extensions: ['.web.js', '.js'],
alias: {
'react-native': 'react-native-web',
},
},
};
EOF
### add react-navigation, comment this out if you don't need it ###
yarn add react-navigation babel-plugin-transform-imports
# Add basic StackNavigator example
cat << EOF > src/App.js
import React from 'react';
import { View, StyleSheet, Text } from 'react-native';
import { StackNavigator } from 'react-navigation';
class HomeScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
</View>
);
}
}
export default StackNavigator({
Home: {
screen: HomeScreen,
},
});
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
EOF
# Add stub components because react-native-web does not provide them
mkdir src/compat
for component in DeviceInfo ViewPagerAndroid
do
cat << EOF > src/compat/$component.web.js
const $component = {
type: 'screen',
}
export {
$component
}
EOF
done
#EOF
@raarts
Copy link
Author

raarts commented Jan 7, 2018

If someone encounters the same problems as I had when trying Expo with react-native-web, here's my solution.
This bash script takes a directory name, and creates an expo blank template project with react-native-web support.
It works from ios, android, web, and it builds and deploys correctly.

For expo stuff use the familiar exp commands, for react/web operations, use yarn
If you get strange problems, try clearing caches, for example exp start -c

@jacklinton
Copy link

Brilliant, and excellent timing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment