Node.js 原生模块调用
前言
Electron 的主进程运行在 Node.js 环境中,理论上可以安装和使用任何 npm 包。但原生模块(Native Modules,即包含 C/C++ 编译代码的包)有一个关键问题:Node.js 版本与 Electron 内置 Node.js 版本不匹配。本文将详细讲解原生模块的安装、编译和常见场景的最佳实践。
原生模块的版本问题
Electron 使用的是自己的 Node.js
Electron 内置的 Node.js 版本(如 v20.x)和系统全局安装的 Node.js 版本可能不同。原生模块(如 sqlite3)使用 node-gyp 编译时,链接的是全局 Node.js 的 .node 文件,在 Electron 中运行时就会出现版本不匹配的崩溃:
Error: The module '/node_modules/sqlite3/build/Release/sqlite3.node'
was compiled against a different Node.js version.解决方案一览
| 方案 | 原理 | 适用场景 |
|---|---|---|
| electron-rebuild | 用 Electron 版本重新编译原生模块 | 需要使用原生模块时 |
| @electron/rebuild | electron-rebuild 的更新维护版 | 同上 |
| 纯 JS 替代方案 | 用纯 JavaScript 实现相同功能 | 性能要求不高的场景 |
| prebuild-install | 预编译二进制分发 | CI/CD 环境 |
| node-pre-gyp | 运行时动态下载预编译二进制 | 跨平台发布 |
使用 electron-rebuild
安装与配置
# 安装 electron-rebuild
pnpm add -D @electron/rebuild
# 安装原生模块(以 sqlite3 为例)
pnpm add sqlite3编译原生模块
# 手动触发一次编译
pnpm exec electron-rebuild
# 指定 Electron 版本
pnpm exec electron-rebuild -v 35.2.0
# 只编译特定模块
pnpm exec electron-rebuild -m ./node_modules/sqlite3集成到 package.json
{
"scripts": {
"postinstall": "electron-rebuild",
"rebuild": "electron-rebuild"
}
}postinstall 脚本会在每次 pnpm install 后自动执行,确保原生模块与当前 Electron 版本兼容。
Forge 项目中的 electron-rebuild
Electron Forge 内置了 @electron-forge/plugin-auto-unpack-natives,会在打包时自动处理原生模块。但为了保险起见,仍建议在开发环境中运行 electron-rebuild:
# Forge 项目中重新编译
pnpm rebuild常见原生模块使用
better-sqlite3(推荐替代 sqlite3)
socket3 是纯 JavaScript 实现的 SQLite,性能略低于原生模块但安装更简单:
pnpm add better-sqlite3
pnpm exec electron-rebuild -m ./node_modules/better-sqlite3// main.ts — 使用 better-sqlite3
import Database from 'better-sqlite3';
import path from 'path';
import { app } from 'electron';
const dbPath = path.join(app.getPath('userData'), 'app.db');
const db = new Database(dbPath);
// 创建表
db.exec(`
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
// 插入
const insert = db.prepare(
'INSERT INTO notes (title, content) VALUES (?, ?)'
);
insert.run('学习 Electron', 'IPC 通信是 Electron 的核心');
// 查询
const rows = db.prepare('SELECT * FROM notes ORDER BY created_at DESC').all();
console.log('笔记列表:', rows);
// 关闭
db.close();sharp(图片处理)
原生图片处理库,处理缩略图、格式转换非常高效:
pnpm add sharp
pnpm exec electron-rebuild -m ./node_modules/sharp// main.ts — 图片处理
import sharp from 'sharp';
import path from 'path';
import { ipcMain, app } from 'electron';
ipcMain.handle('image:thumbnail', async (_event, inputPath: string, width: number) => {
const outputPath = path.join(app.getPath('temp'), `thumb_${Date.now()}.jpg`);
await sharp(inputPath)
.resize(width, null, { withoutEnlargement: true })
.jpeg({ quality: 80 })
.toFile(outputPath);
return outputPath;
});
ipcMain.handle('image:convert', async (_event, inputPath: string, format: 'png' | 'webp' | 'jpeg') => {
const outputPath = inputPath.replace(/\.\w+$/, `.${format}`);
await sharp(inputPath)[format]({ quality: 85 }).toFile(outputPath);
return outputPath;
});
ipcMain.handle('image:meta', async (_event, imagePath: string) => {
const meta = await sharp(imagePath).metadata();
return {
width: meta.width,
height: meta.height,
format: meta.format,
size: meta.size,
};
});node-fetch(HTTP 请求)
Electron 主进程虽然可以用 Node.js 的 fetch(Node 18+),但如果需要更精细的控制,可以使用 node-fetch 或 axios:
pnpm add node-fetch// main.ts — 在主进程中使用 node-fetch
import fetch from 'node-fetch';
import { ipcMain } from 'electron';
ipcMain.handle('http:download', async (_event, url: string) => {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const buffer = await response.buffer();
return buffer.toString('base64');
});纯 JavaScript 替代方案
如果原生模块的编译过程过于复杂,可以考虑纯 JS 替代:
| 原生模块 | JS 替代 | 说明 |
|---|---|---|
sqlite3 | sql.js | WebAssembly 实现的 SQLite,无需编译 |
sharp | @squoosh/lib | 图片压缩,但功能较少 |
node-canvas | SVG 渲染 | Electron 渲染进程直接用 Canvas |
nodejieba | — | 无成熟 JS 替代,可尝试预编译二进制 |
使用 sql.js(无编译的 SQLite)
pnpm add sql.js// main.ts — sql.js 使用(无需 electron-rebuild)
import initSqlJs from 'sql.js';
import fs from 'fs';
import { ipcMain, app } from 'electron';
let db: Awaited<ReturnType<typeof initSqlJs>>['Database'];
ipcMain.handle('db:init', async () => {
const SQL = await initSqlJs();
const dbPath = path.join(app.getPath('userData'), 'app.sqlite');
if (fs.existsSync(dbPath)) {
const buffer = fs.readFileSync(dbPath);
db = new SQL.Database(buffer);
} else {
db = new SQL.Database();
}
return { success: true };
});
ipcMain.handle('db:query', async (_event, sql: string) => {
try {
const results = db.exec(sql);
return { columns: results[0]?.columns, rows: results[0]?.values ?? [] };
} catch (error) {
return { error: (error as Error).message };
}
});
ipcMain.handle('db:save', async () => {
const data = db.export();
const buffer = Buffer.from(data);
fs.writeFileSync(path.join(app.getPath('userData'), 'app.sqlite'), buffer);
});注意:sql.js 在 WebAssembly 模式下运行,数据存储需要手动保存到文件系统。
Worker Threads 多线程
主进程中执行 CPU 密集型任务(如大文件处理、JSON 解析)会阻塞事件循环,导致 UI 卡顿。Worker Threads 可以在后台线程执行这些任务:
// main.ts — Worker Threads 示例
import { Worker } from 'worker_threads';
import path from 'path';
import { ipcMain } from 'electron';
// 创建 Worker 执行后台任务
function runWorker(data: unknown) {
return new Promise((resolve, reject) => {
const worker = new Worker(path.join(__dirname, 'worker.js'), {
workerData: data,
});
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0) reject(new Error(`Worker 退出码: ${code}`));
});
});
}
// worker.js — Worker 线程入口
import { workerData, parentPort } from 'worker_threads';
const result = heavyComputation(workerData);
parentPort?.postMessage(result);
function heavyComputation(data: unknown) {
// 这里是 CPU 密集型计算,不会阻塞主线程
// ...
return result;
}// main.ts — IPC 处理器
ipcMain.handle('worker:process', async (_event, data: unknown) => {
return runWorker(data);
});electron-rebuild 在 CI/CD 中
GitHub Actions 中的完整构建流程:
# .github/workflows/build.yml
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install
- name: Rebuild native modules
run: pnpm exec electron-rebuild
- name: Build app
run: pnpm make
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: electron-app
path: out/make常见问题
编译报错 “node-gyp not found”
# 安装 node-gyp 依赖(macOS 需要 Xcode)
brew install python3 node-gyp
# Windows 需要 Visual Studio Build Tools
# npm install --global --production windows-build-toolspnpm 安装原生模块失败
// package.json
{
"pnpm": {
"onlyBuiltDependencies": ["better-sqlite3", "sharp", "sqlite3"]
}
}原生模块在打包后找不到
// forge.config.js — 标记为非 asar 打包
packagerConfig: {
asar: {
unpack: '**/node_modules/better-sqlite3/**',
},
}小结
- 原生模块需要与 Electron 内置的 Node.js 版本匹配才能运行
electron-rebuild是重新编译原生模块的标准工具better-sqlite3和sharp是常用的高性能原生模块sql.js(WebAssembly)提供了免编译的 SQLite 替代方案Worker Threads用于在主进程中进行 CPU 密集型计算而不阻塞 UI- 生产构建中注意
asar.unpack配置,确保原生二进制正确打包
下一篇文章我们将介绍 React / Vue 与 Electron 集成,使用 electron-vite 工具链构建现代化前端 + Electron 应用。
评论
Written by
AI-Writer
Related Articles
Electron Forge 开发环境与项目配置
使用 Electron Forge 脚手架快速创建项目、配置 Vite/Webpack 构建、开发热重载调试、解析 package.json 关键字段与标准目录结构
Read MoreIPC 通信机制详解
深入理解 Electron 的 IPC 模块:invoke/handle 双向异步模式、ipcRenderer.on 事件监听、channel 命名规范、进程间数据传输规则
Read MoreElectron 应用打包与分发
使用 electron-builder 配置多平台打包(Windows NSIS / macOS DMG / Linux AppImage),设置自动更新(electron-updater),配置代码签名(Authenticode / Apple Developer),搭建 GitHub Actions CI 流水线
Read More