开源 · MIT · 四个仓库

在 Android 或鸿蒙应用里,跑起真正的 Node.js

不是 WebView、不是云函数、不是子进程。JNI/NAPI 引导件在应用自己的进程内调用 dlopen("libnode.so") + node::Start,并在 JavaScript 与 Kotlin / ArkTS 之间建立双向 JSON 桥。

  • Android · minSdk 24
  • 鸿蒙 · 2in1 / 平板
  • Node.js v24.2.0
  • arm64-v8a
  • 4个仓库
  • 2个移动平台
  • v24.2.0Node.js 引擎
  • 100%进程内运行

nm -D libnode_bridge.so | grep Start

一个进程,一台引擎,两个包

桥只是一个加载器和一个说 JSON 的宿主。它从不链接引擎,而是运行期用 dlopen 找到 libnode.so、经 node::Start(int, char**) 进入。引擎本身就是一个普通共享对象—— ET_DYN、无 PT_INTERP。

NAPI addon 故意保留未定义的 napi_* 符号(-z undefs), 运行期再对着已加载的引擎(RTLD_GLOBAL)解析——用的永远是 Node 自己的 N-API 实现。换 Node 版本,不必重编桥。

  • libnode_bridge.so加载器——只链接 log/dl;dlopen + dlsym
  • libnode_host_client.soNAPI addon,经 process.dlopen 加载
  • libnode.soNode.js + V8,来自运行时包
  • bootstrap.js · host.jsJS 载荷,从应用 assets 物化到沙箱
in-process.txt
01

靠契约咬合,不靠链接

唯一需要的符号是 node::Start 与稳定的 N-API 表面。桥不关心、也不需要知道 提供它们的是哪个 Node 小版本。

02

每个平台两个包

约半 MB 的桥走自己的 semver;约 41 MB 的引擎,版本号就是 Node 版本。 发桥的修复不用碰引擎,反之亦然。

03

锁版本、可复现、可校验

每台引擎都由 CI 从官方 Node 源码包交叉编译;SHA-256 锁进 runtime.lock.json,7 项 ELF 门禁检查 ET_DYN、 PT_INTERP=0、ABI 与导出的入口符号。

04

失败永远可见

Idle → Starting → Running → Exited/Failed 是可观察的状态机;平台硬限制 一律上报给调用方,绝不粉饰。

./quickstart --platform

加两个依赖,调一次 start

桥提供 API,运行时提供引擎,两者缺一不可——桥从不硬依赖某个具体的引擎构建。

Android

Maven Central
build.gradle.kts
dependencies {
    implementation("io.github.fellow99:node-bridge:0.2.0")
    // 引擎,按 Node.js 版本钉死
    runtimeOnly("io.github.fellow99:node-runtime:24.2.0")
}
NodeRuntime.kt
// 先注册宿主能力,再启动(顺序不能反)
val rt = NodeRuntime.getInstance(context)
rt.host.register("fs.pickFolder") {
    """{"ok":true,"path":"/sdcard/Download"}"""
}
rt.start(NodeRuntimeOptions(
    entry = "bootstrap.js",
    appEntry = "selfcheck.js",
))
// 空闲 → 启动中 → 运行中 → 已退出 / 失败
查看 Android 接入指南

HarmonyOS 鸿蒙

ohpm
terminal
ohpm install @fellow99/node-bridge
ohpm install @fellow99/node-runtime@24.2.0
# 两个包都要装——桥不依赖运行时包
JIT 权限:V8 JIT 需要在 module.json5 中申请 ohos.permission.kernel.ALLOW_WRITABLE_CODE_MEMORY ACL;没有它 --jitless 仍可运行,但 WebAssembly 不可用。
NodeRuntime.ets
import { NodeRuntime } from '@fellow99/node-bridge';

NodeRuntime.getInstance().start(ctx, {
  scriptPath: '<沙箱路径>/payload/bootstrap.js',
});
查看鸿蒙接入指南

如实告知的限制——上报,绝不静默降级

  • node::Start 每进程只能执行一次,崩溃即终态;需要可重启就把 Node 放进独立的 :node 进程。
  • 鸿蒙只面向 2in1 / 平板(不做手机);Android 需 minSdk 24。两端都只提供 arm64-v8a。
  • 应用 assets 不是文件系统路径;启动前 JS 载荷会被物化进应用沙箱——这件事由库自动完成。
  • JIT 已在真机验证:JIT 与 WebAssembly 在 Android 15 与鸿蒙 2in1 设备上均确认可用。

git clone --recurse-submodules

四个仓库,零个黑盒

每一个引擎二进制都由 CI 从官方 Node.js 源码构建,发布前过 SHA-256 校验与 ELF 门禁。 总控工作区以 submodule 把四个仓挂在一起。