鸿蒙 PC 移植 JupyterLab 全链路实战——Electron 壳、浏览器内 Python 内核与 HAP 真机避坑

适用基线:JupyterLab 4.x|OpenHarmony PC arm64|compatibleSdkVersion / targetSdkVersion 6.0.1(21)|2026-09

HarmonyOS OpenHarmony 鸿蒙PC JupyterLab Electron Python Pyodide WebAssembly HAP DevEco Studio

把 JupyterLab 以独立桌面应用的形式搬到鸿蒙 PC,真正困难的不是“把页面打开”,而是重新划分 Electron、Jupyter Server 与 Python 内核的运行边界。本文从可复现的工程路径出发,完整拆解 Electron 壳、前端静态化、Pyodide 浏览器内核、HAP 资源打包、SDK 锁定、签名、HNP 共存与真机验收;同时给出 node-static、设备 CPython、外部 Jupyter Server 三种模式的取舍、常见错误码的根因与修复顺序,以及浏览器 Python 在科学计算包、文件系统、网络和终端能力上的真实边界。目标不是做一个“能亮屏的 Demo”,而是跑通可安装、可启动、可编辑、可执行、可保存、可重开的完整 Notebook 闭环。

目录

  • 0. 30 秒先看结果:这次到底搬成了什么
  • 1. 先把概念边界说清:JupyterLab 本体不是 Electron
  • 2. 为什么不能原样搬:三个桌面假设同时失效
  • 3. 路线选择:默认 node-static + Pyodide,另外两条路什么时候用
  • 4. 工程架构:把“服务端依赖”拆成可替换边界
  • 5. 环境与目录:先把版本、架构和资源树钉死
  • 6. 第一步:从共用 runtime 派生 Lab 身份
  • 7. 第二步:把 Python 执行挪进浏览器
  • 8. 第三步:资源同步到 web_engine,并构建 HAP
  • 9. 第四步:签名、安装与启动
  • 10. 六类高频坑:从 10705000 到 ImportError 的完整排查链
  • 11. 真机验收:别只验证“能打开”
  • 12. Pyodide 的能力边界:能跑 Python,不等于等同桌面 CPython
  • 13. 什么时候切到设备 CPython 或 external Server
  • 14. 一条可复现的最短路径
  • 15. 常见问题 FAQ
  • 16. 收束:这类跨平台迁移真正要迁的是“边界”

0. 30 秒先看结果:这次到底搬成了什么

如果只看最终界面,你会觉得这件事像是“给 JupyterLab 套了一个鸿蒙窗口”。实际上,真正完成的是一条从 Web IDE 到 HAP 桌面应用的运行时重构链:JupyterLab 的前端工作区被保留下来,Electron 负责 PC 端窗口和加载,本地静态服务负责交付前端资源,Python 单元格默认不再依赖设备上的 Jupyter Server,而是进入浏览器内核;需要完整科学计算环境时,再切换到设备 CPython 或远程 Server。

表 1 不要用“能打开”代替“能用”:建议至少完成以下验收

验收点

结果

为什么它重要

HAP 可构建、可签名、可安装、可启动

必须通过

这是 T0,连这一层都不稳,后面的功能没有意义

Launcher / 多文档工作区正常

必须通过

证明加载的不是单一 Notebook 页面,而是完整 Lab 工作区

新建 / 打开 Notebook

必须通过

文件与文档模型进入可用状态

Python 单元格执行并回显

必须通过

证明 kernel adapter → 浏览器 Python 的执行链真的闭环

保存、关闭、重新打开

必须通过

“看起来能用”升级为“数据能留下来”

文件浏览、Markdown、语法高亮

建议通过

验证核心前端能力没有被静态化过程破坏

与 Notebook 姊妹应用共存

建议通过

验证 bundle、HNP 与签名身份已经彻底分离

关键判断  这条路线追求的不是一上来就复制 Linux 桌面的全部能力,而是先让“打开 → 编辑 → 执行 → 保存 → 重开”成为稳定闭环,再按需求补完整 CPython、终端和插件生态。

1. 先把概念边界说清:JupyterLab 本体不是 Electron

