前言

大家好,我是易师傅,在现如今 vite 工具快开始盛行之下,我们是不是可以去做一件有意义的事呢,比如写一个 vite 插件,你觉得怎么样?

刚好我们可以趁 vite 插件 生态还未很成熟阶段,做一个让自己顺心,让领导赏心,让社区开心的插件,与之携手共进。

如果大家对 vite 感兴趣可以去看看专栏: 《Vite 从入门到精通》:juejin.cn/column/7074954144817086472[1]

通过本文你可以学到

  • 如何创建一个 vite 插件模板

  • vite 插件的 各个钩子作用

  • vite 插件的 钩子执行顺序

  • 如何写一个自己的插件

了解 vite 插件

1. 什么是 vite 插件

vite 其实就是一个由原生 ES Module 驱动的新型 Web 开发前端构建工具。

vite 插件 就可以很好的扩展 vite 自身不能做到的事情,比如 文件图片的压缩、 对 commonjs 的支持、 打包进度条 等等。

2. 为什么要写 vite 插件

相信在座的每位同学,到现在对 webpack 的相关配置以及常用插件都了如指掌了吧;

vite 作为一个新型的前端构建工具,它还很年轻,也有很多扩展性,那么为什么我们不趁现在与它一起携手前进呢?做一些于你于我于大家更有意义的事呢?

快速体验

要想写一个插件,那必须从创建一个项目开始,下面的 vite 插件通用模板 大家以后写插件可以直接clone使用;

插件通用模板 github:体验入口:github.com/jeddygong/vite-templates/tree/master/vite-plugin-template[2]

插件 github:体验入口:github.com/jeddygong/vite-plugin-progress[3]

建议包管理器使用优先级:pnpm > yarn > npm > cnpm

长话短说,直接开干 ~

创建 vite 插件通用模板

1. 初始化

1.1 创建一个文件夹并且初始化:初始化按照提示操作即可

mkdir vite-plugin-progress && cd vite-plugin-progress && pnpm init
复制代码

1.2 安装 typescript

pnpm i typescript @types/node -D
复制代码

1.3 配置 tsconfig.json

{"compilerOptions": {"module": "ESNext","target": "esnext","moduleResolution": "node","strict": true,"declaration": true,"noUnusedLocals": true,"esModuleInterop": true,"outDir": "dist","lib": ["ESNext"],"sourceMap": false,"noEmitOnError": true,"noImplicitAny": false},"include": ["src/*","*.d.ts"],"exclude": ["node_modules","examples","dist"]
}
复制代码

1.4 安装 vite

// 进入 package.json
{..."devDependencies": {"vite": "*"}...
}
复制代码

2. 配置 eslint 和 prettier(可选)

  1. 安装 eslint

pnpm i eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev
  1. 配置 .eslintrc:配置连接[4]

  2. 安装 prettier (可选)

pnpm i prettier eslint-config-prettier eslint-plugin-prettier --save-dev
  1. 配置 .prettierrc :配置连接[5]

3. 新增 src/index.ts 入口

