I am running an express server that will act as an API for my React app that is being bundled and served by webpack-dev-server.
I am trying to get hot module replacement to work, and am almost there, when I make changes to my files, I get this in the console:
But the app is never re-rendered, unless manually refreshed. Unaware if this is relevant, but when I update my .scss
files, it refreshes without manually doing so, and updates as I would expect.
Versions:
"webpack": "2.1.0-beta.22"
"webpack-dev-server": "2.1.0-beta.8"
"react-hot-loader": "3.0.0-beta.5"
I tried the latest webpack but it gave me validation errors that could not be overcome.
I am running webpack via: "webpack": "webpack-dev-server --port 4000 --env.dev"
, and my express server is being ran on http://localhost:3000
.
Here is my webpack.config.babel.js
:
const webpack = require('webpack');
const { resolve, join } = require('path');
const { getIfUtils, removeEmpty } = require('webpack-config-utils')
const getEntry = (ifDev) => {
let entry
if (ifDev) {
entry = {
app: [
'react-hot-loader/patch',
'webpack/hot/dev-server',
'webpack-dev-server/client?http://localhost:4000/',
'./js/index.js'
],
vendor: ['react']
}
} else {
entry = {
bundle: './js/index.js',
vendor: ['react']
}
}
return entry
}
const config = env => {
const { ifProd, ifDev } = getIfUtils(env)
return {
entry: getEntry(ifDev),
output: {
path: resolve('./public/dist/'),
publicPath: 'http://localhost:4000/',
filename: '[name].bundle.js',
},
context: resolve(__dirname, 'assets'),
devtool: env.prod ? 'source-map' : 'eval',
devServer: {
contentBase: resolve('./public/dist/'),
headers: { 'Access-Control-Allow-Origin': '*' },
publicPath: 'http://localhost:4000/',
hot: true,
noInfo: true,
inline: true
},
bail: env.prod,
module: {
loaders: [
{ test: /\.scss$/, loaders: [ 'style', 'css', 'sass' ], exclude: /node_modules|lib/ },
{ test: /\.(js|jsx)$/, exclude: /node_modules/, loaders: [ 'babel-loader' ] },
{ test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/, loader: 'file-loader' }
]
},
resolve: {
extensions: ['.js', '.jsx']
},
plugins: removeEmpty([
ifDev(new webpack.NoErrorsPlugin()),
ifDev(new webpack.NamedModulesPlugin()),
ifDev(new webpack.HotModuleReplacementPlugin()),
new webpack.DefinePlugin({
'process.env': { NODE_ENV: JSON.stringify((env.prod) ? 'production' : 'development') }
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: Infinity,
filename: 'vendor.bundle.js'
}),
ifProd(new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false
})),
ifProd(new webpack.optimize.UglifyJsPlugin({
compress: { warnings: false },
output: { comments: false },
sourceMap: false
}))
]),
}
}
module.exports = config
Here is my .babelrc
, where I call react-hot-loader
{
"presets": [["es2015", { modules: false }], "stage-0", "react"],
"plugins": ["react-hot-loader/babel"],
"env": {
"test": {
"plugins": ["istanbul"],
"presets": ["es2015", "stage-0", "react"]
}
},
"sourceMaps": "inline"
}
With React Hot Loader v3 and the Babel transform, you want to do this in the root of your component (where you do your rendering, or where you create your Redux provider):
render(
<AppContainer>
<Root
store={ store }
/>
</AppContainer>,
document.getElementById('root')
);
if (module.hot) {
module.hot.accept('./containers/Root', () => {
const RootContainer = require('./containers/Root').default;
render(
<AppContainer>
<RootContainer
store={ store }
/>
</AppContainer>,
document.getElementById('root')
);
});
}
With the new version of Hot Loader, you have to explicitly accept the hot update with module.hot.accept
.
In a more complex Redux project (with routing and hot reloading reducers) you could do something like this:
/**
* Starts the React app with the Router, and renders it to the given DOM container
* @param {DOMElement} container
*/
export default function app(container) {
const store = createStore(
combineReducers({
...reducers,
routing: routerReducer,
form: formReducer,
}),
compose(
applyMiddleware(
routerMiddleware(hashHistory),
thunkMiddleware,
promiseMiddleware
),
process.env.NODE_ENV !== 'production' && window.devToolsExtension ? window.devToolsExtension() : (param) => param
)
);
if (module.hot) {
module.hot.accept('./reducers', () => {
const nextReducers = require('./reducers');
const nextRootReducer = combineReducers({
...nextReducers,
routing: routerReducer,
form: formReducer,
});
store.replaceReducer(nextRootReducer);
});
}
const history = syncHistoryWithStore(hashHistory, store);
render({ store, history, container });
store.dispatch(loadEventsWhenLoggedIn());
if (module.hot) {
module.hot.accept('./render', () => {
const newRender = require('./render').default;
newRender({ store, history, container });
});
}
}
(and render.js)
/**
* Starts the React app with the Router, and renders it to the given DOM container
* @param {DOMElement} container
*/
export default function render({ store, history, container }) {
ReactDOM.render(
<Provider store={store}>
<div className='container'>
<Routes history={history} store={store} />
</div>
</Provider>,
container
);
}
For more examples you should have a look at Dan Abramov's Redux devtools example repo, for example this file: https://github.com/gaearon/redux-devtools/blob/master/examples/todomvc/index.js
HMR works automatically for CSS because style-loader
supports that out-of-the-box.
With React that's not the case. You need react-hot-loader.
Install it with npm, and change this:
{ test: /\.(js|jsx)$/, exclude: /node_modules/, loaders: [ 'babel-loader' ] },
To:
{ test: /\.(js|jsx)$/, exclude: /node_modules/, loaders: [ 'react-hot-loader', 'babel-loader' ] },
If you want to know more, I recommend to read "Webpack’s HMR & React-Hot-Loader — The Missing Manual".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With