本文记录下移植nodejs三方库bcrypt和 sqlite3到鸿蒙PC上的踩坑过程和经验分享。鸿蒙PC平台nodejs三方库移植也很简单。鸿蒙 PC 安装 bcrypt 和sqlite3三方库实录:预编译与源码编译双路实战,关于 HarmonyOS + Node.js 原生模块的兼容性测试验证总结笔记。
更多交流学习,欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/
欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
猫哥的博客:https://blog.csdn.net/qq8864
背景
在鸿蒙 PC 上,(安装好node环境的前提下)执行了这样一个简单的命令:
npm install bcrypt
然后——没有任何报错。node-gyp 没有崩毁,编译器没有报缺头文件,3 个包安静地装好,测试用例全部通过。
这在 Node.js 原生模块的安装史上,算得上一个"小反常"。
不过后面我们进一步验证了:不仅预编译能装,强制走源码编译也完全 OK。鸿蒙 PC 上的 C++ 工具链(来自 Harmonybrew 的 clang、make、python3)完整可用。
注: 踩坑记,从应用市场上下载安装的node-H软件包,版本23.x有问题啊,直接崩掉,建议卸载掉它。推荐使用 Harmonybrew 的软件包工具安装node环境。
安装成功真机截图:

bcrypt 是什么?
bcrypt 是一个密码哈希库,以 C++ 编写,通过 Node-API 暴露给 JavaScript。它的安装有两种路径:
路径 A(预编译):
bcrypt 官方发布的 .node 二进制
→ node-gyp-build(安装时脚本)
复制到 node_modules 中
路径 B(源码编译):
bcrypt 源码 (.cc/.h)
→ node-gyp 调用 C++ 编译器
生成 .node 二进制
路径 A 静默无感,路径 B 则依赖完整的 C/C++ 编译工具链。绝大多数安装报错都发生在路径 B。
常规安装常见的坑
如果走源码编译路径,鸿蒙系统并不默认提供 GNU/Unix 开发工具链,典型的报错有:
| 错误信息 | 根因 |
|---|---|
gyp ERR! stack Error: not found: make | 缺 GNU Make |
fatal error: 'node.h' file not found | Node 头文件未安装 |
fatal error: 'openssl/opensslv.h' file not found | OpenSSL 开发头文件缺失 |
gyp ERR! stack Error: Could not find any Python installation | Python 未安装(node-gyp 依赖) |
clang: error: no such file or directory | 编译器路径未配置 |
环境准备:在鸿蒙 PC 上搭建 C++ 编译工具链
在尝试安装原生模块之前,需要先确保鸿蒙 PC 具备基本的 C/C++ 编译环境。这块依赖 Harmonybrew(专为 OpenHarmony 移植的 Homebrew 发行版)来完成。
⛔️ HMDFS 文件系统缺陷
HiShell 环境中TMPDIR默认指向/storage/Users/currentUser,该目录承载在 HMDFS 上。HMDFS 存在已知文件系统缺陷,可能导致node-gyp编译过程中临时文件写入失败。建议在安装工具链后,先设置环境变量将临时目录和工作目录指向非 HMDFS 路径:
export TMPDIR=/data/storage/el2/base/files/tmp mkdir -p $TMPDIR详见 featured-packages.md 场景一 中的完整说明。
安装 Harmonybrew
| 步骤 | 操作 |
|---|---|
| ① 卸载冲突软件 | 如果安装了 GitNext 和 DevBox,先卸载 |
| ② 打开安全开关 | 设置 → 系统 → 开发者选项 → 打开"开发者选项";设置 → 隐私和安全 → 高级 → 打开"运行来自非应用市场的扩展程序" |
| ③ 开启隐藏开发者菜单 | 设置 → 关于本机 → 连续点击"软件版本"7 次 → 确认重启 |
| ④ 安装 Harmonybrew | zsh -c "$(curl -fsSL https://harmonybrew.atomgit.com/install.sh)" |
| ⑤ 配置环境变量 | echo 'eval "$(/storage/Users/currentUser/.harmonybrew/bin/brew shellenv)"' >> ~/.zshrc 然后执行 eval 使生效 |
本文收录于 Harmonybrew 特色软件包清单
安装编译所需工具
推荐使用 Harmonybrew 的一站式开发工具包:
# 安装开发工具包(含 ohos-sdk clang、llvm-gcc-compat、make、coreutils 等)
brew install devel-base
# 同时安装 Python(node-gyp 依赖)
brew install python
💡 为什么推荐
devel-base? 它相当于 Debian 中的build-essential,级联依赖了ohos-sdk、llvm-gcc-compat、make、coreutils等包。其中ohos-sdk提供的 clang 链接器经过了封装,编译产物会自动带有代码签名,确保.node文件能被 Node.js 运行时正常加载。
如果只需要验证编译链路通不通,安装普通 LLVM 也可以:
brew install llvm make cmake python
但正式项目建议使用 devel-base,避免因代码签名问题导致运行时加载失败。
安装后检查关键工具是否就绪:
clang++ --version # C++ 编译器
make --version # GNU Make
python3 --version # node-gyp 脚本依赖
💡 提示:
brew list可查看已安装的全部包;brew search [keyword]搜索可用包;brew update更新包索引。
这套环境就绪后,鸿蒙 PC 就具备了编译绝大多数 node-gyp 原生模块的能力。下面安装 bcrypt 就不再依赖预编译路径——即使用走源码编译,工具链也完整可用。
为什么这次这么顺利?
1. bcrypt@6.0.0 用了预编译策略,且覆盖了鸿蒙的架构
这是最核心的原因。
bcrypt 从 6.x 开始,安装脚本从 node-gyp rebuild(每次都编译)切换为 node-gyp-build(优先下载预编译二进制)。而 bcrypt@6.0.0 的官方预编译包覆盖了以下平台:
prebuilds/
├── darwin-arm64
├── darwin-x64
├── linux-arm
│ ├── bcrypt.glibc.node
│ └── bcrypt.musl.node
├── linux-arm64
│ ├── bcrypt.glibc.node → glibc 变体
│ └── bcrypt.musl.node → musl 变体,鸿蒙命中
├── linux-x64
│ ├── bcrypt.glibc.node
│ └── bcrypt.musl.node
├── win32-arm64
├── win32-x64
安装时 node-gyp-build 检测到 linux-arm64 + musl libc,精准匹配了 bcrypt.musl.node,直接复制使用,完全绕过了 C++ 编译环节。
2. --tag-libc 构建参数正确生成了 musl 变体
bcrypt@6.0.0 的 npm 构建脚本是:
prebuildify --napi --tag-libc --strip
--tag-libc 参数让构建 CI 分别为 glibc 和 musl 各生成一个 .node 变体:
linux-arm64/
├── bcrypt.glibc.node → 链接 glibc,适用 Ubuntu/Debian 等
└── bcrypt.musl.node → 链接 musl,适用 Alpine/HarmonyOS
鸿蒙 PC 的系统 libc 是 musl,node-gyp-build 自动选择了 bcrypt.musl.node。不依赖任何二进制兼容层,完美匹配。
3. 即使强制走源码编译,Harmonybrew 工具链也完全胜任
我们还做了另一个实验——删除 build/ 目录后强制走源码编译:
rm -rf node_modules/bcrypt/build
npm rebuild bcrypt --foreground-scripts
输出清晰显示:clang++ 被调用编译了 src/blowfish.cc、src/bcrypt.cc 和 src/bcrypt_node.cc,链接生成了 bcrypt_lib.node。测试全部通过。