这类移植最容易在第一句话里就把边界说错。JupyterLab 官方把自己定义为面向 Notebook、代码和数据的 Web 交互式开发环境[1];它的 UI 本体运行在浏览器里,传统桌面使用方式通常是本机启动 Jupyter Server,再由浏览器连接。本文中的 Electron 并不是 JupyterLab 的“原生组成部分”,而是为了把 Web 应用包装成鸿蒙 PC 上可独立安装、可从桌面启动的 HAP 应用而引入的承载壳。

这一点非常重要,因为它直接决定迁移目标:你不是要把整个 JupyterLab 改写成 ArkUI,也不是把一套 Linux Python 环境粗暴塞进 HAP;更合理的做法,是尽量保留上游前端,把平台差异压缩在“窗口运行时、静态资源交付、内核执行、文件持久化、签名安装”这几层。

一句话架构  JupyterLab 负责“工作区与交互”,Electron 负责“桌面承载”,Pyodide / CPython / external Server 负责“代码执行”。把这三个角色分开,后面的取舍才不会乱。

图 1 迁移后的整体架构:保留 JupyterLab 前端,把 Electron、静态资源与 Python 内核解耦。

2. 为什么不能原样搬:三个桌面假设同时失效

2.1 假设一:系统里天然有桌面 Electron 运行时

普通 Windows / macOS / Linux 桌面应用可以把 Electron runtime 随应用一起发货,并依赖成熟的 Chromium、Node.js 与桌面窗口能力。到了鸿蒙 PC,你不能假定系统预装了与你版本匹配的桌面 Electron 运行时;工程需要借助 OpenHarmony PC 侧的 Electron 适配层与 web_engine,并按 HAP 的资源与模块规则重新组织。项目主线使用的就是 `libelectron.so` 承载路径[2]。

2.2 假设二:本地随时可以 `python -m jupyterlab`

标准 JupyterLab 桌面体验背后其实有一整套 Server 语义:contents、sessions、kernels、WebSocket、终端、扩展与认证。如果选择“完整本地模式”,就要把 CPython、Jupyter Server 以及依赖一起带到 OHOS arm64。真正麻烦的不是纯 Python 包,而是 NumPy、SciPy、cryptography 等包含 C/C++/Rust 扩展的依赖:它们需要与 OHOS 的 ABI、musl 工具链和目标架构匹配。

2.3 假设三:把前端 build 目录复制进包里就能跑

JupyterLab 是 Web 应用,但它并不是“纯静态站点”。如果直接把前端资源丢给静态服务器,很多默认调用仍会寻找 Server API。因此所谓“前端静态化”并不是简单复制文件,而是把内核、内容、会话等能力改成浏览器侧或适配层可提供的实现。JupyterLite 的做法证明了这条方向:内核可以在浏览器的 Web Worker 中运行,Python 可由 Pyodide 或 Xeus Python 提供[3]。本文工程采用的是相似的浏览器内核思想,但桌面壳、文件落盘与 HAP 打包仍有自己的适配层。

3. 路线选择:默认 node-static + Pyodide,另外两条路什么时候用

图 2 三种服务模式的取舍:先把核心闭环跑通,再决定是否为完整科学计算生态支付移植成本。

表 2 三种服务模式没有绝对优劣,选择依据是“运行边界”而不是功能数量

模式

执行位置

优点

主要代价

推荐场景

node-static + Pyodide

浏览器 Web Worker / WASM

启动链短;不要求设备先装 Python;最利于先跑通 HAP

包兼容受 WASM wheel、浏览器网络与线程模型限制

教学、脚本、轻量数据分析、离线 Notebook、产品原型

设备 CPython

鸿蒙 PC 本机 Python + Jupyter Server

行为最接近桌面 Jupyter;Server 语义完整

解释器、native wheel、体积与更新链路复杂

强依赖本地包、需要终端/PTY、需要原生扩展

external Server

局域网 / 云端 Jupyter Server

本地包最轻;远端可保留 Conda/GPU/完整生态

依赖网络、Token、证书、CORS/反向代理

企业计算集群、GPU 工作站、统一环境管理

当前主线文档把 `node-static` 作为鸿蒙 PC 的默认模式,并使用 Pyodide 作为浏览器内 Python 内核[2]。这里还有一个版本兼容点值得特别说明:早期轻量分支或旧截图里可能看到 Skulpt。Skulpt 是纯浏览器 Python 实现,体积和接入门槛很低,但语言与第三方包兼容性更有限;当前主线转向 Pyodide 后,执行语义更接近 CPython,且可以加载大量已经编译到 WebAssembly 的科学计算包[4]。

版本核对  如果你手上的分支状态栏仍显示 Skulpt,不要直接照着 Pyodide 的包安装与 WASM 说明排错。先确认 `runtime`、前端配置和内核包来自同一基线,再继续。

4. 工程架构:把“服务端依赖”拆成可替换边界

跨平台移植最怕把所有差异揉进一个巨大的 `if (ohos)`。更稳定的做法,是先把上游能力拆成边界,再让每个边界有自己的替代实现。这次移植可以分成五层:HAP / Stage 外壳、Electron 运行时、本地静态服务、JupyterLab 前端、Python 内核。

表 3 真正可维护的迁移:平台差异集中在边界层,而不是改遍整个上游代码

层级

原桌面环境常见做法

鸿蒙 PC 适配做法

尽量不动的部分

应用交付

exe/dmg/AppImage 或浏览器访问

HAP + Stage 生命周期

JupyterLab 业务前端

窗口与 Web Runtime

系统浏览器或 Electron

OpenHarmony Electron / web_engine

页面 DOM、Lumino、CodeMirror

Web 服务

Jupyter Server 提供 HTTP/WebSocket

node-static + 适配层,或 external

Lab 静态资源

Python 内核

ipykernel + CPython

Pyodide Web Worker;可选设备 CPython

Notebook 消息与单元格交互模型

文件与会话

Server contents/session API

本地适配、浏览器持久化或远端 Server

`.ipynb` 文档格式

图 3 构建链路:身份化、资源同步、Hvigor、签名和真机验收必须连成一条可重复流水线。

5. 环境与目录:先把版本、架构和资源树钉死

表 4 建议在真正改代码前把这些基础条件一次核对完

项目

建议基线

说明

Node.js

18+

用于 bootstrap、资源同步与构建辅助脚本,不代表目标设备需要预装 Node.js

DevEco Studio

可正常使用 HarmonyOS SDK 与 hvigorw

第一次建议用图形界面完成 Sync 和调试签名

compatibleSdkVersion

6.0.1(21)

当前已验证基线;不要无意写入不匹配的 beta stage

targetSdkVersion

6.0.1(21)

与上面保持一致,降低 web_engine 工具链漂移

目标架构

arm64-v8a / aarch64

设备侧 `.so`、可选 CPython 与 native wheel 必须同架构

bundleName

org.jupyter.lab.ohos

必须拥有独立签名 profile,不能复用姊妹应用签名

ohos_JupyterLab/
├── scripts/
│   └── bootstrap-from-notebook.mjs
├── pkg/ohos/
│   ├── build-package.mjs
│   ├── build-hap.mjs
│   ├── runtime/
│   ├── python/          #
可选设备 CPython
│   └── pylibs/          #
可选离线 site-packages
└── ohos_hap/
    ├── AppScope/app.json5
    ├── build-profile.json5
    ├── electron/src/main/module.json5
    └── web_engine/src/main/resources/resfile/resources/app

路径漂移说明  不同提交可能把 `electron/`、`web_engine/` 展平到仓库根目录,也可能保留在 `ohos_hap/` 下。不要死记仓库层级;真正要死记的是“应用资源最终进入 web_engine 的 resfile”。

6. 第一步:从共用 runtime 派生 Lab 身份

Jupyter Notebook 7 与 JupyterLab 大量复用同一套前端组件,因此在 OHOS 端让两个工程共享 Electron runtime 是合理的工程选择。这样既能减少重复维护,也能把平台修复统一沉淀在一套 runtime 里。代价是“产品身份”必须彻底分离,否则签名、HNP、bundle 和环境变量会互相污染。

# 仅同步 Electron 主进程与 static server
node scripts/bootstrap-from-notebook.mjs

#
首次拉起完整 HAP 工程树
node scripts/bootstrap-from-notebook.mjs --with-hap

