导航菜单

  • 0.api
  • 0.Async
  • 0.module
  • 1.ES2015
  • 2.Promise
  • 3.Node
  • 4.NodeInstall
  • 5.REPL
  • 6.NodeCore
  • 7.module&NPM
  • 8.Encoding
  • 9.Buffer
  • 10.fs
  • 11.Stream-1
  • 11.Stream-2
  • 11.Stream-3
  • 11.Stream-4
  • 12-Network-2
  • 12.NetWork-3
  • 12.Network-1
  • 13.tcp
  • 14.http-1
  • 14.http-2
  • 15.compress
  • 16.crypto
  • 17.process
  • 18.yargs
  • 19.cache
  • 20.action
  • 21.https
  • 22.cookie
  • 23.session
  • 24.express-1
  • 24.express-2
  • 24.express-3
  • 24.express-4
  • 25.koa-1
  • 26.webpack-1-basic
  • 26.webpack-2-optimize
  • 26.webpack-3-file
  • 26.webpack-4.tapable
  • 26.webpack-5-AST
  • 26.webpack-6-sources
  • 26.webpack-7-loader
  • 26.webpack-8-plugin
  • 26.webpack-9-hand
  • 26.webpack-10-prepare
  • 28.redux
  • 28.redux-jwt-back
  • 28.redux-jwt-front
  • 29.mongodb-1
  • 29.mongodb-2
  • 29.mongodb-3
  • 29.mongodb-4
  • 29.mongodb-5
  • 29.mongodb-6
  • 30.cms-1-mysql
  • 30.cms-2-mysql
  • 30.cms-3-mysql
  • 30.cms-4-nunjucks
  • 30.cms-5-mock
  • 30.cms-6-egg
  • 30.cms-7-api
  • 30.cms-8-roadhog
  • 30.cms-9-yaml
  • 30.cms-10-umi
  • 30.cms-12-dva
  • 30.cms-13-dva-ant
  • 30.cms-14-front
  • 30.cms-15-deploy
  • 31.dva
  • 31.cms-13-dva-antdesign
  • 33.redis
  • 34.unittest
  • 35.jwt
  • 36.websocket-1
  • 36.websocket-2
  • 38.chat-api-1
  • 38.chat-api-2
  • 38.chat-3
  • 38.chat-api-3
  • 38.chat
  • 38.chat2
  • 38.chat2
  • 39.crawl-0
  • 39.crawl-1
  • 39.crawl-2
  • 40.deploy
  • 41.safe
  • 42.test
  • 43.nginx
  • 44.enzyme
  • 45.docker
  • 46.elastic
  • 47.oauth
  • 48.wxpay
  • index
  • 52.UML
  • 53.design
  • index
  • 54.linux
  • 57.ts
  • 56.react-ssr
  • 58.ts_react
  • 59.ketang
  • 59.ketang2
  • 61.1.devops-linux
  • 61.2.devops-vi
  • 61.3.devops-user
  • 61.4.devops-auth
  • 61.5.devops-shell
  • 61.6.devops-install
  • 61.7.devops-system
  • 61.8.devops-service
  • 61.9.devops-network
  • 61.10.devops-nginx
  • 61.11.devops-docker
  • 61.12.devops-jekins
  • 61.13.devops-groovy
  • 61.14.devops-php
  • 61.15.devops-java
  • 61.16.devops-node
  • 61.17.devops-k8s
  • 62.1.react-basic
  • 62.2.react-state
  • 62.3.react-high
  • 62.4.react-optimize
  • 62.5.react-hooks
  • 62.6.react-immutable
  • 62.7.react-mobx
  • 62.8.react-source
  • 63.1.redux
  • 63.2.redux-middleware
  • 63.3.redux-hooks
  • 63.4.redux-saga
  • 63.5.redux-saga-hand
  • 64.1.router
  • 64.2.router-connected
  • 65.1.typescript
  • 65.2.typescript
  • 65.3.typescript
  • 65.4.antd
  • 65.4.definition
  • 66-1.vue-base
  • 66-2.vue-component
  • 66-3.vue-cli3.0
  • 66-4.$message组件
  • 66-5.Form组件
  • 66-6.tree
  • 66-7.vue-router-apply
  • 66-8.axios-apply
  • 66-9.vuex-apply
  • 66-10.jwt-vue
  • 66-11.vue-ssr
  • 66-12.nuxt-apply
  • 66-13.pwa
  • 66-14.vue单元测试
  • 66-15.权限校验
  • 67-1-network
  • 68-2-wireshark
  • 7.npm2
  • 69-hooks
  • 70-deploy
  • 71-hmr
  • 72.deploy
  • 73.import
  • 74.mobile
  • 75.webpack-1.文件分析
  • 75.webpack-2.loader
  • 75.webpack-3.源码流程
  • 75.webpack-4.tapable
  • 75.webpack-5.prepare
  • 75.webpack-6.resolve
  • 75.webpack-7.loader
  • 75.webpack-8.module
  • 75.webpack-9.chunk
  • 75.webpack-10.asset
  • 75.webpack-11.实现
  • 76.react_optimize
  • 77.ts_ketang_back
  • 77.ts_ketang_front
  • 78.vue-domdiff
  • 79.grammar
  • 80.tree
  • 81.axios
  • 82.1.react
  • 82.2.react-high
  • 82.3.react-router
  • 82.4.redux
  • 82.5.redux_middleware
  • 82.6.connected
  • 82.7.saga
  • 82.8.dva
  • 82.8.dva-source
  • 82.9.roadhog
  • 82.10.umi
  • 82.11.antdesign
  • 82.12.ketang-front
  • 82.12.ketang-back
  • 83.upload
  • 84.graphql
  • 85.antpro
  • 86.1.uml
  • 86.2.design
  • 87.postcss
  • 88.react16-1
  • 89.nextjs
  • 90.react-test
  • 91.react-ts
  • 92.rbac
  • 93.tsnode
  • 94.1.JavaScript
  • 94.2.JavaScript
  • 94.3.MODULE
  • 94.4.EventLoop
  • 94.5.文件上传
  • 94.6.https
  • 94.7. nginx
  • 95.1. react
  • 95.2.react
  • 96.1.react16
  • 96.2.fiber
  • 96.3.fiber
  • 97.serverless
  • 98.websocket
  • 100.1.react-basic
  • 101.1.monitor
  • 101.2.monitor
  • 102.java
  • 103.1.webpack-usage
  • 103.2.webpack-bundle
  • 103.3.webpack-ast
  • 103.4.webpack-flow
  • 103.5.webpack-loader
  • 103.6.webpack-tapable
  • 103.7.webpack-plugin
  • 103.8.webpack-optimize1
  • 103.9.webpack-optimize2
  • 103.10.webpack-hand
  • 103.11.webpack-hmr
  • 103.11.webpack5
  • 103.13.splitChunks
  • 103.14.webpack-sourcemap
  • 103.15.webpack-compiler1
  • 103.15.webpack-compiler2
  • 103.16.rollup.1
  • 103.16.rollup.2
  • 103.16.rollup.3
  • 103.16.vite.basic
  • 103.16.vite.source
  • 103.16.vite.plugin
  • 103.16.vite.1
  • 103.16.vite.2
  • 103.17.polyfill
  • 104.1.binary
  • 104.2.binary
  • 105.skeleton
  • 106.1.react
  • 106.2.react_hooks
  • 106.3.react_router
  • 106.4.redux
  • 106.5.redux_middleware
  • 106.6.connected-react-router
  • 106.6.redux-first-history
  • 106.7.redux-saga
  • 106.8.dva
  • 106.9.umi
  • 106.10.ketang
  • 106.11.antdesign
  • 106.12.antpro
  • 106.13.router-6
  • 106.14.ssr
  • 106.15.nextjs
  • 106.16.1.cms
  • 106.16.2.cms
  • 106.16.3.cms
  • 106.16.4.cms
  • 106.16.mobx
  • 106.17.fomily
  • 107.fiber
  • 108.http
  • 109.1.webpack_usage
  • 109.2.webpack_source
  • 109.3.dll
  • 110.nest.js
  • 111.xstate
  • 112.Form
  • 113.redux-saga
  • 114.react+typescript
  • 115.immer
  • 116.pro5
  • 117.css-loader
  • 118.1.umi-core
  • 119.2.module-federation
  • 119.1.module-federation
  • 120.create-react-app
  • 121.react-scripts
  • 122.react-optimize
  • 123.jsx-runtime
  • 124.next.js
  • 125.1.linux
  • 125.2.linux-vi
  • 125.3.linux-user
  • 125.4.linux-auth
  • 125.5.linux-shell
  • 125.6.linux-install
  • 125.7.linux-system
  • 125.8.linux-service
  • 125.9.linux-network
  • 125.10.nginx
  • 125.11.docker
  • 125.12.ci
  • 125.13.k8s
  • 125.14.k8s
  • 125.15.k8s
  • 125.16.k8s
  • 126.11.react-1
  • 126.12.react-2
  • 126.12.react-3
  • 126.12.react-4
  • 126.12.react-5
  • 126.12.react-6
  • 126.12.react-7
  • 126.12.react-8
  • 127.frontend
  • 128.rollup
  • 129.px2rem-loader
  • 130.health
  • 131.hooks
  • 132.keepalive
  • 133.vue-cli
  • 134.react18
  • 134.2.react18
  • 134.3.react18
  • 135.function
  • 136.toolkit
  • 137.lerna
  • 138.create-vite
  • 139.cli
  • 140.antd
  • 141.react-dnd
  • 142.1.link
  • 143.1.gulp
  • 143.2.stream
  • 143.3.gulp
  • 144.1.closure
  • 144.2.v8
  • 144.3.gc
  • 145.react-router-v6
  • 146.browser
  • 147.lighthouse
  • 148.1.basic
  • 148.2.basic
  • 148.3.basic
  • 148.4.basic
  • 148.5.basic
  • 149.1.vite
  • 149.2.vite
  • 149.3.vite
  • 149.4.vite
  • 150.react-window
  • 151.react-query
  • 152.useRequest
  • 153.transition
  • 154.emotion
  • 155.1.formily
  • 155.2.formily
  • 155.3.formily
  • 155.3.1.mobx.usage
  • 155.3.2.mobx.source
  • 156.vue-loader
  • 103.11.mf
  • 157.1.react18
  • 158.umi4
  • 159.rxjs
  • 159.rxjs2
  • 160.bff
  • 161.zustand
  • 162.vscode
  • 163.emp
  • 164.cors
  • 1. 什么是HMR
  • 2. 搭建HMR项目
    • 2.1 安装依赖的模块
    • 2.2 package.json
    • 2.2 webpack.config.js
    • 2.3 src\index.js
    • 2.4 src\title.js
    • 2.5 src\index.html
    • 2.6 dist\bundle.js
  • 3. webpack的编译流程
  • 3. 实现热更新
    • 3.1 webpack.config.js
    • 3.2 index.js
  • 4. debug
  • 5. 源代码位置
    • 5.1. 服务器部分
    • 5.2. 客户端部分
    • 5.3 相关代码
  • 6. 实现热更新
    • 6.1 webpack-dev-server.js
    • 6.2 client.js
    • 6.3 HotModuleReplacement.runtime.js