这意味着 Harmonybrew 安装的 clang 编译器、GNU Make、Python 3、全链路可用。
| 依赖 | 状态 |
|---|---|
clang++(C++ 编译器) | ✅ 来自 Harmonybrew |
make(构建工具) | ✅ 来自 Harmonybrew |
python3(node-gyp 脚本依赖) | ✅ 来自 Harmonybrew |
node.h(Node API 头文件) | ✅ 随 Node 安装自带 |
4. 完整的测试用例验证了功能正确性
为了确认编译产物正确可用,我们编写了一组覆盖 bcrypt 核心 API 的测试用例,涵盖:
- 异步 hash + compare:多个密码串(含中文、空密码)
- 不同 saltRounds:验证
$2b$格式和轮数编码 - 同步 API:
hashSync/compareSync - 边界情况:空密码输入
- getRounds:校验 hash 中编码的 saltRounds 值
const bcrypt = require('bcrypt');
const passwords = ['test123', 'Hello@2024', 'aB3#xyZ!', '密码123', ''];
const saltRounds = [4, 10, 12];
async function runTests() {
let passed = 0;
let failed = 0;
console.log('🧪 bcrypt 测试用例\n' + '='.repeat(40));
// 测试 1: 基础 hash + compare
console.log('\n📌 测试 1: hash + compare 基本流程');
for (const pw of passwords) {
try {
const hash = await bcrypt.hash(pw, 10);
const match = await bcrypt.compare(pw, hash);
const wrong = await bcrypt.compare('wrong-' + pw, hash);
const ok = match === true && wrong === false;
console.log(` ${ok ? '✅' : '❌'} hash("${pw || '(空)'}") → compare → ${ok ? '正确' : '失败'}`);
ok ? passed++ : failed++;
} catch (e) {
console.log(` ❌ hash("${pw || '(空)'}") 抛出异常: ${e.message}`);
failed++;
}
}
// 测试 2: 不同 saltRounds
console.log('\n📌 测试 2: 不同 saltRounds');
for (const r of saltRounds) {
try {
const hash = await bcrypt.hash('test123', r);
const parts = hash.split('$');
const rounds = parts[2];
const salt = (parts[3] || '').slice(0, 6);
const ok = rounds === String(r) && parts[1] === '2b';
console.log(` ${ok ? '✅' : '❌'} saltRounds=${r} → $${parts[1]}$${rounds}$${salt}...`);
ok ? passed++ : failed++;
} catch (e) {
console.log(` ❌ saltRounds=${r} 异常: ${e.message}`);
failed++;
}
}
// 测试 3: 同步 API
console.log('\n📌 测试 3: 同步 API (hashSync / compareSync)');
try {
const hash = bcrypt.hashSync('sync-test', 8);
const ok = bcrypt.compareSync('sync-test', hash) && !bcrypt.compareSync('wrong', hash);
console.log(` ${ok ? '✅' : '❌'} hashSync + compareSync ${ok ? '正常' : '异常'}`);
ok ? passed++ : failed++;
} catch (e) {
console.log(` ❌ sync API 异常: ${e.message}`);
failed++;
}
// 测试 4: 空值保护
console.log('\n📌 测试 4: 边界情况');
try {
const h1 = bcrypt.hashSync('', 4);
const ok = bcrypt.compareSync('', h1);
console.log(` ${ok ? '✅' : '❌'} 空密码 hash+compare ${ok ? '正常' : '异常'}`);
ok ? passed++ : failed++;
} catch (e) {
console.log(` ❌ 空密码处理: ${e.message}`);
failed++;
}
// 测试 5: getRounds
console.log('\n📌 测试 5: getRounds 解析 hash 轮数');
try {
const hash = bcrypt.hashSync('rounds-test', 11);
const rounds = bcrypt.getRounds(hash);
const ok = rounds === 11;
console.log(` ${ok ? '✅' : '❌'} getRounds() → ${rounds} ${ok ? '(正确)' : '(应为11)'}`);
ok ? passed++ : failed++;
} catch (e) {
console.log(` ❌ getRounds 异常: ${e.message}`);
failed++;
}
// 汇总
console.log('\n' + '='.repeat(40));
const total = passed + failed;
console.log(`📊 总计: ${total} 用例 | ✅ 通过: ${passed} | ❌ 失败: ${failed}`);
console.log(total === passed ? '\n🎉 全部通过!' : `\n⚠️ 有 ${failed} 个失败`);
}
runTests().catch(e => console.error('测试框架异常:', e));
输出结果(从源码编译的 bcrypt_lib.node 上运行):

