webpack 学习三:模块热替换 HMR

2020 年 06 月 10 日

admin
webpack

在上一节中,我们知道了如何让 webpack 自动构建,并刷新浏览器。但是,有个问题,它会刷新整个页面,哪怕我只改动了一小部分,它也会刷新整个页面,重新加载全部资源,如果项目比较大,无疑会影响我们的开发效率。那有没有什么办法能够让 webpack 做到,只更新变更内容,没改的地方不动;而且还要保存当前状态?有,它就是 HMR 模块热替换

什么是 HMR

HMR (Hot Module Replacement) 模块热替换。它会在程序运行过程中,通过替换,新增或删除方式,来更新各种模块,而无需进行完全刷新。主要是通过以下几种方式,来显著加快开发速度:

  • 保留在完全重新加载页面时丢失的应用程序状态。
  • 只更新变更内容,以节省宝贵的开发时间。
  • 调整样式更加快速 - 几乎相当于在浏览器调试器中更改样式。

了解了 HMR 的概念后,如何开启 HMR 呢?这个,跟项目是以哪种方式启动的有关,分为 wbepack-dev-server 模式,与 webpack-dev-middleware 模式。

webpack-dev-server 模式下

首先,假定我们有如下 目录结构:

.
├── src
│   ├── index.css
│   ├── index.js
│   ├── print.js
│   └── wheel-arrow.png
├── package.json
└── webpack.config.js

然后,webpack.config.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'
  },
  devtool: 'inline-source-map',
  devServer: {
    contentBase: './dist',
    hot: true
  },
  module: {
    rules: [
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },
      { test: /\.(png|svg|jpg|gif)$/, use: 'file-loader' }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Hot Module Replacement'
    }),
  new webpack.NamedModulesPlugin(),
  new webpack.HotModuleReplacementPlugin()
  ],
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};

index.js 添加 module.hot 逻辑,内容如下

import print from './print';
import './index.css';

const component = function() {
  let el = document.createElement('div');
  let input = document.createElement('input');
  el.className = 'hello';
  el.innerHTML = '2222'
  el.onclick = print;
  el.appendChild(input)
  return el;
}

document.body.appendChild(component());

if (module.hot) {
  // console.log(module, 'module');
  module.hot.accept('./print.js', function() {
    // console.log(arguments)
    console.log('Accepting the updated printMe module!');
    print();
  })
}

print.js 内容如下:

export default function printMe() {
  console.log('1')
}

这个时候,修改 print.js 或者 index.css,打开控制台,查函 html 发现,文件改动时,只是部分更新,其他地方没变。说明,已经,实现了 HMR

在输入框中输入值后,修改 print.js ,保存,可以发现,输入的值还在,这就说明 HMR程序状态 保存起来了。(在不开启 HMR 的情况下,这样的操作,会使 输入框中的内容清空)

问题

修改 print.js 时,虽然,if(module.hot) 里面的 print() 打印的是最新的,但是当我们点击的时候,发现它打印的还是上次的旧值。

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

如果我们是 el.onclick =function() { print(); }; 就不会有这个问题了,单这不是解决这个问题的办法。

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

import print from './print';
import './index.css';

const component = function() {
  let el = document.createElement('div');
  let input = document.createElement('input');
  el.className = 'hello';
  el.innerHTML = '2222'
  el.onclick = print;

  el.appendChild(input)
  return el;
}

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

if (module.hot) {
  module.hot.accept('./print.js', function() {
    document.body.removeChild(element);
    element = component();
    document.body.appendChild(element);
  })
}

这样,修改 print.js 后,点击就能打印最新的值了。

webpack-dev-middleware 模式

在根目录 添加 server.js 内容如下

const webpackDevServer = require('webpack-dev-server');
const webpack = require('webpack');

const config = require('./webpack.config.js');
const options = {
  contentBase: './dist',
  hot: true,
  host: 'localhost'
};

webpackDevServer.addDevServerEntrypoints(config, options);
const compiler = webpack(config);
const server = new webpackDevServer(compiler, options);

server.listen(5000, 'localhost', () => {
  console.log('dev server listening on port 5000');
});

然后,启动 npm run server (前提是你在 package.json 中 配了 server 命令),就能达到同样的目的。

不过 上面的配法,只是简单的例子, 在我本地有点卡,大概需要 1s 才能反应过来。

实际情况

在实际项目中,我们应该是不需要想上面那像,需要在每个文件的 默认 配置 if(modle.hot) 接收变动文件 module.hot.accept,这样去操作的。vue react 都有相关的 loader 帮我们做了类似的处理。

0 / 500

webpack 学习三:模块热替换 HMR

2020 年 06 月 10 日

0

Like

0

Download

142

Viewed

技术是第一生产力