paint-brush
为 Vite 创建自定义插件:最简单的指南经过@gmakarov
5,450 讀數
5,450 讀數

为 Vite 创建自定义插件:最简单的指南

经过 German Makarov9m2024/01/21
Read on Terminal Reader

太長; 讀書

对于开发服务器,它使用带有原生 ES 模块的 esbuild,这些模块受到现代浏览器的支持,我们不需要将代码捆绑到单个文件中,并且它为我们提供了快速的 HRM(热模块替换)。 对于捆绑包,它使用 rollup.js,因为它灵活且拥有庞大的生态系统;它允许创建具有不同输出格式的高度优化的生产包。 Vite 的插件界面基于 Rollup 的,但具有用于与开发服务器配合使用的附加选项和挂钩。
featured image - 为 Vite 创建自定义插件:最简单的指南
German Makarov HackerNoon profile picture
0-item

Vite在开发者中越来越受欢迎,但由于社区没有Webpack那么大,您可能需要创建自己的自定义插件来解决您的问题。在这篇文章中,我们将讨论如何为Vite创建一个插件,并且我将分解我自己的插件。

插件在 Vite 中的工作原理

要创建插件,重要的是要知道 Vite 对开发服务器(命令vite )和捆绑包(命令vite build )使用不同的构建系统。


对于开发服务器,它使用带有原生 ES 模块的esbuild ,这些模块受到现代浏览器的支持,我们不需要将代码捆绑到单个文件中,并且它为我们提供了快速的 HRM(热模块替换)。


对于捆绑包,它使用rollup.js,因为它很灵活并且拥有庞大的生态系统;它允许创建具有不同输出格式的高度优化的生产包。


Vite 的插件界面基于 Rollup 的,但具有用于与开发服务器配合使用的附加选项和挂钩。

创建一个基本插件

创建插件时,您可以将其内联到vite.config.js中。无需为其创建新包。一旦您发现某个插件在您的项目中有用,请考虑与社区分享并为 Vite 生态系统做出贡献。


另外,由于 rollup.js 拥有更大的社区和生态系统,您可以考虑为 rollup.js 创建一个插件,它在 Vite 中也能正常工作。因此,如果您的插件功能仅适用于捆绑包,您可以使用 rollup.js 插件而不是 Vite,并且用户可以在其 Vite 项目中使用您的 rollup 插件,不会出现任何问题。


如果您为 rollup 创建插件,您将覆盖更多仅使用 rollup.js 的用户。如果您的插件会影响开发服务器,那么您可以使用 Vite 插件。


让我们开始直接在vite.config.ts中创建插件:


 // vite.config.ts import { defineConfig, Plugin } from 'vite'; function myPlugin(): Plugin { return { name: 'my-plugin', configResolved(config) { console.log(config); }, }; } export default defineConfig({ plugins: [ myPlugin(), ], });


在此示例中,我创建了一个名为myPlugin的插件,一旦在控制台中的两个阶段(开发服务器和捆绑包)中解析 Vite 配置,该插件就会打印它。如果我只想在开发服务器模式下打印配置,那么我应该为捆绑添加apply: 'serve'apply: 'build'


 // vite.config.ts import { defineConfig, Plugin } from 'vite'; function myPlugin(): Plugin { return { name: 'my-plugin', apply: 'serve', configResolved(config) { console.log(config); }, }; } export default defineConfig({ plugins: [ myPlugin(), ], });


另外,我可以返回一组插件;它对于分离开发服务器和捆绑包的功能很有用:


 // vite.config.ts import { defineConfig, Plugin } from 'vite'; function myPlugin(): Plugin[] { return [ { name: 'my-plugin:serve', apply: 'serve', configResolved(config) { console.log('dev server:', config); }, }, { name: 'my-plugin:build', apply: 'build', configResolved(config) { console.log('bundle:', config); }, }, ]; } export default defineConfig({ plugins: [ myPlugin(), ], });


差不多就是这样了;您可以轻松地将小插件添加到 Vite 配置中。如果插件太大,我更喜欢将其移动到另一个文件,甚至创建一个包。


如果您需要更复杂的东西,您可以在 Vite 文档中探索许多有用的钩子。但作为一个例子,让我们在下面分解我自己的插件。

分解一个真正的插件

所以,我有一个基于图标文件创建 SVG 精灵的插件 - vite-plugin-svg-spritemap