1. 什么是HMR #

  • Hot Module Replacement是指当你对代码修改并保存后,webpack将会对代码进行得新打包,并将新的模块发送到浏览器端,浏览器用新的模块替换掉旧的模块,以实现在不刷新浏览器的前提下更新页面。
  • 相对于live reload刷新页面的方案,HMR的优点在于可以保存应用的状态,提高了开发效率

2. 搭建HMR项目 #

2.1 安装依赖的模块 #

cnpm i webpack@4.39.1 webpack-cli@3.3.6 webpack-dev-server@3.7.2 mime html-webpack-plugin express socket.io -S

2.2 package.json #

package.json

{
  "name": "zhufeng_hmr",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack",
    "dev": "webpack-dev-server"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "webpack": "4.39.1",
    "webpack-cli": "3.3.6",
    "webpack-dev-server": "3.7.2"
  }
}

2.2 webpack.config.js #

webpack.config.js

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    mode:'development',
    entry: './src/index.js',
    output: {
        filename: 'main.js',
        path: path.join(__dirname, 'dist')
    },
    devServer: {
        contentBase:path.join(__dirname, 'dist')
    },
    plugins:[
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html'
        })
    ]
}

2.3 src\index.js #

src\index.js

let root = document.getElementById('root');
function render(){
   let title = require('./title').default;
   root.innerHTML= title;
}
render();

