This is a boilerplate for React apps:
- WebPack 2
- Babel 6
- Flow
- Eslint (AirBnB)
- React Hot Loader 3
- AVA + Enzyme
- Code coverage reports
- Null loaders to allow unit testing with components that use WebPack loaders
- WebSockets via Socket.IO set up on server (delete if not needed)
- Semantic release
You can generate a new project by first installing builder-init
:
npm install -g builder-init
We are using builder-init
for template string replacement mainly in package.json
. This archetype is designed to be used with npm install
rather than builder install
. To create a new project using this template first clone this repo and then run:
builder-init git+ssh://[email protected]:philholden/react-project-archetype.git
git init
npm install
Note: Run git init
before npm install
so that ghooks
can find the git repo. If you forget to do this reinstall ghooks
after git init
:
npm uninstall -D ghooks
npm install -D ghooks
Node Package Manager (npm) provides all the ingredients (modules) for our application while the package.json
provides the recipe what we need (dependencies) and how to cook it (scripts).
Node Version Manager enables us to have several versions of Node running on the same computer. To install nvm first uninstall Node if already installed and then follow these instructions.
For this boiler plate we assume you are using Node 6 and npm 3.
nvm install 6
# or if 6.x is installed already
nvm use 6
You can also have a .nvmrc
file in each project which tells nvm which version to use. It is not automatic but has to be invoked with nvm use
. Be warned it is a bad idea to change npm version part way through a project. If you upgrade npm 2.x to version 3.x you should also delete the node_modules
folder and run npm install
again.
To install a newly created project cd
into the project folder:
git init
npm install
The projects package.json
file has a scripts section which defines the tasks that can be run on our project. Using npm's scripts means grunt
and gulp
are seldom needed.
To run the project in dev mode:
npm start
Then open a browser to http://localhost:3000
To build the project for production:
npm run build:webpack
Then open a browser to http://localhost:3000/index-dist.html
Babel allows modern JS to be used today. It transpiles ES2015, ES2016 and other experimental syntax to ES5 so it can be used in older browsers.
// input
const inc = x => x + 1
// output
var inc = function inc(x) {
return x + 1;
};
It can also transpile React's JSX to plain JS:
// input
const Hello = () => <div>Hello</div>
// output
var Hello = function Hello() {
return React.createElement(
"div",
null,
"Hello"
);
};
You can experiment with Babel here
Babel presets are used to choose which transformations to apply to code. See package.json
for the Babel presets installed:
"babel-preset-es2015": "^6.9.0",
"babel-preset-es2015-webpack": "^6.4.1",
"babel-preset-react": "^6.11.1",
"babel-preset-react-hmre": "^1.1.1",
"babel-preset-stage-1": "^6.5.0",
These presets are enabled through the .babelrc
file. .babelrc
can be set up to use different presets based on an environment variable. Many of the scripts
in package.json
set the environment variable NODE_ENV
or BABEL_ENV
before running. This Allows a different setup for test, dev and production.
Webpack 2 is a module bundler. It takes the devDependencies
defined in package.json
and bundles them into a single file that can be requested from HTML files. It can output more than one bundle allowing browser caches to work more efficiently:
<script src="/static/bundle-vendor.js"></script>
<script src="/static/bundle-app.js"></script>
Webpack is configured in webpack.config.js
. Webpack 2 supports tree-shaking. By using ES6 modules imports
and exports
Webpack can remove code that never gets run from our bundle creating a smaller bundle. To test this run:
npm run build:webpack
Look at src/components/app.js
note it contains the following strings 'Hello World.'
and 'Not Used.'
. Now open dist/bundle-app.js
search for these two strings. You should find 'Hello World.'
but not 'Not Used.'
. The NotUsed
function has been removed because Webpack detected our app will never call this function.
Webpack is also minifying our code using Uglify. It has loaders configured which load non JS files as if they were JS. E.g. url-loader
which we use to convert png
s to base64 strings so they can be used in the src
attribute of <img>
tags (see src/components/app.js
). We can package all our scripts, image icons and CSS in a single JS file.
One problem with minification and bundling is that it makes code hard to debug. Webpack solves this by creating source maps. These map our minified code back to the original. In production the source map file is only downloaded if the browser's devtool is open.
In development source maps are evaluated on the fly as the developer edits and saves code. You can test source maps are working by throwing an error:
// src/app.js line 9
throw new Error('error')
Now if you run the code in the browser in dev or production and open up the devtools console and click on the error you should jump to the correct line and file in the source.
Getting fast feedback enables developers to work more effectively. Hot reloading allows us to edit our code and reload it in the browser while keeping the state data intact. To try this out run the dev server:
npm start
And open http://localhost:3000
now change 'src/components/app.js' change line 7 to:
export const HelloWorld = () => <div>Hello People.</div>
Now save the file. The browser automatically updates the text. If you would like state to persist between reloads consider using a library like Redux to store your state.
Ava is a modern testing framework designed to work well with ES2015. It runs tests concurrently which should make testing faster.
See: https://github.com/avajs/ava
# run tests once
npm test
# run tests in watch mode
npm run test:watch
# generate test coverage report
npm run test:coverage
# view code coverage
npm run open-coverage
You can use the npm run check-coverage
script to error if coverage falls below a specified threshold. This can be used in other scripts to make builds or commits fail if it causes the percentage of unit tested code to fall below a certain threshold.
We use expectJSX and shallow rendering in the example tests for components. This is fine for stateless components. But for more complex components you may want to consider using enzyme with jsdom or karma.
AirBnB's linter config for Eslint is fast becoming the defacto standard for ES2015 React code. Perhaps because they have such a comprehensive style guide that few would envy the task of rewriting it.
However probably most users will want to override some of the rules in the .eslintrc
file.
Eslint should be used in conjunction with an editor plugin to provide errors and warnings as the user types.
Flow is a JS type checker made by FaceBook and used by their React team. Types can help developers to ensure the correctness of code before running it. Warnings can be shown in an editor when saving a file.
Flow is often able to infer types from the way functions are used. It warns the developer when they use function arguments inconsistently. Some developers have commented they find type checking their code catches more errors than unit testing.
Types enable tooling in IDEs like jumping to function definitions. They can also be used to detect dead code.
With React flowTypes
can be used in the place of reacts propTypes
giving a single type system in the code base.
Facebook has created and extension to the Atom editor called Nuclide which adds Flow support to the editor.
Additional linting rules have been added to .eslintrc
to support Flow. Add in the following rules if you want to force types everywhere.
"flowtype/require-parameter-type": 1,
"flowtype/require-return-type": [
1,
"always",
{"annotateUndefined": "never"}
]
This may be overkill if you only want Flow for public exports.
This forced you to enter git commit messages in such a way that they can be parsed to automatically generate a semver compliant version number. To use it run:
npm run commit
Git hooks can be used to run tasks such as unit tests, coverage test and linters before each commit. If any of these tests error then no commit is performed. On the plus side it ensures code quality on the minus side it can lead to less frequent commits and so a greater chance of losing work. See the ghooks
property of package.json
. Note for ghooks to initialise correctly there must be a git repo present before you run npm install
on the project.
This is really only useful for open source projects. It works with Conventional Change Log to release a new version of your module to npm
when you push to master
on Github. The build is run on Travis CI. Release notes and a new semantic version tag are created in the git repo on Github. See these videos for more info.
npm install -g semantic-release-cli
semantic-release-cli setup
While not suitable for internal projects it gives an idea of what to should aim for with an internal build system.