首页/文章/vite工具

20分钟掌握 Vite 插件开发

2023-11-29
362513 分钟
...

以往,面试官:“你自己开发过 Webpack 的插件吗?”

现在,面试官:“你自己开发过 Vite 的插件吗?”

如果你还在纠结 Webpack 的插件该开发姿势的话,点我这篇文章# 手把手带你开发webpack5.x - plugin(保姆教程)。 写 Vue 的朋友肯定最爱 Vite,当然写 React 的朋友肯定也爱,反正我爱,毕竟谁不希望自己快一点呢>_<

关于 Vite 你肯定需要知道的两点就是, 1. Vite 的原理是什么,为什么它这么快? 2. Vite 的插件是如何开发出来的,原理下一篇来唠,本篇文章先聊后者, Vite 插件的开发,手摸手,带你又快又稳的get它。

Vite 作为一个单纯的构建工具,根 Webpack 一样,其提供了很多 生命周期 钩子,这些钩子主要是在 Vite 从开始构建,到结束构建的过程中 提供给程序员使用的。旨在让你可以在构建中途的某个时间点,定制化一些功能

Vite 是基于 Rollup 这个构建工具来封装的,所以 Vite 中的一部分钩子其实就是Rollup 中的钩子(不知道Rollup的朋友不要有心理负担,一样能看的明白)

Vite 的钩子主要分为两类:

  1. 通用钩子(也就是沿用了Rollup的钩子)
  2. 独有的钩子

!!!了解这些钩子函数的执行时间节点非常重要,强烈建议你跟着文章按顺序过一遍再看后面的代码实操

在介绍这些钩子函数执行效果之前,我们先用一张图先大体介绍一下 Vite 的基本工作流程

知道 Vite 是靠 Http 服务不断向浏览器发送整个项目需要的各个文件资源后,再来看它的钩子们

通用钩子:

  • options 这是构建阶段的第一个钩子,通常用于插件开发中的参数阅读选项

myPlugin({
  name: '蜗牛',
  age: 18
})
  • buildStart 这是构建阶段的第二个钩子,读取到入口文件后开始构建。

buildStart 钩子函数的主要作用包括但不限于以下几点:

  1. 自定义任务:你可以在构建开始前执行自定义任务,例如清理临时文件、生成一些构建配置、执行前置操作等。
  2. 日志记录:你可以在构建开始前添加一些日志记录,以记录构建过程的开始时间、项目信息等,以便后续分析和调试。
  3. 状态检查:在构建开始前,你可以执行一些状态检查,确保构建所需的条件满足,如果有问题,可以提前终止构建并给出错误提示。
  4. 设置环境变量:你可以在构建开始前设置一些环境变量,以影响构建过程中的行为,例如根据不同的环境配置不同的构建选项。
  • resolveId 主要用于自定义模块解析的行为。模块解析是指当你在代码中导入模块时,Vite 需要确定模块的位置和如何加载它。resolveId 钩子函数允许你在模块解析过程中介入,以满足特定的项目需求。
// 举个例子
import { createApp } from 'Vue'

// 当Vite执行到需要解析这种模块加载的代码时,就会触发resolveId钩子

1.  **自定义模块解析规则**你可以使用 `resolveId` 钩子函数来添加自定义的模块解析规则例如你可以为特定的文件扩展名或文件夹路径设置自定义解析逻辑
1.  **模块别名**通过 `resolveId` 钩子函数你可以实现模块别名功能将某个模块的导入路径重定向到另一个路径以简化模块导入
1.  **动态加载模块**你可以在 `resolveId` 中执行异步操作例如从远程服务器加载模块或根据环境条件选择不同的模块实现
1.  **解析外部依赖项**如果你的项目依赖于不同的包管理器例如 npmYarnpnpm),你可以使用 `resolveId` 钩子来处理这些不同包管理器的依赖项解析差异
1.  **增强性能**通过自定义模块解析逻辑你可以优化模块的加载方式以提高项目的性能例如你可以将某些模块预构建以减少加载时间
  • load 执行时间点:在模块加载时。

使用场景:用于自定义模块加载逻辑,例如加载动态数据或从外部源加载模块

  • transform 执行时间点:在模块代码构建期间。

使用场景:用于修改模块的源代码,可以在构建期间对模块进行转换和处理,例如添加额外的代码、转换特定格式的文件等。

比如:Vite 在加载到 Vue 项目中的 main.js 后我们可以在 transform 钩子中对 main.js的代码做一些修改

  • buildEnd 作用:buildEnd 钩子函数在 Vite 构建结束后触发。

使用场景:你可以使用 buildEnd 钩子来执行一些与构建结束相关的操作,例如生成构建报告、自动化部署、通知团队构建已完成等。这个钩子通常用于处理构建后的事务。

  • closeBundle 作用:closeBundle 钩子函数在 Vite 打包生成 bundle 文件时触发。

