当前位置: 首页 > news >正文

Webpack和Vite插件的开发与使用

在现代开发中一般各公司都有自己的监控平台,对前端而言如果浏览器报错的话就可以通过埋点收集错误日志,再结合sourcemap文件可以帮助我们定位到错误代码,帮助我们排查问题。这里就记录一下之前在webpack和vite两个环境中的插件开发,可以在生产构建时将sourcemap上传到内部的文件服务器配合后续的监控日志来一起使用

Webpack插件开发

Compiler 和 Compilation

在插件开发中有两个概念比较重要,分别是CompilerCompilation,他们是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的两套插件可以分别在WebpackRollup中使用了。

参考文件

Webpack插件钩子
Vite插件钩子
Vite插件开发

相关文章:

Webpack和Vite插件的开发与使用

在现代开发中一般各公司都有自己的监控平台,对前端而言如果浏览器报错的话就可以通过埋点收集错误日志,再结合sourcemap文件可以帮助我们定位到错误代码,帮助我们排查问题。这里就记录一下之前在webpack和vite两个环境中的插件开发&#xff0…...

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 回车和换行 先问大家一个问题&#xff1a;回车换行是什么&#xff0c;或者说回车和换行是同一个概念吗&#xff1f;   可能大家对回车换行有一定的误解&#xff0…...

Qt 中使用 SQLite 数据库的完整指南

SQLite 是一款轻量级、嵌入式的关系型数据库&#xff0c;无需独立的服务器进程&#xff0c;数据以文件形式存储&#xff0c;非常适合桌面和移动端应用的本地数据管理。Qt 通过 Qt SQL 模块提供了对 SQLite 的原生支持&#xff0c;开发者可以轻松实现数据库的增删改查、事务处理…...

数智化时代的工单管理:从流程驱动到数据驱动-亿发

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

Large Language Model Distilling Medication Recommendation Model

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

floodfill算法系列一>被围绕的区域

目录 整体思想&#xff1a;代码设计&#xff1a;代码呈现&#xff1a; 整体思想&#xff1a; 代码设计&#xff1a; 代码呈现&#xff1a; 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章——入门概述与安装配置

一、入门概述 &#xff08;1&#xff09;是什么 Redis&#xff1a;REmote Dictionary Server&#xff08;远程字典服务器&#xff09;官网解释&#xff1a;Remote Dictionary Server(远程字典服务)是完全开源的&#xff0c;使用ANSIC语言编写遵守BSD协议&#xff0c;是一个高…...

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实现单行、多行文本溢出显示省略号(…)

在网页设计中&#xff0c;我们常常遇到这样的情况&#xff1a;文本内容太长&#xff0c;无法完全显示在一个固定的区域内。为了让界面看起来更整洁&#xff0c;我们可以使用省略号&#xff08;…&#xff09;来表示内容溢出。这不仅能提升用户体验&#xff0c;还能避免内容溢出…...

服务器中部署大模型DeepSeek-R1 | 本地部署DeepSeek-R1大模型 | deepseek-r1部署详细教程

0. 部署前的准备 首先我们需要足够算力的机器&#xff0c;这里我在vultr中租了有一张A16显卡一共16GB显存的服务器作为演示。部署的模型参数为14b的。如果需要部署满血版本671b的&#xff0c;需要更大的算力支持&#xff0c;这里由于是个人资金有限&#xff0c;就演示14b的部署…...

元学习之孪生网络Siamese Network

简介&#xff1a;元学习是一种思想&#xff0c;一般以神经网络作为特征嵌入的工具&#xff0c;实现对数据特征的提取&#xff0c;然后通过构造某种指标以引导优化器对模型参数进行优化。而最小化距离是最常见的学习目标&#xff0c;这就是熟知的度量学习&#xff0c;度量学习里…...

深入HBase——引入

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

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站&#xff0c;会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后&#xff0c;网站没有变化的情况。 不熟悉siteground主机的新手&#xff0c;遇到这个问题&#xff0c;就很抓狂&#xff0c;明明是哪都没操作错误&#x…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误

HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误&#xff0c;它们的含义、原因和解决方法都有显著区别。以下是详细对比&#xff1a; 1. HTTP 406 (Not Acceptable) 含义&#xff1a; 客户端请求的内容类型与服务器支持的内容类型不匹…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

Cesium1.95中高性能加载1500个点

一、基本方式&#xff1a; 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

基础测试工具使用经验

背景 vtune&#xff0c;perf, nsight system等基础测试工具&#xff0c;都是用过的&#xff0c;但是没有记录&#xff0c;都逐渐忘了。所以写这篇博客总结记录一下&#xff0c;只要以后发现新的用法&#xff0c;就记得来编辑补充一下 perf 比较基础的用法&#xff1a; 先改这…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...

虚拟电厂发展三大趋势:市场化、技术主导、车网互联

市场化&#xff1a;从政策驱动到多元盈利 政策全面赋能 2025年4月&#xff0c;国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》&#xff0c;首次明确虚拟电厂为“独立市场主体”&#xff0c;提出硬性目标&#xff1a;2027年全国调节能力≥2000万千瓦&#xff0…...

C++ 设计模式 《小明的奶茶加料风波》

&#x1f468;‍&#x1f393; 模式名称&#xff1a;装饰器模式&#xff08;Decorator Pattern&#xff09; &#x1f466; 小明最近上线了校园奶茶配送功能&#xff0c;业务火爆&#xff0c;大家都在加料&#xff1a; 有的同学要加波霸 &#x1f7e4;&#xff0c;有的要加椰果…...