一个合格的 bootstrap 至少应该自动处理以下四件事:

  • 把 `org.jupyter.notebook.ohos` 替换为 `org.jupyter.lab.ohos`,确保系统把它视为独立应用。
  • 把 UI 品牌与启动文案从 Notebook 切换为 JupyterLab,避免“外壳变了、产品身份没变”。
  • 清理旧工程的签名材料,让 DevEco 为新 bundle 重新签发 profile。
  • 把 `jupyterlab_python.hnp` 调整为应用私有,避免与已安装的 Notebook 抢占同名 public HNP。

为什么不手改  这类身份修改看似只有几个字段,但它们分散在 app.json5、build-profile.json5、module.json5、运行时环境变量和资源文案里。脚本化的价值不是省几分钟,而是避免下一次同步上游时漏掉某一处。

7. 第二步:把 Python 执行挪进浏览器

7.1 node-static 模式的关键,不是“静态”,而是“内核换位置”

把 JupyterLab UI 变成静态资源只能解决“页面从哪里来”,不能解决“代码在哪里执行”。默认路线的核心是:Notebook 单元格执行请求不再转发给设备上的 `ipykernel`,而是交给浏览器内核适配层,再由 Pyodide 在 Web Worker / WebAssembly 中执行。

Notebook Cell
    ↓
JupyterLab
前端命令 / Kernel Adapter
    ↓
Web Worker
    ↓
Pyodide (CPython → WebAssembly)
    ↓
stdout / rich output / error
    ↓

回写到当前单元格

Pyodide 官方定义是“基于 WebAssembly/Emscripten 的浏览器与 Node.js Python 发行版”,本质上是 CPython 的 WASM 移植[4]。它比“只解释一小部分 Python 语法”的轻量方案更接近真正的 Python 运行时,同时仍保持浏览器侧执行的部署优势。

7.2 先用最小代码验证内核,不要一上来测大型包

print("hello harmony pc")

def fib(n):
    a, b = 0, 1
    out = []
    for _ in range(n):
        out.append(a)
        a, b = b, a + b
    return out

fib(10)

这个测试足以验证语法解析、函数调用、循环、列表对象、标准输出与结果回显。只有这条链稳定后,再进入第三方包验证。

7.3 Pyodide 能装包,但别把“官方支持”误写成“你的 HAP 已适配”

Pyodide 可以通过 `micropip` 安装纯 Python wheel,也能加载已经为 wasm32/emscripten 构建的二进制包;官方发行版还包含 NumPy、pandas、SciPy、Matplotlib、scikit-learn 等大量包[4]。但这是 Pyodide 发行版层面的能力,不等于你的 HAP 已经把对应 WASM、lock 文件、wheel、网络访问策略和缓存路径全部打包好。

# 只有当你的 Pyodide 分发中已经包含对应资源时再做这一步
import numpy as np

x = np.arange(6).reshape(2, 3)
x.sum(axis=1)

边界意识  遇到 `micropip` 找不到包时,先区分:它是纯 Python wheel、Pyodide/emscripten wheel,还是只提供 Linux/Windows/macOS 原生 wheel。后者不能直接塞进浏览器内核。

8. 第三步:资源同步到 web_engine,并构建 HAP

这一阶段最值得形成肌肉记忆的是“资源路径契约”。Electron entry 只是应用入口,真正被 web_engine 装载的运行时和前端资源需要进入正确的 resfile。路径放错时最危险,因为构建过程可能完全成功,直到真机启动才给你一个没有上下文的白屏。

# 同步 Electron 主进程、JupyterLab 前端、Pyodide 等资源
node pkg/ohos/build-package.mjs

#
关键资源落点
ohos_hap/web_engine/src/main/resources/resfile/resources/app

不要放错模块  `ohos_hap/electron/src/main/resources/resfile/...` 看起来也像“资源目录”,但它不是当前方案真正的应用资源打包树。构建成功 ≠ 资源进包。

8.1 锁定 SDK,避免 `import lazy` 被工具链组合误伤

{
  "compatibleSdkVersion": "6.0.1(21)",
  "targetSdkVersion": "6.0.1(21)"
  //
不写 compatibleSdkVersionStage
}

