You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 14:53:13