2.4 src\title.js #

src\title.js

export default 'hello';

2.5 src\index.html #

src\index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>webpack热更新</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>

2.6 dist\bundle.js #

dist\main.js

(function(modules) {
    var installedModules = {};
    //封装的客户端的require方法
    function __webpack_require__(moduleId) {
        //判断此模块是否在缓存中
        if(installedModules[moduleId]) {
      //如果在缓存中的话返回缓存模块的导出对象
            return installedModules[moduleId].exports;
        }
    //创建一个模块并且把它放在缓存中
        var module = installedModules[moduleId] = {
            i: moduleId,//模块ID
            l: false,//是否已经加载false
            exports: {}//导出对象默认为空对象
        };

        //执行模块函数
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

        //把模块标识为已经加载
        module.l = true;

        //返回模块的导出对象
        return module.exports;
    }

    //暴露模块对象并挂载到__webpack_require__.m属性上
    __webpack_require__.m = modules;

    //暴露已经安装的模块到模板的缓存上
    __webpack_require__.c = installedModules;

    // define getter function for harmony exports
  //在exports对象上定义name属性的getter方法
    __webpack_require__.d = function(exports, name, getter) {
    //判断exports对象上是否有name属性
        if(!__webpack_require__.o(exports, name)) {
      //在exports对象上添加name属性,可枚举为true,get为getter,当访问该属性时,该方法会被执行,
            Object.defineProperty(exports, name, { enumerable: true, get: getter });
        }
    };

    //在导出对象上定义__esModule属性
    __webpack_require__.r = function(exports) {
    //如果有这样的Symbol的话
        if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
            Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
        }//否则定义一个__esModule属性
        Object.defineProperty(exports, '__esModule', { value: true });
    };

    // create a fake namespace object 创建一个命名空间对象
    // mode & 1: value is a module id, require it 值是模块ID,加载它
    // mode & 2: merge all properties of value into the ns 把所有的属性合并到命名空间上
    // mode & 4: return value when already ns object 当已经是命名空间对象的话直接返回值
    // mode & 8|1: behave like require 就像require一样
    __webpack_require__.t = function(value, mode) {
        if(mode & 1) value = __webpack_require__(value);//直接加载
        if(mode & 8) return value;  //不用加载,直接返回
    //如果value已经是ns对象并且__esModule属性为true的话就直接返回value
        if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
        //否则就创建一个空对象,加载这个对象,
    var ns = Object.create(null);
        __webpack_require__.r(ns);//在对象上设置__esModule属性为true
        Object.defineProperty(ns, 'default', { enumerable: true, value: value });
    //如果mode为2,并且value不是字符串,把值的所有属性都定义到ns对象上
        if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
        //返回ns对象
    return ns;
    };

    // getDefaultExport function for compatibility with non-harmony modules
    __webpack_require__.n = function(module) {
    //如果是__esModule,getDefault方法返回模块的default属性,getModuleExports返回模块本身
        var getter = module && module.__esModule ?
            function getDefault() { return module['default']; } :
            function getModuleExports() { return module; };
    //给getter添加一个a的属性,就是gett方法本身  
        __webpack_require__.d(getter, 'a', getter);
        return getter;
    };

    // Object.prototype.hasOwnProperty.call 判断对象上是否有属性 o=own
    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };

    // __webpack_public_path__ 公开访问路径
    __webpack_require__.p = "";

    // Load entry module and return exports 加载入口模块并且返回导出对象
    return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
