react-hot-loader 和 webpack-dev-server 不重新加载更改

react-hot-loader and webpack-dev-server don't reload changes

我试着按照 react-hot-loader/getstarted 起床并在我的 webpack 配置中 运行 react-hot-loader。当我对我的组件进行一些更改时,react-hot-loader 不会重新加载更改。我更新了 npm 脚本,将 react-hot-loader 添加到 babel 和 webpack 配置中,并将 module.hot.accept 添加到 index.jsx.

package.json

"scripts": {
    "dev": "webpack-dev-server -d --progress --colors --port 8090 --hot --inline",
  },
    "dependencies": {
        "aphrodite": "^2.2.1",
        "bootstrap": "^4.1.1",
        "bootstrap-select": "^1.13.1",
        "classnames": "^2.2.5",
        "d3": "^3.5.17",
        "fixed-data-table-2": "^0.8.13",
        "font-awesome": "^4.7.0",
        "jquery": "^3.3.1",
        "js-cookie": "^2.2.0",
        "lodash": "^4.17.10",
        "moment": "^2.22.1",
        "popper.js": "^1.14.3",
        "react": "^16.3.2",
        "react-bootstrap-typeahead": "^3.1.3",
        "react-datetime": "^2.14.0",
        "react-dom": "^16.3.2",
        "react-hot-loader": "^4.3.3",
        "react-loadable": "^5.4.0",
        "react-modal": "^3.4.4",
        "react-redux": "^5.0.7",
        "react-router": "^4.2.0",
        "react-router-dom": "^4.2.2",
        "react-router-redux": "^5.0.0-alpha.9",
        "react-slidedown": "^1.3.0",
        "react-tippy": "^1.2.2",
        "react-toastify": "^4.0.1",
        "react-transition-group": "^2.3.1",
        "redux": "^4.0.0",
        "redux-saga": "^0.16.0"
      },
      "devDependencies": {
        "autoprefixer": "^8.4.1",
        "babel": "^6.23.0",
        "babel-core": "^6.26.3",
        "babel-eslint": "^8.2.3",
        "babel-loader": "^7.1.4",
        "babel-plugin-syntax-dynamic-import": "^6.18.0",
        "babel-plugin-transform-async-to-generator": "^6.24.1",
        "babel-plugin-transform-class-properties": "^6.24.1",
        "babel-plugin-transform-object-rest-spread": "^6.26.0",
        "babel-polyfill": "^6.26.0",
        "babel-preset-env": "^1.6.1",
        "babel-preset-latest": "^6.24.1",
        "babel-preset-react": "^6.24.1",
        "css-loader": "^0.28.11",
        "eslint": "^4.19.1",
        "eslint-config-airbnb": "^16.1.0",
        "eslint-plugin-import": "^2.11.0",
        "eslint-plugin-jsx-a11y": "^6.0.3",
        "eslint-plugin-react": "^7.7.0",
        "file-loader": "^1.1.11",
        "http-server": "^0.11.1",
        "less": "^3.0.2",
        "less-loader": "^4.1.0",
        "node-sass": "^4.9.0",
        "npm-install-webpack-plugin": "^4.0.5",
        "postcss": "^6.0.22",
        "postcss-loader": "^2.1.4",
        "redux-devtools-extension": "^2.13.2",
        "sass-loader": "^7.0.1",
        "sass-resources-loader": "^1.3.3",
        "style-loader": "^0.21.0",
        "url-loader": "^1.0.1",
        "webpack": "^4.6.0",
        "webpack-cli": "^2.1.2",
        "webpack-dev-server": "^3.1.4",
        "webpack-merge": "^4.1.2",
        "webpack-notifier": "^1.6.0"
      }

.babelrc

{
  "presets": [
    [
      "latest", {
        "es2015": {
          "modules": false
        }
      }
    ],
    "react"
  ],
  "plugins": [
    "transform-object-rest-spread",
    "transform-class-properties",
    "syntax-dynamic-import",
    "transform-async-to-generator",
    "react-hot-loader/babel"
  ]
}

webpack.config.js

const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const path = require('path');
const WebpackNotifierPlugin = require('webpack-notifier');

const TARGET = process.env.npm_lifecycle_event;
console.log(`target event is ${TARGET}`);

let outputFileName = 'app';
outputFileName += TARGET === 'prod' ? '.min.js' : '.js';

