1、webpack热更新

刚刚打开工程文档,然后发现有一行代码我不是很清楚,然后查询了一下(如下代码注释):

//如果当前模块支持热更新
if (module.hot) {

//注册回调,当前App.js模块可以接受title.js的变更,当title.js变更后可以重新调用render方法
    module.hot.accept('./App.js', () => render());

}

配置文件里面也需要有HotModuleReplacementPlugin的配置信息

百度了一下,这个是webpack热更新。为了快速了解webpack热更新的原理和如何应用,我找了一个学习视频:https://www.bilibili.com/video/BV1ey4y127Xm 和一篇学习博客https://segmentfault.com/a/1190000017387984 详细了解了一下 以下是我学习到的内容

1、HMR简介

Hot Module Replacement是指当你对代码修改并保存后,webpack将会对代码进行重新打包,并将新的模块发送到浏览器端。浏览器用新的模块替换掉旧的模块,以实现在不刷新对浏览器的前提下更新页面。

2、使用HMR

除了 Webpack,还需要 webpack-dev-server(或 webpack-dev-middleware)。

为 Webpack 开发环境开启热更新,要做两件事:

  • 使用 HotModuleReplacementPlugin 插件
  • 打开 webpack-dev-server 的热更新开关

HotModuleReplacementPlugin 插件是 Webpack 自带的,在 webpack.config.js 加入webpack.HotModuleReplacementPlugin()就好:

// webpack.config.js
module.exports = {
  // ...
  plugins: [
    webpack.HotModuleReplacementPlugin(),
   // ...
  ]
}

如果直接通过 webpack-dev-server 启动 Webpack 的开发环境,那么可以这样打开 webpack-dev-server 的热更新开关:

// webpack.config.js
module.exports = {
  // ...
  devServer: {
    hot: true,
    // ...
  }
}

也很简单。

3、举例

示例1:没有热更新的情况

这个例子只是把示例页面的功能简单介绍下,并且让你体会下每次修改代码都要重新刷新页面的痛苦。

页面上只有一个元素,用来展示数值:

<div id="root" class="number"></div>

入口模块(index.js)引用了两个模块:

  • timer.js:只提供了一个 start 接口,传入回调函数,然后 timer 会间隔一段时间调用回调函数,并传入一个每次增加的数值
  • foo.js:没啥功能,就简单暴露一个 message,引入它单纯是区别 timer.js 展示不同的模块更新处理方法

入口模块的功能很简单,调用 timer.start(),再传入的回调函数中,每次将得到的数值更新到页面上显示:

import { start } from './timer'
import { message } from './foo'

var current = 0
var root = document.getElementById('root')
start(onUpdate, current)

console.log(message)

function onUpdate(i) {
  current = i
  root.textContent = '#' + i
}

将这个项目运行起来,打开的页面中就是在一直刷新展示增加的数值而已,类似这样:

hmr-demo-1

一旦修改任何模块的代码,例如改变 timer 中定时器的间隔时间(如从1秒改成3秒),或者 onUpdate 中展示的内容(如 '#' + i 改成 '*' + i),页面都会刷新,已经有的状态清除,重新从0开始计数。

示例2:处理依赖模块的热更新

接下来的例子,展示在 index.js 如何处理其他模块的更新。

依赖的模块发生更新,要么是接受变更(页面不用刷新,模块替换下就好),要么不接受(必须得刷新)。

Webpack 将热更新相关接口以 module.hot 暴露到模块中,在使用前,最好判断下当前的环境是否支持热更新,也就是上面看到的这样的代码:

if (module.hot) {
  // ...
}

延续上一个例子,选择接受并处理 timer 的更新,但对于 foo 模块,不接受:

if (module.hot) {
  module.hot.accept('timer', () => {
    // ...
  })
  module.hot.decline('./foo')
}

所以,在热更新的机制中,其实是以这种“声明”的方式告知 Webpack,哪些模块的更新是被处理的,哪些模块的更新又不被处理。当然对于要处理的模块的更新,自行在 module.hot.accept() 的第二个参数即回调函数中进行处理,会在声明的模块被替换后执行。

