Vite 插件简要

我点燃了火,却控制不了它。 ---刘慈欣-

简介

Viite 的插件机制是基于 Rollup 的插件机制实现的,但是又进行了一些扩展。Vite 的插件机制是通过钩子函数实现的,当 Vite 运行时,会通过钩子函数调用插件中的方法,插件可以在这些方法中干预 Vite 的构建过程。

如果插件不使用 Vite 特有的钩子,可以作为 兼容 Rollup 的插件 来实现,推荐使用 Rollup 插件名称约定

  • Rollup 插件应该有一个带 rollup-plugin- 前缀、语义清晰的名称。
  • 在 package.json 中包含 rollup-plugin 和 vite-plugin 关键字。

这样,插件也可以用于纯 Rollup 或基于 WMR 的项目。

对于 Vite 专属的插件:

  • Vite 插件应该有一个带 vite-plugin- 前缀、语义清晰的名称。
  • 在 package.json 中包含 vite-plugin 关键字。
  • 在插件文档增加一部分关于为什么本插件是一个 Vite 专属插件的详细说明(如,本插件使用了 Vite 特有的插件钩子)。

如果你的插件只适用于特定的框架,它的名字应该遵循以下前缀格式:

  • vite-plugin-vue- 前缀作为 Vue 插件
  • vite-plugin-react- 前缀作为 React 插件
  • vite-plugin-svelte- 前缀作为 Svelte 插件

更多详情参见 虚拟模块的相关内容.

Rollup 插件机制

Rollup 的插件机制实现主要基于两点:

  • Rollup 维护了各个插件接口的 Hook 列表,插件可以向这些列表中添加回调函数。
  • 在执行对应过程时,Rollup 会依次触发这些 Hook 列表中的回调函数。
js 复制代码
const hookLists = {
  load: [] // load hook 列表
}

function addHook(hookName, hook) {
  hookLists[hookName].push(hook)  // 向 hook 列表中添加回调函数
}

function load(id) {
  for (const hook of hookLists.load) { // 触发所有 load 钩子函数
    const result = hook(id)  // 调用钩子函数
    if (result) return result  // 使用第一个结果并返回
  }
}

插件可以通过 Rollup 提供的 addHook 方法相对应的 Hook 列表中添加回调函数:

js 复制代码
export function myPlugin() {
  addHook('load', id => {  // 向 load 列表添加回调函数
    // ...
  })
}

Vite

Vite 主要将用户插件排序,然后和内置的插件配置合并,传递给了 Rollup 打包。

关键的部分源码如下:

js 复制代码
// vite/node/config.ts
export async function resolveConfig() {

  // ...

  // resolve plugins
  const rawUserPlugins = (
    (await asyncFlatten(config.plugins || [])) as Plugin[]
  ).filter(filterPlugin)

  const [prePlugins, normalPlugins, postPlugins] =
    sortUserPlugins(rawUserPlugins)

  // run config hooks
  const userPlugins = [...prePlugins, ...normalPlugins, ...postPlugins]

  // ...
}
js 复制代码
// vite/node/build.ts 
export async function build() {

  const config = await resolveConfig(
    inlineConfig,
    'build',
    'production',
    'production',
  )

  //...

  const plugins = (
    ssr ? config.plugins.map((p) => injectSsrFlagToHooks(p)) : config.plugins
  ) as Plugin[]

  const rollupOptions: RollupOptions = {
    context: 'globalThis',
    preserveEntrySignatures: ssr
      ? 'allow-extension'
      : libOptions
      ? 'strict'
      : false,
    cache: config.build.watch ? undefined : false,
    ...options.rollupOptions,
    input,
    plugins,
    external,
    onwarn(warning, warn) {
      onRollupWarning(warning, warn, config)
    },
  }

  // ...

  // write or generate files with rollup
  const { rollup } = await import('rollup')
  bundle = await rollup(rollupOptions)

  // ...
}

使用

Vite 使用插件时,需要将插件放入 plugins 的数组中如下:

js 复制代码
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import autoSwitchPortPlugin from "./src/utils/autoSwitchPortPlugin.js";

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [vue(), autoSwitchPortPlugin()],
})

实践

接下来我们自定义几个插件,感受下 Vite 的插件机制。

写这几个插件是为了理解插件机制,官方已经提供了相关的配置或者现成的插件

🚗 自动切换端口,默认8080

编写一个 Vite 插件,将项目的默认端口设置为 8080,当8080 被占用的时候并自动+1 寻找下一个可用端口。

js 复制代码
import net from 'net'