import type { PluginOption } from 'vite';export default function vitePluginTemplate(): PluginOption {return {// 插件名称name: 'vite-plugin-template',// pre 会较于 post 先执行enforce: 'pre', // post// 指明它们仅在 'build' 或 'serve' 模式时调用apply: 'build', // apply 亦可以是一个函数config(config, { command }) {console.log('这里是config钩子');},configResolved(resolvedConfig) {console.log('这里是configResolved钩子');},configureServer(server) {console.log('这里是configureServer钩子');},transformIndexHtml(html) {console.log('这里是transformIndexHtml钩子');},}
}
复制代码

其中的 vite 插件函数钩子会在下面详细详解 ~

到这里,那么我们的基本模版就建好了,但是我们现在思考一下,我们应该怎么去运行这个插件呢?

那么我们就需要创建一些 examples 例子来运行这个代码了;

4. 创建 examples 目录

我这里创建了三套项目 demo,大家直接 copy 就行了,这里就不详细介绍了

  1. vite-react:github.com/jeddygong/vite-templates/tree/master/vite-plugin-template/examples/vite-react[6]

  2. vite-vue2:github.com/jeddygong/vite-templates/tree/master/vite-plugin-template/examples/vite-vue2[7]

  3. vite-vue3:github.com/jeddygong/vite-templates/tree/master/vite-plugin-template/examples/vite-vue3[8]

如果你的插件需要多跑一些 demo,自行创建项目即可;

那么下面我们就需要配置 examples 下的项目与当前根目录的插件做一个联调了(下面以 examples/vite-vue3 为例)。

5. 配置 examples/vite-vue3 项目

  1. 修改 examples/vite-vue3/package.json

{..."devDependencies": {..."vite": "link:../../node_modules/vite","vite-plugin-template": "link:../../"}
}

上面意思就是说:

  • 要把 examples/vite-vue3 项目中的 vite 版本与根目录 vite-plugin-template 的版本一致;

  • 同时要把 examples/vite-vue3 项目中的 vite-plugin-template 指向你当前根目录所开发的插件;

  1. 引入插件: examples/vite-vue3/vite.config.ts

import template from 'vite-plugin-template';export default defineConfig({...plugins: [vue(), template()],...
});
  1. 安装: cd examples/vite-vue3 && pnpm install

cd examples/vite-vue3 && pnpm install

注意: examples/vite-vue2 和 examples/vite-react 的配置与这一致

思考:

到这里,我们再思考一下,我们把 examples/vite-vue3 中的项目配置好了,但是我们应该怎么去运行呢?

直接去 examples/vite-vue3 目录下运行 pnpm run build 或者 pnpm run dev ?

这样显然是不能运行成功的,因为我们的根目录下的 src/index.ts 是没法直接运行的,所以我们需要把 .ts 文件转义成 .js 文件;

那么我们怎么处理呢?

那么我们不得不去试着用用一个轻小且无需配置的工具 tsup 了。

6. 安装 tsup 配置运行命令

tsup 是一个轻小且无需配置的,由 esbuild 支持的构建工具;

同时它可以直接把 .ts、.tsx 转成不同格式 esm、cjs、iife 的工具;

  1. 安装 tsup

pnpm i tsup -D
  1. 在根目录下的 package.json 中配置

{..."scripts": {"dev": "pnpm run build -- --watch --ignore-watch examples","build": "tsup src/index.ts --dts --format cjs,esm","example:react": "cd examples/vite-react && pnpm run build","example:vue2": "cd examples/vite-vue2 && pnpm run build","example:vue3": "cd examples/vite-vue3 && pnpm run build"},...
}

7. 开发环境运行

  1. 开发环境运行:实时监听文件修改后重新打包(热更新)

pnpm run dev
  1. 运行 examples 中的任意一个项目(以 vite-vue3 为例)

pnpm run example:vue3

注意:

如果你的插件只会在 build 时运行,那就设置 "example:vue3": "cd examples/vite-vue3 && pnpm run build" ;

反之就运行 pnpm run dev

  1. 输出:

Untitled.png

到这里你就可以 边开发边运行 了,尤雨溪看了都说爽歪歪 ~

8. 发布

  1. 安装 `bumpp` 添加版本控制与 tag
pnpm i bumpp -D
复制代码
  1. 配置 `package.json`
{..."scripts": {..."prepublishOnly": "pnpm run build","release": "npx bumpp --push --tag --commit && pnpm publish",},...
}
复制代码
  1. 开发完插件后运行发布
# 第一步
pnpm run prepublishOnly# 第二步
pnpm run release
复制代码

那么到这里,我们的 vite 插件模板 就已经写好了,大家可以直接克隆 vite-plugin-template 模板[9] 使用;

如果你对 vite 的插件钩子 和 实现一个真正的 vite 插件 感兴趣可以继续往下面看;

vite 的插件钩子 hooks 们

1. vite 独有的钩子

  1. enforce :值可以是pre 或 post , pre 会较于 post 先执行;

  2. apply :值可以是 build 或 serve 亦可以是一个函数,指明它们仅在 build 或 serve 模式时调用;

  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():在构建阶段结束后被调用,此处构建结束只是代表所有模块转义完成;

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

5. 插件的执行顺序

  1. 别名处理Alias

  2. 用户插件设置enforce: 'pre'

  3. vite 核心插件

  4. 用户插件未设置enforce

  5. vite 构建插件

  6. 用户插件设置enforce: 'post'

  7. vite 构建后置插件(minify, manifest, reporting)

手撸一个 vite 插件

下面以 vite 打包进度条 插件为例;

demo.gif

插件地址:github[10] 如果您觉得不错欢迎 star ⭐️

该插件已被 vite 官方收集至官方文档:链接地址[11]

因为文章的重点不在于这个插件的详细实现过程,所以本文只会贴上源代码供大家参考,详细介绍会在下一篇文章中讲解,请大家拭目以待吧!

  1. `inde.ts`
import type { PluginOption } from 'vite';
import colors from 'picocolors';
import progress from 'progress';
import rd from 'rd';
import { isExists, getCacheData, setCacheData } from './cache';type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type Merge<M, N> = Omit<M, Extract<keyof M, keyof N>> & N;type PluginOptions = Merge<ProgressBar.ProgressBarOptions,{/*** total number of ticks to complete* @default 100*/total?: number;/*** The format of the progress bar*/format?: string;}
>;export default function viteProgressBar(options?: PluginOptions): PluginOption {const { cacheTransformCount, cacheChunkCount } = getCacheData()let bar: progress;const stream = options?.stream || process.stderr;let outDir: string;let transformCount = 0let chunkCount = 0let transformed = 0let fileCount = 0let lastPercent = 0let percent = 0return {name: 'vite-plugin-progress',enforce: 'pre',apply: 'build',config(config, { command }) {if (command === 'build') {config.logLevel = 'silent';outDir = config.build?.outDir || 'dist';options = {width: 40,complete: '\u2588',incomplete: '\u2591',...options};options.total = options?.total || 100;const transforming = isExists ? `${colors.magenta('Transforms:')} :transformCur/:transformTotal | ` : ''const chunks = isExists ? `${colors.magenta('Chunks:')} :chunkCur/:chunkTotal | ` : ''const barText = `${colors.cyan(`[:bar]`)}`const barFormat =options.format ||`${colors.green('Bouilding')} ${barText} :percent | ${transforming}${chunks}Time: :elapseds`delete options.format;bar = new progress(barFormat, options as ProgressBar.ProgressBarOptions);// not cache: Loop files in src directoryif (!isExists) {const readDir = rd.readSync('src');const reg = /\.(vue|ts|js|jsx|tsx|css|scss||sass|styl|less)$/gi;readDir.forEach((item) => reg.test(item) && fileCount++);}}},transform(code, id) {transformCount++// not cacheif(!isExists) {const reg = /node_modules/gi;if (!reg.test(id) && percent < 0.25) {transformed++percent = +(transformed / (fileCount * 2)).toFixed(2)percent < 0.8 && (lastPercent = percent)}if (percent >= 0.25 && lastPercent <= 0.65) {lastPercent = +(lastPercent + 0.001).toFixed(4)} }// go cacheif (isExists) runCachedData()bar.update(lastPercent, {transformTotal: cacheTransformCount,transformCur: transformCount,chunkTotal: cacheChunkCount,chunkCur: 0,})return {code,map: null};},renderChunk() {chunkCount++if (lastPercent <= 0.95) isExists ? runCachedData() : (lastPercent = +(lastPercent + 0.005).toFixed(4))bar.update(lastPercent, {transformTotal: cacheTransformCount,transformCur: transformCount,chunkTotal: cacheChunkCount,chunkCur: chunkCount,})return null},closeBundle() {// close progressbar.update(1)bar.terminate()// set cache datasetCacheData({cacheTransformCount: transformCount,cacheChunkCount: chunkCount,})// out successful messagestream.write(`${colors.cyan(colors.bold(`Build successful. Please see ${outDir} directory`))}`);stream.write('\n');stream.write('\n');}};/*** run cache data of progress*/function runCachedData() {if (transformCount === 1) {stream.write('\n');bar.tick({transformTotal: cacheTransformCount,transformCur: transformCount,chunkTotal: cacheChunkCount,chunkCur: 0,})}transformed++percent = lastPercent = +(transformed / (cacheTransformCount + cacheChunkCount)).toFixed(2)}
}复制代码
  1. `cache.ts`
import fs from 'fs';
import path from 'path';const dirPath = path.join(process.cwd(), 'node_modules', '.progress');
const filePath = path.join(dirPath, 'index.json');export interface ICacheData {/*** Transform all count*/cacheTransformCount: number;/*** chunk all count*/cacheChunkCount: number
}/*** It has been cached* @return boolean*/
export const isExists = fs.existsSync(filePath) || false;/*** Get cached data* @returns ICacheData*/
export const getCacheData = (): ICacheData => {if (!isExists) return {cacheTransformCount: 0,cacheChunkCount: 0};return JSON.parse(fs.readFileSync(filePath, 'utf8'));
};/*** Set the data to be cached* @returns */
export const setCacheData = (data: ICacheData) => {!isExists && fs.mkdirSync(dirPath);fs.writeFileSync(filePath, JSON.stringify(data));
};复制代码

最后

该系列会是一个持续更新系列,关于整个《Vite 从入门到精通》专栏[12],我主要会从如下图几个方面讲解,请大家拭目以待吧!!!

Untitled.png

宝贝们,都看到这里了,要不点个赞呗

【Vite】1371- 手把手开发 Vite 插件相关推荐

  1. json返回页面读取data里的值都是object_【一】尤大神都说Vite香,让我来手把手分析Vite原理...

    戳蓝字"前端优选"关注我们哦! 一.什么是Vite? 法语Vite(轻量,轻快)vite 是一个基于 Vue3单文件组件的非打包开发服务器,它做到了本地快速开发启动.实现按需编译. ...

  2. Vite + React 组件开发实践

    简介: 毫不夸张的说,Vite 给前端带来的绝对是一次革命性的变化.或者也可以说是 Vite 背后整合的 esbuild . Browser es modules.HMR.Pre-Bundling 等 ...

  3. 探究vite——新一代前端开发与构建工具(一)

    Vite (法语意为 "快速的",发音 /vit/) 是一种新型前端构建工具,能够显著提升前端开发体验.它主要由两部分组成: 一个开发服务器,它基于 原生 ES 模块 提供了 丰富 ...

  4. 在 Vite项目中,使用插件 @rollup/plugin-inject 注入全局 jQuery

    在 Vite项目中,使用插件 @rollup/plugin-inject 注入全局 jQuery 环境 写在前面 方法一.全局静态引入 方法二.使用插件 @rollup/plugin-inject 注 ...

  5. phpcmsV9视频模块插件 - 手把手开发教程

    phpcmsV9官方的视频模块,一点也不完整不系统. 官方给出的文档说明.文件下载.等等都太缺了. 这里给出个人开发过程后,总结出的经验精华,请及时汲取. 1. 准备工作 安装完整地 wampserv ...

  6. 来自一枚rookie的项目开发——vite下的vue3+ts的项目打包及部署

    Vite下Vue3.0+Typescript项目打包 正常的打包操作 npm run build // package.json ..."scripts": {"dev& ...

  7. 用C#来开发CAD插件,含源代

    CAD插件看起来很神秘,其实一个合格码农经过几天就能快速掌握.没什么秘密,开发CAD插件和winform一样简单,多学几个类库用法就是,在CAD里展现界面和winform略有不同.学习CAD插件开发的 ...

  8. QIIME 2教程. 26为QIIME 2开发新插件DevelopingPlugin(2021.2)

    为QIIME 2开发新插件 Developing a QIIME 2 plugin https://docs.qiime2.org/2021.2/plugins/developing/ 注意:本文档还 ...

  9. QIIME 2教程. 25可用和开发中插件AvailableFuturePlugins(2021.2)

    可用插件 Available plugins https://docs.qiime2.org/2021.2/plugins/available/ 用户可以通过插件使用QIIME 2微生物组分析功能.Q ...

最新文章

  1. 广搜--(搜索的第一道题)图像有用区域
  2. MySQL 5.7 深度解析: 半同步复制技术
  3. TensorFlow2-迁移学习
  4. api idea 开发rest_部分介绍使用IDEA的rest client
  5. 语言 高速公路超速处罚_880关注 拆除!高速公路不合理限速标志!
  6. 《构建之法》读后感二
  7. jira导出HTML,JIRA Software 7.4.x 版本说明
  8. Mysql和vs2010 的连接
  9. halcon学习之阈值分割(threshold、binary_threshold、dyn_threshold、var_threshold、auto_threshold、fast_threshold、)
  10. linux 锐捷 dns,锐捷Linux
  11. c语言乐谱提取软件,SmartScore X2 Pro(乐谱扫描识别软件) V10.5.4 官方版
  12. javaScript导出excel表格,数据量过大导出失败问题
  13. C语言_写简易吃豆人小游戏
  14. [算法]详解关键路径算法
  15. 工业相机镜头的参数与选型
  16. 如何将数据导入python
  17. 一般将来时语法课教案_英语时态
  18. 微信群抽奖,有什么好用的抽奖小程序?
  19. arm linux源更新,[Linux] - Manjaro ARM 系统配置(更新镜像源,安装 Docker 和 Dotnet Core)...
  20. 查询数据库中所有表的记录数

热门文章

  1. Linux网络服务配置与管理-WEB服务器配置与管理
  2. 利用AT89C52定时器输出可调PWM
  3. html如何设置超连接的颜色,HTML颜色、超链接设置
  4. android 使用FileProvider 兼容apk 在7.0版本无法安装
  5. Android开发短信拦截与读取,消息通知Notification
  6. 大数据(Big data)
  7. Android M Developer Preview - API Preview(一)
  8. 十、MYSQL数据库的条件查询
  9. X-Analyser 总线分析软件:CANopen、1939解析、UDS诊断、NMEA2000 协议解析、DBC文件解析、仿真工具、CAN报文分析、仿CANoe曲线显示 CAN仪表模拟器
  10. 基于Intel Lake-UP3平台的超声设备方案设计,提供出色的图形和AI性能