第 50 章 / 共 51 章
卷五 · 扩展、运维与实战

webview 与 app 节点:把站点变成桌面应用

前面四十九章产出的都是"站点"——浏览器里输入地址访问。这一章给它一个窗口:class: "app" 服务节点 = 本地 HTTP 服务 + WebView2 桌面窗口,配置即得、自动降级、还能和 xs pack 组合成双击即用的单 exe。

本章导读

站位:桌面形态收官(第 15 扩展 webview 的主场)。核心心智模型一句话——窗口是显示层,服务是本体:app 节点不是第六种协议,而是 http 节点的前端化,host_default / hosts / static / TLS / 热重载全部同义,只多一个 window 描述符与前端生命周期。

50.1 第一个桌面应用

把任意现成站点的服务节点改成 class: "app",加一段 window 描述符:

配置示例 · app 节点
{
  "enabled": true, "class": "app", "name": "main",
  "ip": "127.0.0.1", "port": 0,
  "host_default": { "name": "app", "path": "wwwroot", "devlang": "c",
                    "devfile": "script/http_main.c" },
  "window": {
    "title": "我的应用", "width": 1280, "height": 800,
    "url": "/", "devtools": false,
    "close": "stop", "install_fallback": "ask"
  }
}

启动 xs:先弹加载窗(服务就绪前的占位),本地 HTTP 就绪后自动打开 url 指向的主界面。注意两个与 http 类不同的缺省:ip 默认 127.0.0.1(回环绑定不触发 Windows 防火墙弹窗)与 port 允许 0(自动分配,见 50.3)。window 九个字段的逐字段约束(含 min_width / min_height / install_fallback 取值表)见 xs.json 字段参考。

构建变体须编入 webview 扩展:build.bat webview(或 all);无扩展的变体配置照常解析、只是没有窗口(见 50.4 矩阵)。

50.2 生命周期与 close 语义

  • 启动:加载窗先行(遮住服务初始化的空窗期)→ 服务就绪 → 自动导航到主界面
  • close: "stop"(默认):用户关窗 = 优雅停机,走与 Ctrl+C 相同的停机链——不是杀进程
  • close: "hide":关窗只隐藏、服务继续;再次双击同一应用时经单实例检测(按 exe 路径派生的命名 mutex)唤起既有窗口,新进程直接退出。v1 无托盘图标;hide 模式下停服务走信号路径
  • 崩溃隔离:WebView2 渲染器是独立进程,页面崩溃不传染 xs;窗口线程任何失败只降级为日志,绝不让引擎装配走 fail 路径

50.3 port: 0 自动分配

桌面应用的端口不该让用户看见,也不该和其他应用打架——port: 0 交给 OS 原子分配(仅 app 类允许,其他类 fail-fast):

  • 回读:绑定成功后实际端口写进 XS_ServerInfo.PortBound(同时回写 Port,端点标识与脚本视图一致);前端拼 URL 用的就是它
  • 脚本读法:xsServerFind("main")->PortBound,配进页面让前端知道自己是谁(如 WS 地址)
  • 热重载粘滞:按服务名记粘滞表(跨 generation 存活),换代时显式绑回旧端口——已开窗口无感知;粘滞端口被占则候选失败按既有失败清理
  • 边界:粘滞表不落盘。跨重启要端口稳定请配固定端口(外部系统要回调你时尤其如此)

50.4 降级矩阵与打包发布

构建变体平台WebView2行为
webview 扩展Windows已装完整窗口功能
webview 扩展Windows缺失按 install_fallback:ask 问询 / silent 静默装 / headless 直接降级;失败一律 headless
webview 扩展Linux—headless,日志打出访问 URL
无扩展任意—同 headless + 一行提示;配置照常解析

发布组合拳:xs pack(第 48 章)把站点打进二进制 + xsw.exe(无控制台的 GUI 子系统变体,随发布包提供)——产物是双击即用的单 exe 桌面应用:加载窗 → 界面 → 关窗停机,全程没有黑框。零运行时 DLL:导入表仅系统 DLL,WebView2Loader.dll 运行时从已装 Runtime 目录定位,缺 Runtime 按 install_fallback 兜底。

常见错误

坑:重启后端口变了port:0 的粘滞只在进程存活期(热重载场景);跨重启每次都是新分配。需要外部系统回调或用户收藏地址的场景,配固定端口。
坑:手动把 ip 改成 0.0.0.0 触发防火墙弹窗app 类默认 127.0.0.1 正是为了回环绑定不弹 Windows 防火墙授权框。桌面应用不需要对外监听时别改回去。
坑:Linux 上窗口没出现这是设计行为不是 bug——webview 的窗口后端仅 Windows;Linux 上自动 headless,看日志里的 open http://… in a browser 行拿地址。同一份 xs.json 跨平台可移植。
坑:生产包开着 devtoolswindow.devtools 默认 false;开发期开着方便,发布前关掉——用户按 F12 能看到你的接口与数据结构。

编程练习

基础
把卷二配套静态站套上 app 节点窗口跑起来,依次体验 close: stop 与 close: hide(含二次双击唤起)。
进阶
port: 0 启动,脚本里 xsServerFind 读 PortBound 并渲染进页面(如 WS 连接地址)。
验收
xs pack 出单 exe,拷到一台没装 WebView2 Runtime 的机器验证 install_fallback: "ask" 问询兜底,再改 "headless" 验证直接降级。

小结速查

class: "app" = http 前端化(配置同义 + window 9 字段) · 加载窗→就绪→开 URL→关窗停机 = 生命周期 · port:0 + PortBound = 隐形端口(跨重启要稳配固定) · 降级矩阵 = 窗口 best-effort 服务为本体 · xs pack + xsw.exe = 双击即用的桌面 exe