Electron + React 项目集成 Serialport 包报错无法运行问题咨询
React + Electron + Serialport 可行集成方案
错误根源说明
你遇到的exists is not a function报错,本质是在React运行的Electron渲染进程上下文中直接引入了serialport原生模块。渲染进程默认没有Node.js原生API访问权限,React构建工具也会对Node原生模块做打包处理,导致serialport依赖的fs模块方法缺失,和serialport本身的兼容性无关。
核心设计原则
所有和串口操作相关的逻辑全部放在Electron主进程执行,React渲染进程仅通过IPC通道和主进程通信收发串口数据,不要在React代码中直接引入或调用serialport。
具体操作步骤
- 第一步:配置Electron预加载脚本与基础权限
关闭渲染进程的Node.js集成,开启上下文隔离(Electron官方推荐的安全配置),同时在预加载脚本(preload.js)里暴露IPC通信的方法给渲染进程:// preload.js 示例 const { contextBridge, ipcRenderer } = require('electron') contextBridge.exposeInMainWorld('serialApi', { // 获取串口列表 listPorts: () => ipcRenderer.invoke('serial:list-ports'), // 打开指定串口 openPort: (path, options) => ipcRenderer.invoke('serial:open', path, options), // 监听串口返回数据 onPortData: (callback) => ipcRenderer.on('serial:data', (_, data) => callback(data)), // 向串口写入数据 writePort: (data) => ipcRenderer.invoke('serial:write', data), // 关闭当前串口 closePort: () => ipcRenderer.invoke('serial:close') }) - 第二步:主进程实现串口操作逻辑
直接在主进程代码中引入serialport,注册对应的IPC监听事件:// 主进程 main.js 示例 const path = require('path') const { app, BrowserWindow, ipcMain } = require('electron') const { SerialPort } = require('serialport') let mainWindow let activePort = null function createWindow() { mainWindow = new BrowserWindow({ width: 1000, height: 700, webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, nodeIntegration: false } }) // 开发环境加载React本地开发服务,生产环境加载构建后的静态页面 const loadUrl = process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : `file://${path.join(__dirname, '../build/index.html')}` mainWindow.loadURL(loadUrl) } app.whenReady().then(createWindow) // 注册串口相关IPC处理事件 ipcMain.handle('serial:list-ports', async () => { return await SerialPort.list() }) ipcMain.handle('serial:open', async (_, path, options) => { if (activePort?.isOpen) await activePort.close() activePort = new SerialPort({ path, ...options }) activePort.on('data', (data) => { mainWindow.webContents.send('serial:data', data.toString()) }) return new Promise((resolve, reject) => { activePort.on('open', resolve) activePort.on('error', reject) }) }) ipcMain.handle('serial:write', async (_, data) => { if (!activePort?.isOpen) throw new Error('串口未打开') return activePort.write(data) }) ipcMain.handle('serial:close', async () => { if (activePort?.isOpen) await activePort.close() activePort = null return true }) - 第三步:React代码调用暴露的串口API
不需要在React项目依赖中安装serialport,直接调用预加载脚本暴露的window.serialApi即可:// React组件示例 import { useEffect, useState } from 'react' function App() { const [portList, setPortList] = useState([]) const [receivedData, setReceivedData] = useState('') const [sendContent, setSendContent] = useState('') useEffect(() => { // 页面初始化加载串口列表 window.serialApi.listPorts().then(setPortList) // 注册串口数据监听 window.serialApi.onPortData((data) => { setReceivedData(prev => prev + data) }) }, []) const handleOpenPort = async (portPath) => { try { await window.serialApi.openPort(portPath, { baudRate: 9600 }) alert('串口打开成功') } catch (e) { alert(`串口打开失败:${e.message}`) } } const handleSend = async () => { try { await window.serialApi.writePort(sendContent) setSendContent('') } catch (e) { alert(`发送失败:${e.message}`) } } return ( <div style={{ padding: '20px' }}> <h2>串口列表</h2> {portList.map(port => ( <button key={port.path} onClick={() => handleOpenPort(port.path)} style={{ margin: '0 10px 10px 0', padding: '8px 12px' }} > 打开{port.path} </button> ))} <div style={{ margin: '20px 0' }}> <h3>收到的数据</h3> <div style={{ border: '1px solid #ccc', padding: '10px', minHeight: '100px' }}> {receivedData} </div> </div> <div> <h3>发送数据</h3> <input value={sendContent} onChange={(e) => setSendContent(e.target.value)} style={{ padding: '8px', width: '300px', marginRight: '10px' }} /> <button onClick={handleSend} style={{ padding: '8px 12px' }}>发送</button> </div> </div> ) } export default App
额外注意事项
- 如果serialport安装后运行报错需要重建,使用
electron-rebuild工具重建即可,重建针对主进程依赖模块生效,不需要修改React的构建配置 - 不要修改React构建配置强行把serialport打包到渲染进程,既不符合安全规范也会引发各类兼容问题
- TypeScript项目可以给
window.serialApi添加全局类型声明,避免类型报错
内容的提问来源于stack exchange,提问作者Peter Kusza
相关产品推荐
相关产品推荐