如果 DevEco 的 Sync、Project Structure 或签名 Fix 改写了 `build-profile.json5`,构建前要重新检查这两个字段。跨平台适配工程最怕“代码没变,工具链悄悄变了”。把已验证组合钉死,比追最新版本更重要。

8.2 关闭 GPU:先换稳定性,再谈硬件加速

OHOS Electron 的 GPU 合成路径在某些运行时组合里可能表现为启动白屏或 XComponent / GPU 进程异常。对 JupyterLab 这种以文本编辑、Notebook 和 2D 图表为主的计算 IDE,优先把 UI 稳定跑起来通常比追求硬件合成更划算。

// Electron 主进程的典型做法
app.disableHardwareAcceleration()

//
还可以在启动参数中补充
--disable-gpu
--use-gl=disabled

排障顺序  白屏时不要第一反应去改前端。先检查资源是否真的进包,再确认 GPU 是否禁用,最后才看页面自身报错。这样排查成本最低。

9. 第四步:签名、安装与启动

鸿蒙侧很多“安装失败”并不是业务代码问题,而是产品身份没有完全隔离。bundleName、签名 profile、HNP 类型和 `products[].signingConfig` 四个字段必须同时成立。

9.1 每个 bundle 都要有自己的签名 profile

自动调试签名 profile 与 bundleName 绑定。由 `org.jupyter.notebook.ohos` 生成的 `.p7b` 不能直接拿来给 `org.jupyter.lab.ohos` 签名。正确做法是清理旧签名材料,在 DevEco 的 Signing Configs 中为新 bundle 重新自动签名 / Fix。

9.2 生成材料以后,还要确认 product 真正引用了它

{
  "signingConfigs": [
    {
      "name": "default",
      "material": {
        "...": "..."
      }
    }
  ],
  "products": [
    {
      "name": "default",
      "signingConfig": "default"
    }
  ]
}

如果 `products[].signingConfig` 还是空串,Hvigor 可能直接跳过 SignHap,最后给你一个 `electron-default-unsigned.hap`。所以判断签名是否成功,最直观的不是“我刚点过 Fix”,而是看最终产物文件名和构建任务里是否真的执行了 SignHap。

9.3 HNP 用 private,才能与姊妹应用长期共存

// electron/src/main/module.json5
"hnpPackages": [
  {
    "package": "jupyterlab_python.hnp",
    "type": "private"
  }
]

如果 Notebook 已经把同名 HNP 注册为 public,Lab 再声明同名 public 包,系统会按设备级全局包处理,安装阶段就会冲突。改为 private 后,原生包作用域收回到应用自身,两个 HAP 可以各自持有一份。注意:这是模块元数据,修改后必须重新 `assembleHap`。

9.4 安装与启动

# 方式一:发送后用 bm 安装
hdc file send electron/build/default/outputs/default/electron-default-signed.hap /data/local/tmp/lab.hap
hdc shell bm install -p /data/local/tmp/lab.hap

#
启动
hdc shell aa start -a EntryAbility -b org.jupyter.lab.ohos

10. 六类高频坑:从 10705000 到 ImportError 的完整排查链

图 4 排障顺序:先构建与资源,再签名与 HNP,最后才进入运行时与 Python 包。

表 5 错误码真正有价值的不是“记答案”,而是形成稳定的排查层级

错误 / 现象

典型根因

直接检查

修复

CompileArkTS 10705000

SDK / stage 组合导致 `import lazy` 被拒绝

build-profile.json5 的 compatible/target/stage

锁定 6.0.1(21),移除不匹配 stage,重新 Sync

启动白屏

资源放错模块,或 GPU 合成异常

web_engine/resfile 是否有 app 资源;GPU 参数

重新 build-package;确认 disableHardwareAcceleration / --disable-gpu

SignHap 00303074

复用旧 bundle 的签名 profile

`.p7b` 是否对应新 bundleName

清空旧签名材料,为 Lab 重新 Fix / 自动签名

Install 9568320

构建得到 unsigned HAP

产物名;products[].signingConfig

设为 `default` 后重新 assembleHap

Install 9568407

同名 public HNP 已被另一应用占用

module.json5 的 hnpPackages type

