Webpack和Vite插件的开发与使用
在现代开发中一般各公司都有自己的监控平台,对前端而言如果浏览器报错的话就可以通过埋点收集错误日志,再结合sourcemap文件可以帮助我们定位到错误代码,帮助我们排查问题。这里就记录一下之前在webpack和vite两个环境中的插件开发,可以在生产构建时将sourcemap上传到内部的文件服务器配合后续的监控日志来一起使用
Webpack插件开发
Compiler 和 Compilation
在插件开发中有两个概念比较重要,分别是Compiler和Compilation,他们是Plugin和Webpack之间的桥梁。他们的含义如下:
- Compiler对象包含了Webpack环境所有的配置信息,包含options、loaders、plugins等这些所有的信息,这个对象在Webpack启动的时候被实例化,是全局唯一的,可以把他看成是Webpack的实例
- Compilation 对象包含了当前模块资源以及编译生成资源还有变化的文件等相关信息。在webpack以开发模式运行时,每当检测到一个文件变化,一个新的Compilation就会被创建。Compilation对象提供了很多事件回调给插件做扩展,通过Compilation也能读取到Compiler对象
两者的区别在于:Compiler代表了整个Webpack从启动到关闭的生命周期,而Compilation只是代表了一次新的编译。
事件流
Webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。 插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。
Webpack 通过 Tapable 来组织这条复杂的生产线。 Webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。 Webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。
Webpack 的事件流机制应用了观察者模式,和 Node.js 中的 EventEmitter 非常相似。 Compiler 和 Compilation 都继承自 Tapable,可以直接在 Compiler 和 Compilation 对象上广播和监听事件。
webpack 插件钩子
每个webpack插件都是一个class,其中最终要的两个内容,一个是constructor,可以接受一个option参数,这个就是用户在使用这个插件时传入的参数,第二个就是apply方法,接受一个compiler这个实例,在webpack初始化时,会调用每个插件,执行其中的apply方法,我们就可以在apply这个方法中使用接受到的Compiler实例上提供的各种hook来监听我们需要的事件,在整个流水线工程中有很多事件钩子,如下图所示:
我们可以监听我们需要的时机,参考地址Webpack插件钩子
在这里我们需要在webpack打包结束的时候将构建结果中的.map文件上传到自己的文件服务器,所以需要订阅done这个事件钩子,在每次compilation完成时执行。
插件调用方式
在webpack插件中一共有同步和异步两种调用方式,同步调用是直接使用tap异步调用使用tapAsync还有一种是tapPromise,简单代码展示如下:
apply(compiler) {// 同步钩子compiler.hooks.compilation.tap('MyPlugin', (compilation) => {// 同步处理console.log('同步处理');});
}
apply(compiler) {// 异步钩子,使用回调函数compiler.hooks.done.tapAsync('MyPlugin', (stats, callback) => {// 异步处理setTimeout(() => {console.log('异步处理完成');callback();}, 1000);});
}
apply(compiler) {// 异步钩子,返回 Promisecompiler.hooks.done.tapPromise('MyPlugin', (stats) => {return new Promise((resolve) => {setTimeout(() => {console.log('Promise 异步处理完成');resolve();}, 1000);});});
}
这里我们需要将文件上传,所以明显是一个异步的调用,采用的是tapAsync的调用方式,具体代码如下:
const fs = require('fs');
const path = require('path');
const axios = require('axios');
const FormData = require('form-data');// 将打包文件中的.map文件上传到指定服务器
class UploadSourceMapPlugin {constructor(options) {this.options = options;if (!this.options.uploadUrl) {throw new Error('uploadUrl is required');}}apply(compiler) {compiler.hooks.done.tapAsync('UploadSourceMapPlugin', async (stats, callback) => {try {const outputPath = compiler.options.output.path;// 递归读取目录中的所有文件function getAllFiles(dir) {const files = fs.readdirSync(dir);let fileList = [];files.forEach(file => {const filePath = path.join(dir, file);const stat = fs.statSync(filePath);if (stat.isDirectory()) {fileList = fileList.concat(getAllFiles(filePath));} else {fileList.push(filePath);}});return fileList;}// 获取所有文件const allFiles = getAllFiles(outputPath);// 过滤出 .map 文件const sourceMapFiles = allFiles.filter(file => file.endsWith('.map')).map(file => ({name: path.basename(file),path: file}));// 上传所有的 source map 文件for (const file of sourceMapFiles) {const filePath = file.path;if (fs.existsSync(filePath)) {await this.uploadFile(filePath, file.name);// 如果配置了上传后删除if (this.options.deleteAfterUpload) {fs.unlinkSync(filePath);console.log(`Deleted ${file.name} after upload`);}}}// 通知 webpack 异步操作已完成// webpack 可以继续执行后续步骤// 如果不调用 callback,webpack 的构建过程会一直等待callback();} catch (error) {console.error('Error in UploadSourceMapPlugin:', error);callback();}});}async uploadFile(filePath, fileName) {try {const formData = new FormData();formData.append('sourcemap', fs.createReadStream(filePath), fileName);const response = await axios.post(this.options.uploadUrl, formData, {headers: {...formData.getHeaders(),'Authorization': this.options.token || '' // 可选的认证token}});console.log(`Successfully uploaded ${fileName}`);return response.data;} catch (error) {console.error(`Failed to upload ${fileName}:`, error.message);throw error;}}}module.exports = UploadSourceMapPlugin;
这里有个小小的坑,就是在compiler中有个state对象也是文件列表,但是这里面只能获取到经过Webpack编译之后的文件,所以最保险的还是自己递归遍历一下结果文件进行过滤。这里callback是上传结束之后的回调,告诉webpack已近完成,否则webpack就会一直在这里等待
使用
最终使用这个插件也很简单,我们一般主需要再生产环境使用,开发环境一般是没有必要将sourcemap上传上去的。通过下面的配置就可以使用我们开发的插件了
// 只在生产环境使用...(process.env.NODE_ENV === 'production' ? [new UploadSourceMapPlugin({uploadUrl: 'https://file-server.com/upload',token: 'auth-token', // 可选的认证tokendeleteAfterUpload: true // 是否在上传后删除本地文件})] : []),
Vite
Vite 是一个现代的前端构建工具,因其快速、简单的配置和优化的开发体验而广受欢迎。Vite是基于Rollup来的,速度非常快,如果开发的插件不带Vite特有的钩子一般都可以在Rollup中兼容使用。
Vite中的每个插件一般都是一个返回一个对象的函数,其中有name字段表插件名称,以及对应的钩子函数,在Vite中有通用钩子和Vite专属钩子。
参考地址:Vite插件钩子
这里我们在每次生产环境构建结束时调用这个插件。我们可以用apply: ‘build’, 表示只在构建时调用这个插件,日常开始npm run dev时处于开发状态就不会调用这个插件。
具体代码如下:
import fs from 'fs'
import path from 'path'export default function UploadSourceMapPlugin(options = {}) {const {uploadUrl = '', // 上传服务器地址headers = {}, // 自定义请求头deleteAfterUpload = true // 上传后是否删除本地map文件} = optionsif (!uploadUrl) {throw new Error('uploadUrl is required for UploadSourceMapPlugin')}return {name: 'vite-plugin-upload-sourcemap',apply: 'build', // 仅在构建时应用async closeBundle() {const distDir = path.resolve('dist')const sourcemaps = []// 递归查找所有.map文件function findSourceMaps(dir) {const files = fs.readdirSync(dir)files.forEach(file => {const fullPath = path.join(dir, file)const stat = fs.statSync(fullPath)if (stat.isDirectory()) {findSourceMaps(fullPath)} else if (file.endsWith('.map')) {sourcemaps.push(fullPath)}})}findSourceMaps(distDir)// 上传所有sourcemap文件for (const mapFile of sourcemaps) {const formData = new FormData()formData.append('file', fs.createReadStream(mapFile))try {const response = await fetch(uploadUrl, {method: 'POST',body: formData,headers: {...headers}})if (!response.ok) {throw new Error(`Upload failed for ${mapFile}`)}console.log(`Successfully uploaded: ${mapFile}`)// 如果配置了上传后删除,则删除本地map文件if (deleteAfterUpload) {fs.unlinkSync(mapFile)console.log(`Deleted local file: ${mapFile}`)}} catch (error) {console.error(`Error uploading ${mapFile}:`, error)}}}}
}
其中的代码逻辑其实和webpack中基本保持一致。至此我们就开发了一个上传soucemap的两套插件可以分别在Webpack和Rollup中使用了。
参考文件
Webpack插件钩子
Vite插件钩子
Vite插件开发
相关文章:

Webpack和Vite插件的开发与使用
在现代开发中一般各公司都有自己的监控平台,对前端而言如果浏览器报错的话就可以通过埋点收集错误日志,再结合sourcemap文件可以帮助我们定位到错误代码,帮助我们排查问题。这里就记录一下之前在webpack和vite两个环境中的插件开发࿰…...
HTTP的状态码
HTTP 状态码 当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含 HTTP 状态码的信息头(server header)用以响应浏览器的请求。 常见的HTTP状态码 …...
Python函数-装饰器
装饰器 写好的函数,不做任何修改,就可以改变执行内容,在其头或尾部加入新的流程代码本质上就是使用函数嵌套,在内部嵌套定义的函数中调用原函数,从而可读在前或后加入新的代码使用的关键: 将原函数作为参数…...

【数据可视化-17】基于pyecharts的印度犯罪数据可视化分析
🧑 博主简介:曾任某智慧城市类企业算法总监,目前在美国市场的物流公司从事高级算法工程师一职,深耕人工智能领域,精通python数据挖掘、可视化、机器学习等,发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…...
HTTP请求报文头和相应报文头
一、HTTP请求报文头 HTTP请求报文由请求行、请求头和请求体组成。请求头包含客户端向服务器发送的附加信息。 1.1 请求行 格式: 方法 请求URI HTTP/版本示例: GET /index.html HTTP/1.1 方法: 请求类型,如GET、POST、PUT、DELETE等。 请求URI: 请求的资源…...

19.4.9 数据库方式操作Excel
版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。 本节所说的操作Excel操作是讲如何把Excel作为数据库来操作。 通过COM来操作Excel操作,请参看第21.2节 在第19.3.4节【…...

BFS 走迷宫
#include<bits/stdc.h> using namespace std; int a[100][100],v[100][100];//访问数组 n,m<100 struct point {int x;int y;int step; }; queue<point> r;//申请队列 int dx[4]{0,1,0,-1};//四个方向 右下左上 int dy[4]{1,0,-1,0}; int main() { /* 5 4 1 …...

【Linux系统】—— 简易进度条的实现
【Linux系统】—— 简易进度条的实现 1 回车和换行2 缓冲区3 进度条的准备代码4 第一版进度条5 第二版进度条 1 回车和换行 先问大家一个问题:回车换行是什么,或者说回车和换行是同一个概念吗? 可能大家对回车换行有一定的误解࿰…...
Qt 中使用 SQLite 数据库的完整指南
SQLite 是一款轻量级、嵌入式的关系型数据库,无需独立的服务器进程,数据以文件形式存储,非常适合桌面和移动端应用的本地数据管理。Qt 通过 Qt SQL 模块提供了对 SQLite 的原生支持,开发者可以轻松实现数据库的增删改查、事务处理…...

数智化时代的工单管理:从流程驱动到数据驱动-亿发
在数智化时代,工单管理系统已从简单的任务分发工具演变为企业运营的智能中枢。传统工单系统关注流程的线性推进,而现代工单管理系统则强调数据的全生命周期管理,通过智能算法实现工单的自动分配、优先级判定和效能优化。这种转变不仅提升了运…...

Large Language Model Distilling Medication Recommendation Model
摘要:药物推荐是智能医疗系统的一个重要方面,因为它涉及根据患者的特定健康需求开具最合适的药物。不幸的是,目前使用的许多复杂模型往往忽视医疗数据的细微语义,而仅仅严重依赖于标识信息。此外,这些模型在处理首次就…...

floodfill算法系列一>被围绕的区域
目录 整体思想:代码设计:代码呈现: 整体思想: 代码设计: 代码呈现: class Solution {int m,n;int[] dx {0,0,-1,1};int[] dy {-1,1,0,0};public void solve(char[][] board) {m board.length;n board[…...

Redis 01 02章——入门概述与安装配置
一、入门概述 (1)是什么 Redis:REmote Dictionary Server(远程字典服务器)官网解释:Remote Dictionary Server(远程字典服务)是完全开源的,使用ANSIC语言编写遵守BSD协议,是一个高…...
windows基于cpu安装pytorch运行faster-whisper-large-v3实现语音转文字
1.创建虚拟环境 conda create -n faster-whisper python3.10 conda activate faster-whisper 2.安装cpu版本的pytorch pip3 install torch torchvision torchaudio -i https://pypi.tuna.tsinghua.edu.cn/simple 3.验证pytorch安装结果 (faster-whisper) H:\big-model\faste…...
AI大模型(如GPT、BERT等)可以通过自然语言处理(NLP)和机器学习技术,显著提升测试效率
在软件测试中,AI大模型(如GPT、BERT等)可以通过自然语言处理(NLP)和机器学习技术,显著提升测试效率。以下是几个具体的应用场景及对应的代码实现示例: 1. 自动生成测试用例 AI大模型可以根据需求文档或用户故事自动生成测试用例。 代码示例(使用 OpenAI GPT API): …...

【Prometheus】prometheus黑盒监控balckbox全面解析与应用实战
✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…...
CSS实现单行、多行文本溢出显示省略号(…)
在网页设计中,我们常常遇到这样的情况:文本内容太长,无法完全显示在一个固定的区域内。为了让界面看起来更整洁,我们可以使用省略号(…)来表示内容溢出。这不仅能提升用户体验,还能避免内容溢出…...

服务器中部署大模型DeepSeek-R1 | 本地部署DeepSeek-R1大模型 | deepseek-r1部署详细教程
0. 部署前的准备 首先我们需要足够算力的机器,这里我在vultr中租了有一张A16显卡一共16GB显存的服务器作为演示。部署的模型参数为14b的。如果需要部署满血版本671b的,需要更大的算力支持,这里由于是个人资金有限,就演示14b的部署…...
元学习之孪生网络Siamese Network
简介:元学习是一种思想,一般以神经网络作为特征嵌入的工具,实现对数据特征的提取,然后通过构造某种指标以引导优化器对模型参数进行优化。而最小化距离是最常见的学习目标,这就是熟知的度量学习,度量学习里…...

深入HBase——引入
引入 前面我们通过深入HDFS到深入MapReduce ,从设计和落地,去深入了解了大数据最底层的基石——存储与计算是如何实现的。 这个专栏则开始来看大数据的三驾马车中最后一个。 通过前面我们对于GFS和MapReduce论文实现的了解,我们知道GFS在数…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...

在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...
xmind转换为markdown
文章目录 解锁思维导图新姿势:将XMind转为结构化Markdown 一、认识Xmind结构二、核心转换流程详解1.解压XMind文件(ZIP处理)2.解析JSON数据结构3:递归转换树形结构4:Markdown层级生成逻辑 三、完整代码 解锁思维导图新…...