/** * ============================================================ * 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 // 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 时 // 用途: 注入