[Vite]Vite插件生命周期了解

[Vite]Vite插件生命周期了解

Chunk和Bundle的概念

  1. Chunk

    • 在 Vite 中,chunk 通常指的是应用程序中的一个代码片段,它是通过 Rollup 或其他打包工具在构建过程中生成的。每个 chunk 通常包含应用程序的一部分逻辑,可能是一个路由视图、一个组件或一组相关的模块。
    • 与 Webpack 类似,Vite 也支持代码分割(Code Splitting),可以将代码拆分成不同的 chunk 以实现按需加载,从而优化应用的性能和加载时间。
  2. Bundle

    • bundle 是指最终的输出文件,它是通过打包工具将多个模块、库和资源组合在一起形成的。在 Vite 的生产构建中,Rollup 会将应用程序的代码和依赖项打包成一个或多个 bundle。
    • Bundle 可以包含一个或多个 chunk,并且可能还会包括一些额外的元数据和辅助代码,如运行时代码、模块的映射信息等。
    • Vite 的输出 bundle 旨在优化生产环境的加载和执行,通常会进行压缩、树摇(Tree Shaking)和模块的合并等操作。

vite插件钩子

Vite的插件可以有两种形式,一种是vite插件,仅供vite使用;另一种则是rollup通用插件,它不使用 Vite 特有的钩子,让我们简单介绍一下关于这两种插件的生命周期:

所有钩子
  1. apply - 插件的入口点,Vite 会调用每个插件的 apply 函数,传入 Vite 的配置对象。
  2. config - 在 Vite 的配置被最终确定之前,允许插件修改配置。此钩子接收当前配置并应返回新的配置。
  3. configResolved - 在配置被解析并确定后调用,允许插件访问最终的配置。
  4. configureServer - 允许插件配置或修改 Vite 的开发服务器。
  5. transform - 在开发阶段,Vite 调用此钩子来请求插件对特定文件进行转换。
  6. render - 在开发阶段,Vite 调用此钩子来请求插件对 HTML 模板进行渲染。
  7. buildStart - 在构建开始之前调用。
  8. build - 在构建过程中调用,允许插件参与构建流程。
  9. generateBundle - 在构建结束时调用,允许插件访问或修改最终生成的 bundle。
  10. closeBundle - 在构建过程中,当一个 bundle 被写入磁盘后调用。
  11. writeBundle - 在构建过程中,当 bundle 准备写入磁盘时调用。
  12. optimizeDeps - 允许插件优化依赖,例如决定哪些依赖应该被包含在客户端。
  13. load - 允许插件提供一个模块的加载内容,而不是从文件系统中加载。
  14. resolveId - 允许插件介入模块 ID 的解析过程。
  15. shouldHandleRequest - 允许插件决定是否处理特定的请求。
  16. handleHotUpdate - 在 HMR(热模块替换)过程中,允许插件处理更新。
  17. transformIndexHtml - 在开发阶段,允许插件修改 HTML 模板。
  18. enforce - 指定插件应用的时机,可以是 'pre''post',分别表示在 Vite 默认插件之前或之后执行。
1. vite 独有的钩子
  1. enforce :值可以是prepostpre 会较于 post 先执行;
  2. apply :值可以是 buildserve 亦可以是一个函数,指明它们仅在 buildserve 模式时调用;
  3. config(config, env) :可以在 vite 被解析之前修改 vite 的相关配置。钩子接收原始用户配置 config 和一个描述配置环境的变量env;
  4. configResolved(resolvedConfig) :在解析 vite 配置后调用。使用这个钩子读取和存储最终解析的配置。当插件需要根据运行的命令做一些不同的事情时,它很有用。
  5. configureServer(server) :主要用来配置开发服务器,为 dev-server (connect 应用程序) 添加自定义的中间件;
  6. transformIndexHtml(html) :转换 index.html 的专用钩子。钩子接收当前的 HTML 字符串和转换上下文;
  7. handleHotUpdate(ctx):执行自定义HMR更新,可以通过ws往客户端发送自定义的事件;
