react-cool-starter

A starter boilerplate for an universal web application with the best development experience and best practices.


Keywords
starter, boilerpate, universal, react, redux, react router, express, webpack 3, es6, flow, hot reloading, babel, postcss, jest, unit testing, performance optimizing, boilerplate, code-splitting, css-modules, performance, react-hooks, react-router, redux-toolkit, security, server-side-rendering, testing-library-react, typescript, unit-testing, webpack
License
MIT
Install
npm install react-cool-starter@2.4.0

Documentation

Banner

A simple but feature rich starter boilerplate with universal, React Router dynamic routing, async Redux reducers, async data fetching and code-splitting.

The project built on the top of Node.js, Express, React, Redux and React Router. Includes all the hot stuff and modern web development tools such as Webpack 2, Babel, PostCSS, React Hot Loader 3, Immutable-js and Redux Devtools Extension. See section β€œFeatures” for more other awesome features you can expect.

I will improve the starter boilerplate continuously and keep all of the technologies on trend. Welcome to join me if you want. Hope you guys love it πŸ˜‰

πŸ‘» I'm curious what this starter boilerplate helps you guys do anything? Please feel free to tell me, let's make some sharing between us.

Build Status dependencies Status devDependencies Status Coverage Status MIT licensed

Features

Really cool starter boilerplate with the most popular technologies:

  • Universal rendering, dynamic routing, async redux reducers, async data fetching and code-splitting.
  • React as the view.
  • React Router as the router.
  • Redux's futuristic Flux implementation.
  • Immutable-js provides persistent data collections which increase efficiency and simplicity.
  • Express server.
  • Webpack 2 for bundling and "Tree-Shaking" support.
  • Babel for ES6 and ES7 transpiling.
  • React Hot Loader 3 to tweak React components in real time.
  • nodemon to monitor for any changes in your node.js application and automatically restart the server.
  • axios for universal data fetching/rehydration on the client.
  • redux-thunk as the middleware to deal with asynchronous action.
  • react-router-redux to keep your router in sync with Redux state.
  • react-helmet to manage title, meta, styles and scripts tags on both server and client.
  • webpack-isomorphic-tools to allow require() work for statics both on client and server.
  • Webpack Dev Middleware serves the files emitted from webpack over the Express server.
  • Webpack Hot Middleware allows you to add hot reloading into the Express server.
  • react-addons-shallow-compare for a performance boost, it works perfectly with immutable data structure.
  • morgan the HTTP request logger for server side debugging.
  • Redux Devtools Extension for next generation developer experience.
  • ESLint to maintain a consistent javascript code style (Airbnb's code style).
  • StyleLint to maintain a consistent css/scss code style.
  • CSS and SASS support with PostCSS for advanced transformations (e.g. autoprefixer). CSS Modules enabled.
  • Image (with image-webpack-loader for optimizing) and Font support.
  • Split vendor's libraries from client bundle.
  • No other view engines, just javascript based HTML rendering template.
  • Shared app config between development and production.
  • 404 error page and redirect handling.
  • karma, mocha, enzyme, chai and sinon as the integrated solution for wrting unit tests.
  • Testing code coverage support.

Requirements

Getting Started

1. You can start by clone the repository on your local machine by running:

git clone https://github.com/wellyshen/react-cool-starter.git
cd react-cool-starter

2. Install all of the npm packages:

npm install

3. Start to run it:

npm run start:production    # Building bundle and running production server

Now the app should be running at http://localhost:8080/

NPM Script Commands

I use better-npm-run to manage the scripts in a better way, which also provides the compatibility of corss-platform. All of the scripts are listed as following:

npm run <script> Description
start Run your app on the development server at localhost:3000. HMR will be enabled.
start:production Bundle your app to ./public/assets and run it on the production server at localhost:8080.
start:prod Run your app on the production server only at localhost:8080.
build Remove the previous bundled stuff and bundle your app to ./public/assets.
lint Lint all .js and .scss files.
lint:js Lint all .js files.
lint:style Lint all .scss files.
test Run testing once.
test:watch Run testing on every test file change.
clean:all Remove the ./public/assets and the ./coverage folder.
clean:build Remove the ./public/assets folder to clean the bundled stuff.
clean:coverage Remove the ./coverage folder to clean the code coverage report.

Note: If you get the the following message, try to run npm run build to fix it.

webpack-isomorphic-tools (waiting for the first webpack build to finish)

App Structure

Here is the structure of the app, which serve as generally accepted guidelines and patterns for building scalable apps.

.
β”œβ”€β”€ public                          # Server static files path
β”‚   β”œβ”€β”€ assets                      # All the bundled files will be placed into it
β”‚   └── favicon.ico                 # Favicon is placed in the same path with the main HTML page
β”œβ”€β”€ src                             # App source code
β”‚   β”œβ”€β”€ config                      # App configuration settings
β”‚   β”‚   β”œβ”€β”€ default.js              # Default settings
β”‚   β”‚   β”œβ”€β”€ index.js                # Configuration entry point
β”‚   β”‚   └── prod.js                 # Production settings (overrides the default settings)
β”‚   β”œβ”€β”€ components                  # Reusable components (including scss/testing files)
β”‚   β”œβ”€β”€ containers                  # Container components (including assets/action/reducer/scss/testing files)
β”‚   β”œβ”€β”€ util                        # App-wide util (including HTML render view, helpers)
β”‚   β”œβ”€β”€ redux                       # Redux related configuration scripts
β”‚   β”‚   β”œβ”€β”€ reducers.js             # The root reducer (registry and injection)
β”‚   β”‚   └── store.js                # Configure and instrument Redux store   
β”‚   β”œβ”€β”€ theme                       # App-wide style, vendor style, generally settings
β”‚   β”œβ”€β”€ client.js                   # App bootstrap and rendering (webpack entry)
β”‚   β”œβ”€β”€ routes.js                   # Routes shared between client and server side
β”‚   └── server.js                   # Express server (with webpack dev/hot middlewares)                  
β”œβ”€β”€ tools                           # Project related configurations (testing/build etc.)
β”‚   β”œβ”€β”€ openBrowser                 # Utility for opening Google Chrome 
β”‚   β”œβ”€β”€ testing                     # Testing configuration settings
β”‚   β”‚   β”œβ”€β”€ karma.conf.js           # Karma configuration file
β”‚   β”‚   └── test-bunlder.js         # Karma pre-processor settings file
β”‚   β”œβ”€β”€ webpack                     # Webpack configuration settings
β”‚   β”‚   β”œβ”€β”€ config.babel.js         # Webpack configuration file
β”‚   β”‚   β”œβ”€β”€ config.test.babel.js    # Webpack configuration file for testing (for karma config)
β”‚   β”‚   └── WIT.config.js           # Webpack Isomorphic Tools configuration file        
└── index.js                        # App start point

Server Side Security and Performance

Concering to the security and performance for Express in production, I already setup some middlewares for you:

  • helmet - Helps secure Express server with various HTTP headers.
  • hpp - Express middleware to protect against HTTP Parameter Pollution attacks.
  • compression - Gzip compression support for speeding up Express server responses.

Note: It's just a basic protected mechanism for your app, you can see the security best practices for more advanced configuration.

Setup Redux DevTools Extension

The Redux Devtools Extension let us wire up our Redux app to a time-traveling debugger. It's enabled in development only. You can follow the installation guide to use it:

For Chrome

For Firefox

For Electron

For other browsers and non-browser environment

Overview

Stateless Functional Components

React 0.14 introduced a simpler way to define components called stateless functional components. These components are written in plain JavaScript functions. In the starter boilerplate I use it wherever possible.

Adding Routes and Async Reducers

React Router provides the dynamic routing by "Code-Splitting". It's great for building scableable apps (see the document for the detail).

Here I use the System.import syntax to achieve loading the components and async reducers (by Redux) via a Promise based api, which already support by Webpack 2.

You can add your routes and async reducers in ./src/routes.js. For example:

import { injectReducer } from './redux/reducers';

// ...

export default function createRoutes(store) {
  return {

    // ...

    childRoutes: [
      {
        path: '/path',                                                         // Define your route path here
        getComponent(location, cb) {
          const importModules = Promise.all([
            System.import('./containers/MyNewRouteComponent'),                 // Add your route component here
            System.import('./containers/MyNewRouteComponent/myAsyncReducer'),  // Add your async reducer here
          ]);

          const renderRoute = loadModule(cb);

          importModules
            .then(([Component, reducer]) => {
              injectReducer(store, 'userInfo', reducer.default);               // Inject your async reducer 
                                                                               // to the store
              renderRoute(Component);
            })
            .catch(errorLoading);
        },
      },

      // ...

    ],
  };
}

Managing Title, Meta, Styles and Scripts

The parent App.js defines the base title and meta in a <Helmet {...config.app} /> component. Any sub-component can override/add properties (supports meta, link, script, style tags and html attributes). See the react-helmet documents for more info.

App config

You can store app settings under ./src/config. By default the default.js will be loaded. If the process.env.NODE_ENV matches to production, the prod.js will be used insteadlly, and it inherits the data info from default.js.

You can access the correct config with:

import config from './config';

Styles

The starter boilerplate supports CSS, SASS and CSS Modules is enabled by default. I use PostCSS plugin to parse CSS and add autoprefixer to your stylesheet. You can access your stylesheet with two ways.

With CSS Modules:

import styles from './styles.scss';

// ...

render() {
  return (
    <div className={styles.Home}> // The className matches one of CSS classes in your SCSS file
      <Helmet title="Home" />
      {this.displayUserList()}
    </div>
  );
}

Without CSS Modules (you need to turn off CSS Modules from ./tools/webpack/config.babel.js):

import './styles.scss';

// ...

render() {
  return (
    <div className="Home"> // Use the CSS class as normal
      <Helmet title="Home" />
      {this.displayUserList()}
    </div>
  );
}

By the way, if you want to use your based stylesheet or a vendor CSS framework, just import it through the ./src/containers/App/index.js file, for example:

import '../../theme/normalize.css';     // import a vendor stylesheet here
import styles from './styles.scss';     // import your based stylesheet here

const App = ({ children }) => (

  // ...

);

For the better development experience, don't forget to include those files in the ./src/util/renderHtmlPage.js, for example:

// ...

${
  Object.keys(assets.styles).length === 0 ?
    `<style>${
      // Include the vendor stylesheet and the stylesheets which you have used here
      require('../theme/normalize.css')._style +                
      require('../containers/App/styles.scss')._style +              
    }</style>` : ''
}

// ...

Image and Font

It's super easy to render the image and font both on client and server, the usage would be like below.

Using image:

// Require an image 
<img src={require('./assets/logo.svg')} alt="Logo" role="presentation" />

Using font-awesome:

// With CSS Modules
import styles from './myStyle.scss';

// ...

return (
  <div>
    <div><i className={styles.iconUser}></i> Welly</div>
  </div>
);

// Without CSS Modules
import './font-awesome.css';

// ...

return (
  <div>
    <div><i className="fa fa-user"></i> Welly</div>
  </div>
);

For using CSS Modules, you have to set the proper font path in your scss file:

$fa-font-path:"../node_modules/font-awesome/fonts";
@import "../node_modules/font-awesome/scss/font-awesome";
.icon-user {
  @extend .fa;
  @extend .fa-user;
}

Data fetching and client hydration

Just write Redux actions and stores as normal (read the Redux guide if you are new). The starter boilerplate using axios as the data fetcher, it's quite simple and easy to use. If the action creator is asynchronous then it will return a Promise (or a Promise.all) in the inner function.

You can write dispatches for actions that must be called for the container to be ready:

// Write a static function which be called by server and client
static fetchData(dispatch, params) {
  // Add the asynchronous actions which must be called while paga loading here
  return Promise.all([
    dispatch(action.fetchDataIfNeeded(params.id)),
  ]);
}

Then invoke the actions in componentDidMount. This ensures that if the component is reached on the client, then the same actions will be invoked. It's up to the action to figure out if fetches for data need to be made or not:

componentDidMount() {
  const { dispatch, params } = this.props;

  // Invoke the action for client rendering
  UserInfo.fetchData(dispatch, params);
}

Boost App Performance by Shallow Compare

If your React component's render function is "pure" (in other words, it renders the same result given the same props and state), you can use shallowCompare with shouldComponentUpdate for preventing it from re-render.

Luckily, we writing our stores using Immutable-js, the immutable data structures provides you a cheap and less verbose way to track changes on objects, which is all we need to implement shouldComponentUpdate. See the React Advanced Performance topic for more info.

How Shallow Compare is practiced:

import shallowCompare from 'react-addons-shallow-compare';

// ...

class Home extends Component {

  // ...

  shouldComponentUpdate(nextProps, nextState) {
    // Implement the Shallow Compare helper function
    return shallowCompare(this, nextProps, nextState);
  }

  // ...
}

Unit Tests

The starter boilerplate uses mocha to run your unit tests, it uses karma as the test runner, and uses enzyme as the testing utility for React, which makes it easier to assert, manipulate, and traverse your React Components' output. Moreover it also uses chai as the assertion library and uses sinon to provide the standalone test spies, stubs and mocks. The unit tests focus on four parts as below:

  • Containers
  • Components
  • Actions
  • Reducers

By the way, I use babel-plugin-istanbul to instruments your code with Istanbul coverage, the report is generated in ./coverage folder. You can configure ./tools/webpack/config.test.babel.js to ignore the files which you don't want to cover. For example:

{
  // ...

  plugins: [
    'transform-runtime',
    ['istanbul', {
      exclude: [
        '**/*-test.js',   // Ignore the files which you don't want to cover here
      ],
    }],
  ],

  // ...
}

You can also use istanbul's ignore hints to specify specific lines of code to skip instrumenting.

Troubleshooting

If the hot reloading doesn't working, it might caused by the following error. To solve the problem, just click the refresh button from your browser.

Refused to execute script from 'app.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.

If you run the example of the app. And you encounter the checksum error like below, try to restart the server to solve the problem. (it's a react universal issue, this solution might not works for your further developing scenarios)

React attempted to use reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server.

To Do...

There're some features or improvements I'd like to do in the near future. If you have any great ideas or suggestions, feel free to fork the repository and share it.

  • Replacing the Mocha testing framework with Jest
  • Optimizing Webpack bundle performance
  • Wepback backend bundling