您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

WebPack 模块热替换

模块热替换(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的之一。它允许在运行时更新各种模块,而无需进行完全刷新。本重点介绍实现,而概念提供了更多关于它的工作原理以及为什么它有用的细节。

HMR 不适用于生产环境,这意味着它应当只在开发环境使用。更多详细信息,请查看生产环境构建指南。

启用 HMR

启用此实际上相当简单。而我们的,就是更新  的配置,和使用 webpack 内置的 HMR 。我们还要掉 print.js 的入口起点,因为它现在正被 index.js 模块使用。

如果你使用了 webpack-dev-middleware 而没有使用 webpack-dev-server,请使用  package 包,以在你的服务或应用程序上启用 HMR。

webpack.con.js

  const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
  const CleanWebpackPlugin = require('clean-webpack-plugin');+ const webpack = require('webpack');

  module.exports = {
    entry: {-      app: './src/index.js',-      print: './src/print.js'+      app: './src/index.js'
    },
    devtool: 'inline-source-map',
    devServer: {
      contentBase: './dist',+     hot: true
    },
    plugins: [
      new CleanWebpackPlugin(['dist']),
      new HtmlWebpackPlugin({
        title: 'Hot Module Replacement'
      }),+     new webpack.NamedModulesPlugin(),+     new webpack.HotModuleReplacementPlugin()
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };

你可以通过命令来  的配置:webpack-dev-server --hotOnly。

注意,我们还了 NamedModulesPlugin,以便更容易查看要修补(patch)的依赖。在起步阶段,我们将通过在命令行中运行 npm start 来启动并运行 dev server。

现在,我们来 index.js ,以便当 print.js 内部发生变更时可以告诉 webpack 接受更新的模块。

index.js

  import _ from 'lodash';
  import printMe from './print.js';

  function component() {
    var element = document.createElement('div');
    var btn = document.createElement('button');

    element.innerHTML = _.join(['Hello', 'webpack'], ' ');

    btn.innerHTML = 'Click me and check the console!';
    btn.onclick = printMe;

    element.appendChild(btn);

    return element;
  }

  document.body.appendChild(component());
++ if (module.hot) {+   module.hot.accept('./print.js', function() {+     console.log('Accepting the updated printMe module!');+     printMe();+   })+ }

更改 print.js 中 console.log 的,你将会在浏览器中看到如下的。

print.js

  export default function printMe() {-   console.log('I get called from print.js!');+   console.log('Updating print.js...')
  }

console

[HMR] Waiting for update signal from WDS...
main.js:4395 [WDS] Hot Module Replacement enabled.+ 2main.js:4395 [WDS] App updated. Recompiling...+ main.js:4395 [WDS] App hot update...+ main.js:4330 [HMR] Checking for updates on the server...+ main.js:10024 Accepting the updated printMe module!+ 0.4b8ee77….hot-update.js:10 Updating print.js...+ main.js:4330 [HMR] Updated modules:+ main.js:4330 [HMR]  - 20+ main.js:4330 [HMR] Consider using the NamedModulesPlugin for module names.

通过 Node.js API

当使用 webpack dev server 和 Node.js API 时,不要将 dev server 选项放在 webpack 配置对象(webpack con object)中。而是,在创建选项时,将其作为第二个参数传递。例如:

new WebpackDevServer(compiler, options)

想要启用 HMR,还需要 webpack 配置对象,使其包含 HMR 入口起点。webpack-dev-server package 中具有叫做 addDevServerEntrypoints 的,你可以通过使用这个来实现。这是关于如何使用的小例子:

dev-server.js

const webpackDevServer = require('webpack-dev-server');const webpack = require('webpack');const con = require('./webpack.con.js');const options = {
  contentBase: './dist',
  hot: true,
  host: 'localhost'};webpackDevServer.addDevServerEntrypoints(con, options);const compiler = webpack(con);const server = new webpackDevServer(compiler, options);server.listen(5000, 'localhost', () => {
  console.log('dev server listening on port 5000');});

如果你在 使用 webpack-dev-middleware,可以通过  package 包,在开发服务下启用 HMR。

问题

模块热替换可能比较难掌握。为了说明这一点,我们回到刚才的示例中。如果你继续点击示例上的按钮,你会发现控制台仍在打印这旧的 printMe 。

这是因为按钮的 onclick 事件仍然绑定在旧的 printMe 上。

为了让它与 HMR 正常工作,我们需要使用 module.hot.accept 更新绑定到新的 printMe 上:

index.js

  import _ from 'lodash';
  import printMe from './print.js';

  function component() {
    var element = document.createElement('div');
    var btn = document.createElement('button');

    element.innerHTML = _.join(['Hello', 'webpack'], ' ');

    btn.innerHTML = 'Click me and check the console!';
    btn.onclick = printMe;  // onclick 事件绑定原始的 printMe 上

    element.appendChild(btn);

    return element;
  }- document.body.appendChild(component());+ let element = component(); // 当 print.js 改变导致重新渲染时,重新渲染的元素+ document.body.appendChild(element);

  if (module.hot) {
    module.hot.accept('./print.js', function() {
      console.log('Accepting the updated printMe module!');-     printMe();+     document.body.removeChild(element);+     element = component(); // 重新渲染后,component 更新 click 事件处理+     document.body.appendChild(element);
    })
  }

这只是例子,但还有很多其他地方可以轻松地让人犯错。幸运的是,存在很多 loader(其中一些在下面提到),使得模块热替换的过程变得更容易。

HMR 样式表

借助于 style-loader 的帮助,CSS 的模块热替换实际上是相当简单的。当更新 CSS 依赖模块时,此 loader 在使用 module.hot.accept 来修补(patch) <style> 。

所以,可以使用以下命令安装两个 loader :

npm install --save-dev style-loader loader

接下来我们来更新 webpack 的配置,让这两个 loader 生效。

webpack.con.js

  const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
  const webpack = require('webpack');

  module.exports = {
    entry: {
      app: './src/index.js'
    },
    devtool: 'inline-source-map',
    devServer: {
      contentBase: './dist',
      hot: true
    },+   module: {+     rules: [+       {+         test: /\.css$/,+         use: ['style-loader', 'loader']+       }+     ]+   },
    plugins: [
      new CleanWebpackPlugin(['dist'])
      new HtmlWebpackPlugin({
        title: 'Hot Module Replacement'
      }),
      new webpack.HotModuleReplacementPlugin()
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };

热加载样式表,与将其导入模块一样简单:

project

  webpack-demo
  | - package.json
  | - webpack.con.js
  | - /dist
    | - bundle.js
  | - /src
    | - index.js
    | - print.js+   | - styles.css

styles.css

body {
  background: blue;}

index.js

  import _ from 'lodash';
  import printMe from './print.js';+ import './styles.css';

  function component() {
    var element = document.createElement('div');
    var btn = document.createElement('button');

    element.innerHTML = _.join(['Hello', 'webpack'], ' ');

    btn.innerHTML = 'Click me and check the console!';
    btn.onclick = printMe;  // onclick event is bind to the original printMe function

    element.appendChild(btn);

    return element;
  }

  let element = component();
  document.body.appendChild(element);

  if (module.hot) {
    module.hot.accept('./print.js', function() {
      console.log('Accepting the updated printMe module!');
      document.body.removeChild(element);
      element = component(); // Re-render the "component" to update the click handler
      document.body.appendChild(element);
    })
  }

将 body 上的样式为 background: red;,你应该可以立即看到的背景颜色随之更改,而无需完全刷新。

styles.css

  body {-   background: blue;+   background: red;
  }

其他和框架

社区还有许多其他 loader 和示例,可以使 HMR 与各种框架和库(library)平滑地进行交互……

:实时调整 react 组件。

:此 loader 用于 vue 组件的 HMR,提供开箱即用体验。

:用于 Elm 程序语言的 HMR。

:无需 loader 或!只需对 main store 进行简单的。

:No loader necessary! A simple change to your main NgModule file is all that's to have full control over the HMR APIs.没有必要使用 loader!只需对主要的 NgModule 进行简单的,由 HMR API 完全控制。

如果你知道任何其他 loader 或,能够有助于或增强模块热替换(Hot Module Replacement),请提交 pull request 以到此列表中!


联系我
置顶