In this post I will show you how to build and understand a webpack config file for developing applications with react, react–router and redux. I also provide a boilerplate to start developing right away.
If you already know how to set up webpack and wish to learn more on building and understanding react apps, see this post. It includes setting up hot module replacement in react components.
The boilerplate contains:
- a working example of a filterable table which you can play around with.
- ES6 – ES7 Support with Babel
- Redux dev tools to help you keep track of the app’s state
- hot module replacement support so you can change modules (defined in webpack config) without having to reload the browser
- a webpack production config so you can build the app and make it ready for production
- Sass support, just import your styles wherever you need them
- eslint to keep your js readable
- tests (coming soon, I swears)
- much more…
Why Webpack
I have been checking out webpack a lot lately. For those of you that don't know what it is, webpack is a tool that helps you deal with dependencies in your js, like browserify does, and generate a bundle out of them. It doesn't stop at that though, through it's loaders, its able to compile SASS or LESS to css as well, and a whole bunch of other things. As I get deeper in to the react ecosystem it seems most people use it instead of browserify. After some investigation it's easy to see why: webpack brings in stuff right out of the box and can not just replace browserify but also whatever task automator you are using, in my case gulp. Additionally, its hot module replacement and browser error logging are some of the extras that I can't live without anymore. More on them later. Edit: Webpack in the context of this post is used for stand-alone apps (no connection to the back end with templates like mustache). Check out this post for use of webpack for connected set ups.React, Redux, React-Router
I'm assuming you know what these 3 are. I could spend several posts talking on them alone so let's just get a quick recap on what they are:- React: "A javascript library for building user interfaces". Reacts excels at easing development of your UI once you get in to it. It forces you to think of your app in components and sub-components that react whenever the state changes.
- Redux: "Predictable state container for JavaScript apps". Redux is a library that helps you manage your app's state. It derives from various things, one of them the Flux architecture. Redux forces you to use pure functions to calculate the new state whenever an action happens, so : (oldState, action) => newState
- React-Router: A router library for react, it connects nicely to react and redux to help your app support routes.
Webpack config
Let's start out with the heart of our development: webpack's config file. If you downloaded the boilerplate from the link I gave you earlier, you might notice there's two webpack config files, one is for development, the other one is run once before each deploy to a production server. The only difference is that the production version will minify/uglify your assets so they don't take as much space. Here's the whole webpack config file:'use strict'; var path = require('path'); var webpack = require('webpack'); var HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { devtool: 'eval-source-map', entry: [ 'webpack-dev-server/client?http://localhost:3000', 'webpack/hot/only-dev-server', 'react-hot-loader/patch', path.join(__dirname, 'app/index.js') ], output: { path: path.join(__dirname, '/dist/'), filename: '[name].js', publicPath: '/' }, plugins: [ new HtmlWebpackPlugin({ template: 'app/index.tpl.html', inject: 'body', filename: 'index.html' }), new webpack.optimize.OccurenceOrderPlugin(), new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin(), new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('development') }) ], module: { loaders: [ { test: /\.js?$/, exclude: /node_modules/, loader: 'babel' }, { test: /\.json?$/, loader: 'json' }, { test: /\.scss$/, loader: 'style!css!sass?modules&localIdentName=[name]---[local]---[hash:base64:5]' }, { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader?limit=10000&minetype=application/font-woff" }, { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "file-loader" } ] } };Eww, big config file. I know. But let's go through it.
'use strict'; var path = require('path'); var webpack = require('webpack'); var HtmlWebpackPlugin = require('html-webpack-plugin');Here, we are getting our dependencies. path is a module that helps us manipulate file paths. webpack is of course the webpack module and HtmlWebpackPlugin is a plugin which helps us auto-generate html, you will see it's use later.
module.exports = { devtool: 'eval-source-map', entry: [ 'webpack-dev-server/client?http://localhost:3000', 'webpack/hot/only-dev-server', 'react-hot-loader/patch', path.join(__dirname, 'app/index.js') ], output: { path: path.join(__dirname, '/dist/'), filename: '[name].js', publicPath: '/' },This is the start of the config. Inside we find the property devtool set to eval-source-map. This is related to the debugging, more info here. In the entry property you define what the entry file is going to be. In this case it's app/index.js. Webpack will use this file to gather all the dependencies from the app. Entry also accepts an array of entry files. We also pass in 2 entries related to the webpack-dev-server, more on it later. Finally there's a line for react-hot-loader. I will be talking about RHL in the following article, so stay tuned. In short, what it does is help us hot reload react components. The output property contains where you want the outputted files to go. In this case we say put everything inside of the 'dist' folder. the name of the file returned by webpack will be given dynamically.
plugins: [ new HtmlWebpackPlugin({ template: 'app/index.tpl.html', inject: 'body', filename: 'index.html' }), new webpack.optimize.OccurenceOrderPlugin(), new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin(), new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('development') }) ],plugins are modules that can be created by anyone which can tap into webpack's build process. Without them webpack wouldn't be as interesting. This is where the power of the community can be seen. We use quite a few plugins for our build process, most of them come right out of the box with webpack. Let's see what they are doing:
HtmlWebpackPlugin
Simplifies creation of HTML files to serve your webpack bundlesThis plugin handles the creation of HTML files for you. The plugin will generate an HTML5 file that includes all your webpack bundles in the body using
script
tags.
It takes a template file as input which is located at 'app/index.tpl.html' and outputs it into our dist folder.
Why is this useful? Because you don't need to write the script tags yourself. Any dependency that your html needs to have is going to be added by this plugin. It's also useful for when we make use of generated filenames that include a hash which changes in every build. This is used for busting the client's browser cache. Without this plugin we would have to manually enter the new hash into the HTML for each build...
OccurrenceOrderPlugin
I'll be honest and say I'm not 100% sure as to what this plugin does, but it is recommended by the docs as it helps optimise your files. I think it gives shorter ID's to more reoccurring modules in your code.Assign the module and chunk ids by occurrence count. Ids that are used often get lower (shorter) ids. This make ids predictable, reduces to total file size and is recommended.
HotModuleReplacementPlugin
This baby is the best plugin ever for developing web apps. It gives you the ability to change code, save it, and see the change appear almost instantly in your browser, without having to reload it. Best of all, it keeps your app's state as it was before the change, so no need to reproduce all the steps!NoErrorsPlugin
This is helpful when you use webpack in the CLI because it doesn't stop the process when there is an error.
If you are using the CLI, the webpack process will not exit with an error code by enabling this plugin.
DefinePlugin
This plugin helps us pass variables from webpack to our js files. We can for example pass what environment we are in (dev or prod) so that our js files act accordingly. This way we can turn off development tools if we are in a production environment.Loaders
The last part of the config file pertains to loaders:module: { loaders: [ { test: /\.js?$/, exclude: /node_modules/, loader: 'babel' }, { test: /\.json?$/, loader: 'json' }, { test: /\.scss$/, loader: 'style!css!sass?modules&localIdentName=[name]---[local]---[hash:base64:5]' }, { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader?limit=10000&minetype=application/font-woff" }, { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "file-loader" } ] }Loaders are what basically replace other task runners like gulp or grunt. They handle the assets by passing each through a pipeline of transformers. Take for example the sass files, they are passed through the following transformations: sass, css, style.
Loaders allow you to preprocess files as youIn our case, it's not CoffeeScript but Babel, which lets us use ES6 features like arrow functions, the 'class' keyword, spead operators, promises, async/await and all the other good stuff!require()
or “load” them. Loaders are kind of like “tasks” are in other build tools, and provide a powerful way to handle frontend build steps. Loaders can transform files from a different language like, CoffeeScript to JavaScript, or inline images as data URLs. Loaders even allow you to do things likerequire()
css files right in your JavaScript! -webpack docs
Webpack Dev Server
If you check the root of your project you will find a node server called 'server'. This server is only useful for development. Without this server we wouldn't be able to actually run webpack.The webpack-dev-server is a little node.js Express server, which uses the webpack-dev-middleware to serve a webpack bundle. It also has a little runtime which is connected to the server via Socket.IO. The server emits information about the compilation state to the client, which reacts to those events. You can choose between different modes, depending on your needs. - webpack docsThings to note:
var webpack = require('webpack'); var WebpackDevServer = require('webpack-dev-server'); var config = require('./webpack.config');The server requires webpack, webpack-dev-server and the actual config file we defined earlier.
new WebpackDevServer(webpack(config), { publicPath: config.output.publicPath, hot: true, historyApiFallback: true, stats: { colors: true } }).listen(3000, 'localhost', function (err) { if (err) { console.log(err); } console.log('Listening at localhost:3000'); });The wepack-dev-server contains everything you need to actually run the app so we only need to instantiate it. As first argument we pass it in the webpack object itself, initialized with the config file we look at earlier. The second argument contains configuration pertaining to the server itself. Important here is to check 'hot' to 'true', so we can do hot module replacement.
Run it
So how do you run this boilerplate? It's quite easy really. All you need to do is download the project from github. In your terminal at the root of the project you donpm install
and once everything installs, you do npm start
Read the next post on how the app itself works
Leave a Reply