下面来看对 timer 模块更新的处理。

timer 模块的 start 函数调用后返回一个可以终止定时器的 stop 函数,借助它我们实现对旧的 timer 模块的清理,并基于当前状态重新调用新的 timer 模块的 start 函数:

var stop = start(onUpdate, current) // 先记录下返回的 stop 函数

// ...

if (module.hot) {
  module.hot.accept('timer', () => {
    stop()
    stop = start(onUpdate, current)
  })
  // ...
}

处理逻辑如上所述,先通过之前记录的 stop 停止旧模块的定时器,然后调用新模块的 start 继续计数,并且传入当前数值从而不必从0开始重新计数。

看起来还是比较简单的吧。运行起来的效果是,如果修改 timer 中的定时器间隔时间,立即在页面上就能看到效果,而且页面并不会刷新导致重新从0开始计数:

hmr-demo-2

在运行几秒后,修改 timer 模块中定时器的间隔时间为 100ms

修改 foo 中的 message,页面还是会刷新。

有几点额外说明下:

  • timer 模块如果修改后不返回 start 接口,那么上述处理机制显然会失效,所以这里的处理是基于模块的接口不变的情况下
  • timer 模块的 start 调用后显然必须返回一个 stop 函数,否则在 index.js 是没法清除 timer 模块内开启的定时器的,这也很重要
  • 或许你也注意到了,就是对 timer 模块的 start 函数的引用貌似一直没有变过,那为什么在回调函数中的 start 就是新模块了呢?这个其实是有 Webpack 在编译时处理掉的,编译后的代码并非当前的样式,对 start 会进行替换,使得回调中的 start 一定引用到的是新的 timer 模块的 start。感兴趣可以看下 Webpack 文档中对此的相关描述。

此外,除了声明其他模块更新的处理,模块也可以声明自身更新的处理,也是同样的接口,不传参数即可:

  • module.hot.accept() 告诉 Webpack,当前模块更新不用刷新
  • module.hot.decline() 告诉 Webpack,当前模块更新时一定要刷新

而且,依赖同一个模块的不同模块,可以有各自不同的声明,这些声明可能是冲突的,比如有的允许依赖模块更新,有的不允许,Webpack 怎么协调这些呢?

Webpack 的实现机制有点类似 DOM 事件的冒泡机制,更新事件先由模块自身处理,如果模块自身没有任何声明,才会向上冒泡,检查使用方是否有对该模块更新的声明,以此类推。如果最终入口模块也没有任何声明,那么就刷新页面了。这也就是为什么在上一个例子中,虽然开启了热更新,但是模块修改后仍旧刷新页面的原因,因为没有任何模块对更新进行处理。

示例3:处理自身模块的热更新

自身模块的更新处理与依赖模块类似,也是要通过 module.hot 的接口向 Webpack 声明。不过模块自身的更新,可能需要在模块被 Webpack 替换之前就做一些处理,更新后的处理则不必通过特别接口来做,直接写到新模块代码里面就好。

module.hot.dispose() 用于注册当前模块被替换前的处理函数,并且回调函数接收一个 data 对象,可以向其写入需要保存的数据,这样在新的模块执行时可以通过 module.hot.data 获取到:

var current = 0
if (module.hot && module.hot.data) {
  current = module.hot.data.current
}

首先,模块执行时,先检查有没有旧模块留下来的数据,如果有,就恢复。

然后在模块被替换前的执行处理,这里就是记录数据、停掉现有的定时器:

if (module.hot)
  module.hot.accept()
  module.hot.dispose(data => {
    data.current = current
    stop()
  })
}

做了这些处理之后,修改 index.js 的 onUpdate,使得渲染到页面的数值改变,也可以在不刷新的情况下体现:

hmr-demo-3

在运行几秒后,修改 onUpdate() 中的  '#' + i 为  '*' + i
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值