vite-plugin-esmodule 是一个Vite插件,用来将ESModule 转换为CommonJS,在 Electron开发的时候,遇到了使用Vite时无法在Electron/Node环境中使用 ESModule 的问题 无法在主线程中使用 ESM 包

vite-plugin-esmoodule 插件其实很简单,代码只有几百行,但是插件的思路我觉得挺好,但是也感觉这种方式不是很完美的,它的依赖只有三个:

  • webpack
  • vite-plugin-optimuzer
  • lib-sem

vite-plugin-exmodule的使用方式:

import esmodule from 'vite-plugin-esmodule'
 
export default {
  plugins: [
    // Take `execa`, `node-fetch` and `file-type` as examples
    esmodule([
      'execa',
      'node-fetch',
 
      // 🌱 this means that you have explicit specified the entry file
      { 'file-type': 'file-type/index.js' },
    ]),
  ],
}

esmodule 接收一个数组,数组中是需要转换为CommonJS的包的名称,然后使用 vite-plugin-optimuzer插件,本质上 esmodule插件就是将optimuzer封装了一层。

esmodule 会根据不同的环境输出不同的包,例如在Electron渲染进程中,会编译出ESM包,如果是在主进程或者Node进程中,则会编译出CommonJS包,而且编译出来的包,不在原包的目录下,而是编译到node_modules/.vite-plugin-esmodule 文件夹下,在这个文件夹下会存在两个包,一个是 CommonJS,一个是ESModule

编译ESMCommonJS是通过 Webpack来实现的:

 
// webpack 转 CommonJs配置
let config = {
	mode: 'none',
	target: 'node14',
	entry: entries,
	output: {
	  library: {
		type: 'commonjs2',
	  },
	  path: args.dir,
	  filename: '[name]/index.js',
	},
};
 
// 调用 Webpack Api进行打包,将文件输出指 .vite-plugin-esmodule 目录下
webpack.webpack(config).run(())
 

为什么使用 Webpack 进行转换?

一些ESM包依赖于CJS包,这是很容易混淆的。在这种情况下,使用Webpack更加可靠。

当文件被打包到指定文件夹之后,会通过vite-plugin-optimuzer注册Vite 配置:

const plugin = optimizer(
    modules.reduce((memo, mod, idx) => {
      return Object.assign(memo, {
        [mod]: async args => {
          const isElectronRendererServe = env.command === 'serve';
          const { cjsId, electronRendererId } = getModuleId(path.join(args.dir, mod));
          if (idx === modules.length - 1) {
			// webpack 编译
            await buildESModules(args, modules, webpackFn);
            isElectronRendererServe && writeElectronRendererServeESM(args, modules);
          }
 
          return {
            alias: {
              find: mod,
              // 判断是Node/Electron环境还是浏览器环境,加载不同的模块
              replacement: isElectronRendererServe ? electronRendererId : cjsId,
            }
          };
        },
      })
    }, {}),
    { dir: CACHE_DIR },
  );
 
  plugin.name = PLUGIN_NAME;
  const original = plugin.config;
 
  plugin.config = function conf(_config, _env) {
    env = _env;
    if (original) {
      return original(_config, _env);
    }
  }
 
  return plugin;

这里主要注册的配置是 Vitealias 别名配置,例如如下配置:

alias: {
  find: 'get-port',
  replacement: 'node_modeles/.vite-plugin-esmodule/get-port.js'
}

这个配置的作用是当我们在代码中使用 import 'get-port' 时,在打包的时候,会去打包 node_modules/.vite-plugin-esmodule/get-port.js 文件
这里的zx包,index.js文件和index.electron-renderer是两个不同模块的文件。

总结这个插件的核心流程:

  1. 根据传入的模块名称,通过webpack将其编译出ESModuleCommonJS两个不同的模块。
  2. 将打包好的模块放入到node_modules/.vite-plugin-esmodule文件夹下
  3. 通过vite-plugin-optimizer注册Vite alias别名配置