原文链接:Useful patterns for building HTML tools
作者:Simon Willison
我开始使用”HTML 工具”这个术语来指代自己开发的 HTML 应用程序——这些应用将 HTML、JavaScript 和 CSS 整合到单个文件中,从而提供实用功能。过去两年间我已构建了超过 150 个此类工具,其中几乎全部由大型语言模型(LLMs)编写。本文将分享我在实践中总结的一系列实用模式。
首先,举几个例子来说明我所指的内容:
- svg-render 将 SVG 代码渲染为可下载的 JPEG 或 PNG 图片。
- pypi-changelog 让你生成(并复制到剪贴板)不同 PyPI 包版本之间的差异对比 (diffs)。
- bluesky-thread 为 Bluesky 上的讨论串提供嵌套视图。
这些是我最近最喜欢的几款。我还有几十个类似的,经常使用。
你可以在 tools.simonwillison.net 上浏览我的收藏——按月查看功能便于浏览整个收藏库。
若需查看代码和提示词,本文中几乎所有示例的页脚都包含指向 GitHub 的”查看源代码”链接。GitHub 提交记录通常包含提示词本身,或指向用于创建该工具的脚本文件的链接。
- The anatomy of an HTML tool
- Prototype with Artifacts or Canvas
- Switch to a coding agent for more complex projects
- Load dependencies from CDNs
- Host them somewhere else
- Take advantage of copy and paste
- Build debugging tools
- Persist state in the URL
- Use localStorage for secrets or larger state
- Collect CORS-enabled APIs
- LLMs can be called directly via CORS
- Don’t be afraid of opening files
- You can offer downloadable files too
- Pyodide can run Python code in the browser
- WebAssembly opens more possibilities
- Remix your previous tools
- Record the prompt and transcript
- Go forth and build
解构 HTML 工具
以下是在构建此类工具时,我发现最高效的特征和模式:
- 保持单个文件:将 JavaScript 和 CSS 内联在同一个 HTML 文件中,意味着在托管或分发它们时遇到的麻烦最少。至关重要的是,这意味着你可以直接从大语言模型(LLM)的回复中将它们复制粘贴出来。
- 避免使用 React,或者任何需要构建步骤的东西。React 的问题在于 JSX 需要构建步骤,这使得一切都变得极其不方便。我通常会在提示词中加上 “no react”(不要用 React),直接跳过这整个复杂体系。
- 从 CDN 加载依赖。依赖越少越好,但如果有成熟的库能解决问题,我会很乐意从 CDNjs、jsDelivr 等 CDN 加载它。
- 保持工具小规模。几百行代码意味着可维护性不再是大问题:任何一个靠谱的 LLM 都能读懂它们的逻辑,而在 LLM 的帮助下从头重写一个工具只需要几分钟。
最终结果就是,几百行代码,可以干净地复制粘贴进 GitHub 仓库。
使用 Artifacts 或 Canvas 进行原型开发
构建这类工具最简单的方法,是直接从 ChatGPT、Claude 或 Gemini 开始。这三者都支持直接生成并展示一个 HTML + JavaScript 应用。
Claude 把这个功能叫 Artifacts, ChatGPT 和 Gemini 把它叫 Canvas。Claude 默认启用该功能,而 ChatGPT 和 Gemini 可能需要在工具菜单中手动开启。
你可以在 Gemini 或 ChatGPT 中试试这个提示词:
构建一个 canvas,让我粘贴 JSON,并把它转换为 YAML。不使用 React。
在 Claude 中可以这样写:
构建一个 artifact,让我粘贴 JSON,并把它转换为 YAML。不使用 React。
我总是在提示词里加上 “No React”,否则它们往往会用 React 构建,导致文件更难复制粘贴、也更难在别处使用。我发现使用 React 的尝试通常:加载时间更长(因为还要跑构建步骤),更容易出现崩溃性 bug,尤其是在 ChatGPT 中。
使用编码代理(coding Agent) 处理更复杂的项目
像 Claude Code、Codex CLI 这样的编码代理有一个明显优势:它们可以在编写过程中 自己测试代码,比如通过 Playwright。当我在做更复杂的项目时(例如前面提到的 Bluesky 线程查看器),我通常会升级使用这些工具。
我也经常使用异步编码代理(比如 Claude Code for web)来修改已有工具。我曾在《Building a tool to copy-paste share terminal sessions using Claude Code for web》中分享过相关视频。
Claude Code for web 和 Codex Cloud 可以直接操作我的 simonw/tools 仓库,这意味着它们可以通过 Pull Request 发布或升级工具,我甚至不需要自己复制粘贴任何代码。
从 CDN 加载依赖
只要工具里需要额外的 JavaScript 库,我都会选择从 CDN 加载。
三大 LLM 平台在 Artifacts / Canvas 功能中都支持特定 CDN,所以如果你说 “使用 PDF.js”,它们通常能自动生成可用的 CDN URL。有时你需要自己去 cdnjs 或 jsDelivr 查找 URL,然后手动贴进对话。
这些 CDN 已经存在很多年了,只要 URL 中明确包含版本号,我对它们的可靠性是非常放心的。相较之下,使用 npm 并引入构建步骤会显著降低我快速试验工具的效率,也让自托管变得更复杂。
把工具托管在别的地方
我不喜欢把 HTML 工具留在 LLM 平台上托管,原因有几个:首先,LLM 平台通常在非常严格的沙盒中运行工具:这样往往无法加载外部 URL 的数据或图片,有时甚至连跳转外部链接都被禁用。其次,最终用户体验也通常不佳:新用户会看到各种警告,加载速度慢,平台还会频繁展示自身的推广信息。此外,它们的可靠性也不如传统静态托管。如果 ChatGPT 或 Claude 出现故障,我仍然希望能访问我之前构建的工具。
正因如此,我才坚持 不使用 React + 使用 CDN:没有构建步骤,就意味着可以非常轻松地拷贝粘贴进行自托管。
我最常用的是 GitHub Pages:只需要把 HTML 粘贴到 GitHub 上的一个文件中,几秒钟后就能通过一个永久 URL 访问。我的大多数工具都放在 simonw/tools 仓库中,并通过 tools.simonwillison.net 提供静态访问。
充分利用复制与粘贴
复制与粘贴是 HTML 工具中极其重要的输入 / 输出方式。
我经常构建这样的工具:接收粘贴的内容,对其进行转换,再让用户复制结果到剪贴板。
在手机上操作复制粘贴比较麻烦,所以我经常加一个 “复制到剪贴板” 按钮,一键完成操作。
大多数操作系统的剪贴板可以同时保存多种格式的数据。这就是为什么你粘贴数据到 Word 里,数据能保持格式,而粘贴到文本编辑器时只会得到纯文本。JavaScript 的粘贴事件同样可以访问这些富数据格式,
这为 HTML 工具打开了大量可能性。
一些示例:
- hacker-news-thread-export:
粘贴 Hacker News 线程 URL,生成可复制的精简版本,适合粘贴给 LLM 做总结 - paste-rich-text:
粘贴内容即可获得 HTML(在移动端尤其有用) - alt-text-extractor:
粘贴图片,提取并复制其中的 alt 文本
构建调试工具
构建有趣 HTML 工具的关键,在于理解浏览器到底能做什么。而自制调试工具,是探索这些能力的最佳方式。
clipboard-viewer 是我最常用的工具之一:你可以向它粘贴任何内容(文本、富文本、图片、文件),它会逐一展示剪贴板中所有可用的数据类型。
这个工具帮助我发现了大量“不可见”的数据,并成为我构建许多其他工具的基础。
其他调试示例:
- keyboard-debug:显示当前按下的按键及 KeyCode
- cors-fetch:检查某个 URL 是否支持 CORS
- exif:显示选中图片的 EXIF 信息
将状态持久化到 URL
HTML 工具通常无法使用服务器数据库,但实际上你可以直接把大量状态存储在 URL 中。
这非常适合需要书签或分享的工具。
icon-editor:这是我做的一个 24×24 图标编辑器,它会把正在编辑的图标状态完整存储在 URL 中,方便你保存或分享。
使用 localStorage 存储密钥或更大状态
浏览器的 localStorage API 允许 HTML 工具在用户设备上持久保存数据,而无需暴露给服务器。
我通常用它来存储:不适合放在 URL 中的大型状态,API Key 等敏感信息。即便是静态托管,服务器日志也可能不是我能完全控制的,所以我绝不会把密钥放到服务器上。
- word-counter:写作时自动保存进度,防止意外关闭页面丢失内容
- render-markdown:我经常用它写博客,不希望内容丢失
- haiku:让用户输入 Claude API Key,并存储在 localStorage 中,用来生成关于摄像头画面的俳句
收集支持 CORS 的 API
CORS(跨域资源共享)决定了一个网站上的 JavaScript是否可以请求另一个域名上的 API。
开放 CORS 的 API 是 HTML 工具的金矿。非常值得长期积累。
我常用的一些:
- iNaturalist:获取动物目击记录和照片
- PyPI:获取 Python 包信息
- GitHub:公共仓库内容可以通过 raw.githubusercontent.com 访问,自带 CDN 缓存,几乎不用担心限流
- Bluesky
- Mastodon(如 phanpy.social 使用的接口)
我尤其喜欢 GitHub Gists:它们允许你通过跨域 API 调用,把状态持久化到一个永久 Gist 中。
通过 CORS 直接调用 LLM
OpenAI、Anthropic 和 Gemini 都提供了可通过 CORS 直接调用的 JSON API。
问题在于:你仍然需要 API Key,而如果把 Key 写在 HTML 里,任何人都可以偷走并产生费用。
因此我会结合 localStorage 存储密钥 的模式来使用。体验确实不够好(用户需要自己去申请 Key 再粘贴进来),但它是可行的。
- haiku:调用 Claude API
- openai-audio-output:使用 GPT-4o 音频 API
- gemini-bbox:演示 Gemini 2.5 的图像分割能力
不要害怕打开本地文件
你不需要把文件上传到服务器,就可以通过 <input type="file"> 使用本地文件。JavaScript 可以直接读取文件内容,这解锁了大量功能。
- ocr:使用 PDF.js + Tesseract.js 在浏览器中对 PDF 和图片进行 OCR
- social-media-cropper:打开或粘贴图片,并裁剪为各平台所需尺寸
- ffmpeg-crop:在浏览器中预览视频,拖动裁剪框,生成 ffmpeg 命令
提供可下载文件
HTML 工具可以在不依赖服务器的情况下生成可下载文件。JavaScript 生态中有大量库可生成各种格式的文件。
- svg-render:下载 PNG / JPEG
- social-media-cropper:下载裁剪后的图片
- open-sauce-2025:生成可下载的 ICS 日历文件
Pyodide:在浏览器中运行 Python
Pyodide 是一个将 Python 编译为 WebAssembly、并直接在浏览器中运行的发行版。这是一个工程奇迹,也是 Python 世界中最被低估的项目之一。
它可以从 CDN 加载,这意味着在 HTML 工具中使用它几乎没有理由拒绝。
更棒的是,Pyodide 自带 micropip,可以通过 CORS 从 PyPI 加载纯 Python 包。
- pyodide-bar-chart: 演示了如何在浏览器中直接运行 Pyodide、Pandas 和 matplotlib 来绘制条形图。
- numpy-pyodide-lab:是针对 Numpy 的实验性交互式教程。
- apsw-query:演示了在浏览器中运行的 APSW SQLite 库,利用该库展示 SQLite 查询的 EXPLAIN QUERY 执行计划。
WebAssembly 打开更多可能性
Pyodide 的存在本身就得益于 WebAssembly。这意味着大量原本用其他语言编写的软件,现在都可以在 HTML 工具中使用。
Squoosh.app 是我见过的首个令我深信此模式强大之处的实例。它让多个业界顶尖的图像压缩库直接在浏览器中即可使用。
我自己的工具中也使用了 WebAssembly:
- ocr:Tesseract.js 和 Tesseract OCR 引擎。
- sloccount:将 Perl / C 工具移植到浏览器
- micropython:更轻量级的 Python 运行方案
重组你以前的工具
拥有一个包含 100+ 工具的公共集合,最大的优势在于:LLM 可以轻松重组它们。
有时我会将之前的工具复制粘贴到当前上下文中,但当我与编码助手协作时,可以直接通过名称调用它们——或者在助手开始工作前,指示它搜索相关示例。
任何可运行的工具源代码,同时也是实现特定功能的清晰文档,其中包含使用编辑库的模式。当大型语言模型(LLM)的上下文中存在一两个现有工具时,它生成可运行代码的可能性会大幅提高。
记录提示词和对话记录
我喜欢保留并公开我和 LLM 的所有交互记录,以便持续提升自己使用它们的能力。对于 Claude Code、Codex CLI 或其他编码工具,我会将终端中的完整对话记录复制粘贴到终端转 HTML 工具中,然后通过 Gist 分享该内容。
无论哪种情况,当我将完成的工具保存到我的代码库时,都会在提交信息中附上这些记录的链接。您可以在我的 tools.simonwillison.net 网站查看这些链接。
去构建吧
在过去一年半里,我用这种方式探索 LLM 的能力,并从中获得了巨大的乐趣。这种构建方式不仅帮助我理解了 HTML 的潜力,也让我更深入理解了 LLM 的能力边界。
如果你也想开始自己的工具集合,我强烈推荐你这样做。你只需要一个免费的 GitHub 仓库,并启用 GitHub Pages,就可以开始复制粘贴你生成的 .html 文件了。
