Last active
June 16, 2020 16:28
-
-
Save raarts/8b8637a2a2a04a2ce54214554b1b3468 to your computer and use it in GitHub Desktop.
Script to create an Expo blank template, with react-native-web support
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
Brilliant, and excellent timing!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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, useyarn
If you get strange problems, try clearing caches, for example
exp start -c