Webpack 自动刷新和HMR

最近想了解一下webpack的功能,基于如今的4.x版本,过了一遍官方文档,内容实在太多,根本记不住...,不过发现了几个有意思的扩展内容,HMR就是其中之一(另外一个是PWA的SW-Service Worker),因此就花点时间整理下

一、自动刷新

webpack.config配置参数devServer中存在inline参数,参数能够设置devServer的两种模式inlineiframe,默认使用inlinecss

devServer: {
    contentBase: "./dist",
    // inline: false,
    hot: true,
},
复制代码

官方是这样描述inline模式的html

Toggle between the dev-server's two different modes. By default the application will be served with inline mode enabled. This means that a script will be inserted in your bundle to take care of live reloading, and build messages will appear in the browser console.vue

大体意思是说在inline模式下,在bundle.js中会插入代码来处理自动刷新,因为没看源码,根据开关inline模式之后,webpack-dev-server启动时候的控制台输出,和官方文档说明,因此我的猜想,大体实现:webpack

  1. bundle.js插入socket的片断,在浏览器加载后创建socket链接
  2. 利用webpack -watch来监听文件变化,发送socket信息告知浏览器,浏览器接受信息调用reload来实现刷新页面刷新

经过分析浏览器请求(inline模式会在加载bundle.js后发起一个websocket请求)和bundle.js代码确认了这一点(onSocketMsg-> reloadApp-> applyReload),也就是说使用webpack-dev-server启动就能够开启自动刷新功能了web

2. HMR

HMR(Hot Module Replacement),添加、修改模块(修改JS/CSS)后浏览器内容经过非刷新的方式自动更新,提升开发效率,要注意HMR只应在开发环境使用vue-cli

HMR在配置成功之后,修改CSS/JS不会进行页面刷新(注意保存文件变动的时候,浏览器Tab页是不是自动刷新)json

2.1 HMR配置

HMR配置有四个关键点:api

  1. 使用webpack-dev-server做为服务器启动
  2. webpack.configdevServer中配置hot: true
  3. webpack.config的plugins增长HotModuleReplacementPlugin
  4. 使用module.hot.accept增长HMR代码

webpack.config数组

module.exports = {
  mode: 'development',
  devtool: 'inline-source-map',
  entry: {
    main: __dirname + '/app/main.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  devServer: {
    contentBase: "./dist",//本地服务器所加载的页面所在的目录
    // inline: true, //实时刷新
    hot: true,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: __dirname + '/app/index.tmpl.html'
    }),
    new webpack.HotModuleReplacementPlugin()
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
};
复制代码

main.js浏览器

import './extra.js'
import './style.css'

if (module.hot) {
    module.hot.accept('./extra.js', function() {
      console.log('Accepting the updated printMe module!');
    })
    // 关闭指定子模块的HMR
    // module.hot.decline('./extra.js')
}
复制代码

extra.js

if(module.hot){
    // 监听当前模块HMR异常
    // module.hot.accept(() => {
    //     console.log('error handler')
    // })
    // 关闭当前模块HMR
    // module.hot.decline()
    // 存储模块数据
    // module.hot.dispose(disposeHander)
    // if (module.hot.data) {
    //    console.log(module.hot.data.a)
    // }
    // 移除模块数据
    // module.hot.removeDisposeHandler(disposeHander)
}
复制代码

配置成功之后修改extra.js或者style.css保存,都会触发HMR,控制台会输出[HMR]开头的日志

PS:

  1. css文件咱们会配置style-loaderstyle-loader中已经增长了module.hot.accept的支持,因此即便不配置module.hot.accept,对于css也能够HMR,可是若是JS没有调用module.hot.accept,HMR执行找不到对应的内容,则会直接刷新页面,能够设置参数hotOnly: true来防止自动刷新

  2. webpack plugins未添加HotModuleReplacementPlugin使用hot: true的时候会报错 Uncaught Error: [HMR] Hot Module Replacement is disabled.

  3. 官网配置分为Node版本和Web版本,因为本身也了解不深,因此不探讨Node版本如何使用webpack的HMR

2.2 使用HMR API

要想使用HMR,须要利用到HMR的API,在添加了HotModuleReplacementPlugin模块后,就可使用module.hot的API

Module API

用于模块处理的API

2.2.1 accept

module.hot.accept(dependencies,callback):添加须要HMR的模块,以及触发变动后的回调,dependencies支持字符串或者字符串数组

module.hot.accept(errorHandler):被添加的模块内部使用能够捕获HMR异常时候抛出的异常

2.2.2 decline

module.hot.decline(dependencies):和accept操做相反,标记一个不须要HMR管理的模块

module.hot.decline():JS模块中使用,将致使该模块没法被HMR

2.2.3 dispose

module.hot.addDisposeHandler/moduel.hot.dispose(data =>{}):即便使用HMR,每次更新后数据是不能进行保留的,若是想将数据传递给更新后的模块,使用dispose对全局对象data赋值,以后能够经过module.hot.data来获取

module.hot.removeDisposeHandler(callback):移除经过disposeaddDisposeHandler添加的回调,移除后module.hot.data将变为空对象{}(调用dispose以前module.hot.dataundefined)

Management API

用于HMR状态管理的API

2.2.4 status

module.hot.status(): HMR过程是存在变化状态的,经过该函数来获取到HMR的当前状态

2.3 vue-cli3配置HMR

使用vue-cli3构建,经过vue-cli-service serve启动的项目,其自己会添加HMR,在vue.config.jsdevServer中配置hot: false能够关闭。

可是项目中发现一个状况,当配置了https: true之后,必须指定host: localhost,HMR功能才能够开启,不然HMR功能将失效(实际上是整个自动更新功能都失效了,不会发起socket请求),而在webpack中并无出现该状况。

据此揣测,vue-cli-service在使用https: true的时候,socket连接必须指定使用hostname才能够进行构建,根据查找bundle.js代码,确认该逻辑(能够在bundle.js查找socket关键字,能够看到hostname相关检测逻辑)。

三、HMR实现

3.1 工做原理

本小节内容都不是人话,可跳过~

1、应用程序(Applicationn):

  1. 应用程序在获取到socket服务器发送的更新信息
  2. 检查HMR的运行状态
  3. HMR运行环境异步下载更新内容并通知应用程序
  4. 应用程序告诉HMR运行环境去应用更新
  5. HMR运行环境应用变动信息

2、编译部分(Compiler):

修改文件之后,编译器触发更新操做,更新两部份内容:manifestchunks

manifest包含了新的变化的全部模块的代码块,编译器确保module IDschunk IDs在本次构建中

3、模块(Module)

只影响HMR中包含了的模块(module.hot.accept()),好比:style-loader,其中实现了HMR接口,因此在样式更新的时候,新样式替换老样式

若是模块没有HMR的处理,则更新操做持续冒泡,也就是模块树中只要单个模块发生了更新,整个都会从新加载

4、运行过程(Runtime)

运行过程会持续使用checkapply来进行module.hot.staus的更新

check: 请求更新清单,请求成功后,比较更新的内容和当前的内容,将全部更新的内容进行存储直到全部内容更下载完成,切换到ready状态准备

apply: 将updated的模块标记为invalid,一直冒泡标记到entry point,全部无效模块处理完成后,更新全部的accepthandlers都会被触发,而后状态切换为idle状态

3.2 原理解析

查看bundle.js中的代码,将3.1中的内容翻译一下:

  1. webpack-dev-server配置了hot:true的基础上,bundle.js中的reloadApp()会进行HMR的逻辑(另外一个逻辑是以前面的自动刷新),这个时候会开始判断HMRstatus,并发起一个AJAX请求chunk.hot-update.json,该请求会返回一个JSON对象
{
    c: {
        main: true
    },
    h: nextChunk
}
复制代码
  1. 请求成功之后,继续更新HMR的status,会比较返回的nextChunk和当前的chunk是否相同,若是相同结束HMR,若是不一样将nextChunk存储并继续进行HMR
  2. 根据chunk拼接<script>的src,并将<script>动态插入html的<head>中(好比个人配置就是:main.chunk.hot-update.js),操做后chunk数组序号+1
  3. 因为<head>插入了新的脚本<script src="main.chunk.hot-update.js"></script>,实现动态更新JS而不刷新页面

补充:对于添加了module.hot.dispose的部分,建立了全局对象data={}来保存数据,从而实现从新加载JS之后,仍然能够访问该数值的内容

3. 总结

HMR的核心点有两个:

  1. 如何在服务端文件变化以后通知客户端?使用socket
  2. 如何判断文件是否发生变化?客户端和服务端都保存chunk,比较是否发生变化

webpack的内容其实真的有点多,仅仅HMR包含的内容就不少,本身的总结也不够完整,只能说是恰好入门。不过整个过程一边本身猜测,一遍跟源码,而后验证本身猜测,真的是颇有意思的过程。

4. 参考

webpack官方文档:

HMR

HMR API

Dev Server

CSDN上一篇特别棒的资料:

完全弄懂webpack-dev-server的热更新

相关文章
相关标签/搜索