使用场景:你可以使用 closeBundle 钩子来执行一些与打包后的 bundle 文件相关的操作,例如自动化地上传 bundle 文件到 CDN、生成版本号、进行代码压缩或加密等。这个钩子通常用于处理 bundle 文件的后续处理。

以上 7 个 钩子是 Vite 中的通用钩子

独有的钩子:

  • config: 允许你在 Vite 配置对象被创建之前对其进行修改和扩展。这个钩子函数在 Vite 配置加载过程中的早期阶段被触发,允许你动态地修改 Vite 的配置,以满足项目的特定需求。

场景举例:

自定义配置:你可以在 config 钩子中添加、修改或删除 Vite 配置的属性和选项,以适应项目的需求。例如,你可以修改构建输出目录、设置自定义别名、更改开发服务器的选项等。

  • configResolved: 用于在 Vite 配置对象被解析和应用后执行自定义操作。这个钩子函数在配置加载过程的较早阶段触发,允许你检查和修改已解析的 Vite 配置。

场景举例:

配置检查与修改:你可以在 configResolved 钩子函数中检查和修改 Vite 配置。这通常用于在配置加载后动态地调整配置选项,以适应不同的项目需求。

  • configureServer: 用于配置开发服务器。这个钩子函数在 Vite 开发服务器启动之前执行,允许你自定义开发服务器的行为。

场景举例:

添加中间件:你可以在 configureServer 中添加自定义中间件到开发服务器中。这使得你可以处理请求、修改响应、添加身份验证等。

  • configurePreviewServer: 与 configureServer 相同,但用于预览服务器。

  • transformIndexHtml: 允许你在构建过程中修改生成的 HTML 文件。这个钩子函数在生成最终的 index.html 文件之前执行,允许你自定义 HTML 内容或添加额外的标签、脚本等。

  • handleHotUpdate: 用于在模块发生热更新(Hot Module Replacement,HMR)时执行自定义逻辑。HMR 是一种开发工具,允许你在不刷新整个页面的情况下替换、添加或删除模块,以加快开发过程。

场景举例: 动态加载模块:你可以在热更新时动态加载新的模块,以实现按需加载或懒加载的效果。

以上 6 个 就是 Vite 中独有的钩子函数,Vite 总共提供了 13 个 钩子函数给开发人员,我们可以在打造插件的之前考虑清楚你的插件的功效,从而决定用哪个时间节点的钩子来完成

先制定一个需求,偶然,我看到了掘金官方网站的控制台

这个打印有点意思哦,那我们就以这个打印效果为目标,实现一个 Vite 插件,在项目运行起来后我们也能在控制台看到一些奇思妙语

声明一下:控制台的打印明明只要在项目中直接 console.log('xxx') 就可以了,我为啥非要写一个插件来实现?我们是为了学习 Vite 的插件的开发才刻意这么干的,不是吃饱了撑得!

思路: 在项目被 Vite 构建的过程中,当 Vite 读取到项目全局的 js 文件时,我们给它写入一个有趣的 console.log(),就这么简单

所以如果我们在问,你想用哪个钩子函数来实现效果,你有答案了吗?

接下来的步骤就变得简单了起来

  1. 用 Vite 创建一个 Vue3 的项目
  2. 在项目的根目录创建一个 plugins 文件夹,用来写插件
  3. 再在其中创建 randomLetterPlugin.js 文件

这里我们干了什么:

  • 抛出一个函数 randomLetterPlugin (这是 Vite 的语法,插件必须是一个函数)
  • 里面 return 一个对象 (也是 Vite 固定语法)
  • 对象中,设置 name 属性 (也是 Vite 固定语法)
  • 紧接着使用了一个钩子 configureServer (查询第一点我们对所有钩子的介绍,该钩子函数执行时间为 Vite 启动 HTTP 服务时)

这么干能实现浏览器控制台的打印吗?

当然不行

我只是在这里给大家测试一下效果,既然是在启动 HTTP 服务时触发的,那就意味着,我能在终端执行 yarn dev 的时候看到这个函数的执行结果

但是现在依然测试不了,我们要让这个插件生效

  1. 在 vite.config.js 中调用我们的插件

此时再运行项目测试一下:

没问题!果然不出所料

那么要实现浏览器控制台的打印,按照我们的思路,我得读取到 main.js 的代码,再偷偷修改这份代码,所以我们要用到的 钩子是 transform

增加部分:

  • 参数 code 是被构建出来之后的完整代码
  • 找到 main.js 文件,在其中拼接如上的代码

注意:

  1. 这部分代码并不会真正的直接出现在开发中的 main.js 中,只是在编译后被加入,我们看本地的main.js是没有变化的
  2. 相同的 printRandomLetter 函数再次写一遍是因为写在 插件文件中的代码是不能直接作用于项目逻辑中的,只在编译过程生效

