Skip to content

Latest commit

 

History

History
689 lines (529 loc) · 37.2 KB

README.md

File metadata and controls

689 lines (529 loc) · 37.2 KB

REACT COOL STARTER

A simple but feature rich starter boilerplate for creating your own universal app. It built on the top of React, Redux, React Router and Express. Includes all the hot stuff and modern web development tools such as Redux Toolkit, TypeScript, Webpack, Babel, PostCSS, React Refresh, Jest and React Testing Library. See the “Features” section for other awesome features you can expect.

I will maintain the starter boilerplate and keep all of the technologies on trend. Welcome to join me if you want. Hope you guys love it 🤩

👻 I'm curious what kind of app that you guys building via this starter? Please feel free to tell me, let's make some sharing between us.

build status coverage status dependencies status devDependencies status code style: prettier MIT licensed All Contributors PRs welcome Twitter URL

Real Case Study

  • BAM Music is a new generation of music platform for audio visual professionals, filmmakers and content producers. Upgrade your soundtrack with great music that emphasizes your story!
  • Rendah Mag is a music magazine which exists to bring exposure to an ever-growing community, focusing on the latest Halftime, Beats & Experimental news & releases. Congrats for the amazing product.
  • DealDrop is the best place to find verified coupon codes, deals, promos and offers for thousands of stores & brands you love. Never pay full price again.
  • BECK Friends is an international delivery service, which is built based on this starter boilerplate. Congrats for successful migrating to React.

Features

Really cool starter boilerplate with the most popular technologies:

  • Universal rendering with async data fetching.
  • React as the view library.
  • Redux as the state management.
  • Redux Toolkit for efficient Redux development.
  • React Router as the router.
  • Connected React Router to bind Redux with React Router. Refer to document to see how it works.
  • Express server.
  • TypeScript as the static type checker for JavaScript.
  • Webpack for app bundling.
  • Babel for transpile ES6+ to ES5.
  • React Refresh to fast refresh components without losing their state.
  • nodemon to monitor for any changes in your Node.js application and automatically restart the server.
  • axios as the Promise-based HTTP client for the browser and Node.js.
  • react-helmet to manage title, meta, styles and scripts tags on both server and client.
  • loadable-component to lazy load a component when needed. Reduce your bundle size without stress.
  • 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.
  • css-modules-require-hook compiles CSS Modules in runtime for SSR.
  • asset-require-hook allows your assets files required during runtime for SSR.
  • webpack-manifest-plugin generates an assets manifest with hash so you can use them for SSR.
  • Webpack Bundle Analyzer creates a visualize size of webpack output files with an interactive zoomable treemap.
  • helmet helps secure Express apps with various HTTP headers.
  • morgan the HTTP request logger for server side debugging.
  • ESLint to maintain a consistent TypeScript/JavaScript code style (with Airbnb configuration).
  • StyleLint to maintain a consistent css/scss code style.
  • Prettier to format code and style.
  • CSS and SASS support with PostCSS for advanced transformations (e.g. autoprefixer, cssnext etc.). CSS modules enabled.
  • Image (compressed by image-webpack-loader) and Font support.
  • Split vendor's libraries from client bundle.
  • No other view engines, just JavaScript based HTML rendering component.
  • Shared app configuration between development and production.
  • 404 error page and redirect handling.
  • Integrate Jest with React Testing Library as the solution for writing unit tests with code coverage support.
  • Yarn as the package manager.

Who's the Starter for?

They're several React frameworks today, however this is a DIY oriented start-kit. It shows you how to build a universal web app from scratch and how to test it. If you're new to React or you want a ready-to-go solution, I'd recommend the following alternatives for you:

Requirements

Looking for Docker Image?

You can find Docker support version on this branch.

Getting Started

1. You can start by cloning 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 dependencies:

yarn

3. Start to run it:

yarn build  # Building bundle
yarn start  # Running production server

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

Note: You can change the port that you want from the ./package.json.

Script Commands

I use cross-env to set and use environment variables across platforms. All of the scripts are listed as following:

yarn <script> Description
dev Run your app on the development server at localhost:3000. HMR will be enabled.
start Run your app on the production server only at localhost:8080.
build Delete the previous bundled files and bundle it to the ./public/assets.
analyze Visualize the contents of all your bundles.
lint Lint all .tsx?, .jsx? and .scss files.
lint:code Lint all .tsx? and .jsx? files (With --fix to auto fix eslint errors).
lint:type Run type checking for .tsx? files.
lint:style Lint all .scss files (With --fix to auto fix stylelint errors).
lint:format Format all files except the file list of .prettierignore.
test Run testing.
test:watch Run an interactive test watcher.
test:cov Run testing with code coverage reports.
test:update Update jest snapshot.
clean Delete the client/server bundled stuff and the coverage report.
clean:build Delete the ./public/assets folder to clean the client bundled files.
clean:cov Delete the ./coverage folder to clean the code coverage report.

