如何在Tauri桌面应用中集成NPM包?以serialport为例
解决Tauri中集成NPM包(如serialport)的方案
Tauri前端没有内置Node.js环境,依赖Node核心API或C++扩展的NPM包(比如serialport)没法直接在前端代码里用,下面给两种可行方案:
方案1:用Tauri Sidecar运行Node脚本
把需要用serialport的逻辑单独写成Node脚本,作为sidecar让Tauri启动,通过标准IO和前端通信。
步骤1:编写Node脚本(serialport-service.js)
const SerialPort = require('serialport'); const Readline = require('@serialport/parser-readline'); // 处理Tauri发来的指令 process.stdin.on('data', async (data) => { const { action, portPath } = JSON.parse(data.toString().trim()); if (action === 'list') { // 列出所有串口 const ports = await SerialPort.list(); process.stdout.write(JSON.stringify(ports) + '\n'); } else if (action === 'open') { // 打开指定串口并监听数据 const port = new SerialPort(portPath, { baudRate: 9600 }); const parser = port.pipe(new Readline({ delimiter: '\n' })); parser.on('data', (line) => { process.stdout.write(JSON.stringify({ type: 'data', content: line }) + '\n'); }); port.on('error', (err) => { process.stderr.write(JSON.stringify({ type: 'error', msg: err.message }) + '\n'); }); } });
步骤2:配置Tauri打包规则(tauri.conf.json)
推荐用pkg把Node脚本打包成可执行文件(避免依赖系统Node),先执行npm i -g pkg,再运行pkg serialport-service.js生成对应平台的可执行文件,然后在配置文件中添加:
"tauri": { "bundle": { "externalBin": ["./serialport-service"] } }
步骤3:Rust后端启动sidecar并通信(src-tauri/src/main.rs)
use tauri::{api::process::Command, Manager}; use serde_json::Value; #[tauri::command] async fn get_serial_ports() -> Result<Vec<Value>, String> { // 创建sidecar命令 let mut cmd = Command::new_sidecar("serialport-service") .map_err(|e| format!("创建sidecar失败: {}", e))?; let (mut rx, mut child) = cmd.spawn() .map_err(|e| format!("启动sidecar失败: {}", e))?; // 发送"list"指令 child.write_all(b"{\"action\":\"list\"}\n") .map_err(|e| format!("向sidecar写入指令失败: {}", e))?; // 读取返回结果 if let Some(output) = rx.recv().await { let ports = serde_json::from_str(&output.stdout) .map_err(|e| format!("解析串口列表失败: {}", e))?; Ok(ports) } else { Err("未收到sidecar响应".into()) } } fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![get_serial_ports]) .run(tauri::generate_context!()) .expect("Tauri应用启动失败"); }
步骤4:前端调用Rust命令
async function loadSerialPorts() { try { const ports = await window.__TAURI__.invoke('get_serial_ports'); console.log('串口列表:', ports); } catch (err) { console.error('获取串口列表出错:', err); } }
方案2:用Rust库替代NPM包(推荐)
直接用Rust的串口库实现功能,更贴合Tauri架构,性能更好还不用依赖Node。
步骤1:添加Rust依赖(src-tauri/Cargo.toml)
[dependencies] serialport = "0.11" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0"
步骤2:Rust后端实现串口逻辑(src-tauri/src/main.rs)
use serialport::SerialPort; use tauri::{Manager, State}; use std::sync::Mutex; // 用State管理全局串口连接 struct SerialConn { port: Mutex<Option<Box<dyn SerialPort>>>, } #[tauri::command] fn list_ports() -> Result<Vec<serialport::SerialPortInfo>, String> { serialport::available_ports() .map_err(|e| format!("获取串口列表失败: {}", e)) } #[tauri::command] async fn open_port(port_path: &str, state: State<'_, SerialConn>) -> Result<(), String> { let mut port = serialport::new(port_path, 9600) .open() .map_err(|e| format!("打开串口失败: {}", e))?; *state.port.lock().unwrap() = Some(port); Ok(()) } #[tauri::command] async fn read_port_data(state: State<'_, SerialConn>) -> Result<String, String> { let mut port_guard = state.port.lock().unwrap(); if let Some(ref mut port) = *port_guard { let mut buf = [0; 1024]; let bytes_read = port.read(&mut buf) .map_err(|e| format!("读取串口数据失败: {}", e))?; Ok(String::from_utf8_lossy(&buf[..bytes_read]).to_string()) } else { Err("未打开串口".into()) } } fn main() { tauri::Builder::default() .manage(SerialConn { port: Mutex::new(None) }) .invoke_handler(tauri::generate_handler![list_ports, open_port, read_port_data]) .run(tauri::generate_context!()) .expect("Tauri应用启动失败"); }
步骤3:前端调用命令
// 列出串口 async function getPorts() { const ports = await window.__TAURI__.invoke('list_ports'); console.log('串口:', ports); } // 打开串口并读取数据 async function startReading(portPath) { try { await window.__TAURI__.invoke('open_port', { port_path: portPath }); console.log('串口已打开'); // 定时读取数据 setInterval(async () => { const data = await window.__TAURI__.invoke('read_port_data'); console.log('收到数据:', data); }, 100); } catch (err) { console.error('操作出错:', err); } }
为什么直接导入serialport不行?
Tauri前端运行在WebView里,没有Node.js运行时,也不支持Node核心API(比如fs、net)和C++扩展,所以依赖这些的NPM包没法直接在前端代码里运行。
内容的提问来源于stack exchange,提问作者masaaki1999
相关产品推荐
相关产品推荐

