vite面试题提交

This commit is contained in:
2026-06-26 17:56:46 +08:00
parent 40a1a2248b
commit 82c0f31cae
7 changed files with 1176 additions and 19 deletions

533
plugins/plugin-hooks.js Normal file
View File

@@ -0,0 +1,533 @@
/**
* ============================================================
* 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
// ============================================================