({

 "./src/index.js":
 (function(module, __webpack_exports__, __webpack_require__) {
eval(`
__webpack_require__.r(__webpack_exports__);//因为是es模块,所以要添加__esModule属性
var _title__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(\"./src/title.js\");
function render(){
  let root = document.getElementById('root');
  root.innerHTML= _title__WEBPACK_IMPORTED_MODULE_0__[\"default\"];
}
render();`);
}),

 "./src/title.js":
 (function(module, __webpack_exports__, __webpack_require__) {
eval(`
__webpack_require__.r(__webpack_exports__);//因为是es模块,所以要添加__esModule属性
__webpack_exports__[\"default\"] = ('hello');
`);
 })
});

3. webpack的编译流程 #

  • 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数; 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
  • 确定入口:根据配置中的 entry 找出所有的入口文件;
  • 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  • 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  • 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

    在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。 chunk 就是若干 module 打成的包,一个 chunk 应该包括多个 module,一般来说最终会形成一个 file。而 js 以外的资源,webpack 会通过各种 loader 转化成一个 module,这个模块会被打包到某个 chunk 中,并不会形成一个单独的 chunk

3. 实现热更新 #

3.1 webpack.config.js #

webpack.config.js

module.exports = {
    devServer:{
+        hot:true,
        contentBase:path.join(__dirname,'dist')
    },
    plugins:[
+        new webpack.HotModuleReplacementPlugin()
    ]
}