🧪 bcrypt 测试用例
========================================
📌 测试 1: hash + compare 基本流程
✅ hash("test123") → compare → 正确
✅ hash("Hello@2024") → compare → 正确
✅ hash("aB3#xyZ!") → compare → 正确
✅ hash("密码123") → compare → 正确
✅ hash("(空)") → compare → 正确
📌 测试 2: 不同 saltRounds
✅ saltRounds=4 → $2b$4$zQX5l...
✅ saltRounds=10 → $2b$10$WrMpR...
✅ saltRounds=12 → $2b$12$VgCfD...
📌 测试 3: 同步 API (hashSync / compareSync)
✅ hashSync + compareSync 正常
📌 测试 4: 边界情况
✅ 空密码 hash+compare 正常
📌 测试 5: getRounds 解析 hash 轮数
✅ getRounds() → 11 (正确)
========================================
📊 总计: 10 用例 | ✅ 通过: 10 | ❌ 失败: 0
🎉 全部通过!
5 组测试覆盖了 10 个子用例,全部通过。无论是预编译 .node 还是源码编译的 bcrypt_lib.node,功能表现完全一致。
一个有意思的细节
新版 npm(10.x+)中,--build-from-source 已被废弃:
npm warn Unknown command: "--build-from-source"
正确的做法是:
# 方式一:删预编译二进制触发回退
rm -rf node_modules/bcrypt/prebuilds
npm rebuild bcrypt
# 方式二:直接调用 node-gyp
npm rebuild bcrypt --foreground-scripts
# 方式三:最底层
cd node_modules/bcrypt && npx node-gyp rebuild --verbose
--foreground-scripts 可以把编译过程的每一条 clang++ 命令都输出到终端,方便调试。
延伸实战:sqlite3 的移植教训
除了 bcrypt,我们还在同一台机器上移植了另一个 native 模块——sqlite3@6.0.1。这次遇到的情况完全不同,正好验证了工具链的健壮性。
首次安装:编译失败
npm install sqlite3
报错关键信息:
514 error make: ar: No such file or directory
514 error make: *** [../node-addon-api/nothing.target.mk:130: Release/obj.target/../node-addon-api/nothing.a] Error 127

根因分析
ar(GNU Archiver)是 binutils 的一部分,负责将 .o 目标文件打包为 .a 静态库。node-gyp 在编译 native 模块时,通过 Makefile 调用 ar 来创建中间静态库,但 Harmonybrew 默认的 devel-base 没有包含 ar。
为什么 bcrypt 没遇到这个问题?
| 模块 | 安装路径 | 结果 |
|---|---|---|
| bcrypt | prebuild 预编译 → 直接下载 .node 二进制 | ✅ 成功,零编译 |
| sqlite3 | prebuild-install 超时 → 回退 node-gyp rebuild 源码编译 | ❌ 缺少 ar 失败 |
bcrypt 的预编译路径完全绕过了 C++ 编译环节;而 sqlite3 在预编译下载超时后回退到源码编译,才暴露出 ar 缺失的问题。
修复
安装提供 ar 的工具包:
brew install binutils
或更全面的:
brew install llvm-gcc-compat binutils
验证:
which ar # → /harmonybrew/bin/ar
ar --version # → GNU ar (GNU Binutils) ...
重新安装
npm rebuild sqlite3 --foreground-scripts
这次编译链路完整走通:clang++ 编译 .cc → ar 打包 .a → clang++ 链接生成 .node,最终输出 added 57 packages,无任何 gyp ERR!。