2. vite 与 rollup 的通用钩子之构建阶段
  1. options(options) :在服务器启动时被调用:获取、操纵Rollup选项,严格意义上来讲,它执行于属于构建阶段之前;
  2. buildStart(options):在每次开始构建时调用;
  3. resolveId(source, importer, options):在每个传入模块请求时被调用,创建自定义确认函数,可以用来定位第三方依赖;
  4. load(id):在每个传入模块请求时被调用,可以自定义加载器,可用来返回自定义的内容;
  5. transform(code, id):在每个传入模块请求时被调用,主要是用来转换单个模块;
  6. buildEnd(error?: Error):在构建阶段结束后被调用,此处构建结束只是代表所有模块转义完成;
3. vite 与 rollup 的通用钩子之输出阶段
  1. outputOptions(options):接受输出参数;
  2. renderStart(outputOptions, inputOptions):每次 bundle.generate 和 bundle.write 调用时都会被触发;
  3. augmentChunkHash(chunkInfo):用来给 chunk 增加 hash;
  4. renderChunk(code, chunk, options):转译单个的chunk时触发。rollup 输出每一个chunk文件的时候都会调用;
  5. generateBundle(options, bundle, isWrite):在调用 bundle.write 之前立即触发这个 hook;
  6. writeBundle(options, bundle):在调用 bundle.write后,所有的chunk都写入文件后,最后会调用一次 writeBundle;
  7. closeBundle():在服务器关闭时被调用
4. 插件钩子函数 hooks 的执行顺序

vite插件开发钩子函数 (1).png

按照顺序,首先是配置解析相关:

  1. config:vite专有
  2. configResolved :vite专有
  3. options
  4. configureServer :vite专有

接下来是构建阶段的钩子:

  1. buildStart
  2. Resolved
  3. load
  4. transform
  5. buildEnd

然后是输出阶段的钩子:

  1. outputOptions
  2. renderStart
  3. augmentChunkHash
  4. renderChunk
  5. generateBundle
  6. transformIndexHtml
  7. writeBundle
  8. closeBundle
5. 插件的执行顺序
  1. 别名处理Alias
  2. 用户插件设置enforce: 'pre'
  3. vite 核心插件
  4. 用户插件未设置enforce
  5. vite 构建插件
  6. 用户插件设置enforce: 'post'
  7. vite 构建后置插件(minify, manifest, reporting)

举例

统计打包后dist大小

实现一个统计打包后dist大小的插件

主要使用的是closeBundle这个钩子。

