把 OpenFront.io 搬上鸿蒙 PC:在线策略游戏的离线化与 WebGL 适配实战
把 OpenFront.io 搬上鸿蒙 PC:在线策略游戏的离线化与 WebGL 适配实战
欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/
欢迎在 PC 社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_OpenFrontIO
本文记录将开源实时策略游戏 OpenFront.io 适配到 HarmonyOS PC 的完整过程。它是我做过"反项"最多的一个:上游是纯服务端权威的在线游戏,直接搬上鸿蒙连开局都进不去——本文先讲怎么把它改造成完全离线可玩的单机版(剥离广告 SDK、追踪脚本、人机验证,让它脱离服务器也能跑完整对局);再讲 HAP 壳工程上三处与既有适配经验完全相反的决策(保留 GPU、起本地 HTTP 服务、伪造本机接口应答)。最后给出真机验收证据与 T0/T1/T2 迁移能力分级。所有结论均为鸿蒙 PC 2in1 真机实测。

图 0:鸿蒙 PC 桌面上的 OpenFront 启动画面——窗口标题"OpenFront (内测版)“,背景的六边形世界地图已由 WebGL 渲染出来,弹窗显示 AGPL-3.0 授权信息与"游戏正在启动…”

一、缘起:为什么是 OpenFront.io
此前我在鸿蒙 PC 上适配过的 Web 承载类应用——Zettlr、Clumsy Bird、2048——有个共同点:负载轻。要么是纯 DOM 渲染,要么是简单 Canvas,软件渲染路径完全兜得住。这条路走熟了之后,我反而开始警惕一件事:舒适区里的成功证明不了路线的上限。
鸿蒙 PC 的游戏品类里,休闲益智已经补上了(2048),但策略类还是空白。OpenFront.io 进入了视野:一款以真实地理地图为棋盘的在线多人实时策略游戏——玩家从世界地图上任一国家起步,扩张领土、建造设施、组建联盟、发动战争,地图寻路和回合模拟跑在 Web Worker 里,渲染层是 WebGL2。上游是 WarFront.io 的开源重写分支,AGPL-3.0 协议。

选它有三个理由:
第一,它代表"重前端"这一档。 WebGL2 渲染、Worker 内模拟、近 2000 个资源文件——如果能跑通,Web 承载路线的天花板就被实质性抬高了一截,后来者可以直接引用这套经验。
第二,它的离线化改造有真实的技术含量。 它不是"把网页存下来"那么简单:整个游戏的服务端逻辑、广告系统、账号体系、人机验证盘根错节,把"必须联网"的部分全部剥离而不伤游戏本体,是个真正的工程问题。
第三,品类稀缺 + 视觉冲击力。 一款真实世界地图上推兵线的 RTS,在鸿蒙 PC 上的演示效果远超又一个休闲小游戏。
适配目标从一开始就定得很苛刻:做成可完全离线运行的单机版——不连服务器、不加载广告与追踪、不做云端人机验证,开箱即玩。理由很实际:上游是纯服务端权威(server-authoritative)架构,直接搬上鸿蒙会因无法访问 api.openfront.io 与 Cloudflare Turnstile 卡死在开局前;而且"桌面游戏要始终在线才能玩"本身就不是合格的桌面体验。
上游是 TypeScript + Lit(Web Components)+ Vite 构建,无 tag、持续演进,本次适配锁定 commit 3e3b962。
二、破局点:单机路径本来就是本地模拟

