面试官必问:如何 Debug 一个 Vite 插件

179 阅读1分钟

经常遇到打包编译一分钟的情况,这时候怎么办,到底是哪个插件慢,哪个插件报错。高级工程师经常要解决问题。本文会详细介绍如何 Debug 一个 vite 插件的思路。

当然你也可以使用这个成型的插件。开箱即用

github.com/lbb00/speed…

回到正题,Vite 打包的流程是会先解析 Plugins,按插件排列依次顺序读取配置,然后依次执行插件中的不同钩子。

image.png

可以理解 Vite 或是 Webpack 都是一边一边运行相应的钩子函数

如何定位

根据上面的流程,我们可以通过穿插一个 Debug 插件来查看到底是哪卡住了。先用 LLM 写一个钩子函数

function createDebugPlugin(name) {
  return {
    name: `vite-plugin-debug-${name}`,
    enforce: undefined, // 可以是 'pre', 'post' 或 undefined
    
    // 配置解析完成后
    configResolved(config) {
      console.log(`[${name}] configResolved - 配置已解析`);
    },
    
    // 构建开始
    buildStart(options) {
      console.log(`[${name}] buildStart - 构建开始`);
    },
    
    // 解析 ID 
    resolveId(source, importer, options) {
      // 只记录特定模块的解析
      if (source.includes('vite') || source.includes('vue')) {
        console.log(`[${name}] resolveId: ${source}`);
      }
      return null;
    },
    
    // 加载模块
    load(id) {
      // 只记录特定模

然后每个插件都插入一个,这样你就知道哪一个有问题。比如,

plugins: [
    createDebugPlugin('after-mkcert'),
    after-mkcert(),
    createDebugPlugin('vue'),
    vue()
]

汇总信息

这里有个知识点 enfore 属性,可以保证这个在最后一个执行。上 llm

// 创建一个专门用于跟踪插件完成情况的插件
function createFinalPlugin() {
  return {
    name: 'vite-plugin-final-tracker',
    enforce: 'post', // 确保最后执行
    
    configResolved(config) {
      console.log(`[最终插件] 所有配置已解析`);
      // 记录所有注册的插件名称
      const pluginNames = config.plugins.map(p => p.name).join(', ');
      console.log(`[最终插件] 已注册的插件: ${pluginNames}`);
    },
    
    buildStart() {
      console.log(`[最终插件] 所有插件已初始化,构建开始`);
    },
    
    buildEnd(error) {
      if (error) {
        console.error(`[最终插件] 构建失败,错误信息:`, error);
      } else {
        console.log(`[最终插件] 所有插件已执行完毕,构建结束`);
      }
    },
    
    closeBundle() {
      console.log(`[最终插件] 整个构建过程已完成`);
    }
  };
}

错误嵌套

这是一个常用方法,如果你会好奇 apply 函数什么时候才能用到。这就是一个很好的场景

// 包装现有插件以添加错误处理
function wrapPluginWithErrorHandler(plugin, pluginName) {
  const originalHooks = { ...plugin };
  const wrappedPlugin = {
    ...plugin,
    name: plugin.name || pluginName,
  };
  
  // 包装所有钩子添加错误处理
  for (const hookName of Object.keys(plugin)) {
    if (typeof plugin[hookName] === 'function' && hookName !== 'name') {
      wrappedPlugin[hookName] = async function(...args) {
        try {
          return await originalHooks[hookName].apply(this, args);
        } catch (error) {
          console.error(`[错误] 插件 ${pluginName}${hookName} 钩子中出错:`, error);
          throw error; // 继续抛出错误以便 Vite 处理
        }
      };
    }
  }
  
  return wrappedPlugin;
}