const common = {
  entry: ['babel-polyfill', 'react-hot-loader/patch', './index.jsx'],
  output: {
    publicPath: '/',
  },
  module: {
    rules: [
      {
        test: /\.js[x]?$/,
        exclude: /(node_modules)/,
        use: {
          loader: 'babel-loader',
        },
      },
    ],
  },
  plugins: [
    new webpack.ProvidePlugin({
      jQuery: 'jquery',
      $: 'jquery',
      jquery: 'jquery',
      'window.jQuery': 'jquery',
    }),
    new WebpackNotifierPlugin(),
  ],
  resolve: {
    modules: [
      path.resolve('.'),
      path.resolve('script'),
      path.resolve('script', 'views'),
      'node_modules',
    ],
    extensions: ['.js', '.jsx', '.json'],
  },
};

if (TARGET === 'dev' || !TARGET) {
  module.exports = webpackMerge(common, {
    devtool: 'eval-source-map',
    output: {
      filename: 'bundle.js',
      sourceMapFilename: '[file].map',
    },
    module: {
      rules: [
        {
          test: /\.scss$/,
          loaders: [
            'style-loader',
            'css-loader',
            {
              loader: 'postcss-loader',
              options: {
                config: {
                  path: 'postcss.config.js',
                },
              },
            },
            'sass-loader',
          ],
        },
        {
          test: /\.less$/,
          loaders: ['style-loader', 'css-loader', 'less-loader'],
        },
        {
          test: /\.css$/,
          use: ['style-loader', 'css-loader'],
        },
        {
          test: /\.(eot|ttf|svg|gif|png|jpg|otf|woff|woff2)$/,
          loader: 'url-loader',
        },
      ],
    },
    devServer: {
      contentBase: path.resolve(__dirname), // New
      historyApiFallback: true,
    },
    plugins: [
      new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify('development'),
      }),
      new webpack.HotModuleReplacementPlugin(),
    ],
  });
}

index.jsx

import React from 'react';
import ReactDOM from 'react-dom';
import createHistory from 'history/createBrowserHistory';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { ConnectedRouter, routerReducer, routerMiddleware } from 'react-router-redux';
import { Provider } from 'react-redux';
import { AppContainer } from 'react-hot-loader';
import createSagaMiddleware from 'redux-saga';
import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';

import 'bootstrap';
import 'styles/custom.scss';

import App from 'App';
import reducers from 'state';
import sagas from 'sagas';

const history = createHistory();
const sagaMiddleware = createSagaMiddleware();

const store = createStore(
  combineReducers({
    ...reducers,
    router: routerReducer,
  }),
  composeWithDevTools(applyMiddleware(routerMiddleware(history), sagaMiddleware)),
);

if (module.hot) {
  module.hot.accept('state', () => {
    store.replaceReducer(require('state').default);
  });
}

sagaMiddleware.run(sagas);

const render = (Component) => {
  ReactDOM.render(
    <AppContainer>
      <Provider store={store}>
        <ConnectedRouter history={history}>
          <Component />
        </ConnectedRouter>
      </Provider>
    </AppContainer>,
    document.getElementById('app'),
  );
};

render(App);

if (module.hot) {
  module.hot.accept('App', () => { render(App); });
}

index.html

<!DOCTYPE html>

<html lang="en">
<head>
    <meta charset="utf-8">

    <title>Test</title>

    <meta name="description" content="">

    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

</head>
<body>

    <div id="app">

    </div>

  <script type="text/javascript" src="/bundle.js"></script>

</body>
</html>
  1. 您可以使用 react-scriptreact-app-rewire-hot-loader
  2. 你可能不会用react-hot-loader/babel

    { test: /\.(js|jsx)$/,
    include: paths.appSrc,
    loader: require.resolve("babel-loader"),
    options: {
      // This is a feature of babel-loader for webpack (not Babel itself).
      // It enables caching results in ./node_modules/.cache/babel-loader/
      // directory for faster rebuilds.
      cacheDirectory: true,
      plugins: ["react-hot-loader/babel"]
      },
    }
    

您是否尝试过将根组件导出为热组件:

import { hot } from "react-hot-loader"
const App = () => <h3>Hello World</h3>
export default hot(module)(App)

您定义了 Hot Module Replacement 两次。您在 package.json 中定义了 --hot 参数,然后在 webpack.config.js 中定义了 new webpack.HotModuleReplacementPlugin()。你应该只定义一次。