App Structure

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

.
├── public                        # Express server static path and Webpack bundled output
│   ├── favicon.ico               # App favicon
│   ├── logo192.png               # App logo small
│   ├── logo512.png               # App logo large
│   └── manifest.json             # App favicon and logo manifest
├── src                           # App source code
│   ├── config                    # App configuration by environments
│   │   ├── default.ts            # Default settings
│   │   ├── index.ts              # Configuration entry point
│   │   └── prod.ts               # Production settings (overrides the default)
│   ├── components                # Reusable components
│   ├── pages                     # Page components
│   ├── app                       # App root component
│   ├── store                     # Redux store creator, actions + reducers (a.k.a slice)
│   ├── services                  # API calls
│   ├── utils                     # App-wide utils (e.g. mock store creator for testing etc.)
│   ├── static                    # Static assets (e.g. images, fonts etc.)
│   ├── theme                     # App-wide style and vendor CSS framework
│   ├── types                     # App-wide type definitions
│   ├── client                    # App bootstrap and rendering (Webpack entry)
│   ├── routes                    # Routes configuration for both client-side and server-side
│   └── server                    # Express server (with Webpack dev and hot middlewares)
├── tools                         # Project related configurations (e.g. build, testing etc.)
│   ├── jest                      # Jest CSS modules and assets mocks settings
│   ├── webpack                   # Webpack settings
│   │   ├── config.babel.js       # Webpack configuration
│   │   └── hooks.js              # Assets require hooks
├── index.ts                      # App entry point
├── postcss.config.js             # PostCSS configuration
└── tsconfig.json                 # TypeScript configuration

Server-Side Security and Performance

Concerning the security and performance of Express in production, I already setup some middleware 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.

Develop with Redux Toolkit

Redux Toolkit is the official, opinionated, batteries-included toolset for efficient Redux development. It includes several utility functions that simplify the most common Redux use cases. In a word, we can do more work with less code, start from the tutorial to learn more about it.

Let's see how powerful it is by a simple asynchronous data fetching example:

import { createSlice } from "@reduxjs/toolkit";
import axios from "axios";

const initialState = {
  readyStatus: "invalid",
  items: [],
  error: null,
};

const userList = createSlice({
  // A name for action types
  name: "userList",
  initialState,
  // An object of "case reducers", key names will be used to generate actions
  reducers: {
    getRequesting: (state) => {
      // Write an immutable updated state by a mutable way, thanks to the built-in Immer middleware
      state.readyStatus = "request";
    },
    getSuccess: (state, { payload }) => {
      state.readyStatus = "success";
      state.items = payload;
    },
    getFailure: (state, { payload }) => {
      state.readyStatus = "failure";
      state.error = payload;
    },
  },
});

// We can get the "reducer" and "actions" from the slice instance
export default userList.reducer;
const { getRequesting, getSuccess, getFailure } = userList.actions;

export const fetchUserList = () => async (dispatch) => {
  dispatch(getRequesting());

  try {
    const { data } = await axios("/api/users");

    // Dispatch the action once data is ready via the built-in Redux Thunk middleware
    dispatch(getSuccess(data));
  } catch (error) {
    dispatch(getFailure(error.message));
  }
};

Overview

Functional Components and Hooks

React v16.8 introduced a series of Hooks, which let you use state and other React features without writing a class. In the starter boilerplate, you can see how I leverage the benefit of functional components + hook APIs to write a demo with clean code.

Adding Routes

This starter use React Router library to manage our routes. For the purpose of SSR with data pre-fetched, I put the routes in a centralized Route Config. You can setup your routes in the ./src/routes/index.ts. For example:

import RouteComponent from "../pages/RouteComponent";

export default [
  {
    // Define your route path
    path: "/top-path",
    // If the route matches the location.pathname exactly or not (used for index route usually)
    exact: true,
    // Add your route component here
    component: RouteComponent,
    // Add your sub route component here
    routes: [
      {
        path: "/top-path/sub-path",
        component: SubRouteComponent,
      },
    ],
  },
  // Other routes...
];

Data Fetching from Server-side

Strongly recommend to write Redux actions and reducers via the createSlice API of Redux Toolkit (start from the tutorial if you are new). The starter using axios as the data fetcher, it's quite simple and easy to use. If the action is asynchronous then it will return a Promise (or a Promise.all) in the inner function.

Register the action(s) in the ./src/routes/index.ts, which have to be called from server-side:

