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