动手前先回答一个生死问题:离线单机版到底成不成立? 毕竟上游是服务端权威架构,把服务端一起移植显然不现实。
答案藏在上游代码里。src/client/Transport.ts 有这样一段:
this.isLocal =
this.lobbyConfig.gameRecord !== undefined ||
this.lobbyConfig.gameStartInfo?.config.gameType === GameType.Singleplayer;
isLocal 为真时,对局由 src/client/LocalServer.ts 在当前页面进程内模拟服务端——回合收集、暂停、胜负判定全部本地完成,不建立任何 WebSocket。也就是说,上游本来就内置了一条完整的单机路径(用于回放和单人练习),它只是被账号体系、广告系统和人机验证层层包裹着。
这就是整个离线单机版成立的根基:不需要移植服务端,只需要把挡在单机路径前面的联网障碍逐一拆掉。
接下来的工作变成了清单题。梳理下来,离线的阻塞点一共三类:构建产物依赖服务端渲染(EJS)、资源加载依赖真实 origin(Blob Worker)、开局流程依赖联网换取 token。逐个拆。
三、三个必须解决的离线阻塞点
3.1 页面是 EJS 模板,静态托管无法渲染
上游 index.html 不是静态页,里面全是 <%- ... %> 占位符——正常流程由服务端 RenderHtml.ts 在每次请求时填充。离线场景没有服务端,页面打开就是一堆未解析的模板语法。
解法:构建期预渲染。 用 EJS 把模板在打包时渲染成纯静态 HTML,注入离线值。原模板保留为 static/index.html.ejs,脚本可重复运行。
3.2 地图加载失败:Blob Worker 里的 fetch 解析不了相对路径
这是三个问题里最隐蔽的一个。游戏 Worker 以 Blob URL 内联(Vite 的 ?worker&inline),Worker 内部用 fetch('/_assets/maps/...') 这类根相对路径取地图数据。在正常部署下这没问题——页面挂在真实域名上,相对路径自然解析。
但离线分发时,页面要么走 file://,要么走本地服务。Blob URL 不是任何特殊 scheme,继承的是创建它的页面上下文:file:// 下 origin 是字面量 null,fetch 一个根相对路径直接抛 Failed to parse URL,地图数据永远取不到,游戏起不来。
上游是怎么绕的?它有 cdnBase 配置——部署在 CDN 时资源用绝对 URL。顺着这个口子,离线版把 cdnBase 注入为运行时表达式 window.location.origin:页面和 Worker 都从当前 origin 拼出绝对 URL,与部署形态解耦。这一改同时解开了 ES module 在 file:// 下的 CORS 问题的一半——但另一半(见 5.1)必须换承载方式才能彻底解决。
3.3 开局被一个联网请求卡死
Transport.joinGame() 会无条件 await getPlayToken()——即使是本地单机局也要先联网换一个 play token。离线时这个请求失败并中断开局,而讽刺的是,这个 token 对本地局毫无用途:LocalServer 从不校验它。
解法:对上游源码做最小改动——isLocal 为真时跳过取 token。这是整个项目对上游源码的唯一一处修改,以 patch 形式维护(patches/0001-offline-singleplayer.patch)。
关于这个 patch 要多说一句:上游是 AGPL-3.0,衍生版本必须公开对源码的修改。这个 patch 文件本身就是合规义务的一部分,随仓库一并公开,谁都能审查、复用、回放。
四、剥离外部脚本:把"在线的皮"一层层撕掉
离线化不只解决"能不能玩",还要解决"玩的时候会不会被联网失败拖垮"。上游 index.html 里挂着 11 个外部脚本:CrazyGames SDK、Cloudflare Turnstile(人机验证)、AdShield(混淆过的反广告拦截脚本)、Playwire ramp.js、Google Analytics / gtag、Cloudflare beacon。
这些在离线版里全部移除。运行时动态注入的请求另行处理,两个典型:
广告闸门。 上游用 window.adsEnabled 作为广告总开关(付费用户、CrazyGames 渠道、桌面壳三者之一即为 false)。离线版用 Object.defineProperty 把这个闸门永久锁为 false——等价于把应用置于"付费用户"状态,从源头不发起任何广告请求,且无需改动任何组件逻辑。
教程视频。 首页嵌着 youtube.com/embed/... 的教程 iframe。不能简单置空字符串——<iframe src=""> 会解析为当前文档自身,导致页面在自己内部递归加载自己。正确做法是把 bundle 里的 TUTORIAL_VIDEO_URL 重指向 about:blank。
处理完这层,还有一个故意保留的"残留":账号/商店接口仍会请求 http://localhost:8787。这不是疏忽,是有意选择——请求打向本机回环地址会立即被拒绝(ECONNREFUSED),不依赖 DNS、不离开设备、不会挂起加载;若改指向某个真实域名,离线时反而要等 DNS 超时。之后再用一个 stub 服务把它接住(见 5.3),连"被拒绝"都省了。

