vitepress主题开发--开发环境下动态导入UMD模块出现差异化报错

387 阅读3分钟

不同项目出现差异化

本地测试项目1通过link链接本地包vitepress-pure-theme-zyco

包中有如下逻辑

// vitepress-pure-theme-zyco的useValine中动态导入UMD模块valine
import("valine").then(({ default: Valine }) => {
  ......
}

在测试项目1中生产和开发环境皆正常。

本地测试项目2通过install安装依赖,生产环境正常,开发环境出现error

报错原因

首先来分析一下为什么会报错?

动态import引入UMD模块,在动态引入模块的上下文环境下,window是undefined,因此UMD模块挂载库代码到window上时出现error

两个项目一个报错,一个正常,出现差异的原因

一开始以为是依赖了不同版本的vite,导致开发环境的差异。分析vite的打包源码后,两个本地项目依赖的vite,无论开发模式还是生产模式下的打包,都会通过@rollup/plugin-commonjs将模块转化为ESM,因此并非vite版本差异导致问题

最后发现是link本地vitepress-pure-theme-zyco,导致vite在开发环境下,进行optimize deps(依赖优化)时,valine会被处理,而通过安装依赖vitepress-pure-theme-zyco,optimize deps会跳过valine

// vite v5.4.9
// 在vite插件import-analysis进行依赖分析后,通过插件vite:resolve解析包信息的时候,利用tryNodeResolve方法解析valine
function tryNodeResolve( ){
  ...
  // 这里skipOptimization决定了是否对模块进行依赖优化,即转化为ES模块
  const skipOptimization = !options.ssrOptimizeCheck && depsOptimizer?.options.noDiscovery
  || !isJsType 
  || importer && isInNodeModules$1(importer)
  || exclude?.includes(pkgId)
  || exclude?.includes(id)
  || SPECIAL_QUERY_RE.test(resolved)
  || !options.ssrOptimizeCheck && !isBuild && ssr 
  || ssr && isFilePathESM(resolved,options.packageCache)
  && !(include?.includes(pkgId) || include?.includes(id));
  ...
  if (skipOptimization) {
    // 跳过了依赖优化
  } else {
    // 这里的逻辑决定该包会被optimize deps
    const optimizedInfo = depsOptimizer.registerMissingImport(id, resolved);
    resolved = depsOptimizer.getOptimizedDepId(optimizedInfo);
  }
}

上面代码中importer && isInNodeModules$1(importer)决定skipOptimizationtrue还是false至关重要

开发环境下,link本地vitepress-pure-theme-zyco,然后本地包的逻辑中动态引入了UMD模块valine,此时vite dev打包逻辑中importer是指向本地文件的一个绝对路径,isInNodeModules$1判断importer不是node_modules里的模块,所以importer && isInNodeModules$1(importer)是false,最终skipOptimization为false,于是会对valine进行依赖优化

这也是为什么在测试项目1的开发环境下没有报错。

开发环境下,安装依赖vitepress-pure-theme-zyco,包逻辑中动态引入了UMD模块valine,此时vite dev打包逻辑中importer是指向node_modules的一个绝对路径,isInNodeModules$1判断importer是node_modules里的模块文件,所以importer && isInNodeModules$1(importer)是true,最终skipOptimization为true,于是依赖优化会跳过valine

这也是为什么在测试项目2的开发环境下会出现Error,即依赖的依赖会被跳过。

解决方案

因为vite在创建开发服务器的前置逻辑中,创建了依赖优化器,可以利用配置optimizeDeps.include强行优化指定模块

function createServer(inlineConfig = {}) {
  return _createServer(inlineConfig, { hotListen: true });
}
async function _createServer(inlineConfig = {}, options) {
  ...
  await initServer();
}
const initServer = async () => {
  if (serverInited) return;
  if (initingServer) return initingServer;
  initingServer = async function() {
    await container.buildStart({});
    if (isDepsOptimizerEnabled(config, false)) {
      // 这里将会对vite配置中optimizeDeps.include指定的包进行依赖优化
      await initDepsOptimizer(config, server);
    }
   ...
  }();
  return initingServer;
}

修改vite配置

{
  optimizeDeps: {
      include: [`vitepress-pure-theme-zyco > valine`],
  }
}

最后再提一句,如果仅仅是include: [valine]是无效的,vite在第一层依赖中找不到指定包(因为该包是“依赖的依赖“),需要明确依赖的层级关系才有效。