3.2 index.js #

src\index.js

import './client';
let root = document.getElementById('root');
function render(){
   let title = require('./title').default;
   root.innerHTML= title;
}
render();

+if(module.hot){
+  module.hot.accept(['./title'],()=>{
+      render();
+  });
+}

4. debug #

webpackhmr.png

debugger.js

debugger
require('./node_modules/webpack-dev-server/bin/webpack-dev-server.js');

5. 源代码位置 #

5.1. 服务器部分 #

  1. 启动webpack-dev-server服务器
  2. 创建webpack实例
  3. 创建Server服务器
  4. 添加webpack的done事件回调,在编译完成后会向浏览器发送消息
  5. 创建express应用app
  6. 使用监控模式开始启动webpack编译,在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中
  7. 设置文件系统为内存文件系统
  8. 添加webpack-dev-middleware中间件
  9. 创建http服务器并启动服务
  10. 使用sockjs在浏览器端和服务端之间建立一个 websocket 长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,浏览器端根据这些socket消息进行不同的操作。当然服务端传递的最主要信息还是新模块的hash值,后面的步骤根据这一hash值来进行模块热替换
步骤 代码位置
1.启动webpack-dev-server服务器 webpack-dev-server.js#L159
2.创建webpack实例 webpack-dev-server.js#L89
3.创建Server服务器 webpack-dev-server.js#L100
4. 添加webpack的done事件回调 Server.js#L120
编译完成向客户端发送消息 Server.js#L183
5.创建express应用app Server.js#L121
6. 添加webpack-dev-middleware中间件 Server.js#L121
中间件负责返回生成的文件 middleware.js#L20
启动webpack编译 index.js#L51
7. 设置文件系统为内存文件系统 fs.js#L115
8. 创建http服务器并启动服务 Server.js#L135
9. 使用sockjs在浏览器端和服务端之间建立一个 websocket 长连接 Server.js#L745
创建socket服务器 SockJSServer.js#L34

5.2. 客户端部分 #

  1. webpack-dev-server/client-src/default/index.js端会监听到此hash消息,会保存此hash值
  2. 客户端收到ok的消息后会执行reloadApp方法进行更新
  3. 在reloadApp中会进行判断,是否支持热更新,如果支持的话发射webpackHotUpdate事件,如果不支持则直接刷新浏览器
  4. 在webpack/hot/dev-server.js会监听webpackHotUpdate事件,然后执行check()方法进行检查
  5. 在check方法里会调用module.hot.check方法
  6. 它通过调用 JsonpMainTemplate.runtime的hotDownloadManifest方法,向 server 端发送 Ajax 请求,服务端返回一个 Manifest文件,该 Manifest 包含了所有要更新的模块的 hash 值和chunk名
  7. 调用JsonpMainTemplate.runtime的hotDownloadUpdateChunk方法通过JSONP请求获取到最新的模块代码
  8. 补丁JS取回来后会调用JsonpMainTemplate.runtime.js的webpackHotUpdate方法,里面会调用hotAddUpdateChunk方法,用新的模块替换掉旧的模块
  9. 然后会调用HotModuleReplacement.runtime.js的hotAddUpdateChunk方法动态更新模块代 码
  10. 然后调用hotApply方法进行热更新
