├── .nvmrc ├── .npmrc ├── .eslintignore ├── public ├── robots.txt ├── favicon.ico ├── favicons │ ├── favicon-128.png │ ├── mstile-70x70.png │ ├── favicon-16x16.png │ ├── favicon-196x196.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── mstile-144x144.png │ ├── mstile-150x150.png │ ├── mstile-310x150.png │ ├── mstile-310x310.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon-57x57.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-72x72.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon-114x114.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-144x144.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-180x180.png │ └── safari-pinned-tab.svg ├── browserconfig.xml └── manifest.json ├── internal ├── jest │ ├── assetMock.js │ └── styleMock.js ├── scripts │ ├── deploy.js │ ├── clean.js │ ├── build.js │ ├── preinstall.js │ └── analyze.js ├── .eslintrc ├── docs │ ├── ADDING_AN_API_BUNDLE.md │ ├── DEPLOY_TO_NOW.md │ ├── PKG_SCRIPTS.md │ ├── FEATURE_BRANCHES.md │ ├── FAQ.md │ ├── PROJECT_OVERVIEW.md │ └── PROJECT_CONFIG.md ├── webpack │ ├── withServiceWorker │ │ ├── offlinePageTemplate.js │ │ └── index.js │ └── configFactory.js ├── utils.js └── development │ ├── index.js │ ├── listenerManager.js │ ├── hotClientServer.js │ ├── hotNodeServer.js │ ├── hotDevelopment.js │ └── createVendorDLL.js ├── .modernizrrc ├── shared ├── utils │ ├── logic │ │ ├── index.js │ │ ├── ifElse.js │ │ └── __tests__ │ │ │ └── ifElse.test.js │ ├── arrays │ │ ├── index.js │ │ ├── removeNil.js │ │ └── __tests__ │ │ │ └── removeNil.test.js │ └── objects │ │ ├── index.js │ │ ├── __tests__ │ │ ├── mergeDeep.test.js │ │ └── filterWithRules.test.js │ │ ├── mergeDeep.js │ │ └── filterWithRules.js ├── components │ ├── DemoApp │ │ ├── Header │ │ │ ├── Logo │ │ │ │ ├── logo.png │ │ │ │ └── index.js │ │ │ ├── Menu │ │ │ │ ├── __tests__ │ │ │ │ │ ├── Menu.test.js │ │ │ │ │ └── __snapshots__ │ │ │ │ │ │ └── Menu.test.js.snap │ │ │ │ └── index.js │ │ │ └── index.js │ │ ├── Error404 │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── Error404.test.js.snap │ │ │ │ └── Error404.test.js │ │ │ └── index.js │ │ ├── AsyncCounterRoute │ │ │ ├── index.js │ │ │ └── CounterRoute.js │ │ ├── AsyncAboutRoute │ │ │ ├── index.js │ │ │ ├── __tests__ │ │ │ │ ├── AboutRoute.test.js │ │ │ │ └── __snapshots__ │ │ │ │ │ └── AboutRoute.test.js.snap │ │ │ └── AboutRoute.js │ │ ├── AsyncHomeRoute │ │ │ ├── index.js │ │ │ ├── __tests__ │ │ │ │ ├── HomeRoute.test.js │ │ │ │ └── __snapshots__ │ │ │ │ │ └── HomeRoute.test.js.snap │ │ │ └── HomeRoute.js │ │ ├── globals.css │ │ └── index.js │ └── HTML │ │ └── index.js └── README.md ├── server ├── middleware │ ├── clientBundle.js │ ├── serviceWorker.js │ ├── errorHandlers.js │ ├── offlinePage.js │ ├── reactApplication │ │ ├── getClientBundleEntryAssets.js │ │ ├── index.js │ │ └── ServerHTML.js │ └── security.js └── index.js ├── client ├── components │ └── ReactHotLoader.js ├── polyfills │ └── index.js ├── registerServiceWorker.js └── index.js ├── .babelrc ├── .editorconfig ├── .gitignore ├── .eslintrc ├── .env_example ├── LICENSE ├── config ├── components │ └── ClientConfig.js ├── utils │ └── envVars.js ├── index.js └── values.js ├── package.json ├── .all-contributorsrc └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v6.11.1 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact = true 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | -------------------------------------------------------------------------------- /internal/jest/assetMock.js: -------------------------------------------------------------------------------- 1 | module.exports = '/asset/mock'; 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.modernizrrc: -------------------------------------------------------------------------------- 1 | { 2 | "minify": true, 3 | "options": [], 4 | "feature-detects": [ 5 | "elem/picture" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /public/favicons/favicon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/favicon-128.png -------------------------------------------------------------------------------- /public/favicons/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/mstile-70x70.png -------------------------------------------------------------------------------- /public/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicons/favicon-196x196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/favicon-196x196.png -------------------------------------------------------------------------------- /public/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/favicon-96x96.png -------------------------------------------------------------------------------- /public/favicons/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/mstile-144x144.png -------------------------------------------------------------------------------- /public/favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /public/favicons/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/mstile-310x150.png -------------------------------------------------------------------------------- /public/favicons/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/mstile-310x310.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/public/favicons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /shared/utils/logic/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | 3 | import ifElse from './ifElse'; 4 | 5 | export { ifElse }; 6 | -------------------------------------------------------------------------------- /internal/jest/styleMock.js: -------------------------------------------------------------------------------- 1 | // internal/test/styleMock.js 2 | // Return an object to emulate css modules (if you are using them) 3 | module.exports = {}; 4 | -------------------------------------------------------------------------------- /shared/components/DemoApp/Header/Logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrlplusb/react-universally/HEAD/shared/components/DemoApp/Header/Logo/logo.png -------------------------------------------------------------------------------- /shared/utils/arrays/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | 3 | import removeNil from './removeNil'; 4 | 5 | export { removeNil }; 6 | -------------------------------------------------------------------------------- /shared/utils/objects/index.js: -------------------------------------------------------------------------------- 1 | import filterWithRules from './filterWithRules'; 2 | import mergeDeep from './mergeDeep'; 3 | 4 | export { filterWithRules, mergeDeep }; 5 | -------------------------------------------------------------------------------- /internal/scripts/deploy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Deploys to now. 3 | * @see https://zeit.co/now 4 | */ 5 | 6 | import { exec } from '../utils'; 7 | const cmd = 'now'; 8 | exec(cmd); 9 | -------------------------------------------------------------------------------- /shared/README.md: -------------------------------------------------------------------------------- 1 | # src/shared 2 | 3 | This directory contains code that is shared between our bundles and should be considered safe to execute on either a `node` or `web` target (i.e. "Universal" code). 4 | -------------------------------------------------------------------------------- /shared/components/DemoApp/Error404/__tests__/__snapshots__/Error404.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` renders 1`] = ` 4 |
5 | Sorry, that page was not found. 6 |
7 | `; 8 | -------------------------------------------------------------------------------- /internal/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "global-require": 0, 4 | "no-console": 0, 5 | "import/no-extraneous-dependencies": 0, 6 | "import/no-dynamic-require": 0, 7 | "import/newline-after-import": 0 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /shared/components/DemoApp/Header/Logo/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import logo from './logo.png'; 3 | 4 | function Logo() { 5 | return Logo; 6 | } 7 | 8 | export default Logo; 9 | -------------------------------------------------------------------------------- /shared/components/DemoApp/AsyncCounterRoute/index.js: -------------------------------------------------------------------------------- 1 | import { asyncComponent } from 'react-async-component'; 2 | 3 | export default asyncComponent({ 4 | resolve: () => System.import(/* webpackChunkName: "counter" */ './CounterRoute'), 5 | }); 6 | -------------------------------------------------------------------------------- /public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #2b2b2b 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /shared/components/DemoApp/AsyncAboutRoute/index.js: -------------------------------------------------------------------------------- 1 | import { asyncComponent } from 'react-async-component'; 2 | 3 | export default asyncComponent({ 4 | // include home and about route in same chunk e.g main 5 | resolve: () => System.import(/* webpackChunkName: "main" */ './AboutRoute'), 6 | }); 7 | -------------------------------------------------------------------------------- /shared/components/DemoApp/AsyncHomeRoute/index.js: -------------------------------------------------------------------------------- 1 | import { asyncComponent } from 'react-async-component'; 2 | 3 | export default asyncComponent({ 4 | // include home and about route in same chunk e.g main 5 | resolve: () => System.import(/* webpackChunkName: "main" */ './HomeRoute'), 6 | }); 7 | -------------------------------------------------------------------------------- /shared/utils/arrays/removeNil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Filters out all null/undefined items from the given array. 3 | * 4 | * @param {Array} as - the target array 5 | * 6 | * @return {Array} The filtered array. 7 | */ 8 | export default function removeNil(as) { 9 | return as.filter(a => a != null); 10 | } 11 | -------------------------------------------------------------------------------- /shared/components/DemoApp/Header/Menu/__tests__/Menu.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | 3 | import React from 'react'; 4 | import { shallow } from 'enzyme'; 5 | 6 | import Menu from '../index'; 7 | 8 | describe('', () => { 9 | test('renders', () => { 10 | const wrapper = shallow(); 11 | expect(wrapper).toMatchSnapshot(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /shared/components/DemoApp/globals.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | font-family: Arial, sans-serif; 4 | } 5 | 6 | *, 7 | *:before, 8 | *:after { 9 | box-sizing: border-box; 10 | margin: 0; 11 | padding: 0; 12 | } 13 | 14 | /* Inline our ul for our menu */ 15 | ul { 16 | margin: 0; 17 | padding: 0; 18 | list-style-type: none; 19 | } 20 | 21 | ul li { display: inline; margin: 0 .5rem; } 22 | -------------------------------------------------------------------------------- /server/middleware/clientBundle.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { resolve as pathResolve } from 'path'; 3 | import appRootDir from 'app-root-dir'; 4 | import config from '../../config'; 5 | 6 | /** 7 | * Middleware to server our client bundle. 8 | */ 9 | export default express.static(pathResolve(appRootDir.get(), config('bundles.client.outputPath')), { 10 | maxAge: config('browserCacheMaxAge'), 11 | }); 12 | -------------------------------------------------------------------------------- /shared/components/DemoApp/AsyncHomeRoute/__tests__/HomeRoute.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | 3 | import React from 'react'; 4 | import { shallow } from 'enzyme'; 5 | 6 | import HomeRoute from '../HomeRoute'; 7 | 8 | describe('', () => { 9 | test('renders', () => { 10 | const wrapper = shallow(); 11 | expect(wrapper).toMatchSnapshot(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /shared/components/DemoApp/AsyncAboutRoute/__tests__/AboutRoute.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | 3 | import React from 'react'; 4 | import { shallow } from 'enzyme'; 5 | 6 | import AboutRoute from '../AboutRoute'; 7 | 8 | describe('', () => { 9 | test('renders', () => { 10 | const wrapper = shallow(); 11 | expect(wrapper).toMatchSnapshot(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /shared/components/DemoApp/Header/Menu/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from 'react-router-dom/Link'; 3 | 4 | function Menu() { 5 | return ( 6 |
    7 |
  • Home
  • 8 |
  • Counter
  • 9 |
  • About
  • 10 |
11 | ); 12 | } 13 | 14 | export default Menu; 15 | -------------------------------------------------------------------------------- /shared/components/DemoApp/Header/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Logo from './Logo'; 3 | import Menu from './Menu'; 4 | 5 | function Header() { 6 | return ( 7 |
8 | 9 |

React, Universally

10 | 11 | A starter kit for universal react applications. 12 | 13 | 14 |
15 | ); 16 | } 17 | 18 | export default Header; 19 | -------------------------------------------------------------------------------- /internal/scripts/clean.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This script removes any exisitng build output. 3 | */ 4 | 5 | import { resolve as pathResolve } from 'path'; 6 | import appRootDir from 'app-root-dir'; 7 | import rimraf from 'rimraf'; 8 | import config from '../../config'; 9 | 10 | function clean() { 11 | rimraf(pathResolve(appRootDir.get(), config('buildOutputPath')), () => { 12 | console.log(`Cleaned ${pathResolve(appRootDir.get(), config('buildOutputPath'))}`); 13 | }); 14 | } 15 | 16 | clean(); 17 | -------------------------------------------------------------------------------- /client/components/ReactHotLoader.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable import/no-extraneous-dependencies */ 3 | 4 | import React from 'react'; 5 | 6 | // We create this wrapper so that we only import react-hot-loader for a 7 | // development build. Small savings. :) 8 | const ReactHotLoader = process.env.NODE_ENV === 'development' 9 | ? require('react-hot-loader').AppContainer 10 | : ({ children }) => React.Children.only(children); 11 | 12 | export default ReactHotLoader; 13 | -------------------------------------------------------------------------------- /shared/components/DemoApp/Error404/__tests__/Error404.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | 3 | import React from 'react'; 4 | import { shallow } from 'enzyme'; 5 | 6 | import Error404 from '../index'; 7 | 8 | describe('', () => { 9 | test('renders', () => { 10 | const staticContext = {}; 11 | const wrapper = shallow(); 12 | expect(wrapper).toMatchSnapshot(); 13 | expect(staticContext.missed).toBeTruthy(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /server/middleware/serviceWorker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | 3 | import { resolve as pathResolve } from 'path'; 4 | import appRootDir from 'app-root-dir'; 5 | import config from '../../config'; 6 | 7 | // Middleware to serve our service worker. 8 | function serviceWorkerMiddleware(req, res, next) { 9 | res.sendFile( 10 | pathResolve( 11 | appRootDir.get(), 12 | config('bundles.client.outputPath'), 13 | config('serviceWorker.fileName'), 14 | ), 15 | ); 16 | } 17 | 18 | export default serviceWorkerMiddleware; 19 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | // We use the .babelrc here to help transpile our tools and support tooling 2 | // (such as testing libs). 3 | // I would recommend that you keep this file in feature parity with 4 | // the config/babelConfigResolver. That way you get to use the same level of 5 | // javascript syntax across the entire project. 6 | // It's nice not having to worry/think about what level of javascript syntax 7 | // is supported for each context. 8 | { 9 | "presets": [ 10 | ["env", { "targets": { "node": true } }], 11 | "stage-3", 12 | "react" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /shared/components/DemoApp/AsyncAboutRoute/AboutRoute.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Helmet from 'react-helmet'; 3 | 4 | function AboutRoute() { 5 | return ( 6 |
7 | 8 | About 9 | 10 | 11 |

Produced with ❤️

12 | 13 |

14 | View our contributors list on our 15 | {' '} 16 | GitHub 17 | {' '} 18 | page. 19 |

20 |
21 | ); 22 | } 23 | 24 | export default AboutRoute; 25 | -------------------------------------------------------------------------------- /shared/components/DemoApp/Error404/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class Error404 extends Component { 5 | componentWillMount() { 6 | const { staticContext } = this.props; 7 | if (staticContext) { 8 | staticContext.missed = true; 9 | } 10 | } 11 | 12 | render() { 13 | return
Sorry, that page was not found.
; 14 | } 15 | } 16 | 17 | Error404.propTypes = { 18 | // eslint-disable-next-line react/forbid-prop-types 19 | staticContext: PropTypes.object, 20 | }; 21 | 22 | Error404.defaultProps = { 23 | staticContext: {}, 24 | }; 25 | 26 | export default Error404; 27 | -------------------------------------------------------------------------------- /shared/utils/arrays/__tests__/removeNil.test.js: -------------------------------------------------------------------------------- 1 | import { removeNil } from '../'; 2 | 3 | describe('removeNil', () => { 4 | test('should do nothing if there are no null or undefined values', () => { 5 | const array = ['val1', 'val2', 'val3']; 6 | expect(removeNil(array)).toEqual(array); 7 | }); 8 | 9 | test('should remove undefined and null values', () => { 10 | const array = ['val1', 'val2', undefined, 'val3', null]; 11 | expect(removeNil(array)).toEqual(['val1', 'val2', 'val3']); 12 | }); 13 | 14 | test('should do nothing on an empty array', () => { 15 | const array = []; 16 | expect(removeNil(array)).toEqual([]); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /shared/components/DemoApp/AsyncHomeRoute/__tests__/__snapshots__/HomeRoute.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` renders 1`] = ` 4 |
5 | 9 | 10 | Home 11 | 12 | 13 |

14 | Hello world! 15 |

16 |

17 | This starter kit contains all the build tooling and configuration you need to kick off your next universal React project, whilst containing a minimal project set up allowing you to make your own architecture decisions (Redux/Mobx etc). 18 |

19 |
20 | `; 21 | -------------------------------------------------------------------------------- /client/polyfills/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import Modernizr from 'modernizr'; 4 | 5 | // This is just an illustrative example. Here you are testing the client's 6 | // support for the "picture" element, and if it isn't supported then you 7 | // load a polyfill. 8 | if (!Modernizr.picture) { 9 | console.log('Client does not support "picture", polyfilling it...'); 10 | // If you want to use the below do a `npm install picturefill -E -S` and then 11 | // uncomment the lines below: 12 | /* 13 | require('picturefill'); 14 | require('picturefill/dist/plugins/mutation/pf.mutation'); 15 | */ 16 | } else { 17 | console.log('Client has support for "picture".'); 18 | } 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # See http://editorconfig.org/ 2 | # EditorConfig helps developers define and maintain consistent coding styles 3 | # between different editors and IDEs. The EditorConfig project consists of a 4 | # file format for defining coding styles and a collection of text editor plugins 5 | # that enable editors to read the file format and adhere to defined styles. 6 | # EditorConfig files are easily readable and they work nicely with version 7 | # control systems. 8 | 9 | root = true 10 | 11 | [*] 12 | indent_style = space 13 | indent_size = 2 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /shared/components/DemoApp/AsyncAboutRoute/__tests__/__snapshots__/AboutRoute.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` renders 1`] = ` 4 |
11 | 15 | 16 | About 17 | 18 | 19 |

20 | Produced with ❤️ 21 |

22 |

23 | View our contributors list on our 24 | 25 | 28 | GitHub 29 | 30 | 31 | page. 32 |

33 |
34 | `; 35 | -------------------------------------------------------------------------------- /shared/components/DemoApp/AsyncHomeRoute/HomeRoute.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Helmet from 'react-helmet'; 3 | 4 | import config from '../../../../config'; 5 | 6 | function HomeRoute() { 7 | return ( 8 |
9 | 10 | Home 11 | 12 | 13 |

{config('welcomeMessage')}

14 | 15 |

16 | This starter kit contains all the build tooling and configuration you 17 | need to kick off your next universal React project, whilst containing a 18 | minimal project set up allowing you to make your own architecture 19 | decisions (Redux/Mobx etc). 20 |

21 |
22 | ); 23 | } 24 | 25 | export default HomeRoute; 26 | -------------------------------------------------------------------------------- /shared/components/DemoApp/Header/Menu/__tests__/__snapshots__/Menu.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` renders 1`] = ` 4 |
    13 |
  • 14 | 18 | Home 19 | 20 |
  • 21 |
  • 22 | 26 | Counter 27 | 28 |
  • 29 |
  • 30 | 34 | About 35 | 36 |
  • 37 |
38 | `; 39 | -------------------------------------------------------------------------------- /internal/docs/ADDING_AN_API_BUNDLE.md: -------------------------------------------------------------------------------- 1 | # Adding an "API" Bundle 2 | 3 | A fairly common requirement for a project that scales is to create additional servers bundles, e.g. an API server. 4 | 5 | Instead of requiring you to hack the Webpack configuration we have have provided a section within the centralised project configuration that allows you to easily declare additional bundles. You simply need to provide the source, entry, and output paths - we take care of the rest. 6 | 7 | _IMPORTANT:_ One further requirement for this feature is that within your new server bundle you export the created http listener. This exported listener will be used by the development server so that it can automatically restart your server any time the source files for it change. 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Environment Configuration 2 | .env 3 | 4 | # Build output folders 5 | build/ 6 | 7 | # Logs 8 | logs 9 | *.log 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Dependency directory 23 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 24 | node_modules 25 | 26 | # Debug log from npm 27 | npm-debug.log 28 | 29 | # IntelliJ IDE ignore 30 | .idea 31 | 32 | # Visual Studio Code 33 | .vscode 34 | .history 35 | 36 | # flow-typed Lib Defs 37 | flow-typed/ 38 | 39 | # Flow Coverage Report 40 | flow-coverage/ 41 | 42 | # Happypack 43 | .happypack 44 | 45 | # OSX Files 46 | .DS_Store 47 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "env": { 5 | "browser": true, 6 | "es6": true, 7 | "node": true, 8 | "jest": true 9 | }, 10 | "parserOptions": { 11 | "ecmaVersion": 6, 12 | "sourceType": "module", 13 | "ecmaFeatures": { 14 | "defaultParams": true 15 | } 16 | }, 17 | "rules": { 18 | // A jsx extension is not required for files containing jsx 19 | "react/jsx-filename-extension": 0, 20 | // This rule struggles with flow and class properties 21 | "react/sort-comp": 0, 22 | // ignore linebreak style. the CRLF / LF endings wont matter 23 | // if a windows user correctly converts CRLF to LF upon commits otherwise 24 | // there are errors every line. 25 | "linebreak-style": 0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.env_example: -------------------------------------------------------------------------------- 1 | # ============================================================================== 2 | # This is an example environment configuration file. You can create your own 3 | # .env implementation or pass them via the CLI. 4 | # 5 | # You could also decide to supply some of your environment variables from a .env 6 | # file, and others via the CLI. This allows you to easily store "safe" 7 | # environment variables within this file, and then manage your more sensitive 8 | # environment variables seperately. 9 | # ============================================================================== 10 | 11 | # The host on which to run the server. 12 | HOST=localhost 13 | 14 | # The port on which to run the server. 15 | PORT=1337 16 | 17 | # The port on which to run the client bundle dev server (i.e. used during 18 | # development only). 19 | CLIENT_DEV_PORT=7331 20 | -------------------------------------------------------------------------------- /internal/webpack/withServiceWorker/offlinePageTemplate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is used by the HtmlWebpackPlugin to generate an html page that we will 3 | * use as a fallback for our service worker when the user is offline. It will 4 | * embed all the required asset paths needed to bootstrap the application 5 | * in an offline session. 6 | */ 7 | 8 | import React from 'react'; 9 | import { renderToStaticMarkup } from 'react-dom/server'; 10 | 11 | import HTML from '../../../shared/components/HTML'; 12 | 13 | module.exports = function generate(context) { 14 | // const config = context.htmlWebpackPlugin.options.custom.config; 15 | const ClientConfig = context.htmlWebpackPlugin.options.custom.ClientConfig; 16 | const html = renderToStaticMarkup( 17 | } />, 18 | ); 19 | return `${html}`; 20 | }; 21 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "React Universally", 3 | "short_name": "Rct-Unvrslly", 4 | "description": "A starter kit giving you the minimum requirements for a production ready universal react application.", 5 | "lang": "en", 6 | "start_url": "./", 7 | "icons": [ 8 | { 9 | "src": "\/android-chrome-512x512.png", 10 | "sizes": "512x512", 11 | "type": "image\/png" 12 | }, 13 | { 14 | "src": "\/android-chrome-192x192.png", 15 | "sizes": "192x192", 16 | "type": "image\/png" 17 | }, 18 | { 19 | "src": "\/apple-touch-icon.png", 20 | "sizes": "180x180", 21 | "type": "image\/png" 22 | }, 23 | { 24 | "src": "\/favicon-32x32.png", 25 | "sizes": "32x32", 26 | "type": "image\/png" 27 | }, 28 | { 29 | "src": "\/favicon-16x16.png", 30 | "sizes": "16x16", 31 | "type": "image\/png" 32 | } 33 | ], 34 | "theme_color": "#2b2b2b", 35 | "background_color": "#ffffff", 36 | "display": "standalone" 37 | } 38 | -------------------------------------------------------------------------------- /shared/utils/logic/ifElse.js: -------------------------------------------------------------------------------- 1 | const execIfFunc = x => (typeof x === 'function' ? x() : x); 2 | 3 | /** 4 | * This is a higher order function that accepts a boolean condition and will 5 | * return a function allowing you to provide if/else values that should be 6 | * resolved based on the boolean condition. 7 | * 8 | * @param {Boolean|() => Boolean} condition: 9 | * The condition to test against. This can be a function for lazy resolution. 10 | * 11 | * @return {(X|() => X, Y|() => Y) => X|Y} 12 | * A function where the first paramater is the "if" and the second paramater 13 | * is the "else". Each of these allows lazy resolving by providing a function. 14 | * 15 | * @example 16 | * const ifDev = ifElse(process.env.NODE_ENV === 'development'); 17 | * ifDev('foo', () => 'lazy resolved'); // => 'foo' 18 | */ 19 | export default function ifElse(condition) { 20 | return (then, or) => (execIfFunc(condition) ? execIfFunc(then) : execIfFunc(or)); 21 | } 22 | -------------------------------------------------------------------------------- /shared/utils/objects/__tests__/mergeDeep.test.js: -------------------------------------------------------------------------------- 1 | import { mergeDeep } from '../'; 2 | 3 | describe('mergeDeep', () => { 4 | test('merges deeply two objects together', () => { 5 | const object1 = { a: 1, b: 2, c: { a: 1, b: 2 } }; 6 | const object2 = { a: 1, c: { c: 3 } }; 7 | expect(mergeDeep(object1, object2)).toEqual({ 8 | a: 1, 9 | b: 2, 10 | c: { 11 | a: 1, 12 | b: 2, 13 | c: 3, 14 | }, 15 | }); 16 | }); 17 | 18 | test('the object to the right takes the priority', () => { 19 | const object1 = { a: 1, b: 2 }; 20 | const object2 = { a: 1, b: 3 }; 21 | expect(mergeDeep(object1, object2)).toEqual({ 22 | a: 1, 23 | b: 3, 24 | }); 25 | }); 26 | 27 | test('returns an empty object if no args are given', () => { 28 | expect(mergeDeep()).toEqual({}); 29 | }); 30 | 31 | test('returns the only object given as arg if no other args are given', () => { 32 | expect(mergeDeep({ a: 1 })).toEqual({ a: 1 }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /shared/utils/objects/mergeDeep.js: -------------------------------------------------------------------------------- 1 | import removeNil from '../arrays/removeNil'; 2 | 3 | /** 4 | * Deeply merges a given set of objects together. 5 | * 6 | * Objects to the right take priority. 7 | * 8 | * @param {...Object} args - The objects to merge. 9 | * 10 | * @return {Object} - The merged object. 11 | */ 12 | export default function mergeDeep(...args) { 13 | const filtered = removeNil(args); 14 | if (filtered.length < 1) { 15 | return {}; 16 | } 17 | if (filtered.length === 1) { 18 | return args[0]; 19 | } 20 | return filtered.reduce( 21 | (acc, cur) => { 22 | Object.keys(cur).forEach((key) => { 23 | if (typeof acc[key] === 'object' && typeof cur[key] === 'object') { 24 | // eslint-disable-next-line no-param-reassign 25 | acc[key] = mergeDeep(acc[key], cur[key]); 26 | } else { 27 | // eslint-disable-next-line no-param-reassign 28 | acc[key] = cur[key]; 29 | } 30 | }); 31 | return acc; 32 | }, 33 | {}, 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /shared/components/DemoApp/AsyncCounterRoute/CounterRoute.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/no-unescaped-entities */ 2 | import React, { Component } from 'react'; 3 | 4 | class CounterRoute extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.incrementCounter = this.incrementCounter.bind(this); 8 | this.state = { counter: 0 }; 9 | } 10 | 11 | incrementCounter() { 12 | this.setState({ counter: this.state.counter + 1 }); 13 | } 14 | 15 | render() { 16 | return ( 17 |
18 |

Counter

19 |

20 | 21 | This is a small demo component that contains state. It's useful for 22 | testing the hot reloading experience of an asyncComponent. 23 | 24 |

25 |

26 | Current value: {this.state.counter} 27 |

28 |

29 | 30 |

31 |
32 | ); 33 | } 34 | } 35 | 36 | export default CounterRoute; 37 | -------------------------------------------------------------------------------- /shared/components/HTML/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/no-danger */ 2 | /* eslint-disable jsx-a11y/html-has-lang */ 3 | 4 | import React from 'react'; 5 | import PropTypes from 'prop-types'; 6 | 7 | /** 8 | * The is the HTML shell for our React Application. 9 | */ 10 | function HTML(props) { 11 | const { htmlAttributes, headerElements, bodyElements, appBodyString } = props; 12 | 13 | return ( 14 | 15 | 16 | {headerElements} 17 | 18 | 19 |
20 | {bodyElements} 21 | 22 | 23 | ); 24 | } 25 | 26 | HTML.propTypes = { 27 | // eslint-disable-next-line react/forbid-prop-types 28 | htmlAttributes: PropTypes.object, 29 | headerElements: PropTypes.node, 30 | bodyElements: PropTypes.node, 31 | appBodyString: PropTypes.string, 32 | }; 33 | 34 | HTML.defaultProps = { 35 | htmlAttributes: null, 36 | headerElements: null, 37 | bodyElements: null, 38 | appBodyString: '', 39 | }; 40 | 41 | // EXPORT 42 | 43 | export default HTML; 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Sean Matheson 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 | -------------------------------------------------------------------------------- /internal/docs/DEPLOY_TO_NOW.md: -------------------------------------------------------------------------------- 1 | - [Project Overview](/internal/docs/PROJECT_OVERVIEW.md) 2 | - [Project Configuration](/internal/docs/PROJECT_CONFIG.md) 3 | - [Package Script Commands](/internal/docs/PKG_SCRIPTS.md) 4 | - [Feature Branches](/internal/docs/FEATURE_BRANCHES.md) 5 | - __[Deploy your very own Server Side Rendering React App in 5 easy steps](/internal/docs/DEPLOY_TO_NOW.md)__ 6 | - [FAQ](/internal/docs/FAQ.md) 7 | 8 | # Deploy your very own "React, Universally" App in 5 easy steps 9 | 10 | __Step 1: Clone the repository.__ 11 | 12 | git clone https://github.com/ctrlplusb/react-universally 13 | 14 | __Step 2: `cd` into the cloned directory__ 15 | 16 | cd react-universally 17 | 18 | __Step 3: Install the project's dependencies__ 19 | 20 | npm install 21 | 22 | __Step 4: Install the awesome [`now`](https://zeit.co/now) CLI globally__ 23 | 24 | npm install -g now 25 | 26 | __Step 5: Deploy to "now"__ 27 | 28 | npm run deploy 29 | 30 | That's it. Your clipboard will contain the address of the deployed app. Open your browser, paste, go. These guys are seriously awesome hosts. [Check them out.](https://zeit.co/now) 31 | -------------------------------------------------------------------------------- /internal/scripts/build.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This script builds a production output of all of our bundles. 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import appRootDir from 'app-root-dir'; 7 | import { resolve as pathResolve } from 'path'; 8 | import webpackConfigFactory from '../webpack/configFactory'; 9 | import { exec } from '../utils'; 10 | import config from '../../config'; 11 | 12 | // eslint-disable-next-line no-unused-vars 13 | const [x, y, ...args] = process.argv; 14 | 15 | const optimize = args.findIndex(arg => arg === '--optimize') !== -1; 16 | 17 | // First clear the build output dir. 18 | exec(`rimraf ${pathResolve(appRootDir.get(), config('buildOutputPath'))}`); 19 | 20 | // Get our "fixed" bundle names 21 | Object.keys(config('bundles')) 22 | // And the "additional" bundle names 23 | .concat(Object.keys(config('additionalNodeBundles'))) 24 | // And then build them all. 25 | .forEach((bundleName) => { 26 | const compiler = webpack(webpackConfigFactory({ target: bundleName, optimize })); 27 | compiler.run((err, stats) => { 28 | if (err) { 29 | console.error(err); 30 | return; 31 | } 32 | console.log(stats.toString({ colors: true })); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /internal/utils.js: -------------------------------------------------------------------------------- 1 | import HappyPack from 'happypack'; 2 | import notifier from 'node-notifier'; 3 | import colors from 'colors/safe'; 4 | import { execSync } from 'child_process'; 5 | import appRootDir from 'app-root-dir'; 6 | 7 | // Generates a HappyPack plugin. 8 | // @see https://github.com/amireh/happypack/ 9 | export function happyPackPlugin({ name, loaders }) { 10 | return new HappyPack({ 11 | id: name, 12 | verbose: false, 13 | threads: 4, 14 | loaders, 15 | }); 16 | } 17 | 18 | export function log(options) { 19 | const title = `${options.title.toUpperCase()}`; 20 | 21 | if (options.notify) { 22 | notifier.notify({ 23 | title, 24 | message: options.message, 25 | }); 26 | } 27 | 28 | const level = options.level || 'info'; 29 | const msg = `${title}: ${options.message}`; 30 | 31 | switch (level) { 32 | case 'warn': 33 | console.log(colors.yellow(msg)); 34 | break; 35 | case 'error': 36 | console.log(colors.bgRed.white(msg)); 37 | break; 38 | case 'special': 39 | console.log(colors.italic.cyan(msg)); 40 | break; 41 | case 'info': 42 | default: 43 | console.log(colors.green.dim(msg)); 44 | } 45 | } 46 | 47 | export function exec(command) { 48 | execSync(command, { stdio: 'inherit', cwd: appRootDir.get() }); 49 | } 50 | -------------------------------------------------------------------------------- /config/components/ClientConfig.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import serialize from 'serialize-javascript'; 4 | import filterWithRules from '../../shared/utils/objects/filterWithRules'; 5 | import values from '../values'; 6 | 7 | // Filter the config down to the properties that are allowed to be included 8 | // in the HTML response. 9 | const clientConfig = filterWithRules( 10 | // These are the rules used to filter the config. 11 | values.clientConfigFilter, 12 | // The config values to filter. 13 | values, 14 | ); 15 | 16 | const serializedClientConfig = serialize(clientConfig); 17 | 18 | /** 19 | * A react component that generates a script tag that binds the allowed 20 | * values to the window so that config values can be read within the 21 | * browser. 22 | * 23 | * They get bound to window.__CLIENT_CONFIG__ 24 | */ 25 | function ClientConfig({ nonce }) { 26 | return ( 27 |