目标是获取src/icons文件夹中的所有图标.svg ,并将其内容收集到单个.svg文件(称为 SVG sprite)中。让我们从捆绑阶段开始:


 import { Plugin, ResolvedConfig } from 'vite'; import path from 'path'; import fs from 'fs-extra'; function myPlugin(): Plugin { let config: ResolvedConfig; return { name: 'my-plugin:build', apply: 'build', async configResolved(_config) { config = _config; }, writeBundle() { const sprite = getSpriteContent({ pattern: 'src/icons/*.svg' }); const filePath = path.resolve(config.root, config.build.outDir, 'sprite.svg'); fs.ensureFileSync(filePath); fs.writeFileSync(filePath, sprite); }, }; }


  1. Hook configResolved允许我们在决定在下一个钩子中使用它时获取配置;


  2. writeBundle钩子在捆绑过程完成后被调用,在这里,我将创建sprite.svg文件;


  3. getSpriteContent函数返回基于src/icons/*.svg模式准备的 SVG 精灵的字符串。我不会更深入地讨论这一点;你可以看看我的另一篇文章解释了SVG精灵生成的整个过程


  4. 然后,我创建sprite.svg的绝对路径,以使用path.resolve()将 SVG 精灵内容放入其中,使用fs.ensureFileSync确保该文件存在(或创建一个),然后将 SVG 精灵内容写入其中。


现在,最有趣的部分 - 开发服务器阶段。我在这里无法使用writeBundle ,并且在开发服务器运行时无法托管文件,因此我们需要使用服务器中间件来捕获对sprite.svg的请求。


 import { Plugin, ResolvedConfig } from 'vite'; function myPlugin(): Plugin { let config: ResolvedConfig; return { name: `my-plugin:serve`, apply: 'serve', async configResolved(_config) { config = _config; }, configureServer(server) { // (1) return () => { server.middlewares.use(async (req, res, next) => { // (2) if (req.url !== '/sprite.svg') { return next(); // (3) } const sprite = getSpriteContent({ pattern, prefix, svgo, currentColor }); res.writeHead(200, { // (4) 'Content-Type': 'image/svg+xml, charset=utf-8', 'Cache-Control': 'no-cache', }); res.end(sprite); }); }; }, }; }


  1. configureServer是一个用于配置开发服务器的钩子。在Vite内部中间件安装之前触发;就我而言,我需要在内部中间件之后添加自定义中间件,因此我返回一个函数;


  2. 要添加自定义中间件以捕获对开发服务器的每个请求,我使用server.middlewares.use() 。我需要它来检测 URL [localhost:3000/sprite.svg](http://localhost:3000/sprite.svg)的请求,这样我就可以模拟文件行为;


  3. 如果请求 URL 不是/sprite.svg - 跳到下一个中间件(即,将控制权传递给链中的下一个处理程序);


  4. 为了准备文件内容,我将getSpriteContent的结果放入变量sprite中,并将其作为带有配置标头(内容类型和 200 HTTP 状态)的响应发送。


结果,我模拟了文件行为。


但是如果src/icons中的文件被更改、删除或添加,我们应该重新启动服务器以通过getSpriteContent生成新的精灵内容;为此,我将使用文件监视库 - chokidar 。让我们将 chokidar 处理程序添加到代码中:


 import { Plugin, ResolvedConfig } from 'vite'; import chokidar from 'chokidar'; function myPlugin(): Plugin { let config: ResolvedConfig; let watcher: chokidar.FSWatcher; // Defined variable for chokidar instance. return { name: `my-plugin:serve`, apply: 'serve', async configResolved(_config) { config = _config; }, configureServer(server) { function reloadPage() { // Function that sends a signal to reload the server. server.ws.send({ type: 'full-reload', path: '*' }); } watcher = chokidar .watch('src/icons/*.svg', { // Watch src/icons/*.svg cwd: config.root, // Define project root path ignoreInitial: true, // Don't trigger chokidar on instantiation. }) .on('add', reloadPage) // Add listeners to add, modify, delete. .on('change', reloadPage) .on('unlink', reloadPage); return () => { server.middlewares.use(async (req, res, next) => { if (req.url !== '/sprite.svg') { return next(); } const sprite = getSpriteContent({ pattern, prefix, svgo, currentColor }); res.writeHead(200, { 'Content-Type': 'image/svg+xml, charset=utf-8', 'Cache-Control': 'no-cache', }); res.end(sprite); }); }; }, }; }


正如您所看到的,插件创建的 API 并不复杂。您只需要从 Vite 或 Rollup 中找到适合您任务的钩子即可。在我的示例中,我使用 Rollup.js 中的writeBundle (正如我所说,它用于生成包),并使用 Vite 中的configureServer ,因为 Rollup.js 没有本机开发服务器支持。


对于writeBundle来说,它非常简单,我们获取 SVG 精灵内容并将其放入文件中。就开发服务器而言,我很困惑为什么我不能做同样的事情;我查看了其他作者的插件,它们的作用都差不多。


因此,我使用configureServer并通过server参数添加中间件,通过拦截sprite.svg请求来触发对开发服务器的每个请求。

使用 Vite Hooks

正如我之前提到的,要创建更有用的插件,您需要探索挂钩。它们在文档中有详细解释:

https://vitejs.dev/guide/api-plugin#universal-hooks

https://vitejs.dev/guide/api-plugin#vite-specific-hooks

如何命名插件

在命名方面,Vite 对插件有一些约定,大家最好先检查一下再完成。以下是一些要点:


  • Vite 插件应该有一个唯一的名称,并带有vite-plugin-前缀;


  • package.json 中包含vite-plugin关键字;


  • 在插件文档中包含一个部分,解释为什么它是 Vite-only 插件(例如,它使用 Vite 特定的插件挂钩);


  • 如果您的插件仅适用于特定框架,请将其名称作为前缀的一部分( vite-plugin-vue-vite-plugin-react-vite-plugin-svelte- )。

如何发布和共享您的插件

如果您决定在 NPM 中发布您的插件,我建议这样做,因为共享知识和专业知识是 IT 社区的基本原则,可以促进集体成长。要了解如何发布和维护包,请查看我的指南 →创建 NPM 包的最简单方法


我还强烈建议将您的插件提交到 vite 的社区列表 - Awesome-vite 。很多人都在寻找最合适的插件,这将是为Vite生态做出贡献的绝佳机会!在那里提交插件的过程很简单 - 只需确保满足条款并创建拉取请求即可。您可以在此处找到术语列表。


总体来说,它需要针对Vite(而不是rollup),开源,并且有良好的文档。祝你的插件好运!