nwb supports development of production-ready React apps.
Prerequisite: nwb must be installed globally (we're using version 0.22 in this guide):
npm install -g nwb
- Creating a New Project
- Project Layout
npm run
Scripts- Running the Development Server
- Zero Configuration Development Setup
- Thinking in React Components
- Entry Point
- Testing
- Building for Production
- Using nwb in Your Own Express Server via Middleware
- Appendix: Configuration
To walk you though the process, we're going to take the components from the Thinking in React example, split them out into individual modules, perform some basic testing and create a production build, all using features nwb provides without any configuration.
This is a how to use nwb guide, not a how to write React apps guide; if you're new to React, you might want to walk through Thinking in React first, as it's one of the best ways to get a hands-on feel for developing React components and their data flow.
Use the nwb new
command to create a new React app project:
nwb new react-app nwb-thinking-in-react
The following directory structure will be created:
nwb-thinking-in-react/
.gitignore
.travis.yml
CONTRIBUTING.md
nwb.config.js
package.json
README.md
node_modules/
public/
.gitkeep
src/
App.css
App.js
index.css
index.html
index.js
react.svg
tests/
.eslintrc
App.test.js
dist/: When you run a build, output will be generated in the dist/
directory, ready for deployments.
node_modules/: Contains modules installed with npm. npm is the standard way to manage front-end, back-end and development dependencies for React projects. react
and react-dom
dependencies will be installed from npm into node_modules/
as part of project creation.
nwb.config.js: This file can be used to tweak or extend the default configuration nwb creates for React apps. It can be safely deleted if you don't need configuration.
public/: Directory for static assets (such as favicon and app icon files) which will be served by the development server and copied to dist/
when building.
src/: Contains your React app's source. Stylesheets and images used in your application will normally also live here and be directly imported from JavaScript and CSS to be managed by Webpack.
Includes a template for your project's HTML entry point, which will have <link>
and <script>
tags for built resources injected when building.
index.js
and index.html
are the only files nwb assumes will exist as the entry points for the app. How you write and organise the rest of your app's code is up to you.
The initial app in
src/
is intended to show some of the things Webpack allows you to do which might be surprising if you've not encountered it before.
Gist: any file Webpack is configured to deal with (including stylesheets and images) can be imported as if it were a module; Webpack starts from your app's entry module and walks imports to build up a graph of modules to be built.
tests/: nwb will find and run tests in a separate tests/
directory or co-located with your source code under src/
, as long as their filenames end with .test.js
or .spec.js
- an example of a basic React component test is provided.
cd
into the project directory and we can get started on our example app:
cd nwb-thinking-in-react/
package.json
is configured with "scripts"
we can use with npm run
while developing the project.
Command | Description |
---|---|
npm start |
start the app's development server |
npm test |
run tests |
npm run test:coverage |
run tests and produce a code coverage report in coverage/ |
npm run test:watch |
start a test server and re-run tests on every change |
npm run build |
create a production build in dist/ |
npm run clean |
delete built resources |
The initial project is set up so you can successfully run each of these commands and get some meaningful output.
Running npm start
will start the development server.
Every time you make a change to the app, it will refresh the current compilation status:
Console | Browser |
---|---|
If there are any errors, they will be displayed in both the console and the browser, so you're unlikely to miss them while developing:
Console Error | Browser Error |
---|---|
If there's an error in the render()
method of a React component, it will be displayed as a Red Box Of Death overlay in the browser:
A React-specific Hot Module Replacement handler is enabled and will attempt to patch changes into your React components while maintaining their current state:
Changes to stylesheets will also be hot-reloaded into the running app:
The development server will also serve the app's HTML entry file at non-root URLs, for use when developing apps which use the History API (e.g. with React Router).
nwb generates a comprehensive default configuration for creating React apps using Babel and Webpack.
Without any configuration, the main features you get are:
- Write JavaScript with modern features and JSX, transpiled down to ES5.
- Use proposed JavaScript features which make writing React apps more convenient:
- Class properties, for avoiding boilerplate when writing classes.
- Decorators, for libraries which are intended to be used with them.
- Import stylesheets (and font resources), images and JSON into JavaScript, to be handled by Webpack.
- Autoprefixed CSS, so you don't need to write browser prefixes.
The default JavaScript features allow you to write React components in any of the styles you'll see in the React documentation.
We'll start by splitting each of the main components from the final Thinking in React example code into its own module, which is more representative of how a larger React project will be laid out.
A single module is perfectly fine for small apps, demos and examples!
FilterableProductTable.js
holds filter state and composes the other components.SearchBar.js
renders a<form>
with the current filter state and passes changes back up toFilterableProductTable
.ProductTable.js
renders a<table>
of products filtered according to the current filter state. This module also includes subcomponents used to implement theProductTable
component.
Written as a vanilla ES classes, React components need to call super()
before setting anything on this
, and need to manually bind event handlers in the constructor:
class FilterableProductTable extends Component {
constructor(props) {
super(props)
this.state = {
filterText: '',
inStockOnly: false,
}
this.handleUserInput = this.handleUserInput.bind(this)
}
handleUserInput(filterText, inStockOnly) {
this.setState({filterText, inStockOnly})
}
render() {
// ...same in all implementations
}
}
Using proposed class property syntax avoids this constructor boilerplate - =
assignment statements in the class body are actually moved into a generated constructor, so the event handler arrow function inherits the constructor's this
:
class FilterableProductTable extends Component {
state = {
filterText: '',
inStockOnly: false,
}
handleUserInput = (filterText, inStockOnly) => {
this.setState({filterText, inStockOnly})
}
render() {
// ...same in all implementations
}
}
src/index.js
is the entry point for the React app, which is usually responsible for "global" work prior to rendering the app:
- Importing CSS which will apply globally.
- Performing any required polyfilling.
- Performing configuration of libraries being used which need it, e.g. localisation settings.
- Composing and rendering the app's top-level components.
To spruce up the app's display a bit, we will install Bootstrap, import it in index.js
and apply styles to a few of our components:
npm install --save bootstrap
import 'bootstrap/dist/css/bootstrap.css'
import './index.css'
import React from 'react'
import {render} from 'react-dom'
import FilterableProductTable from './FilterableProductTable'
let products = [
{category: 'Sporting Goods', price: '$49.99', 'stocked': true, name: 'Football'},
{category: 'Sporting Goods', price: '$9.99', 'stocked': true, name: 'Baseball'},
{category: 'Sporting Goods', price: '$29.99', 'stocked': false, name: 'Basketball'},
{category: 'Electronics', price: '$99.99', 'stocked': true, name: 'iPod Touch'},
{category: 'Electronics', price: '$399.99', 'stocked': false, name: 'iPhone 5'},
{category: 'Electronics', price: '$199.99', 'stocked': true, name: 'Nexus 7'},
]
render(
<FilterableProductTable products={products}/>,
document.querySelector('#app')
)
You can view the live version of the app here.
nwb provides a default testing setup which uses Karma to run tests written with Mocha and Expect in a headless Chrome browser.
The Testing documentation provides an in-depth overview of what nwb's default testing setup provides (and how to configure things to your liking if you want to), but we'll stick to the defaults and repurpose the initial test in the
tests/
directory to test theProductTable
component.
npm run test:watch
automatically re-runs tests on every change to provide a quick feedback loop while developing, whether you're writing tests up-front, in parallel with implementation or after the fact.
If you're into Test Driven Development, it will give you the flow you want as you write breaking tests followed by implementations which satisfy them.
Here's an example of one of the tests written for the ProductTable
component, which renders it into the DOM and asserts that names of the appropriate categories and products are present:
// expect isn't in the project's package.json - nwb provides this dependency so
// it can ship with working test examples.
import expect from 'expect'
import React from 'react'
import {render, unmountComponentAtNode} from 'react-dom'
// nwb sets up a `src` alias by default so you can use directory-independent
// imports in non-colocated tests.
import ProductTable from 'src/ProductTable'
let productsFixture = [
{category: 'Sportsball', price: '$19.99', stocked: true, name: 'Football'},
{category: 'Sportsball', price: '$29.99', stocked: true, name: 'Baseball'},
{category: 'Sportsball', price: '$39.99', stocked: false, name: 'Basketball'},
{category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'},
{category: 'Electronics', price: '$199.99', stocked: false, name: 'iPhone 5'},
{category: 'Electronics', price: '$299.99', stocked: true, name: 'Nexus 7'},
]
describe('ProductTable component', () => {
let node
beforeEach(() => { node = document.createElement('div') })
afterEach(() => { unmountComponentAtNode(node) })
it('filters by product name', () => {
render(<ProductTable
filterText="Bas"
inStockOnly={false}
products={productsFixture}
/>, node)
expect(node.textContent)
.toContain('Sportsball')
.toNotContain('Football')
.toContain('Baseball')
.toContain('Basketball')
.toNotContain('Electronics')
.toNotContain('iPod Touch')
.toNotContain('iPhone 5')
.toNotContain('Nexus 7')
})
})
Once your tests are working, you can generate a code coverage report by running npm run test:coverage
:
Code coverage percentages on their own are fairly meaningless, but running coverage also produces an HTML report in coverage/html/
showing coverage statistics for each file and annotating your code to show which pieces were and weren't touched during a test run.
This HTML report is handy for finding out what your tests aren't covering, and deciding which uncovered areas you'd feel more comfortable having some tests for.
If you use GitHub for your project's source code hosting, the project is pre-configured for running tests on Travis CI and posting code coverage results to coveralls and codecov.io after successful test runs.
Logging into Travis CI and enabling it for your GitHub project will ensure your tests are run against every subsequent commit and will automatically be run against any Pull Requests your repo receives.
npm run build
will create a minified production build in dist/
, showing the gzipped sizes of generated .js
and .css
bundles.
Image and font resources imported by your JavaScript and CSS will also be copied to dist/, as well as any static assets from public/
.
Build Summary | All Build Output |
---|---|
By default, a separate vendor
bundle is created for anything required from node_modules/
with the assumption that you'll be tweaking your app and its styles more often than you will be changing dependencies.
Generated bundles have a deterministic hash in their filename to support long-term caching (as do imported image and font resources). e.g. if you make a small tweak to your app's JavaScript, only the app.[hash].js
bundle's filename will change.
The following React-specific optimisations are also performed as part of the build:
- Any
propTypes
defined on your app's class components or stateless function components will be stripped from the build, as PropTypes aren't checked in production.
If you're writing an app which needs a server, you can use nwb's development build in your own Express server using middleware.
var express = require('express')
var app = express()
// Pass the express module to nwb's middleware
app.use(require('nwb/express')(express))
app.listen(3000, function(err) {
if (err) {
console.error('error starting server:')
console.error(err.stack)
process.exit(1)
}
console.log('server listening at http://localhost:3000')
})
For a complete example, see nwb-react-tutorial, which uses nwb middleware to provide a development build for the comment box from the React tutorial from the same server it uses to implement the API.
This is the way I use nwb in my Node.js projects at work, conditionally importing and using nwb's middleware based on
process.env.NODE_ENV
.
nwb aims to support a wide range of a React app's needs with its default configuration, but also allows you to tweak and extend its base configuration when that's not enough for your app.
By default, nwb will look for and attempt to import an nwb.config.js
file in the root of your project.
For common configuration tweaks, nwb attempts to make things more convenient e.g. setting up new aliases and expressions to be replaced in Webpack is done with convenience config rather than requiring knowledge of how Webpack implements these, or how nwb configures them:
module.exports = {
webpack: {
aliases: {
img: 'src/img'
},
define: {
__VERSION__: JSON.stringify(require('./package.json').version)
}
}
}
The Configuration documentation provides a complete reference to configuration supported by nwb.
If you prefer to write stylesheets using a preprocessor, or you want to use a CSS framework from source, installing one the following npm packages and including it in your project's devDepenencies
will enable support for the corresponding preprocessor:
nwb-less
for Less (in.less
files)nwb-sass
for Sass (in.scss
and.sass
files)nwb-stylus
for Stylus (in.styl
files)
e.g., to enable Sass support:
npm install --save-dev nwb-sass
You can pass flags when starting the development server to enable additional features.
To pass flags to
npm run
commands, you need to pass a--
argument to indicate all additional arguments should be passed to the command itself.
npm start -- --reload
There are some changes which can't automatically be patched into a running React app by the experimental implementation nwb is using, or which need to be manually hooked up using Webpack's or Module Replacement API if you want to accept them.
Passing a --reload
flag will auto-reload the page when Webpack Hot Module Replacement gets stuck.
npm start -- --install
Having to stop and restart the development server when you need to install a new dependency can be annoying break of flow during initial development.
Passing an --install
flag will use NpmInstallPlugin
to detect missing dependencies and install them from npm (and save them to dependencies
in package.json
) for you, so you just have to add an import
statement or require()
call as if you already had the dependency installed.
There may be edge cases or "gotchas" with this feature, but it's provided as an opt-in improvement to your developer experience.