文件监听和热更新原理分析

4,285 阅读4分钟

文件监听

文件监听是在发现源码发生变化时,自动重新构建出新的输出文件。

wepack 开启监听模式,有两种方式:

  • 启动 webpack 命令时,带上 --watch 参数
  • 在配置 webpack.config.js 中设置 watch: true
"scripts": {
  "build": "webpack",
  "watch": "webpack --watch"
}

原理分析

轮训去判断文件的最后编辑时间是否发生变化。当某个文件发生了变化,并不会立刻告诉监听者,而是先缓存起来,等待一定时间段 aggregateTimeout,在这个时间段内,如果有其他的文件也发生了变化,那么他最终会把这些变化了的文件列表一起去构建,一起把构建的结果生成到 bunlde 文件中去。

module.exports = {
  // 默认 false,不开启
  watch: true,
  // 只有开启监听模式时,watchOptions 才有意义
  watchOptions: {
    // 默认为空,不监听的文件或者文件夹,支持正则匹配
    ignored: /node_modules/,
    // 监听到变化发生后会等 300ms 再去执行,默认 300ms
    aggregateTimeout: 300,
    // 判断文件是否发生变化是通过不停询问系统指定文件有没有变化实现的,默认每秒问 1000 次
    poll: 1000
  }
}

虽然设置了文件监听,但是更新之后我们还是需要手动刷新浏览器才能看到更新,那么 webpack 有没有更好的方式呢?

热更新

借助 webpack-dev-server,当代码有修改时自动去构建,构建完成后通过热更新的方式让浏览器的内容自动去变化。热更新需要 webpack-dev-serverHotModuleReplacementPlugin 一起结合使用。

相比 watch 的另外一个优势是,没有磁盘的 IO,它输出的文件不放在磁盘文件中,而是放在内存中,所以他的构建速度会有更大的优势。

使用

  1. 首先向 package.json 中增加一条命令

    "scripts": {
      "build": "webpack",
      "watch": "webpack --watch",
      "dev": "webpack-dev-server --open" // open 参数每次构建完成自动开启浏览器
    }
    
  2. 热更新在开发模式中才会使用,修改 webpack.config.js 中的 mode 为 development。

    mode: 'development'
    
  3. 配置插件

    plugins: [
      new webpack.HotModuleReplacementPlugin()
    ]
    
  4. 配置热更新

    devServer: {
      contentBase: './dist', // 服务的基础目录
      hot: true // 开启热更新
    }
    

原理分析

首先需要先了解几个概念。Hot Module Replacement 简称 HMR。

Webpack Compile:将 JS 源代码编译成 bundle.js

HMR Server:用来将热更新的文件输出给 HMR Runtime

Bundle Server:提供文件在浏览器的访问,以服务的方式访问

HMR Runtime:会被注入到浏览器,更新文件的变化

bundle.js:构建输出的文件

HMR Runtime 和 HMR Server 会建立起一条链接,通常是 websocket,就可以实时更新文件的变化。

热更新的两个过程

  • 启动阶段 1 - 2 - A - B

    首先我们在文件系统便写完代码之后,Webpack Compile 将源代码和 HMR Runtime 一起编译成 bundle 文件,传输给 Bundle Server,Bundle Server 是一个服务器,这样在浏览器里就可以以服务的方式访问文件。

  • 更新阶段 1 - 2 - 3 - 4

    当我们在文件系统更新文件之后,还是会经过 Webpack Compile 的编译,Webpack Compile 会将编译后的结果传递给 HMR Server,HMR Server 会比较哪些文件发生了变化,因为服务端的 HMR Server 会和客户端的 HMR Runtime 建立起一条 websocket 链接,所以 HMR Server 会以 json 的形式通知给 HMR Runtime 文件做出了哪些变化。

我们来详细看一下实际项目中文件更新的过程:

  1. 当某一个文件或者模块发生变化时,webpack 监听到文件变化对文件重新编译打包,编译生成唯一的 hash 值,这个 hash 值用来作为下一次热更新的标识。根据变化的内容会生成两个补丁文件:说明变化内容的 manifest(文件格式为 hash.hot-update.json,包含了 hash 和 chundId 用来说明变化的内容)和 chunk.js (hash.hot-update.js)模块。 当前的 hash 值为 1240,生成文件的 hash 值为 6554。

    当我们改动一下文件的内容,会发现 hash 值变为了 2381,然后生成文件的 hash 值为 1240。

  2. 因为服务端的 HMR Server 和客户端的 HMR Runtime 已经建立起了一条 websocket 的链接,所以当有文件改动时候,服务端会向浏览器推送一条消息。data 为刚才改动生成的 hash 值 2381,即为下一次热更新的标识。

  3. 在我们接受到这条 socket 消息之前,我们已经在上一次 socket 消息中已经记住了这次的 hash 标识,这时候我们会创建一个 ajax 去想服务端请求说明变化内容的 manifest 文件。返回的 h 为文件改动 webpack 重新 build 生成的 hash 值,也是刚才 socket 推送的消息中的 data 值。c 为改动的模块。

  4. 浏览器端根据收到的 json 文件,去拉取模块变化的内容。

  5. 触发 render 流程,实现局部热加载