拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Deno desktop 启动时如何把 Deno 运行时加载进原生窗口并导航到本地服务

Deno desktop 启动时如何把 Deno 运行时加载进原生窗口并导航到本地服务【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/deno把 Web 应用打包成原生桌面应用时deno desktop的产物在启动阶段要完成三件事让后端二进制把 Deno 运行时作为共享库加载进进程、在本地回环端口起一个 HTTP 服务、等这个服务真正可用后把窗口导航过去。Deno 仓库中的 doc/desktop-architecture.md 专门记录了这条启动链路配合 cli/rt_desktop/lib.rs 的源码可以完整走一遍。本文按“后端如何加载运行时 → 端口如何预发布 → 运行时与服务如何并行 → 窗口何时导航”的顺序拆解这条链路并给出每一环节的可核对验证点。先理解加载模型Deno 运行时是被后端 dlopen 的 cdylib桌面产物的结构是“反置”的拥有main()、事件循环和窗口的可执行文件不是 Deno而是 Laufey 后端CEF 对应laufey系统 WebView 对应laufey_webviewDeno 运行时则是一个名为libdenort的 cdylib定义在 cli/rt_desktop/ 中cli/rt_desktop/Cargo.toml 里crate-type [cdylib]。打包器写出的启动器最终就是同一条调用——后端启动时带上--runtime dylib 路径dlopen它并经由 C ABI 进入它。ABI 握手由laufey::main!(|| { … })宏生成dylib 导出laufey_runtime_init/_start/_shutdown三个符号宏闭包体就是后端在init之后调用的运行时入口cli/rt_desktop/lib.rs 第 1200 行起的laufey::main!。版本安全分两层编译期断言cli/rt_desktop/lib.rs 用const _: () assert!(laufey::LAUFEY_API_VERSION 34, …)保证链接的laufeycrate 与随包发布的预编译后端说的是同一版 C ABI若漂移cargo build直接失败而不是产出一个“静默无法启动”的 dylib。构建期固定cli/tools/desktop.rs 中的LAUFEY_VERSION由cli/build.rs注入固定后端版本下载按仓库内 SHA-256 摘要cli/laufey_sums.lock经LAUFEY_PINNED_SUMS编译进二进制做完整性校验LaufeyBackendResolver的解析顺序是LAUFEY_DEV_DIR检出 → 缓存下载 → 全新下载。dylib 还会用dladdr在自己的函数上反查落盘路径cli/rt_desktop/lib.rs 中的get_dylib_path约第 1091 行用于自动更新的哨兵文件和定位内嵌 payload。核对方式构建后确认产物目录里同时有后端可执行文件和libdenortmacOS 为.dylib、Windows 为denort.dll、Linux 为.so见 cli/standalone/binary.rs 中get_libdenort_path的三平台命名若后端与 dylib 的 Laufey ABI 版本不匹配构建期断言或init阶段就会报错不会静默通过。启动顺序为什么必须是单线程先发布端口再 chdir进入laufey::main!闭包后、tokio 运行时建立之前代码刻意保持单线程因为有两个进程级操作一旦存在线程就不安全先于运行时构建发布端口。闭包中先调用allocate_random_port()分配随机回环端口再执行std::env::set_var(DENO_SERVE_ADDRESS, format!(tcp:127.0.0.1:{}, desktop_serve_port))cli/rt_desktop/lib.rs。原因是 glibc 上setenv非线程安全Rust 1.81 起将其标记为unsafe这一步必须在 mio IO 线程和 inspector 线程出现之前完成。这样用户代码里的Deno.serve/export default { fetch }稍后绑定这个预置地址时不需要任何协调——服务端口是启动时就定好的而不是用户代码自己选的。chdir 进解压后的 VFS 目录。同样是进程级操作必须先于任何任务解析相对路径框架如 Next 的.next/、Vite 的dist/都按 cwd 相对路径找构建产物。源码中的注释明确说明chdir 若在运行时构建后进行会与解析相对路径的代码产生竞争。在这两步之前闭包还处理了三件前置事项理解它们有助于解释“为什么有的进程没有窗口”应用未决更新unixapply_pending_updatefork 再入守卫框架 dev server fork 出的 worker 会重新执行同一个 dylib代码用 argv 形如exe run … script.js加上父进程 worker 环境变量NODE_CHANNEL_FD/NEXT_PRIVATE_WORKER的组合判定 worker检测到的 worker 走 headless 路径、不创建窗口。注释特别说明只用环境变量判断会误伤用户 shell 中恰好已设置该变量的场景。用户代码不是文件而是 dylib 里的一段 section与“dylib 旁边放一堆文件”不同用户代码和资产是 dylib 内部一个名为d3n0l4nd的 section。启动时用libsui::find_section_in_current_image读回cli/rt_desktop/lib.rs 的find_section_in_dylibextract_standalone_with_finder把它解析成 standalone 元数据 VFS——与deno compile的解析方式一致只是数据来源从 argv0 换成了已加载的镜像本身。若metadata.self_extracting存在还会把 VFS 解压到磁盘并 chdir 到root_pathcli/rt_desktop/lib.rs。核对方式对产物执行strings libdenort.ext | grep d3n0l4nd或对应平台的 section 检查命令可以确认内嵌段存在启动日志中若出现failed to read standalone section说明这一环节失败。并行运行与导航用真实 GET 探测服务就绪之后run_on_runtime_thread在专用线程上建 tokio 运行时并block_on(run_desktop(…))cli/rt_desktop/lib.rs。run_desktop第 1695 行起先做若干配置——telemetry、inspector若以deno desktop --inspect[-brk|-wait]启动子进程在父进程分配的DENO_DESKTOP_INSPECT_INTERNAL_PORT上监听、HMR 目录DENO_DESKTOP_HMR、以及DENO_DESKTOP_DEV_URL/DENO_DESKTOP_FRAMEWORK_DEV两个框架 dev 环境变量——然后组装RunOptions并用tokio::select!并发跑两个 futuredenort::run::run_with_options(…)Deno 运行时本身auto_serve: true、serve_port: Some(desktop_serve_port)、serve_host: Some(127.0.0.1)op_state_init闭包里创建事件通道、构建WefDesktopApi并调用api.create_initial_window(800, 600)创建初始窗口。该窗口以隐藏状态创建在on_page_load回调中、内容真正绘制后才显示cli/rt_desktop/lib.rs 的注释引用了 issue #35530避免用户看到导航前的空白帧。窗口标题默认取deno.json里desktop.app.name编译期烘焙进元数据避免应用从未自建BrowserWindow时标题显示为后端的laufey_webview。laufey::run()原生事件循环。第三个任务navigate_futcli/rt_desktop/lib.rs负责把两者桥接起来。它的探测方式值得注意不是一次 TCP connect而是一次完整的GET / HTTP/1.1请求并检查状态行——因为 Vite 这类 dev server 在接受连接之前还不能真正响应。循环最多 60 次、每次间隔 250ms即 15 秒收到HTTP/1.x 2xx或3xx状态行即判定就绪随后对初始窗口 id 调用Window::navigate(http://127.0.0.1:port)。若 15 秒后仍未就绪代码记录Server not ready after 15s, navigating anyway警告后仍然导航。若--inspect-brk/--inspect-wait生效导航前会先阻塞轮询 mux 的/debugger-attached端点直到 DevTools 客户端挂上间隔 200ms防止渲染进程抢跑。还有一个兜底导航后 10 秒无条件对窗口调用show()cli/rt_desktop/lib.rs。因为窗口本应在on_page_load时显示一旦初始导航失败、加载永远不结束这个兜底保证用户至少能看到窗口而不是什么都没有show()幂等与正常路径竞争无副作用。核对方式导航成功后窗口地址就是http://127.0.0.1:desktop_serve_port/可以在系统工具lsof/netstat里确认该端口被产物进程占用、且回环地址为127.0.0.1日志里Server ready after N attempts, navigating to …debug 级表示探测在第 N 次尝试命中navigating anyway警告则意味着 15 秒内服务未就绪——此时窗口虽然打开页面很可能处于错误状态应先排查应用自身的启动错误。两条传输通道分离内容走 HTTP绑定走事件通道窗口里看到的应用内容走的是普通回环 HTTP——WebView 就是一个指向http://127.0.0.1:port/的真实浏览器。但Deno.desktop/ 窗口 API 以及 webview→Deno 的函数调用完全不经过 HTTP而是 mpsc 事件 每次调用一个 oneshot原生 → 运行时单一有界 mpsc 通道承载DesktopEvent容量 1024。高频事件鼠标移动、滚轮走try_send背压时直接丢弃而不是阻塞运行时JS 侧只有一个异步消费循环等待op_desktop_recv_event()把每个事件分发给对应的 DOM 风格EventTarget且该 op 的 promise 被unrefOpPromise过事件泵本身不会把事件循环“续命”webview 看到的 JS 命名空间是bindingslaufey::set_js_namespace(bindings)cli/rt_desktop/lib.rs所以绑定调用形如window.bindings.foo()。一次绑定调用的往返是window.bind(name, fn)注册后webview 端序列化参数、从全局AtomicU32分配call_id并注册 oneshot发DesktopEvent::BindCall { window_id, name, args, call_id }JS 侧 handler 完成后用op_desktop_resolve_bind_call/op_desktop_reject_bind_call按call_id回应。call_id键控让同一窗口的并发绑定调用能在单条事件通道上复用并正确路由回包。启动失败时如何被呈现启动链路的边界行为也值得了解应用主模块还在加载/首次求值阶段抛出的错误会被打上DesktopStartupError标记由桌面外壳自己弹原生错误对话框此时应用自身的错误监听器还没机会运行加载完成后的 JS 错误则留给应用自己的error/unhandledrejection监听器避免重复弹框判定逻辑见should_show_native_error_dialog及其测试。HMR 相关的重启走退出码 75HMR_RESTART_EXIT_CODE交给deno desktop --hmr的父进程监督者重启而不是进程内 re-exec以便保留进程组、Ctrl-C 处理和临时入口清理。--inspect下cli/tools/desktop_devtools.rs 在父进程跑一个 CDP 多路复用器把 Deno inspector 和渲染进程的调试端口统一挂在用户可见的一个/unifiedwebsocket 后面子进程收到的DENO_DESKTOP_INSPECT_INTERNAL_PORT若不是合法SocketAddr会直接bail!让失败可见而不是静默禁用 inspector。小结一条可复述的启动序列后端二进制dlopenlibdenort经laufey_runtime_init/_start进入laufey::main!闭包单线程阶段分配随机回环端口并写入DENO_SERVE_ADDRESS从 dylib 的d3n0l4ndsection 提取 VFS 并 chdirtokio::select!并发启动 Deno 运行时auto_serve绑定预发布端口与 Laufey 原生事件循环同时navigate_fut用真实GET /轮询最多 15 秒收到 2xx/3xx 后Window::navigate到http://127.0.0.1:port窗口在内容绘制后显示10 秒兜底强制显示。如果你要在这个基础上做二次开发例如自定义窗口行为或绑定调用入口就是 cli/rt_desktop/lib.rs 中WefDesktopApi的窗口事件装配与RunOptions.op_state_init闭包若要排查“窗口是空白/一直不出现”的问题按上述顺序检查端口发布DENO_SERVE_ADDRESS、standalone section 提取、GET /轮询结果与on_page_load/兜底show()四个环节的日志即可。【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/deno创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门