A simple but feature rich starter boilerplate for creating your own universal app. It built on the top of Node.js, Express, React, Redux and React Router v4. Includes all the hot stuff and modern web development tools such as Webpack 3, Babel, PostCSS, React Hot Loader 3 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.
- BECK Friends is an international deliver service, which is built base on this starter boilerplate. Congrats for successful migrating to React.
Really cool starter boilerplate with the most popular technologies:
- Universal rendering with async data fetching.
- React as the view.
- React Router v4 as the router.
- Redux's futuristic Flux implementation.
- Express server.
- Webpack 3 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.
- morgan the HTTP request logger for server side debugging.
- Redux Devtools Extension for next generation developer experience.
- Flow as the static type checker for javascript.
- 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 component.
- Shared app config between development and production.
- 404 error page and redirect handling.
- Integrate Jest with enzyme as the solution for writing unit tests with code coverage support.
- Yarn as the package manager.
This starter is for those who with basic knowledge of React and have the need for building a server-sdie app. In other words, it's not for newbie. If you're new to React or you don't need a server-sdie rendering app, I'd recommand you to give create-react-app a try.
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 dependencies:
yarn install
3. Start to run it:
yarn start:production # Building bundle and running production server
Now the app should be running at http://localhost:8080/
I use better-npm-run to manage the scripts in a better way, which also provides the compatibility of cross-platform. All of the scripts are listed as following:
yarn <script> |
Description |
---|---|
start |
Run your app on the development server at localhost:3000 . HMR will be enabled. |
start:production |
Bundles the app to ./build 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 client and server bundled stuff and bundle them to ./build . |
build:client |
Remove the previous client bundled stuff and bundle it to ./build/public/assets . |
build:server |
Remove the previous server bundled stuff and bundle it to ./build . |
lint |
Lint all .js and .scss files. |
lint:js |
Lint all .js files. |
lint:style |
Lint all .scss files. |
flow |
Run type checking for .js files. |
test |
Run testing once (with code coverage reports). |
test:watch |
Run testing on every test file change. |
clean:all |
Remove the client/server bundled stuff and the coverage report. |
clean:client |
Remove the ./build/public/assets folder to clean the client bundled stuff. |
clean:server |
Remove the server bundled stuff from the ./build folder. |
clean:test |
Remove the ./coverage folder to clean the code coverage report. |
Here is the structure of the app, which serve as generally accepted guidelines and patterns for building scalable apps.
.
βββ build # Webpack bundled files will be placed into it
β βββ public # The Express server static path
β βββ 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)
β βββ utils # App-wide utils (e.g. HTML component)
β βββ redux # Redux related configuration scripts
β β βββ reducers.js # The root reducer (registry and injection)
β β βββ store.js # Configure and instrument Redux store
β βββ theme # App-wide style and vendor CSS framework
β βββ types # Flow types for actions, reducers and more
β βββ client.js # App bootstrap and rendering (webpack entry)
β βββ routes.js # Routes configuration for both client and server side
β βββ server.js # Express server (with webpack dev/hot middlewares)
βββ tools # Project related configurations (testing/build etc.)
β βββ flow # Flow types, interface, module aliasing definitions
β βββ openBrowser # Utility for opening Google Chrome
β βββ jest # Jest CSS modules and assets mocks settings
β βββ webpack # Webpack configuration settings
β β βββ config.js # Configuration for CSS modules, vendor registering
β β βββ webpack.client.babel.js # Webpack configuration for client
β β βββ webpack.server.babel.js # Webpack configuration for server
β β βββ WIT.config.js # Webpack Isomorphic Tools configuration file
βββ index.js # App entry point
Concerning 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.
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
- from Chrome Web Store;
- or build it with
npm i && npm run build:extension
and load the extension's folder./build/extension
; - or run it in dev mode with
npm i && npm start
and load the extension's folder./dev
.
For Firefox
- from Mozilla Add-ons;
- or build it with
npm i && npm run build:firefox
and load the extension's folder./build/firefox
(just select a file from inside the dir).
For Electron
- just specify
REDUX_DEVTOOLS
inelectron-devtools-installer
.
For other browsers and non-browser environment
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.
This starter use React Router v4 library to manage our routes. You can setup your routes in ./src/routes.js
. For example:
import MyRouteComponent from './containers/MyRouteComponent';
// ...
export default [
{
// Define your route path
path: '/myPath',
// If the route matches the location.pathname exactly or not (used for index route usually)
exact: true,
// Add your route component here
component: MyRouteComponent,
// ...
},
// Setup other routes...
];
Just write Redux actions and stores as normal (read the Redux guide if you are new). The starter 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.
Register the action(s) in ./src/routes.js
, which have to be called from server-sdie:
// ...
export default [
{
// ...
loadData: (dispatch) => Promise.all([
// Register your server-side call action(s) here
dispatch(myReduxAction()),
]),
},
// ...
];
The action(s) will be dispatched through ./src/server.js
on server-side:
// ...
app.get('*', (req, res) => {
// ...
// Here's the method for loading data on server-side
const loadBranchData = () => {
const promises = [];
routes.some((route) => {
const match = matchPath(req.url, route);
// $FlowFixMe: the params of pre-load actions are dynamic
if (match && route.loadData) promises.push(route.loadData(store.dispatch, match.params));
return match;
});
return Promise.all(promises);
};
// ...
});
// ...
On client-side, don't forget to invoke the action(s) 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(s) to figure out if fetches for data need to be made or not:
componentDidMount() {
// Invoke your redux action(s) for client rendering
this.props.myReduxAction();
}
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.
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 instead, and it inherits the data info from default.js
.
You can access the correct config with:
import config from './config';
The starter 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.myClass}> // The className matches one of CSS classes in your SCSS file
<Helmet title="Home" />
{this.renderUserList()}
</div>
);
}
Without CSS modules (you need to turn off CSS modules from ./tools/webpack/config.js
):
import './styles.scss';
// ...
render() {
return (
<div className="myClass"> // Use the CSS class as normal
<Helmet title="Home" />
{this.renderUserList()}
</div>
);
}
By the way, if you want to use your based style 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
export default routes => {
// ...
};
For the better development experience, don't forget to include those files in the ./src/utils/Html.js
, for example:
// ...
{
_.keys(assets.styles).length === 0 ?
<style
dangerouslySetInnerHTML={{ __html:
// Include the vendor CSS framework and your own style here
require('../theme/normalize.css')._style +
require('../containers/App/styles.scss')._style +
// Other styles...
}}
/>
: null
}
// ...
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 './styles.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/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;
}
If your React component's render() function renders the same result given the same props and state, you can use React.PureComponent for a performance boost.
React.PureComponent is exactly like React.Component but implements shouldComponentUpdate()
with a shallow prop and state comparison. See the Optimizing Performance topic for more info.
How we implemented the optimizing:
import React, { PureComponent } from 'react';
// ...
class Home extends PureComponent { // Use PureComponent instead of Component
// ...
}
Flow, a static type checker for javascript. It adds static typing to javascript to improve developer productivity and code quality. In particular, static typing offers benefits like early error checking, which helps you avoid certain kinds of runtime failures, and code intelligence, which aids code maintenance, navigation, transformation, and optimization.
Flowβs static analysis makes building web apps with React safe by tracking the types of props and state. Flow understands which props are required and also supports default props.
I love to write React, Redux with Flow, I know it's not easy to learn at the beginning. But trust me, it's worth to learn. There're some useful instructions that I can give you as below:
-
If you are new to Flow, five simple examples can get you started writing Flow programs.
-
Learn how to use Flow with React Component's props and state, you can learn from here.
-
Here's an example, which shows you the overall concept of integrating Flow with Redux.
Moreover, often you will want to use third-party libraries. For these circumstances, Flow supports the concept of a "libdef" ("Library Definition") which allows you to describe the interface and types of the library separate from the library and without needing to add types to or change the library itself. You can write a libdef file yourself if you need to or use flow-typed, which is a repository of third-party library interface definitions for use with Flow.
Note: If you don't want to use Flow, just remove the /* @flow */
comment and related typing definitions from each javascript file.
JavaScript lint and style lint are included into webpack compiling for runtime checking. If you don't want them be activated during developing, you can turn off those from ./tools/webpack/config.js
and do the manually checking by yarn lint
.
The starter use Jest as the testing engine. It runs in a Node environment, so you won't have access to the DOM. In addition, Jest support the feature of snapsot testing, which is very powerful for testing React component. Give it a try, you'll be impressed.
I also use enzyme as the testing utility for React, which makes it easier to assert, manipulate, and traverse your React Components' output. The unit tests focus on three parts as below:
- React Components
- Actions
- Reducers
By the way, Jest built-in code coverage reports, the report files are generated in ./coverage
folder. You can configure ./package.json
to define which files that you want to cover. For example:
{
// ...
"jest": {
"collectCoverageFrom": [
"src/containers/**/*.js", // Define the files, which want to be covered
"src/components/**/*.js",
"!src/**/__tests__" // 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.
- If you get the the following message during developing, try to run
yarn build:client
to create the necessarywebpack-assets.json
file for enable related assets (e.g. javascript, styles, image etc.) working on universal rendering.
webpack-isomorphic-tools (waiting for the first webpack build to finish)
- If you encounter the checksum error like following, try to restart the server to solve the it. (it's a react universal issue, which usually occurs due to the non-synchronized rendering result between client and server)
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.
- If you run the starter through a cloud computing service such as AWS EC2 instance etc. and you encounter an
UnhandledPromiseRejectionWarning
like this issue. It might caused by the "openBrowser" tool. You can solve the issue like following.
In the ./package.json
script:
// ...
"start:prod": {
"command": "node ./index.js",
"env": {
"NODE_PATH": "./src",
"NODE_ENV": "production",
"PORT": 8080,
"BROWSER": "none" // Add this node variable to turn off the open browser util
}
},
// ...