Не удается заставить Webpack 2 HMR React работать

В настоящее время я изо всех сил пытаюсь заставить HMR работать в моей настройке Webpack 2. Я объясню всю свою настройку, поэтому надеюсь, что этого достаточно, чтобы кто-то понял, что происходит.

Структура моего проекта:

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

Это содержимое моего webpack.config.js файла, помещенного в корень моего проекта:

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

module.exports = buildConfig;

Итак, в этом файле у меня есть возможность передать различные окружения функции buildConfig. Я использую эти параметры, чтобы использовать разные файлы конфигурации для разработки и производства. Это содержимое моего 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"
  }
}

У меня, конечно, больше полей в моем package.json, но я не буду показывать их здесь, поскольку они не имеют отношения к делу.

Поэтому во время разработки я запускаю команду npm run build:dev в своем терминале. Это будет использовать файл dev.js из папки config. Это содержимое файла 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
    ]
  }
};

И последнее, но не менее важное, моя установка HMR. В моем 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)
  });
}

Итак, когда я запускаю свой npm start build:dev в браузере и перехожу к http://localhost:3000, я вижу, что мой сайт работает должным образом. Это вывод в консоли:

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.

Текст test поступает из функции рендеринга в моем TodoApp компоненте. Эта функция выглядит так:

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>
  );
}

Итак, теперь самое важное. Я обновляю возврат этой функции рендеринга, которая должна вызвать срабатывание HMR. Я меняю функцию рендеринга на эту.

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>
  );
}

Это результат, который я получаю в консоли:

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.

Вы бы сказали, что это хорошо. Но мой сайт НИЧЕГО не обновляет.

Затем я меняю код HMR в моем index.js на этот:

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

И это работает. Я просто не понимаю. Почему не работает, если это мой код HMR:

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

Кстати, эта настройка основана на настройке из https://webpack.js.org/guides/hmr-react/

Я надеюсь, что кто-нибудь может мне помочь. Если кому-то нужна дополнительная информация, не стесняйтесь спрашивать. Заранее спасибо!

ОБНОВЛЕНИЕ

Забыл выложить свой .babelrc файл. Это оно:

{
  "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.
  ]
}

person DavidWorldpeace    schedule 19.04.2017    source источник


Ответы (2)


Импорт является статическим, и после того, как обновление было идентифицировано в module.hot.accept, вы снова визуализируете тот же самый компонент, поскольку TodoApp по-прежнему содержит старую версию вашего модуля, и HMR понимает это и не обновляет и не меняет что-либо в вашем приложении.

Вы хотите использовать Динамический импорт: import(). Чтобы заставить его работать с babel, вам нужно добавить babel-plugin-syntax-dynamic-import, иначе он сообщит о синтаксической ошибке, поскольку не ожидал, что import будет использоваться как функция. react-hot-loader/babel не требуется, если вы используете react-hot-loader/patch в конфигурации веб-пакета, поэтому ваши плагины в вашем .babelrc станут:

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

Теперь в вашей render() функции вы можете импортировать TodoApp и отрендерить его.

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() - это обещание, которое разрешится вместе с модулем, и вы хотите использовать default экспорт.


Несмотря на то, что вышесказанное верно, документация webpack не требует от вас использования динамического импорта, потому что webpack обрабатывает модули ES из коробки, что также описано в _ 15_ docs - Webpack 2, и поскольку webpack также обрабатывает HMR, он будет знать, что делать в этом случае. Чтобы это работало, вы не должны преобразовывать модули в commonjs. Вы сделали это с ["es2015", {"modules": false}], но у вас также есть предустановка latest, настроенная в конфигурации вашего веб-пакета, которая также преобразует модули. Чтобы избежать путаницы, вы должны иметь все конфигурации babel в .babelrc вместо разделения некоторых на параметры загрузчика.

Удалите пресеты в babel-loader полностью из конфигурации вашего веб-пакета, и он будет работать, поскольку у вас уже есть необходимые пресеты в вашем .babelrc. babel-preset-latest устарел, и если вы хотите использовать эти функции, вам следует начать использовать _ 22_, который также заменяет es2015. Итак, ваши пресеты в .babelrc будут такими:

"presets": [
  ["env", {"modules": false}],
  "react"
],
person Michael Jungo    schedule 19.04.2017
comment
Спасибо, это действительно была проблема! Настройки в моем .babelrc файле были перезаписаны моим webpack.config файлом. - person DavidWorldpeace; 20.04.2017
comment
Есть ли причина использовать .babelrc поверх webpack.config? Почему бы просто не использовать webpack.config? - person John Leidegren; 25.08.2017
comment
@JohnLeidegren .babelrc применяется ко всему, что использует Babel. Если вы настроите его в конфигурации webpack, он будет работать только для webpack. За пределами webpack у Babel много применений. Одним из примеров могут быть тесты, поскольку вопрос Unexpected Token - Jest for существующее приложение React + Web-pack показано. - person Michael Jungo; 25.08.2017

Проверьте эту проблему на GitHub или просто используйте ее в своем 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();
}
person Matúš Šťastný    schedule 25.04.2017