步骤 代码
1. webpack-dev-server/client端会监听到此hash消息 index.js#L54
2. 客户端收到ok的消息后会执行reloadApp方法进行更新 index.js#L101
3. 在reloadApp中会进行判断,是否支持热更新,如果支持的话发射webpackHotUpdate事件,如果不支持则直接刷新浏览器 reloadApp.js#L7
4. 在webpack/hot/dev-server.js会监听webpackHotUpdate事件 dev-server.js#L55
5. 在check方法里会调用module.hot.check方法 dev-server.js#L13
6. HotModuleReplacement.runtime请求Manifest HotModuleReplacement.runtime.js#L180
7. 它通过调用 JsonpMainTemplate.runtime的hotDownloadManifest方法 JsonpMainTemplate.runtime.js#L23
8. 调用JsonpMainTemplate.runtime的hotDownloadUpdateChunk方法通过JSONP请求获取到最新的模块代码 JsonpMainTemplate.runtime.js#L14
9. 补丁JS取回来后会调用JsonpMainTemplate.runtime.js的webpackHotUpdate方法 JsonpMainTemplate.runtime.js#L8
10. 然后会调用HotModuleReplacement.runtime.js的hotAddUpdateChunk方法动态更新模块代码 HotModuleReplacement.runtime.js#L222
11.然后调用hotApply方法进行热更新 HotModuleReplacement.runtime.js#L257 HotModuleReplacement.runtime.js#L278

5.3 相关代码 #

  • webpack-dev-server.js
  • Server.js
  • webpack-dev-middleware/index.js
  • SockJSServer.js

6. 实现热更新 #

6.1 webpack-dev-server.js #

const path = require('path');
const express = require('express');
const mime = require('mime');
const webpack = require('webpack');
//1. 启动webpack-dev-server服务器
//2. 创建webpack实例
let config = require('./webpack.config');
let compiler = webpack(config);
class Server{
    constructor(compiler){
        this.compiler = compiler;
        //4. 添加webpack的`done`事件回调,在编译完成后会向浏览器发送消息
        let lastHash;
        let sockets=[];
        compiler.hooks.done.tap('webpack-dev-server', (stats) => {
           lastHash = stats.hash;
           sockets.forEach(socket=>{
             socket.emit('hash',stats.hash);
             socket.emit('ok');
           });
        });
        //5. 创建express应用app
        let app = new express();

        //6. 使用监控模式开始启动webpack编译,在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中
        compiler.watch(config.watchOptions||{}, (err)=>{
            console.log('编译成功');
        });
        //7. 设置文件系统为内存文件系统
        const MemoryFileSystem = require('memory-fs');
        const fs = new MemoryFileSystem();;
        compiler.outputFileSystem = fs;
        //8. 添加webpack-dev-middleware中间件
        const devMiddleware = (req,res,next)=>{
            if(req.url === '/favicon.ico'){
                return res.sendStatus(404);
            }
            let filename = path.join(config.output.path,req.url.slice(1));
            console.error(filename);
            if(fs.statSync(filename).isFile()){
                let content = fs.readFileSync(filename);
                res.header('Content-Type',mime.getType(filename));
                res.send(content);
            }else{
                next();
            }
        }
        app.use(devMiddleware);
        //8. 创建http服务器并启动服务
        this.server = require('http').createServer(app);
        // 10. 使用sockjs在浏览器端和服务端之间建立一个 websocket 长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,浏览器端根据这些`socket`消息进行不同的操作。当然服务端传递的最主要信息还是新模块的`hash`值,后面的步骤根据这一`hash`值来进行模块热替换
        let io = require('socket.io')(this.server);
        io.on('connection',(socket)=>{
            sockets.push(socket);
            if(lastHash){
                socket.emit('hash',lastHash);
                socket.emit('ok');
            }
        });
    }
    //9. 创建http服务器并启动服务
    listen(port){
         this.server.listen(port,()=>{
            console.log(port+'服务启动成功!')
        });
    }
}
//3. 创建Server服务器
let server = new Server(compiler);
server.listen(8000);

6.2 client.js #

src\client.js


let socket = io('/');
class Emitter{
  constructor(){
    this.listeners = {};
  }
  emit(type){
    this.listeners[type]&&this.listeners[type]();
  }
  on(type,listener){
    this.listeners[type] = listener;
  }
}
const hotEmitter= new Emitter();