export default [
  {
    path: "/top-path",
    exact: true,
    component: RouteComponent,
    // Async actions in the loadData function will be fetched from server-side
    // You can access the URL parameters, Redux store, HTTP request and response by the event object
    loadData: ({ params, getState, req, res }) => [
      myReduxAction(),
      // Add other pre-fetched actions...
    ],
  },
];

The action(s) will be dispatched through the ./src/server/ssr.tsx on server-side:

app.get("*", (req, res) => {
  // ...

  // Here's the method for loading data from server-side
  const loadBranchData = () => {
    const branch = matchRoutes(routes, req.path);

    const promises = branch.map(({ route, match }) => {
      if (route.loadData) {
        return Promise.all(
          route
            .loadData({
              params: match.params,
              getState: store.getState,
              req,
              res,
            })
            .map((item) => store.dispatch(item))
        );
      }

      return Promise.resolve(null);
    });

    return Promise.all(promises);
  };

  // ...
});

In client-side, don't forget to invoke the action(s) in componentDidMount or useEffect hook. This ensures that if the component is reached on the client, then the same actions will be invoked. It's up to the action(s) to figure out if fetches for data need to be made or not:

// Use React class component
componentDidMount() {
  // Invoke your redux action(s) for client rendering
  this.props.myReduxAction();
}

// Use functional component
useEffect(() => {
  myReduxAction();
}, [])

Code Splitting

One great feature of the web is that you don’t have to make your visitors download the entire app before they can use it. You can think of code splitting as incrementally downloading the app. It divides your code into small pieces called “chunks” to reduce the size of bundle loaded by user. Reducing the size of a chunk makes it load and run faster.

To accomplish this, I integrate loadable-components into this starter. The reason I choose the library is because of its design philosophy of SSR. It works seamless with the starter rather than others. Let’s see how we split our app by route:

I use the following folder/file structure:

 |- pages
    |- AsyncRouteComponent
       |- index.js             // Wrap the route component into async component
       |- RouteComponent.tsx   // The route component

The index.js will be:

import loadable from "@loadable/component";

import { Error, Loading } from "../../components";

// Import your async route component
const AsyncComponent = loadable(() => import("./AsyncComponent"), {
  // Loading component will be displayed when the component is being loaded
  fallback: <Loading />,
});

export default (props) => (
  // Wrap an <ErrorBoundary /> to catch the error of <AsyncComponent /> (via "componentDidCatch()" life cycle)
  <ErrorBoundary>
    <AsyncComponent {...props} />
  </ErrorBoundary>
);

Then you can setup the route as usual.

Note: I just show a general case route-based splitting, however you can even split your app by component-based depends on your need. For more advanced configuration you can refer to the document of loadable-components.

Managing Title, Meta, Styles and Scripts

The ./src/app/index.tsx (app root component) 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 document for more info.

App Configuration

You can store app settings under the ./src/config. By default the default.ts will be loaded. If the process.env.NODE_ENV matches to production (alias as !__DEV__), the prod.ts will be used instead, and it inherits the properties of default.ts.

You can access the correct configuration with:

import config from "./config";

Styles

The starter supports CSS, SASS and CSS modules is auto enabled for all files the [name].module.* naming convention. 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.module.scss";

// ...

return (
  <div className={styles.myClass}>
    {/* The className matches one of CSS classes in your .scss file */}
    <Helmet title="My title" />
    {this.renderContent()}
  </div>
);

Without CSS modules:

import "./styles.scss";

// ...

return (
  <div className="myClass">
    {/* Use the CSS class as normal */}
    <Helmet title="My title" />
    {this.renderContent()}
  </div>
);

By the way, if you want to use vendor CSS frameworks or global styles, just import it through the ./src/app/index.tsx file (app root component). For example:

import "../../theme/normalize.css"; // Import the vendor stylesheet first
import styles from "./styles.scss"; // Then your based stylesheet

const App = ({ route }) => (
  // ...
};

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:

import logo from "../static/logo.svg";

<img src={logo} alt="Logo" role="presentation" />;

Using font-awesome:

// With CSS modules
import styles from "./styles.scss";

// ...

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

// Without CSS modules
import "./font-awesome.css";

// ...

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

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

$fa-font-path: "../node_modules/font-awesome/fonts";
@import "../node_modules/font-awesome/scss/font-awesome";

.icon-user {
  @extend .fa;
  @extend .fa-user;
}

Boost App Performance

In this starter, you can see I use React.PureComponent and React.memo to demonstrate the basic performance optimizing for React app. The two APIs are used in different ways.

  • React.PureComponent is used for React class components. It can do shallow prop and state comparison for a performance boost:
import React, { PureComponent } from "react";

class MyComponent extends PureComponent {
  // Only re-renders if props change
}
  • React.memo is used for functional components. It plays the same role as React.PureComponent:
import React, { memo } from "react";

