返回资讯中心
外部精选
软件工程
#开发者实时流

webframe 用系统隐藏 webview 在命令行运行网页

来源标题:Webframe runs web pages as CLI programs using a hidden system web view

webframe 将网页加载到隐藏的系统 webview 中,作为 CLI 程序运行,控制台输出转到 stdout、异常转到 stderr,并可从 stdin 执行页面 JavaScript。它复用系统自带 webview(macOS/Linux 用 WebKit,Windows 用 WebView2),是单个小体积二进制,多数平台无需 cgo。可用于脚本、CI、cron 或无头运行单页应用等场景,也可作为跨平台隔离库使用。

Hacker News 最新javatuts2026/11/7 · 7 天前28 分钟阅读

以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。

像运行程序一样运行网页。webframe 在隐藏的 webview 中加载 URL,让它全速运行,并把页面连接到你的终端:console 输出到 stdout,异常输出到 stderr,你可以从 stdin 在页面中执行 JavaScript。 $ webframe examples/hello.html Hello, world! 它通过 glaze 使用操作系统自带的 webview(macOS 上是 WebKit,Linux 上是 WebKitGTK,Windows 上是 WebView2),因此它是一个小巧的单一二进制文件,不捆绑浏览器,而且在大多数平台上不需要 cgo。 无需完整的浏览器自动化栈,即可从脚本、CI 或 cron 中运行仅限浏览器的代码(WASM、WebGPU、Web Audio、DOM 库)。 让页面作为长生命周期的进程在后台持续运行,就像一个守护进程或 worker。 用 -c 从 REPL 中操作一个正在运行的页面。 编写脚本并测试页面:用 -i 注入桩代码,用 -x 运行测试,并通过 webframe.exit(code) 从页面设置退出状态。 以 headless 方式运行单页应用或模拟器,例如 v86,并通过 stdio 或 WebSocket 与它通信。 将其作为库使用,以实现跨平台隔离 安装 从 releases 下载适用于你所在平台的 zip,或使用 Go 1.27+ 从源码构建: git clone https://github.com/tractordev/webframe cd webframe make build # ./webframe 运行时要求: 在 Linux 上,如果 DISPLAY 和 WAYLAND_DISPLAY 都未设置,webframe 会启动自己的私有 Xvfb,并在退出时将其关闭。如果你在 xvfb-run 或桌面会话下运行它,则会改用那个 display。 Debian/Ubuntu:apt install libwebkit2gtk-4.1-0 xvfb webframe [flags] <url|file> 该参数是必需的。它可以是 URL 或本地文件的路径。使用 about:blank 表示空页面。标志可以放在它前面或后面。 console.log 按原样打印到 stdout。其他 console 方法会带前缀: hello 1 {"a":[1,2]} null undefined [warn] careful [error] Error: boom global code@file:///tmp/page.html:4:49 参数的格式化方式与 Node 类似:字符串原样打印,其他值按 JSON 打印,并处理循环引用([Circular])、错误(带堆栈)、函数、undefined、symbol、bigint 和 DOM 节点。输出保持页面的顺序,即使在快速连续输出时也是如此。 异常会带引擎提供的尽可能多的细节打印到 stderr: Unhandled promise rejection: TypeError: nope 来自 file:// 页面上的脚本以及没有 CORS 的跨源脚本的错误只会显示 Script error.。这是 WebKit 在隐藏细节,不是 webframe 的问题。如果你需要完整错误,请通过 http 提供页面。 alert、confirm 和 prompt 会以 [dialog] ... 的形式打印,并按已取消处理:confirm 返回 false,prompt 返回 null。使用 -y 则按已接受处理:confirm 返回 true,prompt 返回其默认文本。 -c 在页面加载后从 stdin 读取 JavaScript,并一次求值一条语句。一条语句可以跨多行:如果某一行使块、调用或字符串处于未闭合状态,就会与后续行合并,直到能解析为止。在终端上,空行会提前结束它。每条语句都作为全局脚本求值,因此 var 和 function 声明在语句之间保持已定义状态。如果结果是 promise,webframe 会等待它并打印解析后的值。字符串会带引号打印。错误输出到 stderr,且不计入 -e。 webframe 在 stdin 关闭后、最后一个结果之后退出。 要在空页面上使用控制台,请使用 about:blank: $ webframe -c about:blank

var x = 40 undefined x + 2 42 new Promise(r => setTimeout(() => r("later"), 100)) "later" function add(a, b) { ... return a + b ... } undefined add(1, 2) 3 nope Uncaught ReferenceError: Can't find variable: nope about:blank 具有不透明源,因此跨源 fetch 和 localStorage 之类的东西需要真实页面。控制台模式适用于任何页面,因此你可以在页面运行时驱动它: $ webframe -c examples/v86.html 没有终端时,它将 stdin 作为流读取,这对于脚本很有用: echo 'document.title' | webframe -c https://example.com 脚本 -x 在页面加载后运行文件,在页面自身的 load 处理器之后。它的求值方式与控制台语句类似,因此可以有多行,并且如果它以 promise 结尾,webframe 会等待它。如果它抛出异常或 reject,webframe 会打印错误并以 1 退出,无论是否带 -e。重复使用 -x 可以按顺序运行多个文件,每个都在上一个完成之后。配合 -c 时,控制台会在它们之后启动。 脚本完成后页面不会退出。调用 webframe.exit(code) 以指定状态退出,或调用 window.close() 以 0 退出;并使用 -t,这样挂起的测试就不会阻塞 CI: // test.js (async () => { document.querySelector("#save").click(); await new Promise(r => setTimeout(r, 100)); webframe.exit(document.querySelector(".saved") ? 0 : 1); })(); webframe -t 30 -x test.js app.html -i 在页面的脚本运行之前将文件注入每个页面,包括在导航和重新加载之后。用它来注入桩代码、fetch 钩子和测试辅助函数。文件按顺序运行,在 webframe 自己的钩子之后,因此 console 和 webframe 已经就绪。它们只在顶层 frame 中运行,不在 iframe 中运行。抛出异常的 -i 脚本会像任何页面异常一样被报告:只有在带 -e 时才会退出,因为它会在每个页面上再次运行。 综合起来,todo.html 通过用 mock-fetch.js 对 fetch 打桩,并用 -y 接受其删除确认,从而在没有服务器的情况下进行测试: $ webframe -y -t 30 -i examples/mock-fetch.js -x examples/todo.test.js examples/todo.html ok loads saved todos ok adds a todo [dialog] Delete "Buy milk"? ok ignores blank input ok deletes after confirm ok saves each change $ echo $? 0 在 Content-Security-Policy 阻止 eval 的页面上,-x 脚本必须是单个表达式,就像上面的 async 函数那样;-i 脚本不受影响。 -E 在任何页面脚本运行之前,将环境变量以字符串形式暴露给页面,位于 webframe.env 上。只有你指定的变量才会被暴露,因此密钥可以留在环境中,而不是写在脚本里: API_KEY=abc webframe -E API_KEY app.html # webframe.env.API_KEY === "abc" webframe -E MODE=test app.html # webframe.env.MODE === "test" 不带值的 -E NAME 如果 NAME 未设置,会以状态 2 退出,因此一个

(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →