Electron 在鸿蒙 PC 上存数据,绕了三条路我选了最朴素的
昨天下午,我把一个 Electron 项目里的 SQLite 依赖删了。不是因为 SQLite 不好,是因为在鸿蒙 PC 上,它让我绕了太多弯路。
昨天下午两点,斌哥在群里说:"用户配置和历史记录得存本地,你搞个数据库吧。“我当时正在喝咖啡,回了句"SQLite 呗,轻量又成熟”。这个轻率的决定,让我接下来三个半小时经历了从自信到自嘲的完整过程。
下午 2:15 —— 方案一:sqlite3
打开项目目录,npm install sqlite3。在 Windows 上这是常规操作,npm 会自动拉取预编译二进制包。但在鸿蒙 PC 上,安装日志里弹出一堆 node-gyp 报错:找不到鸿蒙平台的预编译二进制。sqlite3 的 npm 包只提供了 Windows、macOS 和 Linux 的预编译二进制,鸿蒙不在支持列表里。
const sqlite3 = require('sqlite3').verbose();const db = new sqlite3.Database('./user.db');db.serialize(() => { db.run("CREATE TABLE IF NOT EXISTS config (key TEXT, value TEXT)"); db.run("INSERT INTO config VALUES (?, ?)", ['theme', 'dark']);});db.close();
但在鸿蒙上,第一行的 require('sqlite3') 就过不去。把 sqlite3 从 package.json 里删掉。第一条路,断了。
下午 2:50 —— 方案二:better-sqlite3
sqlite3 不行,那 better-sqlite3 呢?据说它用的是预编译绑定,性能更好。
npm install better-sqlite3,同样的问题:没有鸿蒙平台的预编译二进制,而且编译依赖更复杂。我试着配置了鸿蒙的交叉编译环境,编译跑起来了,我在旁边盯了五分钟,心里升起一丝希望。然后报错来了:uv_dlopen 不支持鸿蒙的动态库格式。鸿蒙 PC 上 Electron 的 Node.js 运行时对原生模块的加载有自己的规则,跟标准 Linux 不完全兼容。我坐在椅子上发了五分钟呆。
下午 3:30 —— 方案三:鸿蒙原生数据存储
这时候我换了个思路。既然 Node.js 的原生数据库模块在鸿蒙上水土不服,那能不能用鸿蒙系统自己的数据存储能力?
翻了翻鸿蒙文档,找到了 @ohos.data.preferences 这个模块。它是鸿蒙提供的轻量级键值存储,类似 Android 的 SharedPreferences。我的思路是在 Electron 主进程里调用它,然后通过 IPC 暴露给渲染进程:
const { ipcMain, app } = require('electron');let preferences;try { preferences = require('@ohos.data.preferences');} catch (e) { console.error('鸿蒙原生存储模块不可用', e.message);}ipcMain.handle('save-config', async (_event, key, value) => { if (!preferences) return false; const pref = await preferences.getPreferences( app.getPath('userData'), 'app_config' ); await pref.put(key, value); await pref.flush(); return true;});
思路很合理,但踩了一个没想到的坑:@ohos.data.preferences 只能在鸿蒙的原生应用环境里使用。Electron 主进程的 Node.js 模块加载路径并不包含鸿蒙的系统模块目录,require 会报 MODULE_NOT_FOUND。我试了 NODE_PATH、符号链接,甚至复制模块文件到 node_modules 里,都不行。这时候已经过去了一个半小时。有一种奇怪的倔强——我就不信这个简单的存储需求搞不定。
下午 4:15 —— 我到底在解决什么问题
去洗手间的路上,脑子里突然闪过一个念头:我到底需要数据库来干什么?
回到座位打开需求文档。用户配置:主题色、窗口大小、最近打开的文件列表,顶多二十个键值对。历史记录:最近 20 次操作记录。全部数据量加起来顶天了几百 KB,没有多表关联,没有并发写入。
"我他妈为什么要为几百 KB 的数据引入一个数据库?"我在心里骂了自己一句。我需要的是持久化存储,不是关系型数据库。一个 JSON 文件就能解决的事,我为什么要折腾 SQLite?不是因为 SQLite 不好,是因为我在用惯性做技术选型,而不是用脑子。
下午 4:30 —— 回归朴素
我删掉了所有数据库相关的依赖,在 main.js 同级目录下新建了 storage.js:
const fs = require('fs').promises;const path = require('path');const { app } = require('electron');const CONFIG_PATH = path.join(app.getPath('userData'), 'app-config.json');async function readConfig() { try { const data = await fs.readFile(CONFIG_PATH, 'utf-8'); return JSON.parse(data); } catch (err) { if (err.code === 'ENOENT') return {}; throw err; }}async function writeConfig(config) { const dir = path.dirname(CONFIG_PATH); await fs.mkdir(dir, { recursive: true }); await fs.writeFile( CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8' );}async function setTheme(theme) { const config = await readConfig(); config.theme = theme; await writeConfig(config);}async function getTheme() { const config = await readConfig(); return config.theme || 'light';}module.exports = { readConfig, writeConfig, setTheme, getTheme };
渲染进程通过 preload 桥接调用:
const { contextBridge, ipcRenderer } = require('electron');contextBridge.exposeInMainWorld('electronAPI', { setTheme: (theme) => ipcRenderer.invoke('set-theme', theme), getTheme: () => ipcRenderer.invoke('get-theme'),});
主进程里注册 handler:
const { ipcMain } = require('electron');const { setTheme, getTheme } = require('./storage');ipcMain.handle('set-theme', async (_event, theme) => { await setTheme(theme);});ipcMain.handle('get-theme', async () => { return getTheme();});
就这么简单。没有编译,没有原生模块依赖,没有平台兼容性烦恼。fs.promises 在鸿蒙的 Node.js 环境里完全支持,JSON 的读写速度对于几百 KB 的数据来说完全可以忽略。
我在鸿蒙 PC 上打包测试,两分钟通过。那一刻的心情有点哭笑不得。
尾声
回头想想,我其实掉进了"技术选型惯性"的坑。以前一说存数据就是 MySQL、PostgreSQL、SQLite,但很多时候我们不是在选"最好的工具",而是在选"最熟悉的工具"。当最熟悉的工具在新平台上失灵时,与其硬磕,不如退一步问问自己:我到底需要解决什么问题?
昨天下午五点,我把这段经历记在了团队 wiki 里,标题叫《关于我在鸿蒙上白折腾三小时这件事》。斌哥回了一个大笑的表情。你遇到过类似的情况吗?欢迎留言聊聊。
关于我
我叫老三,一个写了十年代码的前端 + 鸿蒙 ArkTS 水手。
目前主业做 Taro 多端项目,业余时间全泡在 AI 自动化和独立开发上——不是因为多热爱加班,而是打心底觉得,程序开发这件事正在被 AI 重构,我不跟上就会被甩下。
这个账号记录的就是我在这条路上的真实经历:踩过的坑、推翻过的方案、以及偶尔值得高兴的小进展。不写教科书,不讲大道理,只分享我自己试过、做过、确认过的东西。
如果你也在写代码,或者也在思考 AI 时代开发者该往哪走——欢迎留言聊聊,一起摸索。
本文遵循 MIT 协议,转载请注明出处。