const MyComponent = memo((props) => {
  // Only re-renders if props change
});
  • In addition, you can also use React hooks like useMemo or useCallback to avoid expensive calculations on every render:
import React, { useMemo, useCallback } from "react";

// Performance optimizing via useMemo()
const ParentComponent = (props) => (
  <div>
    {/* Only re-renders if "a" change */}
    {useMemo(
      () => (
        <ChildComponent someProp={a} />
      ),
      [a]
    )}
  </div>
);

// Performance optimizing via useCallback()
const ParentComponent = (props) => (
  <div>
    {/* Return a memorized callback that only changes if "a" changed */}
    {/* This is useful to prevent child component from unnecessary renders */}
    <ChildComponent
      callback={useCallback(() => {
        doSomething(a);
      }, [a])}
    />
  </div>
);

For more performance optimizing techniques. Please see the Optimizing Performance topic.

TypeScript

TypeScript is a typed super-set of JavaScript. It's getting more and more popular in the Front-end world. And being widely used by many libraries. If you are new to TypeScript, you can check out its document here.

TypeScript has been integrated with our application to bring the following benefits:

  • Type safety for components, reducers, state and actions.
  • Many IDEs support the hints of TypeScript, which give us a superior developer experience.
  • Easy refactoring of typed code.
  • In general, more types less bugs. Read this article to learn more.

Code and Style Lint

ESLint (With Airbnb configuration), typescript-eslint, StyleLint, Prettier and lint-staged are integrated into this starter to maintain a consistent code style and give you a elegant code formatting. You can configure your lint rules through the .eslintrc, .stylelintrc and .prettierrc files.

Unit Testing

This starter use Jest as the testing framework. We also use React Testing Library with jest-dom, give you a simple and complete React DOM testing utilities that encourage good testing practices.

Jest support the feature of snapshot testing, which is very powerful for testing React component. Give it a try, you'll be impressed. The unit testing focus on three parts as below:

  • Components
  • Actions
  • Reducers

By the way, Jest built-in code coverage reports, the report files are generated in the ./coverage folder. You can configure the ./jest.config.js to define which files that you want to cover. For example:

module.exports = {
  collectCoverageFrom: [
    "src/pages/**/*.tsx", // Define the files, which want to be covered
    "!src/pages/index.ts", // The files will be ignored by code coverage
  ],
  // Other configurations...
};

You can also use istanbul's ignore hints to specify specific lines of code in a JavaScript file to skip code coverage.

How to Deploy

To deploy you app to cloud service (e.g. AWS, GCP), you can follow the instructions below.

  1. Build then install production dependencies:
yarn build                  # Building bundle
rm -rf node_modules         # After building remove node modules
yarn install --production   # Then install dependencies only
  1. Pack necessary folders/files to your Node.js server:
  • ✅ node_modules
  • ✅ public
  • ✅ src
  • ✅ tools/webpack
  • ✅ index.ts
  • ✅ postcss.config.js
  • ✅ package.json
  • ✅ tsconfig.json
  1. Run your app:
yarn start

Ideally, the above steps can be integrated into your CI. I recommend you to pack the ./yarn.lock for yarn installation by CI.

Troubleshooting

  • If app crash due to the error: Invariant Violation: loadable: SSR requires "@loadable/babel-plugin", please install it (refer to this issue). To solve that, you must use .js extension for code-splitting files (e.g. ./src/Home/index.js).

  • If you encounter the markup mismatches error (it's a React universal issue, which usually occurs due to the non-synchronized rendering result between client and server), you can do:

    • Restart the server to solve it.
    • Or for v16.1.0 up, you can use suppressHydrationWarning attribute for intentional client/server text mismatches (#11126).
  • If you are on windows and encounter the following error: Expected linebreaks to be 'LF' but found 'CRLF' linebreak-style. The following rule must be added to .eslintrc.

// ...
"rules": {
  "linebreak-style": "off",
  // Other rules...
}

Contributors ✨

Thanks goes to these wonderful people (emoji key):


Welly

💻 📖 🚧

Microflow

🌍

Jason Bacchetta

🔧

Nikita Balabaev

💻

James Sherry

🚇 🐛

Zack Pelz

🌍

apapacy

💻

martin2786

📖

iamacup

🔧

Maxim

🔧

Benny Neugebauer

🌍

A. S. Lapkov

💻

Alireza Valizade

💻

Grant Millar

🐛 💻

Bart

🐛

Morteza Tourani

💻

Tom Kiernan

🌍

Nathan KREMER

💻

Amer Lotfi Orimi

🔧 💻

Muhammad Umar

💻

Denny Vuong

📖 💻

Matt Carlotta

💻

This project follows the all-contributors specification. Contributions of any kind welcome!