Skip to content

Content howto hmr #64

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 31, 2016
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 183 additions & 1 deletion content/how-to/hot-module-reload.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,186 @@
---
title: How to Configure Hot Module Reloading?
title: How to Configure Hot Module Replacement?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cc @bebraw

I think since there are many different HMR recipes, maybe we could structure this in like How-To -> HMR Recipies -> Babel/React/Redux

I think that way we encourage other recipes in here for many state libraries and frameworks. And it reinforces a much asked 'cookbook' for certain topics.

---
Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running without a page reload. HMR is particularly useful in applications using a single state tree, since components are "dumb" and will reflect the latest application state, even after their source is changed and they are replaced.

Webpack's power lies in its customizablity, and there are MANY ways of configuring HMR given the needs of a partiular project. The approach described below uses Babel and React, but these tools are not necessary for HMR to work. If you'd like to see examples of other approaches, please request them or, better yet, open up a PR with an addition!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/s/partiular/particular

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add link to "open up a PR".


##Project Config
To use HMR, you'll need the following dependencies:

* webpack 2 (tested with 2.1.0-beta.20)
* webpack-dev-server 2 (tested with 2.1.0-beta.0)

This guide will be demonstrating the use of HMR with Babel, React, and PostCSS (using CSS Modules). To follow along, please add the following deps to your `package.json`:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to give a npm install command as the versions tend to live a lot? That can lead to breakage, though, but we can fix the docs. We'll end up with the same problem either case.


```js
"devDependencies": {
"babel": "^6.5.2",
"babel-core": "^6.11.4",
"babel-loader": "^6.2.4",
"babel-preset-es2015-webpack": "^6.4.2",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    "babel": "^6.5.2",
    "babel-core": "^6.13.2",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.13.2",

Upgrade pakages, after that you may replace deprecated https://github.com/gajus/babel-preset-es2015-webpack on

{
    "presets": [
        [
            "es2015",
            {
                "modules": false
            }
        ]
    ]
}

"babel-preset-react": "^6.11.1",
"babel-preset-stage-2": "^6.11.0",
"css-loader": "^0.23.1",
"postcss-loader": "^0.9.1",
"react-hot-loader": "^3.0.0-beta.1",
"style-loader": "^0.13.1",
"webpack": "^2.1.0-beta.20",
"webpack-dev-server": "^2.1.0-beta.0"
}
```

###Babel Config
Your `.babelrc` file should look like the following:

```js
{
"presets": ["es2015-webpack", "stage-2", "react"],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"presets": [["es2015", {"modules": false}], "stage-2", "react"],

After uprgading Babel till version v6.13.*

"plugins": ["react-hot-loader/babel"]
}
```

There's a few things to note here. First, in the presets, we're using es2015-webpack, which is a Babel preset that enables support for es2015 while DISABLING conversion to CommonJS module format. Since Webpack 2 understands import/export syntax, the es2015-webpack preset (unlike the general es2015 preset) allows Webpack to perform tree-shaking and dead code elimination.

The stage-2 preset is up to your comfort with adopting new technoligies; stage-0 will keep you on the bleeding age.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/s/technoligies/technologies

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add a warning (W>) about using Stage 0 specifically as it can potentially break your code. I prefer to enable features on feature basis over stages to communicate better which custom features I'm using. Better for maintenance.


Finally, the React preset will enable React specific transpilation, while the react-hot-loader/babel plugin will enable your React code to work with HMR.

###Webpack config
While there's many ways of setting up your Webpack config - via API, via multiple or single config files, etc - here is the basic information you should have available:

```js
const { resolve } = require('path');
const webpack = require('webpack');

const ENTRY = ['./index.js'];

module.exports = env => {
return {
entry: env.dev?
[
'react-hot-loader/patch',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've seen conflicting reports of using react-hot-loader/webpack:

gaearon/react-hot-boilerplate#61
https://github.com/gaearon/react-hot-loader/blob/v3.0.0-beta.1/src/webpack/index.js

From what I understand, /patch is for entry and /webpack is a loader.

I haven't tested it, but it'd be great to bring some clarification to this question, especially if it means simplifying entry (which becomes very convoluted in dev mode).

'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/only-dev-server',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two lines (webpack-dev-server and the one after) can be skipped if you run webpack-dev-server in inline mode. A little easier to manage. If I remember right, that might be the new default for webpack 2 even.

].concat(ENTRY)
:
ENTRY,
output: {
filename: 'bundle.js',
path: resolve(__dirname, 'dist'),
publicPath: '/'
},
context: resolve(__dirname, 'src'),
devtool: env.prod ? 'source-map' : 'inline-source-map',
devServer: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, if you enable inline mode here, you can simplify entries.

hot: true,
publicPath: '/'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document why publicPath is set this way. A comment will do.

},
module: {
loaders: [
{ test: /\.js$/, loader: 'babel',
exclude: /node_modules/ },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

include over exclude? Though setting both is actually even a better idea (needed for monorepo setups).

{
test: /\.css$/,
loader: 'style!css-loader?modules&postcss-loader',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe prefer loaders array over loader. Reads better. webpack 2 provides a better query format too so we might want to default to that.

},
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention why NamedModulesPlugin is used.

],
}
};
```