Lab 改 private,重新构建

Python ImportError / 安装失败

wheel 类型不匹配、WASM 资源未带齐、CORS/网络受限

包是否有 pure Python 或 emscripten wheel

预打包兼容 wheel,或改用设备 CPython / external Server

10.1 为什么白屏是最容易误判的问题

白屏看起来像前端问题,实际上至少有三种不同来源:第一,资源根本没进 HAP;第二,Electron 窗口创建成功但 GPU 合成路径挂了;第三,静态服务已经起了,但加载 URL 或端口没有对上。这三类问题的修复方向完全不同。

  1. 先解压 / 检查 HAP 资源或确认 build-package 的目标目录有完整应用文件,排除“空包”。
  2. 确认主进程已经执行 `disableHardwareAcceleration()`,并带上禁用 GPU 的启动参数。
  3. 检查 static server 是否启动、端口是否被占用、BrowserWindow 最终加载的 URL 是否与实际监听地址一致。
  4. 最后才打开前端 DevTools / 日志追踪 JS 运行错误。

11. 真机验收:别只验证“能打开”

图 5 真机验收闭环:只有“保存并重开”成功,才算真正完成 Notebook 主流程。

一个桌面 IDE 的验收不能停在“窗口出来了”。建议把功能拆成 T0、T1、T2 三层,先保证底座,再看日常能力,最后评估哪些桌面增强项值得继续移植。

表 6 把功能分层以后,团队会更容易判断“未实现”到底是缺陷还是主动取舍

层级

能力

建议状态

说明

T0

HAP 构建、签名、安装、启动

必须通过

任何一个失败都说明交付链还不稳定

T0

Launcher 与 Notebook 渲染

必须通过

证明主工作区可用

T1

新建 / 打开 / 编辑 Notebook

必须通过

日常使用核心

T1

Python 单元格执行

必须通过

默认走 Pyodide 浏览器内核

T1

保存 / 重开

必须通过

验证持久化,不接受“只在当前页面看得到”

T1

文件浏览、Markdown、高亮补全

建议通过

大部分属于前端能力,应尽量保持

T2

终端 / PTY

可延期

浏览器内核天然不等于本地 shell

T2

完整 native Python 生态

按需

需要设备 CPython + OHOS wheel 或远端 Server

T2

系统托盘 / 桌面原生菜单

按平台取舍

不要为了“像 Windows”而强搬不存在的系统概念

11.1 一个推荐的 Notebook 验收脚本

# Cell 1:基础执行
print("JupyterLab on Harmony PC")

# Cell 2
:函数、循环、容器
def stats(values):
    total = sum(values)
    return {"count": len(values), "sum": total, "avg": total / len(values)}

stats([3, 5, 8, 13])

# Cell 3
:异常回显
try:
    1 / 0
except Exception as e:
    print(type(e).__name__, str(e))

执行完以后不要立即结束。把 Notebook 重命名,保存,关闭应用,再次启动后从文件浏览器重新打开。如果内容、单元格执行计数和输出能按预期恢复,才算完成了一次真正的持久化验收。

12. Pyodide 的能力边界:能跑 Python,不等于等同桌面 CPython

浏览器内核最大的价值,是把 Python 运行时从“设备必须原生支持”变成“WebAssembly 可以承载”。但 WebAssembly VM 与浏览器安全模型也带来了明确边界。Pyodide 文档明确指出,线程、多进程、原生 socket、PTY/termios 等能力受限或不可用;网络请求也要服从浏览器的 CORS、证书和代理策略[5]。

表 7 不要把“浏览器能执行 Python”误解成“桌面 Python 的所有系统能力都存在”

能力

Pyodide 浏览器内核

设备 CPython

external Server

Python 语法 / 标准库

大部分可用,个别模块受限

完整度最高

由远端环境决定

NumPy / pandas / SciPy

取决于 Pyodide 分发与已打包 WASM 包

需要 OHOS native wheel

通常最完整

`pip install` 任意 PyPI 包

否;只可直接用纯 Python / emscripten wheel 等兼容包

取决于 OHOS wheel

通常可按服务器平台安装

原生 socket / PTY / 终端

明显受限

可实现但需系统适配

通常完整

