Can't get Webpack 2 HMR React to work

I am currently struggling to get HMR to work in my Webpack 2 setup. I will explain my entire setup, so I hope this is enough for someone to understand what is going on.

My project structure:

config
  dev.js
  prod.js 
dist
  css
  js
  index.html
node_modules
src
  components
    // some JavaScript components
  shared
  stylesheets
  index.js
.babelrc
package.json
webpack.config.js

      

This is the content of my file webpack.config.js

placed in the root of my project:

function buildConfig(env) {
  return require('./config/' + env + '.js')(env)
}

module.exports = buildConfig;

      

So, in this file, I have the ability to pass different environments to the function buildConfig

. I use these options to use different config files for development and production. This is the content in the file package.json

:

{
  "main": "index.js",
  "scripts": {
    "build:dev": "node_modules/.bin/webpack-dev-server --env=dev",
    "build:prod": "node_modules/.bin/webpack -p --env=prod"
  },
  },
  "devDependencies": {
    "autoprefixer-loader": "^3.2.0",
    "babel-cli": "^6.18.0",
    "babel-core": "^6.24.1",
    "babel-loader": "^6.2.5",
    "babel-preset-latest": "^6.16.0",
    "babel-preset-react": "^6.16.0",
    "babel-preset-stage-0": "^6.16.0",
    "css-loader": "^0.25.0",
    "extract-text-webpack-plugin": "^2.1.0",
    "json-loader": "^0.5.4",
    "node-sass": "^3.13.1",
    "postcss-loader": "^1.3.3",
    "postcss-scss": "^0.4.1",
    "sass-loader": "^4.1.1",
    "style-loader": "^0.13.1",
    "webpack": "^2.4.1",
    "webpack-dev-server": "^2.4.2"
  },
  "dependencies": {
    "babel-plugin-react-css-modules": "^2.6.0",
    "react": "^15.3.2",
    "react-dom": "^15.3.2",
    "react-hot-loader": "^3.0.0-beta.6",
    "react-icons": "^2.2.1"
  }
}

      

I have, of course, more fields in mine package.json

, but I will not show them here as they are out of date.

So during development, I run the command npm run build:dev

in my terminal. This will use the file dev.js

in the folder config

. This is the content of the file dev.js

:

const webpack = require('webpack');
const { resolve } = require('path');
const context = resolve(__dirname, './../src');

module.exports = function(env) {
  return {
    context,
    entry: {
      app: [
        'react-hot-loader/patch',
        // activate HMR for React
        'webpack-dev-server/client?http://localhost:3000',
        // bundle the client for webpack-dev-server
        // and connect to the provided endpoint
        'webpack/hot/only-dev-server',
        // bundle the client for hot reloading
        // only- means to only hot reload for successful updates
        './index.js'
        // the entry point of our app
      ]
    },
    output: {
      path: resolve(__dirname, './../dist'), // `dist` is the destination
      filename: '[name].js',
      publicPath: '/js'
    },
    devServer: {
      hot: true, // enable HMR on the server
      inline: true,
      contentBase: resolve(__dirname, './../dist'), // `__dirname` is root of the project
      publicPath: '/js',
      port: 3000
    },
    devtool: 'inline-source-map',
    module: {
      rules: [
        {
          test: /\.js$/, // Check for all js files
          exclude: /node_modules/,
          use: [{
            loader: 'babel-loader',
            query: {
              presets: ['latest', 'react'],
              plugins: [
                [
                  "react-css-modules",
                  {
                    context: __dirname + '/../src', // `__dirname` is root of project and `src` is source
                    "generateScopedName": "[name]__[local]___[hash:base64]",
                    "filetypes": {
                      ".scss": "postcss-scss"
                    }
                  }
                ]
              ]
            }
          }]
        },
        {
          test: /\.scss$/,
          use: [
            'style-loader',
            {
              loader: 'css-loader',
              options: {
                sourceMap: true,
                modules: true,
                importLoaders: 2,
                localIdentName: '[name]__[local]___[hash:base64]'
              }
            },
            'sass-loader',
            {
              loader: 'postcss-loader',
              options: {
                plugins: () => {
                  return [
                    require('autoprefixer')
                  ];
                }
              }
            }
          ]
        }
      ]
    },
    plugins: [
      new webpack.HotModuleReplacementPlugin(),
      // enable HMR globally
      new webpack.NamedModulesPlugin()
      // prints more readable module names in the browser console on HMR updates
    ]
  }
};

      

Last but not least, my HMR setup. I have this setting in a file index.js

:

import React from 'react';
import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import TodoApp from './components/TodoApp';
import './stylesheets/Stylesheets.scss';