测试验证
参照 bcrypt 的测试风格,为 sqlite3 编写了 10 组测试用例:
| # | 测试项 | 覆盖内容 |
|---|---|---|
| 1 | 打开/关闭数据库 | 文件创建、连接生命周期 |
| 2 | CREATE TABLE + INSERT | DDL、参数化插入、lastID |
| 3 | SELECT 查询 | db.all()、排序、行数验证 |
| 4 | 参数化查询 | 防 SQL 注入、db.get() |
| 5 | UPDATE | 修改数据、this.changes |
| 6 | DELETE | 删除前后行数对比 |
| 7 | 事务 | BEGIN/COMMIT/ROLLBACK 可见性 |
| 8 | 错误处理 | NOT NULL 约束违反捕获 |
| 9 | serialize 串行化 | 执行顺序保证 |
| 10 | 内存数据库 | :memory: 模式读写 |
全部 10 组通过:

🧪 sqlite3 测试用例
========================================
📌 测试 1: 打开和关闭数据库 ✅
📌 测试 2: 建表 + 插入数据 ✅
📌 测试 3: SELECT 查询 ✅
📌 测试 4: 参数化查询 ✅
📌 测试 5: UPDATE ✅
📌 测试 6: DELETE ✅
📌 测试 7: 事务 ✅
📌 测试 8: 错误处理 ✅
📌 测试 9: serialize 串行化 ✅
📌 测试 10: 内存数据库 ✅
========================================
📊 总计: 10 用例 | ✅ 通过: 10 | ❌ 失败: 0
🎉 全部通过!
sqlite3 移植的核心经验
- 预编译不是万能的——bcrypt 走预编译一路顺风,但 sqlite3 的预编译下载超时后就暴露了工具链缺口
ar是编译链路的关键一环——devel-base包含了clang++、make、python3,但缺少binutils(ar、ld等)。安装binutils即可补齐- 源码编译路径验证了工具链的完整性——修复后 sqlite3 完整走通了 C++ 编译→静态库打包→链接的全流程,证明 Harmonybrew 工具链完全胜任 node-gyp native 模块的编译需求
--foreground-scripts是调试利器——能看到完整的 clang++ 编译命令和ar打包过程,快速定位缺失的工具
最终结论
| 因素 | 角色 |
|---|---|
| bcrypt@6.x 预编译策略 | 关键因素——走预编译路径时零依赖安装 |
--tag-libc 构建参数 | 生成了 -glibc/-musl 双变体,鸿蒙精确命中后者 |
| 鸿蒙系统是 ARM64 + musl | 与预编译包完美匹配,无需兼容层 |
devel-base(Harmonybrew 工具链) | 预编译路径未使用,但源码编译路径完整可用 |
所以这背后是两个好消息:
预编译路径:bcrypt@6.0.0 的构建者用
--tag-libc为不同 libc 分别构建了变体,鸿蒙 PC 是linux-arm64+ musl,node-gyp-build精准命中了bcrypt.musl.node——零编译,装完即用。
源码编译备胎:即使预编译二进制不存在(或被人删了),
devel-base工具链已就绪,直接npm rebuild能顺利从.cc源码编译出.node二进制。两条路都通。
对鸿蒙上 Node.js 开发者的参考
- 优先选择支持 prebuild 的包,安装过程最省心
- 即使包没有预编译,有
devel-base工具链(clang + make + python3),大部分node-gyp模块也能从源码编译 - bcrypt
--tag-libc是一个好实践——如果开发 native 模块,建议也加上这个参数,对齐不同 libc 环境下安装者的体验 - 如果遇到
--build-from-source报 unknown command,换成--foreground-scripts,这是新 npm 的正确方式 - 迁移 sqlite3 等模块的额外教训:安装
binutils补全ar等归档工具,确保源码编译链路完整
📸 参考资料
- 完整工具链安装指南:Harmonybrew 特色软件包清单
- 本文涉及的测试代码:test-bcrypt.js、test-sqlite3.js
579

被折叠的 条评论
为什么被折叠?



