JupyterLab Electron 鸿蒙 PC 适配全记录从 Python 原生崩溃到 node-static 本地工作台一、写在前面欢迎加入鸿蒙 PC 开发者社区共同打造开发者工具生态鸿蒙 PC 开发者社区https://harmonypc.csdn.net/项目开源地址https://atomgit.com/OpenHarmonyPCDeveloper/ohos_JupyterLab欢迎在PC社区平台申请新建项目https://atomgit.com/OpenHarmonyPCDeveloper环境搭建文章https://blog.csdn.net/lbcyllqj/article/details/161286249?sharetypeblogdetailsharerId161286249sharereferPCsharesourcelbcyllqjspm1011.2480.3001.8118这篇文章记录的是一次把JupyterLab适配到 HarmonyOS PC / OpenHarmony Electron 环境的完整过程。JupyterLab 不是一个普通的 Electron 工具也不是简单的静态网页。它本质上是一个交互式计算工作台原始架构里包含JupyterLab 前端 - Jupyter Server - Kernel / Terminal / Contents / Settings / Workspace 等服务在桌面环境里JupyterLab 通常由 Python 启动jupyter lab然后浏览器访问本机的http://127.0.0.1:8888/lab迁移到鸿蒙 PC 后最开始的想法也是尽量保持这个结构OpenHarmony Electron 负责开窗口Python 负责启动 Jupyter Server前端继续访问本地127.0.0.1:8888。但实际真机运行时Python 原生依赖链遇到了比较关键的问题pyzmq/libsodium相关 native extension 在设备上导入时发生崩溃导致完整 Jupyter Server 路线暂时不可用。最后适配方案改成了更稳的一阶段方案HarmonyOS PC Electron HAP - 启动 Electron 主进程 - 用 Node 在本机启动轻量 static server - 服务 JupyterLab 静态资源 - 模拟一部分 Jupyter Server REST API - 保留文件浏览、文本编辑、Markdown 编辑、工作区、设置等前端能力也就是说这次最终跑通的版本不是“完整 Python Kernel 版 JupyterLab”而是一个更适合当前鸿蒙环境的JupyterLab 本地文件工作台版本。它可以启动 JupyterLab 前端可以创建、打开、保存文件也可以使用 JupyterLab 原本的多标签、文件浏览器、编辑器、主题和工作区能力但 Notebook 代码执行、Terminal、Console、Debugger、LSP 等强依赖 Python Kernel / 后端服务的能力在当前阶段先禁用。二、先判断 JupyterLab 原始功能和适配边界在适配之前首先需要判断这个项目到底是什么类型。JupyterLab 官方定位是 Project Jupyter 的下一代用户界面它提供经典 Jupyter Notebook 中的 notebook、terminal、text editor、file browser、rich outputs 等能力并把这些能力组织成一个可扩展的工作台。从功能上拆开看原始 JupyterLab 主要包括Notebook 文档和 cell 执行Python / R / Julia 等 Kernel 管理Code Console 交互式控制台Terminal 终端文件浏览器文本和代码编辑器Markdown、JSON、CSV、图片、PDF 等查看器Settings 设置系统Workspace 工作区布局保存命令面板、菜单、快捷键主题和插件系统Debugger 调试器LSP 语言服务实时协作这些能力并不是同一种技术难度。适配时必须把它们分成两类适合先保留的能力 文件浏览、文件创建、文本编辑、Markdown 编辑、JSON/CSV/图片查看、主题、设置、工作区、多标签布局。 暂时不适合原样保留的能力 Notebook 代码执行、Kernel、Terminal、Console、Debugger、LSP、实时协作。原因也很直接前一类能力主要依赖 JupyterLab 前端资源和文件 API后一类能力依赖 Python Jupyter Server、ZeroMQ、Kernel 协议、WebSocket、PTY、语言服务器等完整后端链路。这次适配的关键不是“把所有功能都宣称支持”而是先让应用在鸿蒙 PC 上稳定活下来并保留最容易落地、最有实际使用价值的一部分功能。三、建立 OpenHarmony Electron HAP 工程适配时新增了两个主要目录jupyterlab-main/ ├── pkg/ohos/ │ ├── build-package.mjs │ ├── build-hap.mjs │ └── runtime/ │ ├── assets/ │ ├── package.json │ └── src/ │ ├── html/ │ └── js/ └── ohos_hap/ ├── AppScope/ ├── electron/ └── web_engine/其中ohos_hap/是 OpenHarmony Electron HAP 工程负责应用身份、签名、模块配置、原生 Electron 运行库和最终 HAP 产物。pkg/ohos/是 JupyterLab 自己的鸿蒙适配层主要负责生成 Electron 主进程资源把运行时代码同步到 HAP 的resources/app复制 JupyterLab 静态资源和必要依赖根据环境变量选择运行模式调用 Hvigor 构建 HAP生成后的 Electron 应用资源会放到ohos_hap/web_engine/src/main/resources/resfile/resources/app这个目录就是 OpenHarmony Electron runtime 加载 Node/Electron 应用的位置。和普通桌面 Electron 的resources/app思路类似只是在 HAP 工程里的路径不同。构建脚本主要有两个pkg/ohos/build-package.mjs pkg/ohos/build-hap.mjsbuild-package.mjs负责同步资源、写入入口、准备运行时文件。build-hap.mjs负责先调用build-package.mjs再调用 DevEco / Hvigor 生成 HAP。常用构建命令如下cd~/XM/jupyterlab-mainJUPYTERLAB_OHOS_LOCAL_PYTHON1nodepkg/ohos/build-hap.mjs最终产物位于ohos_hap/electron/build/default/outputs/default/electron-default-signed.hap四、第一版方案尽量保持原始 Python Jupyter Server 架构一开始的适配方案比较接近原始 JupyterLabElectron 主进程 - spawn Python - python -m jupyterlab - 等待 127.0.0.1:8888/lab 可访问 - BrowserWindow 加载 /lab?token...这条路线的优点很明显如果跑通功能最完整。Notebook、Kernel、Terminal、Console、Debugger 等能力都可以尽量保持原版。为此运行时代码中实现了 Python 启动逻辑pkg/ohos/runtime/src/js/jupyterlab.js大致流程如下resolvePythonPath() - resolveRootDir() - prepareBundledPyLibs() - writePythonBootstrap() - spawn python -m ohos_jupyter_bootstrap jupyterlab - poll http://127.0.0.1:8888/lab - load BrowserWindow同时为了适配鸿蒙上 Python native extension 的加载限制还做过一层 bootstrap把纯 Python 包复制到可写沙箱目录把.sonative extension 放到 HAP native library 目录通过sitecustomize/ import hook 让 Python 导入 native 模块设置LD_LIBRARY_PATH写入python-native-extensions.json这套思路本身是合理的也适合后续继续深挖。但在真机上问题出在更底层的 native 依赖。五、真机问题pyzmq / libsodium 导入阶段崩溃Jupyter Server 和 Kernel 通信依赖pyzmq。在设备上启动 Python 路线时日志里可以看到 native extension 加载到了zmq.backend.cython._zmq相关位置并继续加载libsodium、libzmq等依赖。问题是在这个阶段应用发生了原生崩溃表现为 Jupyter Server 无法真正启动起来Electron 侧只能等待或者显示错误页。这个问题和普通前端白屏不一样。普通白屏可以从 HTML、JS、CSS、接口 404 这类方向排查但这里已经进入了 Python native extension 和系统动态库加载层面继续沿着“完整本地 Python Jupyter Server”路线硬修会变成Python for OpenHarmony - CPython ABI - pyzmq - libzmq - libsodium - libstdc / libgcc - OpenHarmony 动态库加载限制这条链路任何一环不稳定JupyterLab 都无法启动。对于第一阶段目标来说继续在这里死磕并不划算。于是适配策略做了调整不再把 Python/Jupyter Server 当成默认启动路径而是保留为调试 fallback。现在如果确实要强制走 Python 路线可以通过环境变量打开JUPYTERLAB_OHOS_SERVER_MODEpython或者JUPYTERLAB_OHOS_FORCE_PYTHON1默认情况下鸿蒙设备上走新的node-static模式。六、第二版方案用 Node 实现 JupyterLab 静态本地服务确认 Python native 依赖是主要风险后适配方案换成了新的结构Electron 主进程 - startStaticLabServer() - Node http server 监听 127.0.0.1:8888 - 服务 JupyterLab static / themes / schemas - 实现最小 Jupyter Server 兼容 API - BrowserWindow 加载 http://127.0.0.1:8888/lab核心文件是pkg/ohos/runtime/src/js/staticLabServer.js这个文件做了几件关键工作。第一服务 JupyterLab 前端资源/lab /static/* /themes/* /schemas/* /files/*第二实现 JupyterLab 前端启动需要的基础 API/api /api/status /api/me /api/kernelspecs /api/kernels /api/sessions /api/terminals /api/contents /lab/api/settings /lab/api/workspaces /lab/api/translations /api/nbconvert /api/licenses /api/config/section /api/events第三实现contents文件 API让 JupyterLab 前端可以创建、读取、保存、删除、重命名、复制文件并支持 checkpoint 相关请求。这部分是当前 node-static 方案最有实际价值的地方。因为它让 JupyterLab 不只是“界面能打开”而是真正具备了本地文件工作台能力。第四生成 JupyterLab HTML 页面并补齐前端启动所需的 page config。这里踩过一个非常典型的坑JupyterLab 前端主包启动时会读取pageConfig.federated_extensions如果这个字段不存在或类型不对前端会在启动阶段报错表现为窗口白屏。最终修复方式是在 page config 里明确写入federated_extensions:[]这个点很小但非常关键。很多大型前端应用在迁移到自定义 server 时真正的问题并不是资源没加载而是后端模板注入的配置字段不完整。七、主进程运行模式切换最终主进程里保留了两条路线默认路线 OpenHarmony - node-static local server 调试路线 JUPYTERLAB_OHOS_SERVER_MODEpython 或 JUPYTERLAB_OHOS_FORCE_PYTHON1 - Python Jupyter Server核心切换逻辑在pkg/ohos/runtime/src/js/jupyterlab.js简化理解如下functionstartLocalServer(){if(openHarmony!shouldForcePythonLocalServer()){startNodeStaticLocalServer();return;}startPythonLocalServer();}这样做有两个好处。第一默认体验稳定。用户安装 HAP 后不会因为 Python native extension 崩溃而直接白屏。第二后续仍然保留继续研究完整 Kernel 方案的入口。如果后面 pyzmq、libsodium 或 Python runtime 的问题解决可以重新打开 Python 模式验证而不需要推翻整个适配工程。在 node-static 模式下应用会明确禁用或空实现这些能力Kernel execution Terminal Console Debugger LSP Running sessions Extension manager但会保留JupyterLab 主界面 文件浏览器 文本文件创建和保存 Markdown 文件编辑 Settings API Workspace API 主题资源 多标签工作区 静态资源加载 基础 contents API这是一种更符合当前阶段的取舍先把“能用的部分”跑稳再讨论“完整计算环境”。八、真机安装和启动验证构建完成后使用hdc安装 HAPHDC/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc$HDCshell aa force-stop org.jupyter.jupyterlab.ohos$HDCshell hilog-r$HDCshellrm-f/data/app/el2/100/base/org.jupyter.jupyterlab.ohos/files/jupyterlab-runtime.log$HDCinstall-rohos_hap/electron/build/default/outputs/default/electron-default-signed.hap$HDCshell aa start-aEntryAbility-borg.jupyter.jupyterlab.ohos-melectron启动后可以先验证本地服务是否真的起来$HDCshellwget -O - http://127.0.0.1:8888/lab | head -c 500; echo$HDCshellwget -O - http://127.0.0.1:8888/api/contents | head -c 500; echo如果能看到 HTML 片段和 contents JSON说明 node-static server 已经正常工作。然后拉取运行日志$HDCshellcat/data/app/el2/100/base/org.jupyter.jupyterlab.ohos/files/jupyterlab-runtime.log|tail-700正常日志里应该能看到Starting OpenHarmony node-static local server. JupyterLab OpenHarmony node-static runtime Server URL: http://127.0.0.1:8888 Starting application in workspace: default GET /api/kernelspecs - 200 GET /api/me - 200 GET /lab/api/settings - 200 GET /api/contents - 200这一步能确认几个关键点HAP 可以安装OpenHarmony Electron 可以启动Electron 主进程可以启动 Node HTTP server设备内部可以访问127.0.0.1:8888JupyterLab 前端资源可以加载基础 Jupyter Server API 模拟成功九、验证文件创建、编辑和保存JupyterLab 前端真正可用不能只看首页。还要验证文件 API 是否闭环。当前 node-static server 已经实现了/api/contents的主要行为GET /api/contents GET /api/contents/path POST /api/contents PUT /api/contents/path PATCH /api/contents/path DELETE /api/contents/path POST /api/contents/path/checkpoints GET /api/contents/path/checkpoints所以可以在真机界面上测试打开 JupyterLab。在文件浏览器中点击新建文本文件。输入几行内容。保存文件。关闭 tab 后重新打开确认内容仍然存在。再创建一个 Markdown 文件验证 Markdown 编辑器是否能正常打开。从日志里可以看到类似请求POST /api/contents - 201 GET /api/contents/untitled.txt - 200 PUT /api/contents/untitled.md - 200 POST /api/contents/untitled.md/checkpoints - 201这说明 JupyterLab 的前端文档管理流程已经可以跑通。虽然 Kernel 还不可用但作为一个本地文件编辑和查看工作台已经具备基本可用性。十、这次适配中几个关键问题的复盘1. 不要把 JupyterLab 当成普通静态网页JupyterLab 的前端虽然可以通过静态资源加载但它启动时会立刻请求一批 Jupyter Server API。如果只把static/目录丢给 Electron 加载大概率会遇到白屏、插件启动失败、设置加载失败、工作区无法恢复等问题。所以 node-static 方案不是简单的serve static files而是要模拟一组最小 Jupyter Server API。2. page config 字段必须补齐JupyterLab 前端依赖后端注入的pageConfig。其中有些字段看起来不是业务逻辑但缺了会直接影响 bootstrap。这次比较关键的字段是federated_extensions:[]没有它时前端主包启动阶段会对这个字段调用.map()字段异常就会导致白屏。3. Python native 崩溃要及时换路线最开始沿着 Python/Jupyter Server 路线做是因为这是最接近原版的方案。但真机上遇到pyzmq、libsodium这类 native 崩溃后继续硬修会把问题扩大到完整 Python native 生态。在这种情况下先换成 node-static 路线是更稳的工程选择。十一、当前能力和后续方向当前适配后的 JupyterLab 鸿蒙 PC 版本可以实现HAP 构建、签名、安装、启动OpenHarmony Electron 窗口加载Node 本地 server 启动JupyterLab 前端启动文件浏览器基础展示文本/Markdown 文件创建、读取、保存settings APIworkspace APIthemes / schemas / static 资源加载基础 diagnostics 日志当前暂时禁用或不可用Notebook 代码执行Python KernelTerminalCode ConsoleDebuggerLSP实时协作依赖完整 Jupyter Server 的扩展后续可以继续探索三个方向。第一远程 Kernel / 远程 Jupyter Server 模式。鸿蒙端只作为 JupyterLab 客户端Kernel 跑在 Mac、服务器或 DevBox 中。第二修复 Python native 链路。继续研究 pyzmq、libsodium、libzmq 在 OpenHarmony 上的 ABI、动态库加载和崩溃原因最终恢复完整本地 Jupyter Server。第三轻量执行环境。对于部分简单脚本可以考虑 WebAssembly、Pyodide、JavaScript kernel 等方式但这会和标准 Jupyter Kernel 体系有差异需要单独设计。十二、总结这次 JupyterLab 鸿蒙 PC 适配最大的收获不是简单把一个页面塞进 HAP而是重新判断了一个复杂开发者工具在新平台上的落地边界。一开始我们尝试保持原始架构让 Electron 启动 Python Jupyter Server再由 JupyterLab 前端连接本地服务。这条路最完整但在真机上被 Python native 依赖链挡住了尤其是pyzmq/libsodium一类底层库崩溃。最后切换到 node-static 本地服务方案绕开了 Python native 崩溃点。这个方案牺牲了 Kernel 和 Terminal 等完整计算能力但换来了一个稳定可启动、可浏览文件、可编辑保存文件的 JupyterLab 本地工作台。对大型 Electron / Web 开发者工具迁移到鸿蒙 PC 来说这个过程也提供了一个通用经验先识别原项目的核心依赖链 再把强后端能力和纯前端/文件能力拆开 优先跑通可独立落地的部分 最后再逐步恢复更重的运行时能力。JupyterLab 的完整能力仍然值得继续适配但当前这个阶段先让它在鸿蒙 PC 上稳定打开、显示、管理文件、保存内容已经是一次比较关键的工程落点。