使用 webpack 有一段时间了,对其中的热更新的大概理解是:对某个模块做了修改,页面只做局部更新而不需要刷新整个页面来进行更新。这样就能节省因为整个页面刷新所产生开销的时间,模块热加载加快了开发的速度。具体效果可以先看下下图的效果:
可是最近,亲自搭建一个 webpack 应用项目时,在实现开发环境的模块热更新时,遇到这样那样的问题。由于之前都是使用第三方插件来实现应用的热更新,它们都封装了实现热更新的一些细节,导致在不用第三方插件实现模块热更新时出现问题,其实还是理解的不够深入。于是在搞明白之后写下此文与大家分享。
webpack 的自带的 HMR 插件
是使用 webpack 热更新功能的基础。其他的第三方插件如
- HotModuleReplacementPlugin
、
- webpack-hot-middleware
、
- react-hot-loader
等等都是要配合 webpack 自带的 HotModuleReplacementPlugin 插件提供的 api 来实现代码的热更新。例如下面在某个模块中使用 HMR 代码一个例子:
- babel-plugin-dva-hmr
- if (module.hot) {
- module.hot.accept('./containers/rootContainer.js', () = >{
- const NextRootContainer = require('./containers/rootContainer.js').
- default;
- render( < NextRootContainer / >, document.getElementById('react-root'));
- }
- }
当然
为可以使用 HMR 的模块提供了
- HotModuleReplacementPlugin
,它为一个对象,其含有很多 api,具体可以参考这里。这样利用插件提供的这些 api 可以为模块实现自定义的热更新逻辑。
- module.hot
但是,在开发过程中,你们可能也发现了,我们并没有为项目中的每个模块提供这种多余的 HMR 代码,尽管所有代码都有可能变化。那么当这些代码没有 HMR 代码的模块发生变化时,他是如何实现热更新的呢?这就要说到 webpack HMR 更新的冒泡 (bubble) 机制。具体可以看下图所展示的冒泡机制:
从图中可以看出:
配置了
- webpack-hot-middleware
, 那么页面就会整个刷新来加载加载变化,这就变成 liveroad 模式;否则 webpack 就不知道如何加载变化模块,控制台也会有对应的提示。
- reload:true
例如,在本人的实例中,修改了 searchForm.jsx 模块,可以在控制台清晰的看到,它一直冒泡到入口模块 index.js。如下图:
之前,与 webpack 配合的
服务,通过配置就可以实现代码热更新,但是隐藏了实现细节。下面我们手动搭建一个自带 HMR 功能的本地开发 node sever。
- webpack-dev-server
搭建本地服务
- webpack-dev-middleware
webpack-dev-server 就是基于 webpack-dev-middleware 来搭建内部 node server。我们搭建自己的开发环境就用它来直接搭建。
来实现客户端与服务端的通信以接受更新
- webpack-hot-middleware
该模块只是负责客户端与服务器通信及接受变化,但是如何实现根据热加载来完成应用的无缝变化衔接就超出了该模块的范围,正如其官网所描述:
This module is only concerned with the mechanisms to connect a browser client to a webpack server & receive updates. It will subscribe to changes from the server and execute those changes using webpack's HMR API. Actually making your application capable of using hot reloading to make seamless changes is out of scope, and usually handled by another library.
这句话的意思是:
What this means in practice, is you either need to add some code which calls module.hot.accept(), or use a plugin which can automatically add this code to your modules - otherwise webpack doesn't know how to apply the hot update.
也就是, 要么你在模块中增加调用
的代码,要么使用第三方插件自动的为你模块添加这些代码;否则 webpack 不知道怎么更新这些模块。,具体可以参考这里。下面简要说下第三方库在 HMR 上的实现。
- module.hot.accept()
本人项目中用到过第三方插件
来实现代码的热更新。其实就像上面说的,该插件自动替我们在入口模块添加 HMR 代码,例如下面是在入口文件添加的代码:
- babel-plugin-dva-hmr
可以看出
插件自动的为我们的入口模块添加了一个自执行闭包函数,里面有实现代码热更新的逻辑。
- babel-plugin-dva-hmr
在其 3.0.0 版本前,是通过自动为每个使用
- react-hot-loader
的 js 添加 HMR 热加载代码,这样我们就不需要在代码中在添加 HMR 热更新代码了。
- react-hot-loader
例如,在 webpack.config.js 中为 js 文件配置该插件:
- //这样src目录下的所有.js文件都将被自动添加HMR热更新代码
- loaders: [{
- test: /\.js$/,
- loaders: ['react-hot', 'babel'],
- include: path.join(__dirname, 'src')
- }]
自动添加的有关 HMR 代码如下,只截取部分代码:
注意:
react-hot-loader 在 3.0.0 版本之后就废弃掉该方式,不会自动添加 HMR 热更新代码,需要开发者在项目入口模块手动添加 HMR 代码,参考这里
另外,要使用 HMR 功能,需要在 webpack 的配置项的每个入口项数组中添加
, 即:
- webpack-hot-middleware/client
- entry: {
- index: ['webpack-hot-middleware/client', './src/index']
- }
这一步需要使用 webpack 提供的
插件,它提供了处理捕获变化的 apis,需要使用这些 apis 来完成模块的热更新,具体需要两步:
- HotModuleReplacementPlugin
- plugins: [
- ...
- new webpack.HotModuleReplacementPlugin()
- ...
- ]
- if(module.hot){
- module.hot.accpet() //接受模块更新的事件,同时阻止这个事件继续冒泡
- }
若为每个模块添加 HMR 代码来热更新对应的模块机制是不可取的,这会产生大量冗余代码,极不推荐这种做法,除非像第三方插件那样自动帮我们完成。
一般在入口模块添加 module.hot 的相关 api 来更新具体变化,入口模块没有添加的话就不会达到热更新的效果,浏览器控制台也会出现如下警告 (前提是 webpack-hot-middleware 的 reload 配置为 false):
在浏览器控制台中出现这样一句提示:
This is usually because the modules which have changed (and their parents) do not know how to hot reload themselves.
正如提示所说的,修改某个子模块时,若不在模块本身或者顶级的入口模块添加热更新接受机制,那么产生变化的模块及其父模块不知道怎么加载他们。
最终,用户自定义的开发环境 node server 具体的开发代码如下:
- var express = require('express');
- var webpack = require('webpack');
- var webpackDevMiddleware = require('webpack-dev-middleware');
- var webpackHotMiddleware = require('webpack-hot-middleware');
- var opn = require('opn');
- var fs = require('fs');
- var port = 8989;
- var app = express();
- var webpackConfig = require('../webpack.config.js');
- Object.keys(webpackConfig.entry).forEach(function(name) {
- webpackConfig.entry[name] = ['webpack-hot-middleware/client'].concat(webpackConfig.entry[name]);
- }) var compiler = webpack(webpackConfig,
- function(err, stats) {
- if (err) {
- console.log(stats.toString({
- colors: true
- }))
- }
- });
- var devMiddleware = webpackDevMiddleware(compiler, {
- publicPath: webpackConfig.output.publicPath,
- hot: true,
- noInfo: true,
- stats: {
- colors: true
- }
- });
- var hotMiddleware = webpackHotMiddleware(compiler);
- app.use(devMiddleware);
- app.use(hotMiddleware);
- // force page reload when html-webpack-plugin template changes
- compiler.plugin('compilation',
- function(compilation) {
- compilation.plugin('html-webpack-plugin-after-emit',
- function(data, cb) {
- hotMiddleware.publish({
- action: 'reload'
- }) cb()
- })
- });
- app.listen(port,
- function(err) {
- if (err) {
- console.log(err);
- } else {
- var url = 'http://localhost:' + port;
- console.log("listening on port %s", port);
- console.log("auto open browser " + url);
- opn(url);
- }
- })
另外,我们可能会想到,在使用 redux 的 react 项目中,这种热更新会导致应用的 state 丢失,为了防止 state 随热更新而丢失,一般需要在针对
的修改来实现进行 state 的保存,最常用的做法是在 store 模块下添加如下 reducer 热更新代码:
- reducer
- if (module.hot) {
- module.hot.accept('../reducers/index.js', () = >{
- const nextReducer = require('../reducers/index.js');
- store.replaceReducer(nextReducer || nextReducer.
- default);
- })
- }
这样,一个带 HMR 代码热更新功能的本地开发 node server 就搭建成功了,总的来说就四步:
- webpack-hot-middleware/client
来接受模块更新的实现
- module.hot.accpet()
在开发过程中使用 HMR 功能,可能还遇到这样或那样的问题,下面就简单列出几点。
选项的
- --hot
命令时,不要在 webpack 的配置文件在配置 HMR 插件。
- webpack-dev-server
否则会报下面的错误,具体可参考这里。
注意:
webpack-dev-server 的 node api 模式下配置
仍然需要在 webpack 配置文件中配置该插件
- hot: true
时,一定要在项目的入口模块添加
- webpack-hot-middleware
代码来实现热更新。
- module.hot.accept
在本人另一个项目中,使用
插件系列的
- dora
插件来实现热更新,由于没有在入口模块添加 HMR 代码来接受变更,导致模块一有变化即刷新整个页面。
- dora-plugin-webpack-hmr
具体是因为
使用
- dora-plugin-webpack-hmr
时,默认配置了其
- webpack-hot-middleware
(参考这里),所以每次修改都会刷新整个页面。
- reload:true
- const defaultOpts = {
- path: `http://127.0.0.1:${port}/__webpack_hmr`,
- reload: true,
- };
来源: http://www.cnblogs.com/wonyun/p/7077296.html