/**
 * 获取下一个可用端口
 * 在这段代码中,server的作用是查找并确定下一个可用的TCP端口。通过创建一个未绑定到特定端口的TCP服务器实例,并尝试在指定的起始端口上监听连接请求:
 * 使用net.createServer()创建TCP服务器。
 * 调用.unref()方法确保当没有活动连接时,Node.js进程可以正常退出,不影响其他任务执行或进程结束。
 * 在server.listen(port, ...)中,尝试在给定的端口上启动服务器监听。如果端口已被占用,则会触发错误事件,递归调用getNextPort函数以获取下一个端口继续尝试。
 * 若成功监听到指定端口,则关闭服务器(server.close(...)),并使用resolve(port)返回当前找到的可用端口。
 * 整个流程的核心目的是实现一个自动切换端口的插件功能,该功能能够帮助找到下一个可用的TCP端口供服务器应用使用。
 * @param {number} port - 起始端口默认 8080,当被占用,自动递增 port
 * @returns {Promise<number>} - Promise对象,resolve时返回下一个可用端口
 */
function getNextPort(port) {
    return new Promise((resolve) => {
        // net.createServer()是一个函数调用,它创建了一个新的TCP服务器实例。这个服务器等待并处理来自客户端的连接请求。
        const server = net.createServer()
        /**
         * 是对服务器实例调用.unref()方法。此方法的作用是将服务器从事件循环(Event Loop)中的引用计数中移除。
         * 当所有活动的引用都被取消引用(unref)后,且没有其他任务在执行时,Node.js进程可以正常退出,即使服务器仍在监听连接。
         * 换句话说,如果仅有一个对服务器的引用,并对其调用了unref,那么当服务器上没有活动连接时,
         * 主线程在完成其他任务后将能够优雅地关闭,而不会因为服务器还在监听而继续保持运行状态。
         */
        server.unref()

        // 错误处理
        server.on('error', () => {
            resolve(getNextPort(port + 1))
        })

        // 监听指定端口
        server.listen(port, () => {
            server.close(() => {
                resolve(port)
            })
        })
    })
}

/**
 * 自动切换端口插件
 * @returns {object} - 包含name和configResolved方法的对象
 */
function autoSwitchPortPlugin() {
    let port = 8080

    return {
        name: 'auto-switch-port',
        /**
         * 在配置resolved时动态切换服务器端口
         * @param {object} config - 服务器配置对象
         */
        async configResolved(config) {
            port = await getNextPort(port)
            config.server.port = port
        },
    }
}

export default autoSwitchPortPlugin

🚗 为文件加上版本号

由于这个操作是转换 index.html文件,所以需要使用专用钩子transformIndexHtml

js 复制代码
import { createHash } from "crypto"

/**
 * 自动版本插件
 *
 * @returns {Object} - 包含 transformIndexHtml 方法的对象
 */
export default function autoVersionPlugin() {
    return {
        /**
         * 组件名称
         *
         * @type {string}
         */
        name: 'auto-version',
        /**
         * 转换首页 HTML
         *
         * @param {string} html - 需要转换的 HTML 字符串
         * @returns {string} - 转换后的 HTML 字符串
         */
        async transformIndexHtml(html) {
            /**
             * 创建哈希对象
             *
             * @type {Object}
             */
            // 创建一个md5的哈希对象
            const hash = createHash('md5').update(html).digest('hex')
            // 将html中的所有(src|href)="*"替换为(src|href)="*?v=hash",其中hash为上面计算得到的哈希值
            return html.replace(/(src|href)="(.*?)"/g, `$1="$2?v=${hash}"`)

        },
    }
}

在编译后的 dist 目录发现所有外部资源的尾部都添加了 hash 值

引用

通用的钩子Vite

Vite 独有的钩子Vite

rollupRollup

相关推荐
超雄代码狂21 分钟前
ajax关于axios库的运用小案例
前端·javascript·ajax
长弓三石29 分钟前
鸿蒙网络编程系列44-仓颉版HttpRequest上传文件示例
前端·网络·华为·harmonyos·鸿蒙
小马哥编程30 分钟前
【前端基础】CSS基础
前端·css
嚣张农民1 小时前
推荐3个实用的760°全景框架
前端·vue.js·程序员
周亚鑫1 小时前
vue3 pdf base64转成文件流打开
前端·javascript·pdf
Justinc.1 小时前
CSS3新增边框属性(五)
前端·css·css3
neter.asia2 小时前
vue中如何关闭eslint检测?
前端·javascript·vue.js
~甲壳虫2 小时前
说说webpack中常见的Plugin?解决了什么问题?
前端·webpack·node.js
嚣张农民2 小时前
JavaScript中Promise分别有哪些函数?
前端·javascript·面试
光影少年2 小时前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js