Electron+Vite项目集成SerialPort时出现__dirname相关ES模块兼容错误
我太懂这种踩坑的烦躁感了——Electron+Vite的组合本身就有模块格式的兼容门槛,再加上SerialPort这类还没完全适配ES模块的Node.js库,很容易触发这种"ES模块/CommonJS冲突"的报错。先帮你拆解下问题根源,再给你两个可落地的解决方案:
问题根源
你的项目根目录package.json里设置了"type": "module",这会让Node.js把所有.js文件默认当作ES模块处理。但SerialPort内部大量使用了CommonJS的写法,再加上你在主进程里手动模拟__dirname(ES模块环境下原本没有这个变量),多重因素叠加就触发了报错。
解决方案一:用createRequire兼容引入SerialPort(最小改动)
这个方案不用改项目整体的模块格式,只调整主进程里引入SerialPort的方式:
- 在你的
main.ts里,把原本直接import SerialPort的代码,改成用createRequire来引入(你已经在文件里引入了createRequire,刚好可以用上):
// 替换原来的 import { SerialPort, ReadlineParser } from 'serialport'; const require = createRequire(import.meta.url); const { SerialPort, ReadlineParser } = require('serialport');
- 保留你当前的
__dirname模拟代码(ES模块环境下这个写法是合规的):
import { fileURLToPath } from 'url'; import path from 'path'; const __filename = fileURLToPath(import.meta.url); globalThis.__dirname = path.dirname(__filename); // Cria uma variável global __dirname
这个思路的核心是:在ES模块环境下,用Node.js提供的createRequire工具,以CommonJS的方式加载SerialPort这类未完全适配ES模块的库,直接避开模块格式冲突。
解决方案二:拆分主/渲染进程的模块格式(更彻底的长期方案)
如果方案一还是有问题,建议把主进程和渲染进程的模块格式彻底分开:主进程用CommonJS(适配SerialPort这类Node.js库),渲染进程用ES模块(适配Vite+React的开发习惯)。
修改项目根目录的
package.json:- 删除
"type": "module"字段(这样默认所有.js文件会被当作CommonJS处理) - 把
"main"字段改成"dist-electron/main.cjs"(对应主进程编译后的CommonJS输出文件)
- 删除
给主进程单独配置TypeScript编译规则:
在项目根目录新建tsconfig-electron.json:
{ "extends": "./tsconfig.json", "compilerOptions": { "module": "CommonJS", "target": "ES2020", "outDir": "dist-electron" }, "include": ["electron/**/*.ts"] // 假设你的主进程代码在electron目录下 }
- 调整
vite.config.ts里的electron插件配置,让主进程输出为CommonJS:
import { defineConfig } from 'vite' import electron from 'vite-plugin-electron' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [ react(), electron({ main: { entry: 'electron/main.ts', vite: { build: { outDir: 'dist-electron', rollupOptions: { output: { format: 'cjs', // 指定主进程输出为CommonJS entryFileNames: 'main.cjs' // 对应package.json里的main字段 } }, tsconfig: 'tsconfig-electron.json' // 用单独的TS配置 } } }, preload: { entry: 'electron/preload.ts' } }) ] })
- 简化主进程里的
__dirname写法(CommonJS环境下默认存在这个变量,不需要手动模拟):
// 删掉原来的__dirname模拟代码,直接使用原生__dirname import path from 'path'; process.env.APP_ROOT = path.join(__dirname, '..')
验证方案
改完之后,先运行npm run dev测试开发环境是否正常,再执行npm run build验证打包流程是否能通过。如果还有问题,可以检查serialport的版本(你用的v12.0.0是比较新的版本,大概率兼容,但也可以尝试降级到v11.x版本测试)。
备注:内容来源于stack exchange,提问作者João Vitor




