This is the example application I wish I had when I started writing React.
I've found that many template/demo/starters give you too much. It's hard to start when there's lots of moving pieces. I've tried to err on the side of giving you a minimal skeleton instead of a feature rich boilerplate.
You should be able to understand every piece of this demo, and doing so will give you a strong foundation on which to start building larger apps. I believe this is a better approach than starting with a monsterous boilerplate where you're left with either feature bloat or to take things out before you even know what they do.
Features:
- Latest React (^15.5.3)
- Webpack 2.3 (code splitting and tree-shaking)
- Written with ES6 (ECMAScript 2015)
- Styling with SCSS (or SASS)
- Routing with react-router V4
- Based on the "huge-apps" react-router example
- Server side rendering (aka. universal(ish) or isomorphic React)
Clone the repo, then run npm install
to install the dependencies listed in package.json
.
To start the development server on http://localhost:8000
to preview the application,
execute npm run dev
.
This runs webpack-dev-server --hot
which will serve the bundle and update the bundle
on file changes.
Note: webpack-dev-server
does not watch the webpack config file for changes.
Run npm run build:client
to bundle all assets into a build/
dir. This step must be done before server side rendering is possible.
Since all our assets are in jsx, and es6, we have to transpile them to regular Javascript for today's browsers to understand. This is where webpack works it's wonders, including static analysis to determine unused pieces of code that don't need to be included. This is called tree-shaking and can dramatically reduce the size of the resulting code you need to send to a user.
The file server.js
implements server side rendering. It uses ES6+JSX syntax, so
again, we need to convert it to regular Javascript for Node to execute.
Run npm run build:server
to do this. Webpack will bundle it into ./build/server.js
.
Now, you can run node build/server.js
to view the server side rendered
version of your application on http://localhost:8000
.
A shortcut to build both the client and server, and then run the server: npm run start
.
Webpack is a bundler. It does a lot, but at the core, it looks for import and require statements and processes them into assets for the browser. We are using Webpack 2.0 which supports tree-shaking, which eliminates code that is never used, resulting in smaller bundles.
You'll notice in the code, we have things like
import styles from './App.scss';
Why are we importing scss into a jsx file?!
Because we want to apply the styles inside App.scss
to the component in our current file. Webpack handles the
internals of processing the scss file into css, and giving you
an object with keys that refer to string names of the styles you set.
In the Webpack configuration file webpack.client.config.js
, we specify
{
test: /\.scss$/,
loader: 'style-loader!css-loader?modules&loacalIdentName=[name]--[local]--[hash:base64:5]!sass-loader'
}
This tells Webpack to process assets ending in .scss using first
- sass-loader which converts scss to css, then pipe it through:
- css-loader which does some namespacing and other stuff(read their README :),
- Finally through style-loader
Adds CSS to the DOM by injecting a
<style>
tag
The other loader, testing for jsx, is responsible for transpiling ES6 and JSX to ES5 (native browser Javascript).
We're using a couple of plugins for convenience.
The plugin will generate an HTML5 file for you that includes all your webpack bundles in the body using script tags.
From html-webpack-plugin
Using this plugin, we don't have to worry about our entry points and making sure the index.html page includes them.
The index.html page under app/
serves as the template for this Single Page App. So, if you want to set the title,
add meta tags, or whatever else... that's the place to do it.
The variety of favicon sizes and filenames devices expect (see
favicons-best-practices SO question).
is bordering on ridiculous. Luckily we can automatically create all of these with the FaviconsWebpackPlugin.
This plugin automatically generates all of them from the file specified, app/images/favicon.png
and, in
conjunction with the HtmlWebpackPlugin, places them in the head of our templateindex.html
file on build.
One traditional problem with Single Page Applications is that all the code had to be loaded at once on initial page load, even if it wasn't all needed. This often means seconds of blank screen and unnecessary data transfer. Webpack is able to split your code into "chunks" that can be loaded on demand. Only the code for the visible page needs to be fetched from the server and successive page visits fetch new chunks as needed.
See Webpack code splitting documentation.
In this demo, see any route specification like app/routes/About/index.js
if (ONSERVER) {
module.exports = require('./components/About.jsx');
} else {
module.exports = () => (<Bundle load={ () => System.import('./components/About.jsx') }/>);
}
In this Demo, we use System.import
for fetching react-router components. This results in
chunks for for our About page which are only fetched when the client visits /about
. The
Bundle component is a convenience wrapper which displays a loading component while the
Promise returned by System.import is resoliving. Once resolved, Bundle changes it's state to
display the requested component.
About the ONSERVER switch: This constant is defined in the webpack config file
webpack.server.config.js
as true and false in the client config. Although admittedly
not the sexy, it seems to be the most straightforward approach and is necessary at the
moment because the server side renderToString doesn't wait for Promises (ie. chunking)
to resolve. Instead, we have to disable the chunking/asyc module fetch by switching to
a synchronous require(). We could just use require() everywhere (server side and client)
but then we'd loose chunking!