const render = (Component) => {
  ReactDOM.render(
      <AppContainer>
        <Component />
      </AppContainer>,
      document.querySelector('#main')
  );
};

render(TodoApp);

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

      

So when I run mine npm start build:dev

in my browser and navigate to http://localhost:3000

, I can see that my site is working as expected. This is the output in the console:

dev-server.js:49 [HMR] Waiting for update signal from WDS...
only-dev-server.js:66 [HMR] Waiting for update signal from WDS...
TodoApp.js:102 test
client?344c:41 [WDS] Hot Module Replacement enabled.

      

The text test

comes from a render function in my component TodoApp

. This function looks like this:

render() {
  console.log('test');
  return(
      <div styleName="TodoApp">
        <TodoForm addTodo={this.addTodo} />
        <TodoList todos={this.state.todos} deleteTodo={this.deleteTodo} toggleDone={this.toggleDone} updateTodo={this.updateTodo} />
      </div>
  );
}

      

So now this is the important stuff. I am updating the return of this render function which should trigger HMR for input. I change the render function to this.

render() {
  console.log('test');
  return(
      <div styleName="TodoApp">
        <p>Hi Stackoverflow</p>
        <TodoForm addTodo={this.addTodo} />
        <TodoList todos={this.state.todos} deleteTodo={this.deleteTodo} toggleDone={this.toggleDone} updateTodo={this.updateTodo} />
      </div>
  );
}

      

This is the output I get in the console:

client?344c:41 [WDS] App updated. Recompiling...
client?344c:41 [WDS] App hot update...
dev-server.js:45 [HMR] Checking for updates on the server...
TodoApp.js:102 test
log-apply-result.js:20 [HMR] Updated modules:
log-apply-result.js:22 [HMR]  - ./components/TodoApp.js
dev-server.js:27 [HMR] App is up to date.

      

You would say it's good. But my site is not updating ANYTHING.

Then I change the HMR code in mine index.js

to this:

// Hot Module Replacement API
if (module.hot) {
  module.hot.accept();
}

      

And it works. I just do not understand. Why doesn't this work if this is my HMR code:

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

      

By the way, this setting is based on setting https://webpack.js.org/guides/hmr-react/

I hope someone can help me. If anyone needs more information, don't hesitate to ask. Thanks in advance!

UPDATE

Forgot to leave my file .babelrc

. It's him:

{
  "presets": [
    ["es2015", {"modules": false}],
    // webpack understands the native import syntax, and uses it for tree shaking

    "react"
    // Transpile React components to JavaScript
  ],
  "plugins": [
    "react-hot-loader/babel"
    // EnablesReact code to work with HMR.
  ]
}

      

+3


source to share


2 answers


The import is static and after the update has been identified in module.hot.accept

, you show the same component again as it TodoApp

still contains the old version of your module and HMR understands this and does not update or change anything in your application.

You want to use Dynamic Import:import()

. To make it work with babel you need to add babel-plugin-syntax-dynamic-import

, otherwise it will report a syntax error as it didn't expect to import

be used as a function. react-hot-loader/babel

not required if you are using react-hot-loader/patch

webpack in your config, so your plugins in yours .babelrc

will become:

"plugins": [
  "syntax-dynamic-import"
]

      

In your function, render()

you can import TodoApp

and display it.

const render = () => {
  import('./components/TodoApp').then(({ default: Component }) => {
    ReactDOM.render(
      <AppContainer>
        <Component />
      </AppContainer>,
      document.querySelector('#main')
    );
  });
};

render();

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

      



import()

is a promise that will be resolved by the module and you want to use the export default

.


While this is true, the webpack documentation does not require the use of dynamic imports, since webpack handles ES modules out of the box, also described in the react-hot-loader

docs - Webpack 2
, and since webpack also handles HMR, it will know what to do in this case. For this to work, you must not convert modules to commonjs. You did it with ["es2015", {"modules": false}]

, but you also have a preset latest

set up in your webpack config that will convert the modules as well. To avoid confusion, you should have all babel settings in .babelrc

instead of splitting some into bootloader options.

Remove presets babel-loader

entirely from your webpack config and it will work since you already have the required presets in .babelrc

. babel-preset-latest

is deprecated and if you want to use these features you have to start using babel-preset-env

which also replaces es2015

. Thus, your presets .babelrc

will be as follows:

"presets": [
  ["env", {"modules": false}],
  "react"
],

      

+5


source


Check the issue on GitHub or just use this in your index.js:



import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'

import App from './components/App'

const render = Component => { 
    ReactDOM.render(
        <AppContainer>
            <Component/>
        </AppContainer>,
        document.getElementById('react-root')
    )
}

render(App)

if(module.hot) {
    module.hot.accept();
}

      

+1


source







All Articles