五、HAP 壳工程:与先例的三处反向决策
HAP 壳从本组织已验证的 Electron 先例模板派生,身份替换为 com.openfront.ohos。但这次有三处决策与先例完全相反,每一处都值得单独讲。
5.1 反向一:起本地 HTTP 服务,而不是 loadFile
先例(包括 2048)都是主进程 loadFile 直接加载 file:// 页面。本工程不行——第 3.2 节的 origin 问题只是原因的一半:
- 页面是 ES module,
file://下被 CORS 拦截; cdnBase = window.location.origin,file://下为null,绝对 URL 拼出来是null/_assets/...;- Blob Worker 内的
fetch同样依赖真实 origin。
所以 main.cjs 在启动时先起一个**只监听 127.0.0.1、端口由系统分配(listen(0))**的静态服务,再 loadURL 本地地址。这样页面与 Worker 拿到的 origin 与浏览器里验证过的行为完全一致。
这个静态服务有两处容易写错的细节,都踩过:
先解码,再判越界。 素材文件名带空格和非 ASCII 字符(比如 flags/1_East Anglia.svg),资源清单里存的是百分号编码形式,不解码就 404;但 %2e%2e 解码后正是 ..——所以路径穿越检查必须放在解码之后做,顺序反了要么误伤正常资源、要么放走越界请求。
SPA 回落只认无扩展名路径。 无扩展名的路径按 SPA 惯例回落到首页;带扩展名的资源缺失就是真 404。如果无脑回落,.bin 地图数据会被当成 HTML 页面返回,内容全错,且这种错误极难从现象反推。
5.2 反向二:保留 GPU,不禁用硬件加速
这是我所有 Electron 适配里第一次不禁用 GPU。先例全部在最前面 disableHardwareAcceleration() + --disable-gpu 系列开关——因为鸿蒙壳工程的 GPU 子进程在 EGL 路径上会反复崩溃导致白屏,而那些应用都是 DOM/Canvas 负载,软件渲染毫无压力。
但 OpenFront 的渲染层会主动拒绝软件渲染:MapRenderer 用正则 /swiftshader|llvmpipe|software/i 匹配 GL renderer 字符串,命中即抛 GLUnavailableError——上游的理由很充分,软件渲染下地图只有约 1fps,与其给用户一个幻灯片不如直接报错。所以禁用 GPU 在这里等于必然进不去游戏。
处理:保留硬件加速,追加 --ignore-gpu-blocklist 提高在未知 GPU 上拿到硬件加速的概率。真机验证的结果是好消息:WebGL2 硬件加速路径在鸿蒙 PC 上可用,地图渲染流畅(后文截图为证)。这本身就是一个有价值的独立结论——鸿蒙 PC 的 GPU 加速 WebGL2 是可用的,之前"一刀切禁 GPU"的防坑经验只适用于软渲染可承受的负载。
5.3 反向三:伪造本机接口的"成功应答"
第 4 节末尾说账号接口故意指向 localhost:8787,这里的接法很有讲究。最偷懒的做法是不管它——请求被拒绝,日志刷 Failed to fetch,游戏照跑。但实测发现刷屏只是表象:server list 取不到值会触发 Transport 的 WebSocket 连接失败,进而弹出"连接出错!"模态框遮挡界面。
stub 服务的思路是把这些请求成功返回,把"网络层失败"降级为"业务层空数据":
- 账号/鉴权类接口(URL 命中
jwt|auth|token|login|session等)返回 401——不伪造登录态,游戏本就按未登录处理; - 其余接口(cosmetics、server list、news 等)返回空数组——游戏各处的 “request failed, using fallback” 分支自然接管,不再报错。
中间还埋着一个 CORS 细节:页面在 127.0.0.1:<随机端口>,接口在 127.0.0.1:8787,跨端口即跨域;且游戏的账号请求带 credentials,此时 Access-Control-Allow-Origin 不能是通配符 *(浏览器会直接判 CORS 失败)——stub 必须回显请求的具体 Origin、声明 Allow-Credentials 并应答预检请求。这个细节文档里很少有人写全,是实打实踩出来的。
5.4 附带一战:穿透 closed shadow DOM 的弹窗移除器
单机模式下仍有两条联网失败弹窗路径(<error-modal> 与重连耗尽后的 <confirm-dialog>),对单机玩法毫无意义却遮挡界面,终态对话框点确认还会刷新页面。
选择"移除 DOM"而不是改 bundle,有两个理由:不改动上游产物,renderer/ 可随时重新生成;不触碰任何游戏逻辑。实现上有个教科书级的坑:上游是 Lit Web Components,弹窗渲染在 shadow root 里,若任何一层组件以 mode:'closed' 创建 shadow root,外部拿到的 element.shadowRoot 恒为 null,遍历到此断掉——表现就是"弹窗明明在屏幕上,却怎么都找不到"。
解法分两层:
attachShadow钩子:在返回 HTML 时、页面脚本执行之前,把Element.prototype.attachShadow包一层,将mode:'closed'改写为'open'。必须注入在<head>最前——等did-finish-load再改原型,组件早已建好 closed shadow root,改了也没用;- 弹窗移除器:全量遍历(含各层 shadowRoot)→ 按文案关键词定位叶子节点(中英文都覆盖,注意中文界面实际文案是"连接出错!“,不是"连接错误”——漏掉就失效)→ 从命中节点向上回溯最外层的
fixed/absolute定位祖先(那才是弹窗遮罩,只删文案本身没用,删过头会连带删掉应用容器)→ 整块移除。MutationObserver+ 250ms 防抖合并(游戏 HUD 每帧都在改 DOM,不防抖开销不可接受),3 秒低频兜底覆盖不产生 mutation 记录的弹层。
5.5 保留的部分:防坑契约与桌面化细节
不变的防坑契约照旧:contextIsolation: true、全局错误与渲染进程 console 统一落盘 openfront-runtime.log(鸿蒙端没有 DevTools,这份日志依然是唯一的眼睛)。窗口 1440×900,backgroundColor: '#262626' 深色底避免首帧白闪;setWindowOpenHandler 一律拒绝外链(离线版点外链必然失败);单实例锁保证二次启动聚焦既有窗口。主进程全程约 8 KB——这里 Electron 只当浏览器容器,游戏不需要任何 Node 能力。

