webpack(五)热更新

news2024/12/23 19:22:47

webpack-dev-serve 开启本地服务器

现状:打包后将index.html文件通过open in liveServe打开,我们继续修改js、html文件内容,页面没有变化。
解决方法:
方法一:
在打包命令后面加上--watch,重新打包后我们的终端不会终止,我们打包打开项目后,再代码中继续修改,不需要重新打包打开,页面会自动更新

"scripts": {
    "build": "webpack --mode=development --watch"
  },

方法二:

// webpack.config.js
module.exports = {
    watch:true,

以上方案不足:

  • 所有源代码都会重新执行编译
  • 每次编译成功都会删除dist目录,然后进行文件读写,与磁盘进行交互
  • liveServevscode生态下的,webpack也有这样的类似功能实现
  • 不能实现局部刷新:页面包含多个组件,每次不希望整个页面进行刷新,而是单个组件的更新,但是当前是整体刷新

方法三:
npm i webpack-dev-server

// package.json
"scripts": {
    "build": "webpack --mode=development",
    "serve": "webpack serve"
  },

这样是把数据存储在内存里面,提高效率

webpack-dev-middleware 中间件

文档地址
我们追求操作性更高更灵活,vue和react内部是用的webpack-dev-server
npm install --save-dev express webpack-dev-middleware
步骤:

  • 开启服务
  • 将webpack打包后的工具交给服务
// Server.js
const express = require('express')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpack = require('webpack')


const app = express()
// 继续打包

// 获取配置文件
const config = require('./webpack.config')
const compiler = webpack(config)

app.use(webpackDevMiddleware(compiler))
// 开启端口服务
app.listen(3000, () => {
    console.log('服务运行在3000端口上')
})

运行:node ./Server.js

热更新定义

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

例如,我们在应用运行过程中修改了某个模块,通过自动刷新会导致整个应用的整体刷新,那页面中的状态信息都会丢失
如果使用的是HMR,就可以实现只将修改的模块实时替换至应用中,不必完全刷新整个应用。

开启热更新:

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

配置完成之后我们修改css文件,确实能够以不刷新的形式更新到页面中,但是当我们修改保存js文件时,页面仍然自动刷新了,需要进行配置指定哪些模块发生更新时进行HRM

// 如果当前的模块支持热更新,注册回调
if(module.hot){
// index.js模块可以接受title.js的变更,变更后,调用render方法
    module.hot.accept('./util.js',()=>{
        console.log("util.js更新了")
    })
}

可以在回调函数中拿到最新的util文件,进行操作

热更新流程

在这里插入图片描述
初始时:在编写未经webpack打包的源码后,Webpack Compile 将源代码和 HMR Runtime 一起编译成 bundle文件,传输给Bundle Server 静态资源服务器

更新时:当某一个模块或者文件发生变化时,webpack监听到文件变化对文件重新编译打包,编译生成唯一的hash值,这个hash值用来作为下一次热更新的标识

根据变化的内容生成两个补丁文件:manifest(包含了hashchunkId,用来说明变化的内容)和chunk.js 模块
由于socket服务器在HMR RuntimeHMR Server之间建立 websocket链接,当文件发生改动的时候,服务端会向浏览器推送一条消息,消息包含文件改动后生成的hash值,作为下一次热更细的标识

浏览器接受到这条消息之前,浏览器已经在上一次socket 消息中已经记住了此时的hash 标识,这时候我们会创建一个 ajax 去服务端请求获取到变化内容的 manifest 文件

mainfest文件包含重新build生成的hash值,以及变化的模块

浏览器根据manifest文件获取模块变化的内容,从而触发render流程,实现局部模块更新

实现webpack-dev-server

初始搭建

const webpack = require("webpack");
const config = require("../webpack.config.js");
const Server = require("./lib/server/server.js");
// 编译器对象
const compiler = webpack(config);
const server = new Server(compiler);
server.listen(9090, "localhost", () => {
  console.log("服务已经在9090端口使用");
});

启动一个服务器

// server.js
const express = require("express");
const http = require("http");
const path = require("path");
const MemoryFs = require("memory-fs"); //内存级文件系统 因为内存比硬盘操作更换
const mime = require("mime");
const updateCompiler = require("./updateCompiler");
const socketIo = require("socket.io");
/**
 * webpack.config执行得到一个compile文件,compile是一个大管家,代表本次查询的对象,传递给server
 * server中,updateCompiler 向入口注入代码,实现热更新的
 * setupHooks设置钩子 每当新的编译成功后,会触发回调,向客户端发送hash和ok(告诉客户端来拉取我得代码)
 * setupDevMiddleware 设置中间件 以监听模式启动编译 文件发生改变时会饶工webpack重新编译,编译后的文件写到内存文件系统中,触发done回调
 * createServer 客户端想访问文件 启动一个静态服务中间件 app.use使用这个中间件,读取到文件返回给客户端
 * createSocketServer 启动服务,双向通信服务器,编译成功后给所有客户端发送消息,
 * 服务器端要主动往客户端推送代码,所以需要socket服务
 */
// webpack编译时会把文件写到文件系统去  客户端启动服务器通过路由访问中间件 中间件读取文件系统
class Server {
  constructor(compiler) {
    this.compiler = compiler;
    updateCompiler(compiler);
    this.setupAPP(); //创建app
    this.currentHash; //当前的hash值,每次编译都会产生一个hash值
    this.clientSocketList = []; // 存放所有通过websocket链接到服务器的客户端
    this.setupHooks(); // 建立钩子
    this.setupDevMiddleware(); //建立开发中间件
    this.routes(); //配置路由
    this.createServer(); //创建http服务器,以app作为路由
    this.createSocketServer(); //创建socket服务器
  }
  createSocketServer() {
    // websocket 协议握手需要依赖http服务器的
    const io = socketIo(this.server);
    // 服务器要监听客户端的连接,当客户端链接上来后,socket代表跟这个客户端连接对象
    io.on("connection", (socket) => {
      console.log("一个新的客户端已经连接上了");
      this.clientSocketList.push(socket); //把新的socket放到数组
      socket.emit("hash", this.currentHash); //发送hash值
      socket.emit("ok");
      // 监听客户端断开连接,把客户端从数组中删除
      socket.on("disconnect", () => {
        let index = this.clientSocketList.indexOf(socket);
        this.clientSocketList.splice(index, 1);
      });
    });
  }
  routes() {
    let { compiler } = this;
    let config = compiler.options;
    this.app.use(this.middleware(config.output.path));
  }
  setupDevMiddleware() {
    this.middleware = this.webpackDevMiddleware();
  }
  webpackDevMiddleware() {
    let { compiler } = this;
    //以监听的模式启动编译,如果以后文件发生变化了 会重新编译
    compiler.watch({}, () => {
      console.log("监听模式编译成功");
    });
    let fs = new MemoryFs(); //内存文件系统实例
    this.fs = compiler.outputFileSystem = fs;
    // 返回一个中间件,用来响应客户端对于产出文件的请求
    return (staticDir) => {
      return (req, res, next) => {
        let { url } = req; //得到请求路径
        if (url === "/favicon.ico") {
          return res.sendStatus(404);
        }
        url === "/" ? (url = "/index.html") : null;
        let filePath = path.join(staticDir, url); //得到要访问的静态路径 绝对路径
        try {
          // 返回此路径上的描述对象,如果此文件不存在会抛出异常
          let stateObj = this.fs.statSync(filePath);
          if (stateObj.isFile()) {
            let content = this.fs.readFileSync(filePath); //读取文件内容
            res.setHeader("Content-Type", mime.getType(filePath)); //设置响应头 告诉浏览器此文件内容是什么
            res.send(content); //把文件内容发送给浏览器
          } else {
            return res.sendStatus(404);
          }
        } catch (error) {
          return res.sendStatus(404);
        }
      };
    };
  }
  setupHooks() {
    let { compiler } = this;
    // 监听编译完成事件 当编译完成之后会调用此钩子函数
    compiler.hooks.done.tap("webpack-dev-server", (stats) => {
      // stats:描述对象 里卖放着打包后的结果
      console.log("hash", stats.hash);
      this.currentHash = stats.hash;
      // 向所有的客户端广播 告诉客户端我已经编译成功了 新的模块代码已经生成了
      this.clientSocketList.forEach((socket) => {
        socket.emit("hash", this.currentHash);
        socket.emit("ok");
      });
    });
  }
  setupAPP() {
    this.app = express(); //代表http应用对象
  }
  createServer() {
    // 通过app创建一个普通的服务器
    this.server = http.createServer(this.app);
  }
  listen(port, host, callback) {
    this.server.listen(port, host, callback);
  }
}
module.exports = Server;

// updateCompiler.js
/**
 * 实现客户端和服务器通信 需要往入口里面多注入两个文件
 * (webpack)-dev-server/client/index.js
 * (webpack)/hot/der-server.js
 * */
const path = require("path");

function updateCompiler(compiler) {
  const config = compiler.options;
  config.entry = {
    main: [
      path.resolve("../client/index.js"),
      path.resolve("../client/hot/der-server.js"),
      config.entry,
    ],
  };
}
module.exports = updateCompiler;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1118053.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

类的继承简介

一、声明格式: class 子类名:继承方式(public private protected) 父类名{子类成员表} 二、继承过程: 吸取父类成员——>改造父类成员——>添加新成员 三、作用: 子类会继承父类中的方法(不包括构造和析构函数)与属性 …

进阶JAVA篇-深入了解 List 系列集合

目录 1.0 List 类的说明 1.1 List 类的常用方法 1.2 List 集合的遍历方式 2.0 ArrayList 集合的底层原理 2.1 从 ArrayList 集合的底层原理来了解具有该特性的原因: 2.2 ArrayList 集合的优缺点 3.0 LinkedList 集合的底层原理 3.1 从 LinkedList 集合的底层原理来了…

卷积神经网络CNN学习笔记-卷积计算Conv2D函数的理解

目录 1.全连接层存在的问题2.卷积运算3.填充(padding)3.1填充(padding)的意义 4.步幅(stride)5.三维数据的卷积运算6.结合方块思考7.批处理8.Conv2D函数解析9.conv2d代码9.1 stride19.2 stride2 参考文章 1.全连接层存在的问题 在全连接层中,相邻层的神经元全部连接…

多线程-进阶

常见的锁策略 乐观锁和悲观锁 这不是两把具体的锁, 这是两类锁 乐观锁: 预测锁的竞争不是很激烈 悲观锁: 预测锁的竞争会很激烈 乐观和悲观说的都不是绝对的, 唯一的区分就是看预测锁竞争激烈程度的结论, 这两种锁的背后工作是截然不同的, 轻量级锁和重量级锁 轻量级锁加锁解…

浅谈人工智能视频分析技术的原理及行业场景应用

人工智能视频分析技术是利用计算机视觉、模式识别和深度学习算法等技术,对视频数据进行自动化处理和分析的过程。其基本工作原理包括以下几个步骤: 视频采集:通过摄像头或其他视频设备获取源视频数据。 视频预处理:对视频进行去噪…

如何更改eclipse的JDK版本

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、有时候导入一些网上的资源需要更换JDK二、使用步骤1. 总结 一、有时候导入一些网上的资源需要更换JDK 具体操作如下 二、使用步骤 1. 在eclipse上方工具栏找…

使用列表实现向量运算

向量内积 from operator import mul x [4, 6, 9] y [2, 2, 7] print(sum(map(mul, x, y)))相当于 x [4, 6, 9] y [2, 2, 7] print(sum((i*j for i, j in zip(x, y))))两个等长的向量对应元素相加 from operator import add x [4, 6, 9] y [2, 2, 7] print(list(map(ad…

一些经典的神经网络(第20天)

1. 经典神经网络(LeNet) LeNet是早期成功的神经网络; 先使用卷积层来学习图片空间信息 然后使用全连接层来转到到类别空间 【通过在卷积层后加入激活函数,可以引入非线性、增加模型的表达能力、增强稀疏性和解决梯度消失等问题…

基于RSSI的室内wifi定位系统 计算机竞赛

0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 wifi室内定位系统 该项目较为新颖,适合作为竞赛课题方向,学长非常推荐! 🥇学长这里给一个题目综合评分(每项满分5分) 难度系数:…

【经历】在职8个月->丰富且珍贵

在职8个月->丰富且珍贵 2021-3~2021-11:面试进入一家做400电话的公司,我进入公司时,加上我只有四个人(老板、人事、业务),开发只有我,所以:产品~设计~前端~后端~测试~上线~维护~培训,只有我自…

并发性Socket通信源码(基于linux环境下多线程)

服务器端&#xff1a;server.c 1 #include <stdio.h>2 #include <stdlib.h>3 #include <unistd.h>4 #include <string.h>5 #include <arpa/inet.h>6 #include <pthread.h>7 void* working(void *arg);8 //信息结构体9 struct sockinfo10 …

烘焙蛋糕外卖小程序商城的作用是什么

随着经营成本上升及电商业态的冲击&#xff0c;传统烘焙蛋糕门店商家经营止步不前&#xff0c;加之口罩原因更是雪上加霜&#xff0c;引流拓客、经营转化复购大幅度降低&#xff0c;而线上又因外卖平台间的激烈竞争&#xff0c;导致中小烘焙蛋糕商家进退两难。 烘焙蛋糕店经营…

PO模式在selenium自动化测试框架的优势

大家都知道po模式可以提高代码的可读性和减少了代码的重复&#xff0c;但是相对的缺点还有&#xff0c;今天通过本文一起学习下PO模式在selenium自动化测试框架的优势&#xff0c;需要的朋友可以参考下 PO模式简介 1.什么是PO模式 PO模型是:Page Object Model的简写 页面对象…

使用 Sealos 一键私有化部署 Serverless 框架 Laf

太长不看&#xff1a;Laf 上架了 Sealos 的模板市场&#xff0c;通过 Laf 应用模板即可一键部署&#xff01; Laf 是一个完全开源的项目&#xff0c;除了使用公有云之外&#xff0c;还有大量的用户选择私有化部署 Laf。然而&#xff0c;私有化部署通常伴随着更多的复杂性和门槛…

java 实现定时任务

1、EnableScheduling spring自带的定时任务功能&#xff0c;使用比较简单方便&#xff1a; 1、需要定时执行的方法上加上Scheduled注解&#xff0c;这个注解中可以指定定时执行的规则&#xff0c;稍后详细介绍。 2、Spring容器中使用EnableScheduling开启定时任务的执行&…

解决找不到VCRUNTIME140_ 1.dll问题的5个方法分享

近日&#xff0c;许多用户在运行某些软件时遇到了“由于找不到VCRUNTIME140_1.dll无法继续执行此代码”的问题。这个错误通常出现在运行某些软件或游戏时&#xff0c;提示找不到必要的动态链接库文件。本文将详细VCRUNTIME140_ 1.dll文件是什么&#xff0c;并分享如何解决这个问…

1658.将x减到0的最小操作数(滑动窗口)

目录 一、题目 二、分析代码 一、题目 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 二、分析代码 class Solution { public:int minOperations(vector<int>& nums, int x) {int _MaxLength INT_MIN;int _sum 0;int tmp 0;for (aut…

数据回归算法 | Python逻辑回归

逻辑回归是一种经典的机器学习算法,用于解决二分类问题。 它常被用于预测某个事件发生的概率,通过将输入特征映射到一个概率值来进行分类。 简单聊聊 逻辑回归就像一位智能的侦探,根据一些线索(输入特征)判断某件事情是否会发生。 它将这些线索组合起来,计算出一个关于…

洛谷 P1216 [USACO1.5] [IOI1994]数字三角形题解

观察题目我们发现从前往后推会有条件判断&#xff0c;不容易写出来。所以就从后往前推。 也就是说后面的状态已经是推出来了&#xff0c;保证是最大值。 //数字三角形 #include<iostream> using namespace std; const int N 510; int f[N][N], n;int main() {ios::sync…

利用Bootstrap的面包屑组件实现面包屑层次分级导航效果

目录 01-相关基础知识02-一个简单的示例03-改变面包悄中的层级分隔符 01-相关基础知识 可以用类breadcrumb实现面包屑层次导航效果。 当使用 Bootstrap 构建网页时&#xff0c;breadcrumb 类用于创建面包屑导航&#xff08;breadcrumb navigation&#xff09;&#xff0c;这是…