这么简单??

没错

看效果

每次刷新项目,默认会在控制台打印一句随机的话术,这个插件真的很简单呀!

控制台中内容的样式怎么写??? 检查掘金的源码,你会看到

我们翻译一下这份代码:

  1. %c 是一个控制台输出的格式占位符,它表示后面的文本应用特定的样式。
  2. function fixHydrogen() { ... }这个函数包含了一些条件判断和DOM操作,用于修改网页的样式。

fixHydrogen 函数我们用不上,所以直接 %c 就够啦,学到了学到了...

那就只需要修改这一行:

最后的效果:

完美!

最后你已经发现了,熟悉 Vite 的钩子函数是开发插件的基础,当然我们不需要去背这些钩子,学习应该是,掌握一个知识点的结构和边界,绝不是死记硬背,不记得的细枝末节,搜索引擎会帮助你

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
vite工具2024-09-03
为什么vite能够让浏览器识别.vue后缀文件
本文将从 的源码带着大家一起分析我们在使用vite开发Vue项目的时候vite所处理的一些流程,本文不会讲解API的使用,因为本文的难度偏难,如果仅仅是想学习如何配置vite,本文可能不太适合您。 在阅读本文之前,请确保你对vite的基本原理有一定的了解(不一定是了解源码,但是至少要对vite的整体运行过程有一个较为清楚的认识)。众所周知,在 的的生态中,我们通过Vue提供的Loader,分别对Vue单文件组件的template、scr...
学习面试
nuxt2025-10-22
Nuxt SSR 与 Next.js SSR
🧭 引言 在现代前端生态中, Nuxt(Vue) 与 Next.js(React) 是最成熟的两大全栈框架。 它们都具备 SSR(Server Side Rendering) 能力,但实现原理与运行机制差异巨大。 如果你在使用 Nuxt 构建 SSR 项目,却发现“路由跳转后不再 SSR”, 而在 Next.js 中同样操作却会重新发起服务端渲染——这不是 bug,而是 理念差异 。 本文将详细对比这两种框架的 SSR 行为,帮助你彻...
学习面试
vue2025-10-22
shallowRef 与 ref 的区别、场景与坑位
结论先行: 会 深度 地把你放进去的对象转成响应式(递归代理),因此"对象内部的改动"也会触发视图更新。 只在 顶层 做依赖追踪: 更换 才触发更新 ;若你只是"在原对象上改属性",不会触发,除非手动 。 1\. 快速对比 维度 追踪粒度 深度(递归) 仅顶层 对象内部属性变更 会触发更新 不会 触发(除非 或重新赋值) 性能 有递归 & 依赖跟踪开销 更轻量,适合大对象/第三方实例 适用对象 普通标量、普通对象、数组 第三方实例(图表...
学习面试
vue2025-10-20
Vue 3 中的 setup执行时机与异步逻辑详解
Vue 3 中的 执行时机与异步逻辑详解 本文详细介绍 Vue 3 组合式 API 的核心函数 :它在组件生命周期中的执行时机、异步行为( 的影响)、常见用法和最佳实践。 一、 的执行时机 是 Vue 3 组件实例创建后、渲染前执行的函数。 它相当于 Vue 2 中的 和 的合体。 执行流程: 也就是说: 所有响应式数据( 、 、 )都在这里定义; 所有生命周期钩子( 、 等)都在这里注册; 模板渲染会等待 执行完成(或异步返回的 Pr...
学习面试
八股文2025-10-13
cookie跨域介绍
Cookie 跨域问题详解 在 Web 开发中, Cookie 是最常见的客户端存储机制之一,用于记录用户登录状态、偏好设置、会话信息等。然而,当涉及到 跨域请求 时,Cookie 的行为往往变得复杂,尤其在现代浏览器的安全策略下,跨域 Cookie 的传递和写入都有严格的限制。 本文将从基础概念开始,逐步讲解 Cookie 的跨域机制、SameSite 属性、CORS 配置及常见问题解决方案。 一、Cookie 的基础概念 Cooki...
学习面试
八股文2025-10-09
JavaScript 闭包详解
JavaScript 闭包(Closure)详解 一、什么是闭包 闭包(Closure) 是 JavaScript 中一个非常核心且常被问到的概念。简单来说: 闭包是一个函数,它可以“记住”并访问其定义时所在的词法作用域,即使这个函数在其作用域之外被调用。 换句话说,当一个函数“嵌套”在另一个函数中,并且 内部函数引用了外部函数的变量 时,就形成了闭包。 示例: 虽然 已经执行完毕,但 依然可以访问 中的 。这就是闭包。 二、闭包的形成...
学习面试

评论

请登录后发表评论

去登录
加载评论中...

目录