多进程 / 原生线程

受 WebAssembly/浏览器限制

按设备 Python 能力

按服务器能力

离线运行

可做到,但需把资源预打包

可做到,包体会更大

依赖网络

升级成本

前端资源 / WASM 包级别

解释器 + native 依赖

服务器侧集中升级

一个实用原则  如果目标是课堂演示、算法练习、轻量数据处理和离线 Notebook,Pyodide 很合适;如果目标是大型科学计算、系统编程、GPU、PTY 或大量 native 包,应该尽早切换到设备 CPython 或 external Server。

13. 什么时候切到设备 CPython 或 external Server

13.1 设备 CPython:当“离线 + 完整本地”是硬要求

设备 Python 模式的优势是语义最接近传统桌面 Jupyter:kernel、文件系统、终端和第三方包都可以回到 Server 模型。但它把移植成本从“Web 兼容”转移到了“语言运行时与 native 生态”。OpenHarmony PC Developer 侧已经在推进 CPython 3.12 与 `ohos_aarch64` wheel 生态[6],这条路线会随着生态成熟越来越可行,但每个包含 C 扩展的包仍然需要独立验证。

JUPYTERLAB_OHOS_SERVER_MODE=python JUPYTERLAB_OHOS_PYTHON_DIST=/path/to/python-ohos-aarch64-3.12 node pkg/ohos/build-hap.mjs

13.2 external Server:当“本地只是入口,计算在别处”

如果企业已经有 JupyterHub、GPU 工作站或远程 Conda 环境,external 模式往往是工程上最划算的方案。鸿蒙 PC 只需要提供稳定的 JupyterLab 客户端,真正的 kernel、文件与包管理留在服务器侧。这样既保留完整科学计算生态,又能把 HAP 体积和 native 依赖压到最低。

export JUPYTERLAB_OHOS_SERVER_MODE=external
export JUPYTERLAB_EXTERNAL_SERVER_URL=http://192.168.1.100:8888
# Token /
证书请使用安全配置,不要硬编码进仓库

安全边界  external 模式需要把 Token、TLS、反向代理、跨域策略和网络可达性一起设计。它不是“填一个 URL 就结束”,但这些问题比在客户端重新编译整套科学计算栈更容易集中治理。

14. 一条可复现的最短路径

把前面的工程细节压缩成一条可以重复执行的路径,顺序如下。第一次建议在 DevEco 中完成 Sync 与签名,后续再把稳定步骤逐步迁移到命令行。

# 1. 获取工程
git clone https://atomgit.com/OpenHarmonyPCDeveloper/ohos_JupyterLab.git
cd ohos_JupyterLab
npm install

# 2.
首次同步共用 runtime / HAP
node scripts/bootstrap-from-notebook.mjs --with-hap

# 3.
检查 build-profile.json5
# compatibleSdkVersion = 6.0.1(21)
# targetSdkVersion     = 6.0.1(21)
#
不写不匹配的 stage

# 4.
同步 Electron 主进程、Lab 前端、Pyodide 等资源
node pkg/ohos/build-package.mjs

# 5.
构建
node pkg/ohos/build-hap.mjs
#
或进入工程后直接调用 hvigorw assembleHap

# 6. DevEco
org.jupyter.lab.ohos 生成独立调试签名
# 再确认 products[].signingConfig = "default"

# 7.
确认产物是 signed.hap 后安装
hdc file send electron/build/default/outputs/default/electron-default-signed.hap /data/local/tmp/lab.hap
hdc shell bm install -p /data/local/tmp/lab.hap

# 8.
启动
hdc shell aa start -a EntryAbility -b org.jupyter.lab.ohos

如果你的仓库目录与上面略有差异,优先以当前分支的 `README.OpenHarmony_CN.md`、`OHOS_ADAPTATION.md` 和实际 `build-profile.json5` 为准。真正需要保持不变的是步骤关系:身份化 → 资源入包 → 工具链锁定 → 签名 → 安装 → 功能验收。

15. 常见问题 FAQ

Q1:为什么不直接把完整 Python + Jupyter Server 打进 HAP?

