├── .babelrc ├── .flowconfig ├── .gitignore ├── .watchmanconfig ├── App.js ├── App.test.js ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── app.json ├── docs └── master.png ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── App.js ├── App.test.js ├── assets │ └── react-logo.png ├── index.css ├── index.js └── registerServiceWorker.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["babel-preset-expo"], 3 | "env": { 4 | "development": { 5 | "plugins": ["transform-object-rest-spread", "transform-react-jsx-source"] 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore templates for 'react-native init' 6 | /node_modules/react-native/local-cli/templates/.* 7 | 8 | ; Ignore RN jest 9 | /node_modules/react-native/jest/.* 10 | 11 | ; Ignore RNTester 12 | /node_modules/react-native/RNTester/.* 13 | 14 | ; Ignore the website subdir 15 | /node_modules/react-native/website/.* 16 | 17 | ; Ignore the Dangerfile 18 | /node_modules/react-native/danger/dangerfile.js 19 | 20 | ; Ignore Fbemitter 21 | /node_modules/fbemitter/.* 22 | 23 | ; Ignore "BUCK" generated dirs 24 | /node_modules/react-native/\.buckd/ 25 | 26 | ; Ignore unexpected extra "@providesModule" 27 | .*/node_modules/.*/node_modules/fbjs/.* 28 | 29 | ; Ignore polyfills 30 | /node_modules/react-native/Libraries/polyfills/.* 31 | 32 | ; Ignore various node_modules 33 | /node_modules/react-native-gesture-handler/.* 34 | /node_modules/expo/.* 35 | /node_modules/react-navigation/.* 36 | /node_modules/xdl/.* 37 | /node_modules/reqwest/.* 38 | /node_modules/metro-bundler/.* 39 | 40 | [include] 41 | 42 | [libs] 43 | node_modules/react-native/Libraries/react-native/react-native-interface.js 44 | node_modules/react-native/flow/ 45 | node_modules/expo/flow/ 46 | 47 | [options] 48 | emoji=true 49 | 50 | module.system=haste 51 | 52 | module.file_ext=.js 53 | module.file_ext=.jsx 54 | module.file_ext=.json 55 | module.file_ext=.ios.js 56 | 57 | munge_underscores=true 58 | 59 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 60 | 61 | suppress_type=$FlowIssue 62 | suppress_type=$FlowFixMe 63 | suppress_type=$FlowFixMeProps 64 | suppress_type=$FlowFixMeState 65 | suppress_type=$FixMe 66 | 67 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\) 68 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+ 69 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 70 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 71 | 72 | unsafe.enable_getters_and_setters=true 73 | 74 | [version] 75 | ^0.67.0 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.expo 6 | 7 | # testing 8 | /coverage 9 | 10 | # production 11 | /build 12 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | .*.swp 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import HybridApp from "./src/App"; 3 | 4 | export default class NativeApp extends React.Component { 5 | render() { 6 | return ; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import App from './App'; 3 | 4 | import renderer from 'react-test-renderer'; 5 | 6 | it('renders without crashing', () => { 7 | const rendered = renderer.create().toJSON(); 8 | expect(rendered).toBeTruthy(); 9 | }); -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at joseph@fazzino.net. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Joe Fazzino 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native Web Starter 2 | 3 | > ## Notice: You should probably just use Expo now unless you really want to fork this because it has [built in support for React Native Web](https://docs.expo.io/versions/latest/introduction/running-in-the-browser/) 🙂 4 | 5 | ## Introduction 6 | 7 | This repo is intending to provide an easy starting point for developers looking to make fully cross platform applications across both web with [React Native Web](https://github.com/necolas/react-native-web) and mobile with [Expo](https://github.com/react-community/create-react-native-app). 8 | 9 | All branches represent a particular starting point. 10 | 11 | It is bootstrapped with [Create React App](https://github.com/facebook/create-react-app) so you can run `yarn web` in order to start up the development web server with all the hot reloading goodness you've come to expect. 12 | 13 | It has then been integrated with [Create React Native App](https://github.com/react-community/create-react-native-app) and running `yarn ios` or `yarn android` will start the Expo packager. You can also run the project from the Expo XDE program. 14 | 15 | Note that it should be possible to eject from Expo to native using `yarn eject-native`, however it is not possible to automatically eject from CRA web build. There are quite a few ways to manually port out of CRA web build as described in the following resources: 16 | - 17 | - 18 | 19 | ## Branches 20 | 21 | | Branch | Description | 22 | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 23 | | master | The most minimal possible boilerplate, will always be the case | 24 | | redux | Implements redux reducers, actions, store and connection including example | 25 | | navigation-react-router | Using react-router-dom, react-router-native and react-router-navigation in order to have a platform agnostic navigation solution including native look at feel on phones | 26 | | typescript | Uses the TypeScript compiler with command `yarn watch` to track file changes and compile on the fly for stronger typed RNW code | 27 | 28 | ## Get Started 29 | 30 | Clone the branch with the starting point you want and just rename the project (don't forget the `package.json`, Run `git remote rm origin && yarn` to remove the ref to this repo and install node_modules then you're good to go. 🙂 31 | 32 | A full list of the scripts defined in `package.json` is shown below. 33 | 34 | | Script | Action | 35 | | ------------------- | ------------------------------------------------------- | 36 | | `yarn web` | Start CRA Development Build | 37 | | `yarn build-web` | Create production build for web | 38 | | `yarn start-expo` | Start the Expo packager | 39 | | `yarn start-native` | Start the native packager (not supported) | 40 | | `yarn eject-native` | Eject from Expo | 41 | | `yarn android` | Start expo packager and install app to Android Emulator | 42 | | `yarn ios` | Start expo packager and install app to iOS Simulator | 43 | | `yarn test` | not supported | 44 | 45 | ### Future Plans for this Library 46 | 47 | The aim for this library is to make several branches that have different starting points i.e. Navigation, Auth etc. 48 | 49 | Master branch will always be the most minimal starting point. 50 | 51 | I'm also toying with the idea of doing a starting point with React Native CLI instead of Expo however that will mean the UI for mobile will less predictiable than developing with Expo. 52 | 53 | If you have any ideas for a boilerplate then please create an issue or even better a pull request! I'm hoping to get this repo to be a big hub for getting started with React Native Web with tutorials etc. 54 | 55 | #### Tasks 56 | 57 | * ReasonML 58 | 59 | ### Credit 60 | 61 | * Huge thanks to [Nicolas Gallagher](https://github.com/necolas) for making React Native Web 62 | * Thanks to [Expo](https://expo.io/) for making it so easy to get started with React Native development 63 | * Thanks to [Yannick Spark](https://twitter.com/yannickdot) for writing [this great article](https://medium.com/@yannickdot/write-once-run-anywhere-with-create-react-native-app-and-react-native-web-ad40db63eed0) which inspired me to get started with React Native Web 64 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "sdkVersion": "30.0.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /docs/master.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefazz/react-native-web-starter/b21f0b5d9680f0ce2af60622f4cd4847eabd27df/docs/master.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-web-starter", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "expo": "^30.0.0", 7 | "react": "16.5.1", 8 | "react-dom": "^16.5.1", 9 | "react-native": "https://github.com/expo/react-native/archive/sdk-30.0.0.tar.gz", 10 | "react-native-web": "^0.11.2" 11 | }, 12 | "main": "./node_modules/react-native-scripts/build/bin/crna-entry.js", 13 | "scripts": { 14 | "web": "react-scripts start", 15 | "build-web": "react-scripts build", 16 | "start-expo": "expo start", 17 | "start-native": "react-native-scripts start", 18 | "eject-native": "react-native-scripts eject", 19 | "android": "react-native-scripts android", 20 | "ios": "react-native-scripts ios", 21 | "test": "echo 'not supported' && exit 1" 22 | }, 23 | "jest": { 24 | "preset": "jest-expo" 25 | }, 26 | "devDependencies": { 27 | "babel-plugin-react-native-web": "^0.11.2", 28 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 29 | "babel-plugin-transform-react-jsx-source": "^6.22.0", 30 | "babel-preset-expo": "^4.0.0", 31 | "flow-bin": "^0.66.0", 32 | "react-art": "16.5.1", 33 | "react-native-scripts": "^1.13.1", 34 | "react-scripts": "1.1.4", 35 | "react-test-renderer": "16.5.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefazz/react-native-web-starter/b21f0b5d9680f0ce2af60622f4cd4847eabd27df/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, Text, Animated, StyleSheet } from "react-native"; 3 | 4 | const logo = require("./assets/react-logo.png"); 5 | 6 | export default class App extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | 10 | this.imageAnimation = new Animated.Value(0); 11 | } 12 | 13 | componentDidMount() { 14 | Animated.loop( 15 | Animated.timing(this.imageAnimation, { 16 | toValue: 1, 17 | duration: 1005 18 | }) 19 | ).start(); 20 | } 21 | 22 | render() { 23 | const rotationStyle = { 24 | transform: [ 25 | { 26 | rotate: this.imageAnimation.interpolate({ 27 | inputRange: [0, 1], 28 | outputRange: ["0deg", "360deg"] 29 | }) 30 | } 31 | ] 32 | }; 33 | 34 | return ( 35 | 36 | 37 | 42 | Welcome to React Native Web️ 43 | Vanilla Edition 44 | 45 | 46 | To get started, edit src/App.js and save to reload. 47 | 48 | 49 | ); 50 | } 51 | } 52 | 53 | const styles = StyleSheet.create({ 54 | app: { 55 | flex: 1 56 | }, 57 | appHeader: { 58 | flex: 1, 59 | backgroundColor: "#222", 60 | padding: 20, 61 | justifyContent: "center", 62 | alignItems: "center" 63 | }, 64 | headerImage: { 65 | width: 200, 66 | height: 200, 67 | flex: 3 68 | }, 69 | appTitle: { 70 | flex: 1, 71 | fontSize: 16, 72 | color: "white" 73 | }, 74 | appSubtitle: { 75 | color: "white" 76 | }, 77 | appIntro: { 78 | flex: 3, 79 | fontSize: 30, 80 | textAlign: "center" 81 | } 82 | }); 83 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/assets/react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefazz/react-native-web-starter/b21f0b5d9680f0ce2af60622f4cd4847eabd27df/src/assets/react-logo.png -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | 7 | #root { 8 | display: flex; 9 | flex-direction: column; 10 | min-height: 100vh; 11 | max-height: 100vh; 12 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import './index.css'; 5 | import App from './App'; 6 | import registerServiceWorker from './registerServiceWorker'; 7 | 8 | ReactDOM.render( 9 | 10 | , document.getElementById('root')); 11 | registerServiceWorker(); 12 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | } else { 70 | // At this point, everything has been precached. 71 | // It's the perfect time to display a 72 | // "Content is cached for offline use." message. 73 | console.log('Content is cached for offline use.'); 74 | } 75 | } 76 | }; 77 | }; 78 | }) 79 | .catch(error => { 80 | console.error('Error during service worker registration:', error); 81 | }); 82 | } 83 | 84 | function checkValidServiceWorker(swUrl) { 85 | // Check if the service worker can be found. If it can't reload the page. 86 | fetch(swUrl) 87 | .then(response => { 88 | // Ensure service worker exists, and that we really are getting a JS file. 89 | if ( 90 | response.status === 404 || 91 | response.headers.get('content-type').indexOf('javascript') === -1 92 | ) { 93 | // No service worker found. Probably a different app. Reload the page. 94 | navigator.serviceWorker.ready.then(registration => { 95 | registration.unregister().then(() => { 96 | window.location.reload(); 97 | }); 98 | }); 99 | } else { 100 | // Service worker found. Proceed as normal. 101 | registerValidSW(swUrl); 102 | } 103 | }) 104 | .catch(() => { 105 | console.log( 106 | 'No internet connection found. App is running in offline mode.' 107 | ); 108 | }); 109 | } 110 | 111 | export function unregister() { 112 | if ('serviceWorker' in navigator) { 113 | navigator.serviceWorker.ready.then(registration => { 114 | registration.unregister(); 115 | }); 116 | } 117 | } 118 | --------------------------------------------------------------------------------