let hotCurrentHash;
let currentHash;
const onConnected = ()=>{
  console.log('客户端已经连接');
  //1. `webpack-dev-server/client`端会监听到此hash消息
  socket.on('hash',(hash)=>{
    currentHash = hash;
  });
  //2. 客户端收到`ok`的消息后会执行`reloadApp`方法进行更新
  socket.on('ok',()=>{
      reloadApp(true);
  });
  socket.on('disconnect',()=>{
    hotCurrentHash=currentHash=null;
  });
}
// 3. 在reloadApp中会进行判断,是否支持热更新,如果支持的话发射`webpackHotUpdate`事件,如果不支持则直接刷新浏览器
function reloadApp(hot){
  if(!hot){
    return window.location.reload();
  }
  hotEmitter.emit('webpackHotUpdate');
}

//4. 在`webpack/hot/dev-server.js`会监听`webpackHotUpdate`事件,在监听里会调用hotCheck方法
hotEmitter.on("webpackHotUpdate", function() {
  if(!hotCurrentHash || hotCurrentHash === currentHash){
      return hotCurrentHash = currentHash;
  }
  //5. 在check方法里会调用`module.hot.check`方法
  hotCheck();
});
function hotCheck(){
  //6. 它通过调用 `JsonpMainTemplate.runtime`的`hotDownloadManifest`方法,向 server 端发送 Ajax 请求,服务端返回一个 `Manifest`文件,该 `Manifest` 包含了所有要更新的模块的 `hash` 值和chunk名
  hotDownloadManifest().then(update=>{
      let chunkIds = Object.keys(update.c);
      chunkIds.forEach((chunkId)=>{
           //7. 调用`JsonpMainTemplate.runtime`的`hotDownloadUpdateChunk`方法通过JSONP请求获取到最新的模块代码
            hotDownloadUpdateChunk(chunkId);
      });
  });
}

function hotDownloadUpdateChunk(chunkId) {
  var script = document.createElement("script");
  script.charset = "utf-8";
  script.src = "/" + chunkId + "." + hotCurrentHash + ".hot-update.js";
  document.head.appendChild(script);
}
function hotDownloadManifest(){
  return new Promise((resolve,reject)=>{
   var request = new XMLHttpRequest();
   var requestPath = "/" + hotCurrentHash + ".hot-update.json";
   request.open("GET", requestPath, true);
   request.onreadystatechange = function() {
    if(request.readyState == 4){
     let update = JSON.parse(request.responseText);
     resolve(update);
    }
   }
   request.send();
  });
}
//9. 补丁JS取回来后会调用`JsonpMainTemplate.runtime.js`的`webpackHotUpdate`方法,里面会调用`hotAddUpdateChunk`方法,用新的模块替换掉旧的模块
//10. 然后会调用`HotModuleReplacement.runtime.js`的`hotAddUpdateChunk`方法动态更新模块代码
//11. 然后调用`hotApply`方法进行热更新
window.webpackHotUpdate = (chunkId, moreModules)=>{
  for(let moduleId in moreModules){
    let oldModule =  __webpack_require__.c[moduleId];
    let {parents,children} = oldModule;
    var module = __webpack_require__.c[moduleId] = {
          i: moduleId,
          l: false,exports: {},
          parents,children,
          hot: window.hotCreateModule(moduleId)
     };
     moreModules[moduleId].call(module.exports, module, module.exports,__webpack_require__);
     module.l = true;
    parents.forEach(parent=>{
      let parentModule = __webpack_require__.c[parent];
      parentModule.hot&&parentModule.hot._acceptedDependencies[moduleId]&&parentModule.hot._acceptedDependencies[moduleId]();
    });
    hotCurrentHash = currentHash;
  }
}
socket.on('connect',onConnected);

6.3 HotModuleReplacement.runtime.js #

webpack\lib\HotModuleReplacement.runtime.js

function hotCreateModule(moduleId) {
         var hot = {
              _acceptedDependencies: {},
              accept: function(dep, callback) {
                for (var i = 0; i < dep.length; i++){
                  hot._acceptedDependencies[dep[i]] = callback;
                }
              }
        }
        return hot;
    }

访问验证

请输入访问令牌

Token不正确,请重新输入