可以,但这会立刻把问题升级成 CPython 运行时、OHOS aarch64 native wheel、HAP 体积、升级与安全补丁的组合工程。默认 Pyodide 路线的价值,是先把核心 Notebook 闭环从这些依赖里解耦。

Q2:Pyodide 既然支持 NumPy,为什么还要说“科学计算栈受限”?

因为 Pyodide 官方支持的是“发行版中已有的 WASM 包或兼容 wheel”。你的 HAP 是否离线带齐这些资源、是否允许运行时联网下载、是否遇到 CORS、内存与线程限制,是另一层问题。

Q3:早期分支显示 Skulpt,当前文档写 Pyodide,应该信哪个?

信你实际分支的 runtime 与内核配置。Skulpt 与 Pyodide 是两条不同的浏览器 Python 路线;截图、文档和代码如果不在同一提交基线上,排错会被带偏。

Q4:安装时报 9568320,但我已经在 DevEco 里自动签名了?

先看输出文件名。如果还是 `unsigned.hap`,继续检查 `products[].signingConfig` 是否真的指向 `default`。签名材料存在,不代表 SignHap 任务一定执行。

Q5:安装时报 9568407,卸载 Notebook 后又能装了,为什么?

这是典型的同名 public HNP 冲突。Lab 侧把 `jupyterlab_python.hnp` 改为 private,再重新构建,让两个应用各自拥有独立作用域。

Q6:应用启动白屏,最先查什么?

先查 `web_engine/.../resfile/resources/app` 是否有完整资源,再查 GPU 禁用是否生效,最后检查 static server 的端口与 loadURL。不要一开始就重写前端。

Q7:这套方案适合生产环境吗?

如果你的需求是轻量本地 Notebook,它已经具备很清晰的工程闭环;如果需要大型 native 包、GPU、终端或企业级多用户计算,建议把它当客户端,连接 external Server,或继续补设备 CPython 生态。

16. 收束:这类跨平台迁移真正要迁的是“边界”

把 JupyterLab 搬到鸿蒙 PC,最有价值的部分并不是最终那张“能打开 Notebook”的截图,而是重新理解一个复杂桌面工具到底由哪些可替换边界组成。JupyterLab 的前端工作区并不需要因为平台变化而重写;真正需要适配的是窗口运行时、Server 语义、Python 内核、资源打包、签名身份和文件持久化。

这也是为什么“Electron 壳 + 前端静态化 + 浏览器内 Python”是一条很实用的第一阶段路线:它先把最难的 native Python 生态从启动链上拿走,让 HAP 能稳定安装、让 Launcher 能打开、让 Notebook 能执行、让文件能保存。等 T0/T1 稳住,再根据业务场景把设备 CPython、终端、完整科学栈或远程 Server 一项项接回来。

做跨平台迁移时,最危险的思路是“把原平台所有东西原封不动搬过来”;更有效的思路是先问:哪一层是真正的产品价值,哪一层只是原平台的实现方式。这次保住的是 JupyterLab 的交互式计算体验,替换的是承载与执行边界。只要这个原则不变,未来无论 Electron runtime、Pyodide、OHOS Python 生态还是 JupyterLab 上游继续升级,工程都还有清晰的演进路径。

最终检查  构建成功不算结束;只有 signed HAP 可安装、启动不白屏、Notebook 可执行、文件能保存并重开、姊妹应用能共存,这次移植才真正闭环。

参考资料

[1] Project Jupyter — JupyterLab: A Next-Generation Notebook Interface Project Jupyter | Home

[2] OpenHarmonyPCDeveloper — ohos_jupyter / JupyterLab HarmonyOS PC 适配仓库 ohos_jupyter:基于 OpenHarmony 生态的 JupyterLab PC 客户端项目 - AtomGit

[3] JupyterLite Documentation — Adding kernels / browser-based kernels Adding kernels — JupyterLite 0.9.0-alpha.1 documentation

[4] Pyodide Documentation — Python in the browser / loading packages Pyodide — Version 314.0.7

[5] Pyodide Documentation — WebAssembly / browser compatibility constraints Pyodide Python compatibility — Version 314.0.7

[6] OpenHarmony PC Developer — Python 生态与 ohos_aarch64 wheel 方向 Python 生态 · OpenHarmony PC Developer

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