import fs from 'fs'
import path from 'path'
import { Plugin } from 'vite'function getFolderSize(folderPath: string): number {if (!fs.existsSync(folderPath) || !fs.lstatSync(folderPath).isDirectory()) {return 0}let totalSize = 0const files = fs.readdirSync(folderPath)files.forEach(file => {const filePath = path.join(folderPath, file)const stats = fs.statSync(filePath)if (stats.isFile()) {totalSize += stats.size} else if (stats.isDirectory()) {totalSize += getFolderSize(filePath)}})return totalSize
}function formatBytes(bytes: number, decimals: number = 2): string {if (bytes === 0) return '0.00'const megabytes = bytes / (1024 * 1024)return megabytes.toFixed(decimals)
}function calculateDistSizePlugin(): Plugin {let distPath = ''return {name: 'calculate-dist-size',enforce: 'post' as const,apply: 'build' as const,configResolved(config) {// 可以在这里获取打包输出的目录const outDir = config.build.outDirdistPath = outDir},closeBundle() {if (!distPath) {console.error('Fail to get size of dist folder.')return}const distSize = getFolderSize(distPath)const formattedSize = formatBytes(distSize)console.log(`Size of dist folder: ${formattedSize} MB`)}}
}export default calculateDistSizePlugin
自己实现的React热更新+SWC打包插件

这个插件利用 SWC 来编译 JavaScript 和 TypeScript 代码,并在 Vite 开发服务器中提供 React JSX热更新。此外,它还处理构建配置,以确保代码被正确地编译为适用于生产环境的格式。

import { Output, ParserConfig, ReactConfig, transform } from '@swc/core'
import { readFileSync, readdirSync } from 'fs'
import { SourceMapPayload } from 'module'
import { dirname, join } from 'path'
import { fileURLToPath } from 'url'
import { BuildOptions, PluginOption, UserConfig, createFilter } from 'vite'
import { createRequire } from 'node:module'
import { ViteOptions } from './type'
import { runtimePublicPath, preambleCode, refreshContentRE } from './const'const _dirname = typeof __dirname !== 'undefined' ? __dirname : dirname(fileURLToPath(import.meta.url))const _resolve = typeof global.require !== 'undefined' ? global.require.resolve : createRequire(import.meta.url).resolveconst plugin = (_options?: ViteOptions): PluginOption[] => {const options = {..._options,target: _options?.target || 'es2017',jsxImportSource: _options?.jsxImportSource || 'react'}// vite配置中的buildTargetconst buildTarget = options.targetconst filter = options.include || options.exclude ? createFilter(options.include, options.exclude) : null// 核心函数:根据配置调用SWC编译代码const transformWithSwc = async (fileName: string, code: string, reactConfig: ReactConfig) => {if ((!filter && fileName.includes('node_modules')) || (filter && !filter(fileName))) returnconst decorators = trueconst parser: ParserConfig | undefined = fileName.endsWith('.tsx')? { syntax: 'typescript', tsx: true, decorators }: fileName.endsWith('.ts')? { syntax: 'typescript', tsx: false, decorators }: fileName.endsWith('.jsx')? { syntax: 'ecmascript', jsx: true }: fileName.endsWith('.mdx')? // JSX is required to trigger fast refresh transformations, even if MDX already transforms it{ syntax: 'ecmascript', jsx: true }: undefinedif (!parser) returnlet result: Outputtry {const swcTransformConfig: any = {// 允许被配置文件覆盖swcrc: true,rootMode: 'upward-optional',filename: fileName,minify: false,jsc: {// target: buildTarget,parser,transform: {useDefineForClassFields: false,react: {...reactConfig,useBuiltins: true}}},env: {targets: {safari: '11',edge: '79',chrome: '73'},mode: 'usage',coreJs: '3.36'}}// 两者不兼容,只能取其一if (swcTransformConfig.env && swcTransformConfig.jsc.target) {delete swcTransformConfig.jsc.target}result = await transform(code, swcTransformConfig)} catch (e: any) {// 输出错误信息const message: string = e.messageconst fileStartIndex = message.indexOf('╭─[')if (fileStartIndex !== -1) {const match = message.slice(fileStartIndex).match(/:(\d+):(\d+)]/)if (match) {e.line = match[1]e.column = match[2]}}throw e}return result}const silenceUseClientWarning = (userConfig: UserConfig): BuildOptions => ({rollupOptions: {onwarn(warning, defaultHandler) {if (warning.code === 'MODULE_LEVEL_DIRECTIVE' && warning.message.includes('use client')) {return}if (userConfig.build?.rollupOptions?.onwarn) {userConfig.build.rollupOptions.onwarn(warning, defaultHandler)} else {defaultHandler(warning)}}}})const resolveSwcHelpersDeps = () => {let helperList: string[] = []try {const file = _resolve('@swc/helpers')if (file) {const dir = dirname(file)const files = readdirSync(dir)helperList = files.map(file => join(dir, file))}} catch (e) {console.error(e)}return helperList}return [// dev时热更新1:加载热更新功能{name: 'vite:swc:resolve-runtime',apply: 'serve',enforce: 'pre', // Run before Vite default resolve to avoid syscallsresolveId: id => (id === runtimePublicPath ? id : undefined),load: id => (id === runtimePublicPath ? readFileSync(join(_dirname, 'refresh-runtime.js'), 'utf-8') : undefined)},// dev时热更新2:热更新核心插件{name: 'vite:swc',apply: 'serve',config: userConfig => {const userOptimizeDepsConfig = userConfig?.optimizeDeps?.disabledconst optimizeDepsDisabled = userOptimizeDepsConfig === true || userOptimizeDepsConfig === 'dev'// 预编译列表const optimizeDeps = !optimizeDepsDisabled? ['react', `${options.jsxImportSource}/jsx-dev-runtime`, ...resolveSwcHelpersDeps()]: undefinedreturn {esbuild: false,optimizeDeps: {include: optimizeDeps,esbuildOptions: {target: buildTarget,supported: {decorators: true // esbuild 0.19在使用target为es2017时,预构建会报错,这里假定目标浏览器支持装饰器,避开报错}}},resolve: {dedupe: ['react', 'react-dom']}}},transformIndexHtml: (_, config) => [{tag: 'script',attrs: { type: 'module' },children: preambleCode.replace('__PATH__', config.server!.config.base + runtimePublicPath.slice(1))}],async transform(code, _id, transformOptions) {const id = _id.split('?')[0]const result = await transformWithSwc(id, code, {refresh: !transformOptions?.ssr,development: true,runtime: 'automatic',importSource: options.jsxImportSource})if (!result) returnif (transformOptions?.ssr || !refreshContentRE.test(result.code)) {return result}result.code = /*js*/ `import * as RefreshRuntime from "${runtimePublicPath}";if (!window.$RefreshReg$) throw new Error("React refresh preamble was not loaded. Something is wrong.");const prevRefreshReg = window.$RefreshReg$;const prevRefreshSig = window.$RefreshSig$;window.$RefreshReg$ = RefreshRuntime.getRefreshReg("${id}");window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;${result.code}window.$RefreshReg$ = prevRefreshReg;window.$RefreshSig$ = prevRefreshSig;RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {RefreshRuntime.registerExportsForReactRefresh("${id}", currentExports);import.meta.hot.accept((nextExports) => {if (!nextExports) return;const invalidateMessage = RefreshRuntime.validateRefreshBoundaryAndEnqueueUpdate(currentExports, nextExports);if (invalidateMessage) import.meta.hot.invalidate(invalidateMessage);});});`if (result.map) {const sourceMap: SourceMapPayload = JSON.parse(result.map)sourceMap.mappings = ';;;;;;;;' + sourceMap.mappingsreturn { code: result.code, map: sourceMap }} else {return { code: result.code }}}},// 打包时候使用的插件{name: 'vite:swc',apply: 'build',enforce: 'post', // Run before esbuildconfig: userConfig => ({build: {...silenceUseClientWarning(userConfig),target: buildTarget},resolve: {dedupe: ['react', 'react-dom']}}),transform: (code, _id) =>transformWithSwc(_id.split('?')[0], code, {runtime: 'automatic',importSource: options.jsxImportSource})}]
}export default plugin
  1. 导入依赖:代码开始部分导入了所需的 Node.js 内置模块和第三方库,以及 Vite 和 SWC 的相关 API。
  2. 定义插件函数plugin 函数接受一个可能包含用户配置的对象 _options,并返回一个 Vite 插件数组。
  3. 配置选项合并:函数内部,用户配置与默认配置合并,以设置插件的行为,例如编译目标 target 和 JSX 导入源 jsxImportSource
  4. 创建过滤器:如果用户提供了 includeexclude 规则,会创建一个过滤器 filter,用于决定哪些文件应该被插件处理。
  5. SWC 转换函数transformWithSwc 异步函数接收文件名、代码和 React 配置,调用 SWC 的 transform API 来编译代码。
  6. 错误处理:在 SWC 转换过程中,如果出现错误,会尝试提取错误消息中的错误行和列,并重新抛出格式化后的错误。
  7. 静默警告silenceUseClientWarning 函数用于抑制 Rollup 的某些警告,特别是与 'use client' 指令相关的警告。
  8. 解析 SWC 辅助依赖resolveSwcHelpersDeps 函数尝试解析 @swc/helpers 包中的辅助函数文件列表。
  9. 定义 Vite 插件对象:返回的插件数组中包括两个主要的插件对象,一个用于开发环境,另一个用于构建环境。
  10. 开发环境插件
    • 设置了 nameapplyconfigtransformIndexHtmltransform 属性来定义插件的行为。
    • 使用 resolveIdload 处理 React 快速刷新的运行时脚本。
    • transform 方法用于对代码进行转换,并添加了 React 快速刷新的相关代码。
  11. 构建环境插件
    • 设置了 nameapplyconfigtransform 属性。
    • 在构建配置中应用了静默警告配置,并指定了编译目标。
  12. React 快速刷新:在开发环境中,插件通过修改 transformIndexHtmltransform 方法来支持 React 快速刷新。
  13. 导出默认:最后,plugin 函数作为默认导出,使其可以在 Vite 配置中使用。

参考文章和地址

https://juejin.cn/post/7103165205483356168?searchId=20240706212202081D45CDF4733CF7923F#heading-17

https://article.juejin.cn/post/7211745375920586813

https://cn.vitejs.dev/

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

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

相关文章

2024菜鸟春招笔试

第一题 解题思路: 签到题,把帖子按好评度降序排列,再将人按升序排列。 第二题 解题思路 从左到右遍历,如果当前元素没有错排,将其与后一个交换,这样两个元素一定都错排。 第三题 、 解题思路 这题当时暴力…

智能运维场景探索 | 运营分析

【本场景来源于 擎创科技《一体化数智运维AIOps解决方案》白皮书,经过重新编写】 该场景主要围绕生产运行、运营决策两个维度进行展开,通过对配置、性能、业务等运行数据的加工计算,形成可量化运营效果、可衡量发展方向的运营数据。整体以低…

陈志泊主编《数据库原理及应用教程第4版微课版》的实验题目参考答案实验2

实验目的 1.掌握在SQL Server中使用对象资源管理器和SQL命令创建数据库与修改数据库的方法。 2.掌握在SQL Server中使用对象资源管理器或者SQL命令创建数据表和修改数据表的方 法(以SQL命令为重点)。 实验设备 操作系统:Win11…

CV03_mAP计算以及COCO评价标准

COCO数据集回顾:CV02_超强数据集:MSCOCO数据集的简单介绍-CSDN博客 1.1 简介 在目标检测领域中,mAP(mean Average Precision,平均精度均值)是一个广泛使用的性能评估指标,用于衡量目标检测模型…

MongoDB集群搭建-最简单

目录 前言 一、分片概念 二、搭建集群的步骤 总结 前言 MongoDB分片(Sharding)是一种水平扩展数据库的方法,它允许将数据分散存储在多个服务器上,从而提高数据库的存储容量和处理能力。分片是MongoDB为了应对大数据量和高吞吐量需…

创新引领未来,智慧水利在路上:数字孪生技术为水库管理开辟新机遇,带来新挑战,引领水利行业迈向智能化新纪元

目录 前言 一、数字孪生技术概述 二、新机遇:数字孪生技术如何重塑水库管理 1、精准预测,科学调度 2、智能监测,及时预警 3、优化资源配置,提升管理效率 4、促进公众参与,增强透明度 三、新挑战:数字…

Fill - UVA 10603

网址如下&#xff1a; Fill - UVA 10603 - Virtual Judge (vjudge.net) 感觉有点浮躁&#xff0c;没法完全将思绪投入题的思考中 脑袋糊糊的 一道bfs题 代码如下&#xff1a; #include<queue> #include<cstdio> #include<cstring> #include<vector&g…

奇迹MU 骷髅战士在哪

BOSS分布图介绍 我为大家带来各地区怪物分布图。在游戏前期&#xff0c;很多玩家可能会不知道该去哪里寻找怪物&#xff0c;也不知道哪些怪物值得打。如果选择了太强的怪物&#xff0c;弱小的玩家可能会无法抵御攻击。如果选择了低等级的boss&#xff0c;收益可能并不理想。所…

吴恩达机器学习 第三课 week3 强化学习(月球着陆器自动着陆)

目录 01 学习目标 02 概念 2.1 强化学习 2.2 深度Q学习&#xff08;Deep Q-Learning &#xff09; 03 问题描述 04 算法中的概念及原理 05 月球着陆器自动着陆的算法实现 06 拓展&#xff1a;基于pytorch实现月球着陆器着陆 07 总结 写在最前&#xff1a;关于强化学习…

【MindSpore学习打卡】应用实践-自然语言处理-基于RNN的情感分类:使用MindSpore实现IMDB影评分类

情感分类是自然语言处理&#xff08;NLP&#xff09;中的一个经典任务&#xff0c;广泛应用于社交媒体分析、市场调研和客户反馈等领域。本篇博客将带领大家使用MindSpore框架&#xff0c;基于RNN&#xff08;循环神经网络&#xff09;实现一个情感分类模型。我们将详细介绍数据…

UE5 07-给物体添加一个拖尾粒子

添加一个(旧版粒子系统)cascade粒子系统组件 ,在模板中选择一个开发学习初始包里的粒子

智慧文旅(景区)解决方案PPT(42页)

智慧文旅解决方案摘要 行业分析中国旅游业正经历消费大众化、需求品质化、发展全域化和产业现代化的发展趋势。《“十三五”旅游业发展规划》的发布&#xff0c;以及文化和旅游部的设立&#xff0c;标志着旅游业的信息化和智能化建设成为国家战略。2018年推出的旅游行业安全防范…

cs224n作业4

NMT结构图&#xff1a;&#xff08;具体结构图&#xff09; LSTM基础知识 nmt_model.py&#xff1a; 参考文章&#xff1a;LSTM输出结构描述 #!/usr/bin/env python3 # -*- coding: utf-8 -*-""" CS224N 2020-21: Homework 4 nmt_model.py: NMT Model Penchen…

2024年导游资格证题库备考题库,高效备考!

1.台湾著名的太鲁阁公园的特色是&#xff08;&#xff09;。 A.丘陵和溶洞 B.森林和瀑布 C.峡谷和断崖 D.彩林和彩池 答案&#xff1a;C 解析&#xff1a;台湾著名的太鲁阁公园的特色是峡谷和断崖。 2.下列位于台湾的景区中&#xff0c;素有"神秘的森林王国"之…

51单片机STC89C52RC——15.1 AD/DA(模数数模)

目的/效果 1 LCD1602 显示 可调电阻、光敏电阻、热敏电阻值&#xff08;AD&#xff09; 2 模拟信号控制LED明暗&#xff08;DA&#xff09; 一&#xff0c;STC单片机模块 二&#xff0c;AD/DA 2.1 AD/DA 介绍 AD&#xff08;Analog to Digital&#xff09;&#xff1a;模拟…

金丝键合强度测试仪试验条件要求:键合拉脱/引线拉力/剪切力等

金丝键合强度测试仪是测量引线键合强度&#xff0c;评估键合强度分布或测定键合强度是否符合有关的订购文件的要求。键合强度试验机可应用于采用低温焊、热压焊、超声焊或有关技术键合的、具有内引线的器件封装内部的引线-芯片键合、引线-基板键合或内引线一封装引线键合&#…

Redis的zset的zrem命令可以做到O(1)吗?

事情是这样的&#xff0c;当我用zrem命令去移除value的时候&#xff0c;我知道他之前会做的几个步骤 1、查找这个value对应的score&#xff08;通过zset中的dict&#xff09;2、根据这个score查找到跳表中的节点3、删除这个节点 我就想了一下为什么dict为什么要保存score呢&a…

非堆成加密是公私钥使用

对称加密学习-CSDN博客 加密算法学习-CSDN博客 非对称加密算法使用一对密钥&#xff0c;包括一个公钥和一个私钥&#xff0c;它们是数学上相关联的&#xff0c;但公钥可以公开分享&#xff0c;而私钥必须保密。以下是使用非对称加密算法的一般步骤&#xff1a; 密钥生成&…

2024年江苏省研究生数学建模竞赛B题火箭烟幕弹运用策略优化论文和代码分析

经过不懈的努力&#xff0c; 2024年江苏省研究生数学建模竞赛B题火箭烟幕弹运用策略优化论文和代码已完成&#xff0c;代码为B题全部问题的代码&#xff0c;论文包括摘要、问题重述、问题分析、模型假设、符号说明、模型的建立和求解&#xff08;问题1模型的建立和求解、问题2模…

基于Java中的SSM框架实现计算机类考研院校推荐系统项目【项目源码+论文说明】

基于Java中的SSM框架实现计算机类考研院校推荐系统演示 摘要 在互联网时代人们获取信息的方式变得非常快捷&#xff0c;登录网站搜索就能快速查找到相关的信息&#xff0c;但是网络上面的信息数量非常庞大&#xff0c;有很多信息虽然和自己搜索的相关&#xff0c;但并不是自己…