Vite 预构建时通过虚拟模块处理环境兼容问题

180 阅读2分钟

Vite 预构建时通过虚拟模块处理环境兼容问题

问题说明:

现有一个 electron-vite 项目,开启了渲染进程的 Node 集成。在项目内需要接入一个第三方组件 A组件 A 内部引入了一个 CommonJS 模块的 SDK 名为 B,SDK B 内部还引入了一个 .node 文件。在我们的项目中启动时,Vite 会预构建,在预构建过程中遇到 SDK B 时解析 .node 文件时会报错(因为 .node 文件的处理需要在 Node 环境),然而配置 optimizeDeps.exclude 排除 SDK B 的预构建也不行,因为必须要依赖内部的内容。

解决方案:

开发一个 Vite 插件, 使用 Vite 插件的虚拟模块, 在 Vite 预构建 时将 组件 A 中通过 import 语句引入的 sdk B 模块的内容替换为最下方代码块中的内容,其本质是绕过 Vite 对原始模块的解析,通过虚拟模块直接注入一个符合 Vite 预期的 ESM模块。通过上述方法避免了 Vite 预构建时直接处理 .node 文件,将 .node 文件的处理交给 electron 运行时

插件示例伪代码:

插件学习参考官网: cn.vitejs.dev/guide/api-p…

const configString = `
          const C = require("SDK B");
          const D = C.D;
          const E = C.E;
          export { 
            D,
            E
          };
          export default C.default;`

export default function resolve(config) {
  let prefix = "\0FengCh:";
  const log = (...args) => console.log(prefix, ...args);
  log(config)
  return {
    name: "vite-plugin-resolve-node",
    enforce: 'pre',
    resolveId(sources) { 
      if (sources === 'SDK B') {
        return prefix + 'SDK B';
      }
    },
    async load(id) {
      if (id === prefix + 'SDK B') {
        return configString;
      }
    }  
  }
} 

疑问解答:

  1. 为什么在虚拟模块中我们通过在顶层通过 require 来引入模块也被视为直接注入了一个 ESM 模块?
    答:因为 Vite 插件的虚拟模块本质就是一个 EMS 虚拟模块,看似我们是通过 require 来引入的,但是最后的导出是通过 export 来导出的,export 导出时 Vite 会将其视为一个合法的虚拟 ESM 模块,而不需要使用插件进行语法转换,export 导出最终在 electron 运行 时环境是一定不会出错的,提高了容错,而 require 语法 Vite 会用插件将其转换为 import.meta.require 语法,最终由 Node 来执行。
  2. 为什么虚拟模块导出的内容,在顶层我们使用 require 语法(CommonJS )引入 sdk,最后又使用 export 语法(ESModule)来导出,看起来十分矛盾?
    答:这两个语法是为了解决环境差异而存在的,require 语法是为了解决 运行时 问题。因为 sdk 是 CommonJS 模块,Vite 插件将其转换为 import.meta.require 后在运行时就会交给 Nodejs 处理;而 export 语法是为了解决 构建时 逻辑,提高 Vite 插件的处理容错。