六、离线化流水线与端到端验证
上面第三、四、五节的所有改造,都被收敛成一条可重复的流水线——这是本次适配在工程方法上最值得复制的部分:离线化不应该是"改了哪儿记一下"的一次性手术,而应该是"从上游源码出发,一条命令到达可分发产物"的确定性变换。
6.1 构建侧:build-offline.mjs
git clone https://github.com/openfrontio/OpenFrontIO.git
cd OpenFrontIO
git checkout 3e3b962
git apply patches/0001-offline-singleplayer.patch # 唯一的上游源码改动
npm install
npx vite build # 上游产物输出到 static/
GIT_COMMIT=$(git rev-parse --short HEAD) node scripts/build-offline.mjs
build-offline.mjs 依次做五件事:EJS 模板预渲染为纯静态 HTML;注入离线 BOOTSTRAP_CONFIG(含 cdnBase = window.location.origin);移除 11 个外部脚本;锁定 adsEnabled 闸门;重指向教程视频。原模板保留为 static/index.html.ejs,脚本可重复运行——上游更新后重放整条命令即可,不依赖任何"手工改过的中间状态"。
6.2 验证侧:verify-offline.mjs
产物好不好,不能靠"打开页面看一眼"。验证脚本用真实浏览器走完整流程:真实点击「单人模式 → Start Game」、选择出生点,然后断言四件事:
BOOTSTRAP_CONFIG满足ClientEnv校验(必填字段齐全);- 游戏 canvas 完成渲染;
- 游戏时钟推进——启动 12 秒后时钟走到 00:11,证明回合模拟真的在跑,而不是渲染了一张静止地图;
- 公网请求数为 0——离线化的核心 KPI,任何漏网的第三方请求都会在这里现形。
浏览器侧拿到 RESULT: PASS 后,产物才进入 HAP 打包。
6.3 一个反直觉的验证细节:必须用有头浏览器
无头浏览器跑这个验证必然失败,而且不是脚本的问题:游戏会主动拒绝软件渲染——MapRenderer 用 /swiftshader|llvmpipe|software/i 匹配 GL renderer 并抛 GLUnavailableError(软件渲染下地图只有约 1fps,上游硬阻断)。而无头浏览器的 WebGL 只有 SwiftShader 这一条路。
所以验证脚本默认有头运行(HEADED=0 可切回)。这也是一个提前到浏览器阶段就确认的事实:这个游戏在任何没有 GPU 的环境下都起不来——它直接决定了后面 HAP 壳工程"保留硬件加速"的决策(见 5.2),以及真机验收时第一个要看的就是 WebGL 路径是否拿到硬件加速。
流水线全部走通后,才轮到 HAP:hvigor assembleHap 打包(613 MB 资源,打包与签名耗时明显长于常规应用,属预期)→ DevEco 自动签名(绑定 com.openfront.ohos 的 profile)→ 产出 843 MB 签名包 → hdc 安装真机。
七、真机验收
实测环境:HarmonyOS PC 2in1(SN 3QC0124C20000733),签名 HAP 843 MB(含约 613 MB 游戏素材),bundleName com.openfront.ohos。
6.1 启动与主界面
安装后桌面出现 OpenFront 图标,启动即看到图 0 的画面:窗口标题"OpenFront (内测版)",背景的六边形世界地图已由 WebGL 渲染出来——硬件加速路径在真机上直接成立。加载弹窗显示 AGPL-3.0 授权信息,全程无黑屏、无白屏、无启动崩溃。
主界面加载完成后的状态:

图 1:主界面渲染完整——导航栏(游戏/商店/INVENTORY/排行榜/CLANS)、随机分配的匿名昵称、"单人模式"入口。注意界面是中文:上游的多语言资源已随 _assets/ 打包并在真机生效
6.2 单人模式开局
点击"单人模式",进入地图选择页:

图 2:地图选择页——精选/全部标签页,世界、欧洲、北美洲、南美洲、亚洲、非洲六张基础地图的缩略图与搜索框全部正常。这些缩略图与地图数据都来自本地 _assets/,离线可用
选好地图点 START GAME,选出生点后对局开始。游戏时钟从 00:00 开始推进——这和浏览器验证脚本里的断言(启动 12 秒后时钟走到 00:11)一样,是"回合模拟真的在跑"的直接证据,而不是只渲染了一张静止地图。
6.3 多语言资源
验收过程中还确认了一个意外之喜。同一台真机上,地图选择页既能以中文显示(精选 / 全部 / 世界 / 亚洲,见图 2),也能以英文显示(MAP.FEATURED / MAP.ALL / MAP.WORLD / MAP.ASIA):

图 3:地图选择页的英文 locale——与图 2 同一页面。说明上游的多语言资源在离线产物中完整打包、按需加载,并未被离线化过程破坏。这一点对海外用户多的策略游戏很关键
6.4 对局内:渲染、模拟与胜负判定
对局内是验收的重头。中东区域的对局画面:

图 4:对局内画面——中东地图上各国领土彩色渲染(Egypt 17.1K、Iran 18.8K、Saudi Arabia 13.8K),左上排行榜实时刷新(Japan、Rusia、Mongolia 的人口/兵力/领土数据),右上游戏时钟 02:12。中央弹窗"你已经死了"提供退出/观战选项——胜负判定链路在真机上正常工作
这张图信息量很大,逐项对应验收要点:WebGL2 地图渲染(✅)、回合模拟推进(时钟 02:12,✅)、排行榜数据由本地模拟持续更新(✅)、胜负判定与终局交互(✅)、中文多语言(✅)。
6.5 深度对局:32 分钟不间断运行
单机版能不能支撑一局完整的长时间游戏?第二天的验收专门打了一局长的:

图 5:世界地图上的深度对局——游戏时钟 32:50,玩家 AnonOmen3 已扩张到全球领土的 56.7%(人口 175.5M、兵力 4.29M/5.68M),南美全境、非洲大部已染成玩家色。画面中可见贸易船在海上航行(右下提示"Your trade-ship was captured by…")、攻击进度条、教程提示(Step 3 of 22)、资源栏(金币/劳动力/攻击力)
这一局回答了几个单机版最关键的问题:
- 长时程稳定性:32 分钟 50 秒不间断对局,无崩溃、无卡死、无弹窗打断;
- LocalServer 的模拟深度:领土扩张、设施建造、贸易船、NPC 国家 AI 全部正常运转,排行榜上其他"国家"在真实地增长;
- 交互完整:攻击(带进度条的推进攻击)、开图缩放、教程系统均可交互;
- 性能可玩:地图元素远多于开局阶段(数百个建筑图标 + 贸易单位),渲染帧率仍可玩。
八、能力分级(T0 / T1 / T2)
按"真机实测、未验证不写通过"的口径:
T0 —— 基础可用(全部通过)
| 能力项 | 状态 |
|---|---|
| HAP 安装到 2in1 真机(arm64-v8a,843 MB 签名包) | ✅ |
| 启动进入主界面(本地 HTTP 服务承载,无黑屏/白屏) | ✅ |
| 单人模式开局(单人模式 → START GAME → 选出生点) | ✅ |
| 地图渲染(WebGL2 硬件加速路径) | ✅ |
| 游戏时钟推进(回合模拟运行) | ✅ |
T1 —— 完整可用(核心通过)
| 能力项 | 状态 |
|---|---|
| 键鼠操作(选点、菜单、地图缩放、攻击交互) | ✅ |
断网可玩(产物零公网请求,浏览器宿主已断言 Public-host requests: none) | ✅ |
| 联网失败弹窗不干扰主流程(stub + 移除器生效) | ✅ |
| 退出后再次启动 | ✅ |
| 多语言资源(中/英界面均验证,见图 2/图 3) | ✅ |
| 逐语言全量走查 | ⚠️ 未做 |
T2 —— 平台体验
| 能力项 | 状态 |
|---|---|
| 窗口尺寸与首帧体验(1440×900,深色底防白闪) | ✅ |
GPU 硬件加速路径(保留 GPU + --ignore-gpu-blocklist) | ✅ |
单实例锁(已实现 requestSingleInstanceLock) | ⚠️ 已实现,未实测 |
| 窗口拉伸自适应 / 触控与平板形态 | ⚠️ 未专项测试 |
未覆盖:多人/排位/账号/商店/成就(离线单机版的预期范围外);整局打到终局胜利的完整胜负流程未专项回归(图 4 已覆盖"失败"分支)。
九、Q&A:几个高频问题
Q1:为什么不用 ArkWeb(Web 组件)承载,包体能小很多?
本作的 WebGL2 是硬性要求,且资源按 location.origin 拼绝对 URL、Worker 内 fetch 地图数据——ArkWeb 的 onInterceptRequest 方案要逐一模拟这些行为,验证成本高。Electron 路线有前面多个项目沉淀的先验(事件注入行为、日志体系、进程模型),这次反而因为沿用 Electron 才省了时间。包体 843 MB 是"重前端游戏 + 177MB 运行时"的固定成本。
Q2:离线版和上游在线版是什么关系?会跟进上游更新吗?
单机版是基于 commit 3e3b962 的快照,对上游的修改只有一处(isLocal 跳过 play token),以 patch 维护。上游演进后,重放"checkout → apply patch → build → 离线化脚本"即可跟进,renderer/ 可随时重新生成。
Q3:AGPL-3.0 协议对分发有什么约束?
仓库必须保持公开(这一点与公开仓库的定位天然一致),且必须随分发公开对上游源码的修改——patches/ 目录就是这个义务的落点。游戏素材另有 CC BY-SA 4.0,随包保留署名。商用闭源分发不可行。
Q4:为什么弹窗用"移除 DOM"而不是改 bundle?
两个理由:renderer/ 保持可随时重新生成,改 bundle 意味着每次重建都要重打补丁;且移除器只动 UI 层,不触碰任何游戏逻辑,风险面小。代价是要处理 closed shadow DOM 穿透(见 5.4)。
Q5:真机上 WebGL2 性能够吗?
实测够用。硬件加速路径正常,32 分钟深度对局(图 5)后期地图元素数百个,帧率仍可玩。上游自己在软件渲染下也只有约 1fps 并主动报错,说明这个游戏本来就必须吃 GPU——真机能拿到硬件加速是本适配成立的前提,也是最重要的一个结论。
Q6:账号体系完全不可用了吗?
单机版刻意不伪造登录态:账号接口返回 401,游戏按未登录(匿名昵称)运行,排行榜是本地模拟数据。多人、排位、商店、成就等联网功能不可用,这是离线单机版的定义边界,不是缺陷。
十、结语
OpenFront.io 是我做过"预设与实际相反"最多的一次适配:以为难点在 HAP 打包,实际难点在离线化;以为要禁 GPU(前面每个项目都禁),实际禁了就进不去;以为本地接口被拒绝无所谓,实际要伪造一个"成功返回空数据"的 stub 才干净。
三个可复用的结论,按价值排序:
- 鸿蒙 PC 的 GPU 加速 WebGL2 可用。 此前"一刀切禁 GPU"的防坑经验,边界条件是"软渲染可承受的负载"。对 WebGL 类应用,正确的做法是保留硬件加速并实测——这为后续移植更重的图形应用打开了门。
- "服务端权威"应用未必不能离线。 先在源码里找有没有本地模拟路径(本例的
LocalServer),有的话,离线化就是拆障碍而不是移植服务端,工作量差一个数量级。 - 对照上游的部署形态找 origin 依赖。 任何依赖
location.origin、相对路径fetch、Service Worker 的应用,file://承载都是死路;主进程起一个"只听回环 + 先解码再判越界"的本地静态服务,是通用解。
给想做同类适配的后来者一句建议:离线化改造要在浏览器里做完并脚本化验证(断言时钟推进、断言公网请求为零),再上真机。浏览器验证快、可观测、可重复,真机只留给真正需要真机的问题——这次两个阶段的问题一点都没有互相污染,流程干净了很多。
欢迎在评论区交流,也欢迎一起参与鸿蒙 PC 生态建设。
参考资料
- 上游仓库:https://github.com/openfrontio/OpenFrontIO(AGPL-3.0 / 素材 CC BY-SA 4.0)
- 适配工程:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_OpenFrontIO
- 同系列前作:把 2048 搬上鸿蒙 PC(键盘事件桥)、Zettlr / Clumsy Bird / Standard Notes(Web 静态产物 + Electron 壳)
更多推荐



所有评论(0)