There's a lot going on above, and not all of it is related to HMR. If you are new to Webpack configs, please see the documentation elsewhere on this site. The basic assumption here is that your JS entry is located at `./src/index.js`, and that you're using CSS Modules for your styling. Let's look at the HMR specific items.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather add clarifying comments to the configuration above. Then we could skip a sentence or two here.


First, the plugins. While only the HMRPlugin itself is necessary to enable HMR, the NamedModulesPlugin will make your life easier by showing an intelligible module id in your browser console whenever a file is changed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expand HMRPlugin to full name. That NamedModulesPlugin bit can be eliminated by moving the explanation as a code comment.


Next, working backward, is the `devServer` key. While it's possible to configure webpack-dev-server directly from the CLI, or more specifically in a server script from the Node module API, this example approach is using the `devServer` key in the overall webpack config. `hot: true` turns on HMR. `publicPath` denotes the root from wich the server will serve up your code (e.g. `localhost:8080${PUBLIC_PATH}`, so in this case `localhost:8080/`). the `publicPath` should match the `output.publicPath`.

Finally, your entry file should be preceded by the elements included here: `'react-hot-loader/patch'`, `'webpack-dev-server/client?http://localhost:8080'`, and `'webpack/hot/only-dev-server'`. The react-hot-loader entry is only necessary if you are using React. The `webpack-dev-server/client?` line should make sure to point at the dev server instance you are running (in case you have configured a different port, for example). Finally, the `only` in `hot/only-dev-server` means that updates will only be sent on clean builds/bundles; you can drop the `only-` if you would prefer seeing error output in your browser via HMR.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would split the paragraph at Finally. Separate idea.


The module loaders in this config, while not specific to HMR, are worth noting here; we're making sure to use Babel (as configued in the .babelrc) for our JS files, and setting up CSS Modules for our CSS code.

###Code
In this guide, we're using the following files:

```js
// ./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader'

import App from './components/App';

const render = () => {
ReactDOM.render(
<AppContainer>
<App/>
</AppContainer>,
document.getElementById('root')
);
};

render();

// Hot Module Replacement API
if (module.hot) {
module.hot.accept('./components/App', render);
}


// ./src/components/App.js
import React from 'react';
import styles from './App.css';

const App = () => (
<div className={styles.app}>
<h2>Hello, </h2>
</div>
);

export default App;
```

```css
// ./src/components/App.css
.app {
text-size-adjust: none;
font-family: helvetica, arial, sans-serif;
line-height: 200%;
padding: 6px 20px 30px;
}
```

Now, the above code is using React, but it doesn't need to be. In fact, the only thing that matters above is the code refering to `module`. First, we wrap the HMR code inside of `module.hot` check; webpack exposes `module` to the code, and if we are running with `hot: true` configured, we'll enter the inside of the conditional. While the module API offers more options than what's above, the most important element is the `module.hot.accept` call. It specific how to handle changes to specific dependencies. So in this, it will file the `render` method ONLY when `src/components/App.js` changes! Note that would also include when the dependencies of `App.js` change - so the `render` method will file not just for changes made directly to the source of `App.js`, but also changes made to `App.css`, since `App.css` is included in `App.js`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Split up into two, three paragraphs.


###Package.json
Finally, we need to start up webpack dev server to bundle our code and see HMR in action. We can use the following package.json entry:

```js
"start" : "webpack-dev-server --env.dev --content-base dist",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why to define --content-base here? Why not to push it to the configuration above?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe mention both ways, it's okay to stay opinionated right? I usually set content-base on the CLI lol.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, you can mention both ways. T> is ideal for alternatives.

```

Run `npm start`, open up your browser to `localhost:8080`, and you should see the folling entries printed in your console.log:
```
dev-server.js:49[HMR] Waiting for update signal from WDS...
only-dev-server.js:74[HMR] Waiting for update signal from WDS...
client?c7c8:24 [WDS] Hot Module Replacement enabled.
```

Go ahead and edit and save your App.js file. You should see something like the following in your console.log:

```
[WDS] App updated. Recompiling...
client?c7c8:91 [WDS] App hot update...
dev-server.js:45 [HMR] Checking for updates on the server...
log-apply-result.js:20 [HMR] Updated modules:
log-apply-result.js:22 [HMR] - ./components/App.js
dev-server.js:27 [HMR] App is up to date.
```
Note that HMR specifies the paths of the updated modules. That's because we're using the NamedModules plugin!


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bit could use a conclusion as per writer's guide.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also link to my chapter related to the topic. The React chapter has related info too.