Files
vite-plugin-demo/plugins/plugin-hooks.js
2026-06-26 17:56:46 +08:00

534 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* ============================================================
* Rollup / Vite 插件钩子函数 完整参考手册
* ============================================================
*
* 钩子分为两大阶段:
* 1. Build 阶段 — 解析模块、构建依赖图
* 2. Output Generate 阶段 — 生成最终产物chunk/bundle
*
* 钩子类型:
* - async : 可返回 Promise等待 resolve 后继续
* - sync : 同步执行
* - first : 多个插件有该钩子时,按顺序执行,第一个返回非 null 值的生效
* - sequential : 多个插件按顺序依次执行
* - parallel : 多个插件并行执行
*
* 以下是按 Rollup → Vite 分类的全部钩子。
*/
// ============================================================
// 一、Rollup 通用钩子Build 阶段)
// ============================================================
export default function pluginHooksDemo() {
return {
name: 'plugin-hooks-demo',
// ----------------------------------------------------------
// 1. options
// 类型: async, sequential
// 触发时机: 读取完用户传入的 rollup/vite 配置后,开始构建之前
// 用途: 替换或操纵传给 rollup 的 options 对象
// ----------------------------------------------------------
options(rawOptions) {
// rawOptions — 用户的原始配置对象
// 返回 null/undefined 不做修改
// 返回一个 options 对象来覆盖(会合并到现有 options
console.log('[options] 原始配置:', Object.keys(rawOptions))
return null
},
// ----------------------------------------------------------
// 2. buildStart
// 类型: async, parallel
// 触发时机: 构建开始时
// 用途: 构建前的初始化、创建输出目录、注册构建级变量等
// ----------------------------------------------------------
buildStart(options) {
// options — 最终合并后的配置对象
console.log('[buildStart] 构建开始, input:', options.input)
},
// ----------------------------------------------------------
// 3. resolveId
// 类型: async, first
// 触发时机: 解析模块导入路径时(对每个 import 都会调用)
// 用途: 自定义模块解析逻辑,例如 alias、虚拟模块
// ----------------------------------------------------------
resolveId(source, importer, options) {
// source — import 语句中的原始路径,如 './utils' 或 'vue'
// importer — 发起导入的模块的绝对路径(入口模块为 undefined
// options — { attributes, custom, isEntry }
//
// 返回:
// null — 交给下一个插件的 resolveId
// string — 解析后的模块绝对路径
// { id } — 对象形式id 为解析路径
// false — 标记为 external不打包
console.log('[resolveId]', source, '←', importer)
return null
},
// ----------------------------------------------------------
// 4. load
// 类型: async, first
// 触发时机: 加载模块源码内容时resolveId 之后)
// 用途: 返回模块内容,用于虚拟模块、加载非 JS 文件等
// ----------------------------------------------------------
load(id) {
// id — 模块的绝对路径
//
// 返回:
// null — 交给下一个插件
// string — 模块源代码
// { code, map, ... } — 带 sourcemap 等
console.log('[load] 加载模块:', id)
return null
},
// ----------------------------------------------------------
// 5. transform
// 类型: async, sequential
// 触发时机: 拿到模块源码后AST 解析之前
// 用途: 对单个模块源码做转换(最常见的钩子之一)
// ----------------------------------------------------------
transform(code, id) {
// code — 模块源代码(前置插件 transform 后的结果)
// id — 模块的绝对路径
//
// 返回:
// null — 不做转换
// string — 转换后的代码
// { code, map, ... } — 带 sourcemap
// { code, ast, ... } — 直接提供 AST跳过解析
console.log('[transform] 转换模块:', id)
return null
},
// ----------------------------------------------------------
// 6. moduleParsed
// 类型: async, parallel
// 触发时机: 模块 AST 解析完成(所有 transform 之后)
// 用途: 读取模块信息,如 import/export 列表
// ----------------------------------------------------------
moduleParsed(moduleInfo) {
// moduleInfo 主要属性:
// id — 模块路径
// code — 模块源码
// ast — ESTree 语法的 AST 树
// importedIds — 静态 import 的模块 id 数组
// dynamicallyImportedIds — 动态 import
// importedIdResolutions — 静态 import 解析详情
// dynamicallyImportedIdResolutions — 动态 import 解析详情
// importers — 导入了本模块的模块 id 列表
// dynamicImporters — 动态导入了本模块的模块 id 列表
// isEntry — 是否为入口
// isExternal — 是否为 external
// meta — 插件间共享的自定义元数据对象
// assertions/attributes — import assertions
console.log('[moduleParsed] 解析完成:', moduleInfo.id)
},
// ----------------------------------------------------------
// 7. resolveDynamicImport
// 类型: async, first
// 触发时机: 解析动态 import() 时
// 用途: 自定义动态导入的解析逻辑
// ----------------------------------------------------------
resolveDynamicImport(specifier, importer) {
// specifier — import(xxx) 中的 xxx
// importer — 发起动态导入的模块路径
//
// 返回:
// null — 交给下一个插件
// { id, external }
console.log('[resolveDynamicImport]', specifier, '←', importer)
return null
},
// ----------------------------------------------------------
// 8. buildEnd
// 类型: async, parallel
// 触发时机: 构建结束(无论成功/失败)时
// 用途: 清理资源、关闭文件句柄、输出统计信息
// ----------------------------------------------------------
buildEnd(error) {
// error — 如果构建失败则传入 Error 对象,成功则为 null
if (error) {
console.log('[buildEnd] 构建失败:', error.message)
} else {
console.log('[buildEnd] 构建结束')
}
},
// ----------------------------------------------------------
// 9. watchChange [Rollup watch 模式]
// 类型: sync, sequential
// 触发时机: rollup --watch 时文件发生变化
// 用途: 监控文件变化,根据变更决定是否重新构建
// ----------------------------------------------------------
watchChange(id, change) {
// id — 变化文件的绝对路径
// change — { event: 'create' | 'update' | 'delete' }
console.log('[watchChange]', change.event, ':', id)
},
// ----------------------------------------------------------
// 10. closeWatcher [Rollup watch 模式]
// 类型: async, parallel
// 触发时机: watch 进程关闭时
// 用途: 清理 watcher 相关资源
// ----------------------------------------------------------
closeWatcher() {
console.log('[closeWatcher] watcher 关闭')
},
// ============================================================
// 二、Rollup Output Generation 阶段钩子
// ============================================================
// ----------------------------------------------------------
// 11. outputOptions
// 类型: sync, sequential
// 触发时机: 获取 outputOptions 时
// 用途: 替换或操纵输出选项
// ----------------------------------------------------------
outputOptions(outputOptions) {
// outputOptions — 输出配置对象
console.log('[outputOptions] 输出配置:', Object.keys(outputOptions))
return null // 返回新对象则替换
},
// ----------------------------------------------------------
// 12. renderStart
// 类型: async, parallel
// 触发时机: 每次调用 bundle.generate() 或 bundle.write() 时
// 用途: 输出阶段的初始化
// ----------------------------------------------------------
renderStart(outputOptions, inputOptions) {
console.log('[renderStart] 开始生成产物')
},
// ----------------------------------------------------------
// 13. banner
// 类型: async, parallel
// 触发时机: 每个 chunk 生成前
// 用途: 在 chunk 最前面插入内容(如版权声明)
// ----------------------------------------------------------
banner(chunk) {
// chunk — 当前 chunk 信息对象
// 返回 string 或 Promise<string>
// console.log('[banner] chunk:', chunk.fileName)
return null
},
// ----------------------------------------------------------
// 14. footer
// 类型: async, parallel
// 触发时机: 每个 chunk 生成前
// 用途: 在 chunk 最后面追加内容
// ----------------------------------------------------------
footer(chunk) {
return null
},
// ----------------------------------------------------------
// 15. intro
// 类型: async, parallel
// 触发时机: 每个 chunk 生成前
// 用途: 在模块打包代码最前面插入内容banner 之后、wrapper 内部)
// ----------------------------------------------------------
intro(chunk) {
return null
},
// ----------------------------------------------------------
// 16. outro
// 类型: async, parallel
// 触发时机: 每个 chunk 生成前
// 用途: 在模块打包代码最后面追加内容footer 之前、wrapper 内部)
// ----------------------------------------------------------
outro(chunk) {
return null
},
// ----------------------------------------------------------
// 17. renderChunk
// 类型: async, sequential
// 触发时机: 每个 chunk 的代码生成时
// 用途: 对 chunk 代码做最终转换(可返回 sourcemap
// ----------------------------------------------------------
renderChunk(code, chunk, outputOptions) {
// code — chunk 的完整代码
// chunk — chunk 信息对象
// outputOptions — 输出配置
//
// 返回:
// null — 不修改
// string — 修改后代码
// { code, map }
console.log('[renderChunk]', chunk.fileName)
return null
},
// ----------------------------------------------------------
// 18. augmentChunkHash
// 类型: sync, sequential
// 触发时机: 计算 chunk hash 时,用于追加自定义信息
// 用途: 让 chunk hash 也能反映插件自定义内容的变化
// ----------------------------------------------------------
augmentChunkHash(chunkInfo) {
// 返回一个字符串,会被追加到 hash 输入中
// 常用于:[hash] 文件名需要反映插件版本等场景
return null
},
// ----------------------------------------------------------
// 19. renderDynamicImport
// 类型: sync, first
// 触发时机: 渲染动态 import 表达式时
// 用途: 自定义动态 import 的运行时行为
// ----------------------------------------------------------
renderDynamicImport({ format, moduleId, targetModuleId, customResolution }) {
// format — 输出格式 (es / cjs / …)
// moduleId — 发起导入的模块 id
// targetModuleId — 被导入模块 id
// customResolution — resolveDynamicImport 返回的 custom 值
//
// 返回:
// null — 默认渲染
// { left, right } — 自定义 import 表达式的左/右半部分
return null
},
// ----------------------------------------------------------
// 20. resolveFileUrl
// 类型: sync, first
// 触发时机: 渲染文件引用 URL 时(如 import.meta.ROLLUP_FILE_URL_reference_xxx
// 用途: 自定义文件 URL 解析
// ----------------------------------------------------------
resolveFileUrl({ chunkId, fileName, format, moduleId, referenceId, relativePath }) {
// 返回 null 使用默认,或返回自定义 URL 字符串
return null
},
// ----------------------------------------------------------
// 21. resolveImportMeta
// 类型: sync, first
// 触发时机: 渲染 import.meta 属性时
// 用途: 自定义 import.meta.xxx 的展开逻辑
// ----------------------------------------------------------
resolveImportMeta(property, { chunkId, moduleId, format }) {
// property — 访问的属性名,如 'url'
// 返回:
// null — 交给下一个插件
// string — 替换为指定字符串
// false — 保持 import.meta.property 不变
console.log('[resolveImportMeta]', property, 'in', moduleId)
return null
},
// ----------------------------------------------------------
// 22. generateBundle
// 类型: async, sequential
// 触发时机: bundle 生成完毕、写入磁盘之前
// 用途: 增、删、改最终产物(最常用的输出钩子之一)
// ----------------------------------------------------------
generateBundle(options, bundle, isWrite) {
// options — 输出配置
// bundle — { [fileName]: AssetInfo | ChunkInfo }
// isWrite — bundle.write() 为 true, bundle.generate() 为 false
//
// AssetInfo: { type: 'asset', fileName, source, name, needsCodeReference, ... }
// ChunkInfo: { type: 'chunk', fileName, code, map, modules, exports, facadeModuleId, isEntry, ... }
console.log('[generateBundle] 产物数:', Object.keys(bundle).length)
},
// ----------------------------------------------------------
// 23. writeBundle
// 类型: async, parallel
// 触发时机: bundle 写入磁盘后
// 用途: 产物写盘后的后处理(上传 CDN、生成报告等
// ----------------------------------------------------------
writeBundle(options, bundle) {
console.log('[writeBundle] 产物已写入磁盘')
},
// ----------------------------------------------------------
// 24. closeBundle
// 类型: async, parallel
// 触发时机: bundle 生成完成后(最后的清理钩子)
// 用途: 收尾清理
// ----------------------------------------------------------
closeBundle() {
console.log('[closeBundle] Bundle 流程结束,清理资源')
},
// ============================================================
// 三、Vite 独有钩子
// ============================================================
// ----------------------------------------------------------
// 25. config
// 类型: async, sequential
// 触发时机: 解析 Vite 用户配置之前
// 用途: 修改/扩展用户配置,或返回部分配置与已有配置合并
// 这是 Vite 插件最常用的入口钩子
// ----------------------------------------------------------
config(config, env) {
// config — 用户传入的 vite 配置(未解析)
// env — { mode: 'development'|'production', command: 'serve'|'build', ssrBuild }
//
// 返回 null 或 部分配置对象(会被 deep-merge 到用户配置)
console.log('[config] 模式:', env.mode, '命令:', env.command)
return null
},
// ----------------------------------------------------------
// 26. configResolved
// 类型: async, parallel
// 触发时机: Vite 配置解析完毕后
// 用途: 读取最终配置,做插件内部初始化(常在此缓存 resolvedConfig
// ----------------------------------------------------------
configResolved(resolvedConfig) {
// resolvedConfig — 完整解析后的 Vite 配置(只读参考)
console.log('[configResolved] root:', resolvedConfig.root)
},
// ----------------------------------------------------------
// 27. configureServer
// 类型: async, sequential
// 触发时机: Vite 开发服务器创建时
// 用途: 注册自定义中间件、WebSocket 事件等
// ----------------------------------------------------------
configureServer(viteDevServer) {
// viteDevServer 主要属性/方法:
// middlewares — Connect 实例,可用 .use() 添加中间件
// httpServer — Node http.Server 实例
// ws — WebSocket 服务器
// watcher — chokidar 文件监听器
// moduleGraph — 模块图
// config — 最终配置
// listen() — 启动服务器
// close() — 关闭服务器
// printUrls() — 打印服务地址
// transformIndexHtml() — 转换 HTML
// ssrFixStacktrace() — SSR 堆栈修复
// ssrLoadModule() — SSR 加载模块
// ssrTransform() — SSR 转换
viteDevServer.middlewares.use((req, res, next) => {
// 在这里可以拦截请求,自定义响应
next()
})
console.log('[configureServer] 开发服务器已创建')
},
// ----------------------------------------------------------
// 28. configurePreviewServer
// 类型: async, sequential
// 触发时机: vite preview 命令创建预览服务器时
// 用途: 类似 configureServer针对预览服务器
// ----------------------------------------------------------
configurePreviewServer(previewServer) {
// previewServer 接口与 viteDevServer 类似
previewServer.middlewares.use((req, res, next) => {
next()
})
console.log('[configurePreviewServer] 预览服务器已创建')
},
// ----------------------------------------------------------
// 29. transformIndexHtml
// 类型: async, sequential / order: 'pre' | 'post' | undefined
// 触发时机: 转换 index.html 时
// 用途: 注入 <script> / <link> 标签、修改 HTML 内容
// 这是 Vite 插件最常用的输出钩子之一
// ----------------------------------------------------------
transformIndexHtml: {
// order: 'pre' — 在其他插件之前执行
// order: 'post' — 在其他插件之后执行
// 不设 order — 按插件注册顺序
order: 'pre',
handler(html, ctx) {
// html — index.html 文本内容
// ctx — 上下文对象
// ctx.path — 请求路径
// ctx.filename — HTML 文件路径
// ctx.server — ViteDevServer 实例
// ctx.bundle — 构建产物 bundleserve 命令下可能为 undefined
// ctx.chunk — 当前 HTML 对应的 chunk
// ctx.originalUrl — 原始请求 URL
//
// 返回:
// string — 转换后的 HTML
// { html: string, tags: [...] } — HTML + 注入标签
// { tags: [...] } — 仅注入标签
// Tag 格式:
// { tag: 'script', attrs: { src: '/foo.js' }, injectTo: 'head'|'body'|'head-prepend'|'body-prepend' }
// { tag: 'link', attrs: { rel: 'stylesheet', href: '/foo.css' }, injectTo: 'head' }
// { tag: 'meta', attrs: { name: 'viewport', content: 'width=device-width' }, injectTo: 'head' }
return html
}
},
// ----------------------------------------------------------
// 30. handleHotUpdate
// 类型: async, sequential / order: 'pre' | 'post' | undefined
// 触发时机: HMR 热更新时文件变化
// 用途: 自定义 HMR 更新行为:过滤哪些变更触发热更新、
// 执行自定义 HMR 逻辑而非默认重载页面
// ----------------------------------------------------------
handleHotUpdate(ctx) {
// ctx 上下文对象:
// ctx.file — 变更的文件路径
// ctx.modules — 受变更影响的模块数组
// ctx.read() — 读取变更后文件内容
// ctx.server — ViteDevServer 实例
// ctx.timestamp — 变更时间戳
//
// 返回:
// ModuleNode[] — 自定义受影响模块列表
// void — 默认行为,使用 ctx.modules
console.log('[handleHotUpdate] 文件变更:', ctx.file)
},
// ============================================================
// 四、已废弃的 Rollup 钩子(兼容旧插件,不推荐使用)
// ============================================================
// transformBundle(code, options) → 请用 generateBundle 替代
// transformChunk(code, options, chunk) → 请用 renderChunk 替代
// ongenerate(options, bundle) → 请用 generateBundle 替代
// onwrite(options, bundle) → 请用 writeBundle 替代
}
}
// ============================================================
// 附:钩子执行顺序总览
// ============================================================
//
// 【Build 阶段】
// options → buildStart → (每个入口模块并行)
// resolveId → load → transform → moduleParsed
// → resolveDynamicImport遇到动态 import 时)
// → buildEnd
//
// 【Output Generate 阶段】
// outputOptions → renderStart → (每个 chunk 顺序)
// banner → footer → intro → outro
// → renderChunk → augmentChunkHash
// → renderDynamicImport → resolveFileUrl → resolveImportMeta
// → generateBundle → writeBundle → closeBundle
//
// 【Watch 模式】
// watchChange (文件变化时)
// closeWatcher (退出时)
//
// 【Vite 独有】
// config → configResolved
// → configureServer → configurePreviewServer
// → transformIndexHtml
// → handleHotUpdate
// ============================================================