- AI 技能
- AI 插件
【免费下载链接】stitch-skills
A library of Agent Skills designed to work with the Stitch MCP server. Each skill follows the Agent Skills open standard, for compatibility with coding agents such as Antigravity, Gemini CLI, Claude Code, Cursor.
本指南围绕stitch-design插件中的stitch::extract-static-html技能展开,讲解如何把本地运行的前端应用(React、Vue、Svelte、Angular、Next.js 等任意框架)捕获为单文件、自包含的静态 HTML:所有 CSS 内联、图片转 base64、脚本与开发态装饰物被清除,可直接分享、存档或作为素材上传 Stitch。读完你将掌握三种提取策略(Puppeteer 快照、浏览器子代理、MockPage 静态回退)的完整工作流、全部命令行参数与底层处理管线,并能在遇到登录墙、懒加载、暗黑模式、图表 Canvas 等场景时精准选用对应开关。
技能定位与前置条件
stitch::extract-static-html定义在 SKILL.md 中,属于 stitch-design 插件(关键词包含html-extraction、design、ui-generation)。它的核心使命是:无论用户说"保存这个 HTML""把这个页面 mock 下来",还是"准备上传 Stitch 的静态素材",都统一走"提取自包含静态 HTML"这条路径。
技能的前置条件有两层:
- 运行环境:技能通过 frontmatter 声明了
allowed-tools,即执行过程中代理可以使用的能力:stitch*:*:Stitch MCP 的全部工具(提取产物最终可交给stitch::upload-to-stitch等技能上传)Bash、Read、Write:本地脚本执行与文件读写web_fetch:需要时抓取远程资源
- 本地环境:应用能以本地 dev server 运行(如
npm run dev),且安装有 Node.js 与puppeteer(可用node -e "require('puppeteer')"快速自检;缺失时npm install -g puppeteer)。
产物约定输出到项目下的.stitch/目录(如.stitch/home.html),与仓库中其他技能(extract-design-md产出.stitch/DESIGN.md)保持一致的素材约定。
第一步:选择提取策略(必须征求用户确认)
技能强制要求:执行前必须先让用户从两种策略中做选择,代理不得自行决定。两种策略的对比如下:
| Strategy A(Puppeteer) | Strategy B(Browser Subagent) | |
|---|---|---|
| 适用场景 | 应用本地可运行、无登录墙 | 需要先与页面交互(点击、填表) |
| 保真度 | 最高——解析后的计算样式(computed styles) | 较高——渲染后的 DOM |
| 配置成本 | 零——无需 mock | 零——无需 mock |
| 框架兼容 | 任意 | 任意 |
| 输出 | 直接写文件——无大小限制 | 大页面可能在代理上下文中被截断 |
[!WARNING]检查点——必须用户确认。必须把上表呈现给用户、推荐 Strategy A 为默认方案,并等待明确批准后才能继续。不要自行拍板,也不要跳过确认直接执行。
策略 A 之所以是首选,是因为它通过无头 Chrome 拿到的是渲染后、样式已解析的 DOM 快照,且直接落盘为文件,不受代理上下文长度限制;策略 B 则把控制权交给浏览器子代理,适合"先交互、再捕获"的场景,但大页面存在输出截断风险(附录中给出了规避手段)。
Strategy A:Puppeteer 快照(推荐)
策略 A 的完整实现位于 snapshot.ts,脚本头注释即声明了它的定位:"生产级、基于 Puppeteer 的全页 HTML 快照,无需 MockPage.jsx,兼容任意框架"。
前置条件
- 应用在本地运行(例如
npm run dev),并记录端口号; - Node.js 可用
puppeteer(自检命令见上文)。
完整工作流
启动应用并记录端口。
[!WARNING]检查点——必须用户确认。启动本地服务器后,必须先暂停并向用户报告 URL 与端口,请用户确认应用已正常渲染,确认后才能运行快照脚本或启动浏览器子代理。
运行快照脚本:
npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173 \ --output .stitch/home.html \ --wait 2000其中
<SKILL_DIR>指技能目录,即仓库中的plugins/stitch-design/skills/extract-static-html/。多页面——每个路由执行一次:
npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173 --output .stitch/home.html --wait 2000 npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173/pricing --output .stitch/pricing.html --wait 2000 npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173/dashboard --output .stitch/dashboard.html --wait 2000 --html-class dark清理:若 dev server 仅为提取而启动,提取完成后应停止该进程或终止后台任务,避免残留占用端口。
脚本参数详解
技能文档给出的核心参数如下:
| Flag | 默认值 | 说明 |
|---|---|---|
--url | (必填) | 要捕获的 URL |
--output | (必填) | 输出文件路径 |
--wait | 1000 | 网络空闲后的额外等待毫秒数;懒加载应用需加大 |
--viewport | 1280x800 | 视口尺寸,格式WIDTHxHEIGHT |
--html-class | — | 添加到<html>元素的类(如dark) |
--remove-fixed | false | 移除 fixed/sticky 元素(Cookie 横幅、聊天挂件) |
--full-height | false | 将视口拉伸至整页滚动高度 |
--title | — | 覆盖页面标题(建议设为路由路径,如/dashboard) |
--auth-script | — | 指向导出默认async (page) => void函数的 JS/TS 模块,用于登录 |
--inline-canvas | false | 把<canvas>(ECharts、Chart.js、D3)转为 base64<img> |
对照 snapshot.ts 的参数解析与校验逻辑(parseArgs与validateOpts,L74-L238),可以补充以下源码级约束与高级参数:
--wait:必须是非负整数,否则直接报错退出。--viewport:必须匹配WIDTHxHEIGHT且为正整数,上限 7680x4320,超出即校验失败。--timeout(默认60000):全局超时,最小 1000ms。它是"安全网"——超时后打印警告、强制关闭浏览器并process.exit(2),防止僵尸 Chrome 进程残留(L269-L281)。--concurrency(默认6):资源抓取的并发数,范围 1-20。所有图片/CSS 资源的 fetch 都通过浏览器侧的processInBatches分批并行(L460-L499)。--inline-fonts(默认false):默认跳过外部字体文件(.woff2/.ttf/.eot/.otf),避免把大体积字体塞进 HTML;开启后一并内联。同源与相对路径的图标字体则默认内联。--remove-selectors:逗号分隔的自定义选择器列表,逐条querySelectorAll后移除(L431-L443)。--click:捕获前模拟点击指定选择器——先在主文档查找,找不到则递归搜索子 frame,点击后再额外等待 2000ms 让动画/弹层稳定(L367-L395)。--json:向 stdout 输出机器可读的 JSON 统计(样式表数、内联图片数、CSS url 数、SVG/视频/图标数、移除脚本数、耗时、警告列表),便于 CI/CD 集成(L1443-L1446)。
另外两点行为细节值得注意:
- 网络空闲降级链:脚本先以
networkidle0等待,超时则降级networkidle2,再降级domcontentloaded(L307-L331),对持续有请求的页面也能完成导航。 --html-class dark的联动:脚本在给<html>加类的同时,会自动设置data-theme="dark"(light同理),这对依赖data-theme属性切换主题的组件库尤其重要(L400-L411)。
自动处理管线(七步全解析)
脚本在浏览器内完成一整条"自包含化"处理链,每一步在 snapshot.ts 中都有对应实现,汇总如下:
- 捕获 CSSOM 全部规则(L970-L1014):遍历
document.styleSheets逐条序列化cssRules,合并为#extracted-cssom-bundle注入<head>。这解决了 Vite dev 模式 + Tailwind、CSS-in-JS 动态注入样式无法从textContent读到的问题;同时清理包含createHotContext/import.meta.hot的 Vite HMR 样式块,并移除会 404 的字体 preload 链接。 - 内联外部样式表(L1016-L1081):所有
<link rel="stylesheet">经 fetch 后转为<style>块,并用逐字符解析器(extractCssUrls,非正则,可正确处理引号、转义、畸形url())把其中的相对url()解析为绝对地址,保留media属性。 - 内联图片(L1086-L1198):
<img src>、<img srcset>、<source srcset>与内联 style 中的background-image: url()全部转 base64 data URI。失败的srcset条目会被剔除,浏览器自动回退到已内联的src——这正是"关掉服务器图片仍完整"的关键。 - 内联
<style>中的 CSSurl()(L1200-L1256):对外部字体按--inline-fonts决定取舍,其余http(s)、同源、相对路径资源并发抓取后替换。 - 附加资源(L1261-L1361):SVG 内
<image href/xlink:href>、<video poster>、favicon(含 apple-touch-icon)、<object data>(超过 500KB 跳过)。 - 移除脚本与开发态装饰物(L1366-L1399):删除全部
<script>、<noscript>,并按框架选择器清理 dev overlay:Vite(vite-error-overlay)、Next.js([data-nextjs-dialog-overlay]、nextjs-portal)、Webpack/CRA(#webpack-dev-server-client-overlay*)、Parcel、Nuxt([data-v-inspector])。 - 输出(L1408-L1420):取
document.documentElement.outerHTML落盘,文件无大小限制。
此外还有三处容易被忽视的隐性能力:
- iframe 原生内联(L655-L870):同源/
srcDociframe 在浏览器侧递归内联,跨域 iframe 通过 Puppeteer frame 循环按嵌套深度排序提取,并把子页面<body>的类与属性同步到占位 wrapper,保证嵌套页面结构完整。 --full-height全页高度捕获(L873-L934):扫描全 DOM 找最大可滚动高度(含overflow: auto/scroll容器),视口拉伸至"最大高度 + 120px 缓冲",并强制h-screen/100vh/svh等容器解除视口高度锁定,避免长页面被截断。--inline-canvas图表序列化(L938-L959):canvas.toDataURL('image/png')后以<img>替换,保留className、id与内联样式;被污染(跨域)的 canvas 会静默跳过。--remove-fixed的启发式(L414-L428):仅移除position: fixed/sticky且满足"rect.top > 100或rect.height < 50"的元素——即大概率是悬浮横幅或挂件,避免误伤页头导航。- VeloUI 等设计工具覆盖层清理(L627-L653):
[data-screenshot-ignore="true"]及 VeloUI 的 pause overlay / probe / scan 元素会被移除。
框架适配建议
| 框架 | 建议 |
|---|---|
| React + Vite | 开箱即用,--wait 1000 |
| Next.js | SSR 水合需要--wait 3000;URL 为http://localhost:3000;/_next/image的<img srcset>会自动内联为 base64 |
| Angular(@angular/cli / v17+) | ng serve开箱即用(默认http://localhost:4200);Angular Material / PrimeNG 动画水合与懒加载路由建议--wait 2000 |
| Vue / Nuxt | 开箱即用 |
| Svelte / SvelteKit | 开箱即用 |
| Storybook | 使用 story URL:--url http://localhost:6006/?path=/story/... |
| SSR(Webpack) | 可能需要更长的--wait |
常见问题排查
| 问题 | 解决方案 |
|---|---|
| 图片缺失 | 加大--wait |
| 服务器停止后图片显示为破图 | 确认srcset已内联——检查日志中的 "Inlined N images"。srcset抓取失败会自动剔除,浏览器回退到已内联的src |
| 图标显示为文本 / serif 未样式字体 | 确认脚本已从document.styleSheets捕获 CSSOM(第 0 步),且同源图标字体(@font-face)以 base64 data URI 内联 |
Next.js/_next/image未内联 | 快照时 dev server 必须保持运行——脚本是从运行中的服务器抓取优化后图片的 |
| 暗黑模式未生效 | 加--html-class dark |
| 输出里有 Cookie 横幅 | 加--remove-fixed |
| 页面需要登录 | 使用--auth-script ./auth.ts(见下文) |
| 图表/图形显示为空白框 | 使用--inline-canvas把<canvas>序列化为 base64<img> |
Cannot find module 'puppeteer' | 执行npm install -g puppeteer |
登录态页面(Auth-Gated Pages)
对于带路由守卫的应用(Vue RouterbeforeEach、ReactProtectedRoute等),创建一个在 Puppeteer 会话内执行的小型登录脚本,导出默认异步函数即可:
// auth-myapp.ts import type { Page } from 'puppeteer'; export default async function authenticate(page: Page) { // 示例 1:填写并提交登录表单 await page.type('#username', 'admin'); await page.type('#password', 'password123'); await page.click('#login-button'); await page.waitForNavigation({ waitUntil: 'networkidle2' }); // 示例 2:直接注入 cookie / localStorage // await page.evaluate(() => { // localStorage.setItem('token', 'mock-jwt-token'); // }); // 示例 3:通过模块注入调用应用自身登录 API(Vue/Vite) // await page.evaluate(() => { // return new Promise((resolve) => { // const script = document.createElement('script'); // script.type = 'module'; // script.textContent = ` // import { useUserStore } from '/src/store/modules/user.ts'; // import { fetchLogin } from '/src/api/auth.ts'; // const res = await fetchLogin({ userName: 'Admin', password: '123456' }); // useUserStore().setToken(res.token, res.refreshToken); // window.dispatchEvent(new CustomEvent('auth-done')); // `; // document.head.appendChild(script); // window.addEventListener('auth-done', () => resolve(true), { once: true }); // }); // }); }然后这样调用:
npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173/#/dashboard \ --output .stitch/dashboard.html \ --auth-script ./auth-myapp.ts \ --inline-canvas \ --wait 5000执行顺序为:脚本先导航到--url(此时可能被重定向到登录页)→ 运行你的登录函数 →携带已认证会话重新导航到原始--url再捕获(对应 snapshot.ts L340-L364)。登录脚本失败不会中断流程,但会产生警告并记录到统计信息中。
Strategy B:浏览器子代理捕获
当需要先与页面交互(点击按钮、填写表单、切换标签页)再捕获时使用此策略。浏览器子代理拥有完全控制权,但大页面的输出可能在代理上下文中被截断。
工作流
本地启动应用;
使用浏览器子代理导航到目标页面;
按需交互(点击、滚动、填表);
提取 DOM:
document.documentElement.outerHTML[!WARNING] 大页面可能被截断,处理方式:
- 提取前移除
<style>标签:document.querySelectorAll('style').forEach(el => el.remove()) - 再静态补回样式(Tailwind CDN 链接、源码 CSS)
- 提取前移除
保存到文件。
与策略 A 相比,策略 B 不产出自包含文件,样式需要手动处理,因此技能将其定位为"交互优先"场景的备选。
附录:静态回退方案(MockPage.jsx)
[!NOTE] 这是最后手段:仅当应用完全无法本地运行(依赖损坏、后端缺失、登录墙无法绕过)时使用。它要求把 React 组件手工拍平为单个 JSX 文件。任何情况下优先 Strategy A。
何时使用
- 应用完全无法本地运行;
- 页面需要登录且没有 mock/绕过手段;
- 需要导航无法到达的特定 UI 状态(错误页、空状态)。
快速参考
npx tsx <SKILL_DIR>/scripts/extract_inline_html.ts \ --index-css src/css/App.css \ --extra-css index.html \ --outdir .stitch \ --page src/MockPage.jsx:Page.html:"Page Title"关键参数:--no-tailwind(非 Tailwind 应用)、--html-class dark(暗黑模式)、--css-files(额外 CSS 文件)。
自动检测:Tailwind 配置自动探测(依次查找tailwind.config.js/.ts/.mjs/.cjs,见 extract_inline_html.ts L817-L823);检测到@apply指令时自动改用<style type="text/tailwindcss">。
MockPage.jsx 编写规则
- 包含完整布局——header、sidebar、footer(先读
App.js); - 拍平所有条件分支——只保留一种状态,删除所有三元表达式与
&&守卫; - 硬编码所有数据——把
{variable}替换为具体值,展开.map()循环; - 保留 Logo——使用带本地路径的
<img>(后处理脚本会内联它们); - 移除浮动元素——Cookie 横幅、聊天挂件、反馈按钮。
源码视角:JSX 如何变成自包含 HTML
extract_inline_html.ts 承担了 JSX → HTML 的转换,其工程细节值得了解:
- Babel AST 转换而非正则(L584-L664):优先提取默认导出函数/箭头函数内的 JSX
return;没有默认导出时回退到"最大 JSX 树"策略。 - React/SVG 属性映射(L556-L576):
className → class、htmlFor → for、strokeWidth → stroke-width等;跳过事件处理器(onXxx)、key、ref、dangerouslySetInnerHTML与展开属性{...props};<Link>降级为<div>(并跳过to属性),未知大写组件只渲染子节点。 - style 对象转 CSS(L794-L811):
{ fontSize: 16 }→font-size: 16px(0不加单位),camelCase 转 kebab-case。 - Tailwind 装配(L873-L936):自动注入 Tailwind CDN(含
forms,container-queries插件)、内联tailwind.config(转换为tailwind.config = ...赋值)、把@import转<link>。 - 远程图片内联的安全护栏(L251-L403):内置SSRF 防护(
isSafeUrl拦截 localhost、[::1]、127/8、10/8、172.16/12、192.168/16、169.254/16等私网/保留地址)、重定向环防护(最多 5 跳)、超时控制,失败资源回退为 1×1 透明 GIF data URI,绝不抛出导致全流程失败。
后处理(Post-Processing)
MockPage 生成的 HTML 中,本地图片仍以相对路径引用,需要用 post_process.ts 内联:
npx tsx <SKILL_DIR>/scripts/post_process.ts \ .stitch/Page.html --base-dir <app-directory>该脚本扫描 HTML 中的本地图片引用(src/poster/data属性、CSSurl()、SVGhref/xlink:href)并替换为 base64 data URI。它同样使用逐字符 CSSurl()解析器,并额外提供:
--base-dir:解析相对路径的基准目录;--dry-run:只报告将要内联的内容,不改动文件;--max-size(默认5MB):超过该体积的资源跳过并记录;--json:输出机器可读统计。
实现上还做了两项健壮性设计:通过同一文件描述符原子地完成 stat、读取与回写(消除 TOCTOU 竞态,L248-L270、L425-L453);MIME 类型由扩展名映射表推导(image/svg+xml、image/webp、image/avif等,L27-L41)。
与其他 Stitch 技能的衔接
提取出的自包含 HTML 是整个设计工作流的关键中间产物:
- 代码转设计:
stitch::code-to-design技能的标准流程正是"HTML 提取 + 设计系统 + 上传",静态 HTML 快照是其输入环节(见 code-to-design); - 上传素材:提取完成的
.stitch/*.html可由stitch::upload-to-stitch技能上传至 Stitch 项目,作为设计还原/评审的素材(见 upload-to-stitch); - 技能安装:作为 stitch-design 插件的一部分,可通过仓库 README.md 中记录的 Codex、Claude Code、Cursor 等方式安装启用。
总结
stitch::extract-static-html的核心价值在于把"捕获页面"这件事从脆弱的"复制粘贴 DOM"升级为一条生产级流水线:Puppeteer 拿渲染后 DOM → CSSOM 全量序列化 → 图片/字体/图标 base64 内联 → 脚本与开发 overlay 清除 → 单文件落盘。使用时记住三条主线:首选 Strategy A 并先获用户确认;遇到登录墙用--auth-script,遇到图表用--inline-canvas,遇到暗黑模式用--html-class dark;只有当应用完全无法运行时,才退回到 MockPage +extract_inline_html.ts+post_process.ts的静态方案。所有实现细节均可在仓库 scripts 目录 中对照阅读。
- AI 技能
- AI 插件
【免费下载链接】stitch-skills
A library of Agent Skills designed to work with the Stitch MCP server. Each skill follows the Agent Skills open standard, for compatibility with coding agents such as Antigravity, Gemini CLI, Claude Code, Cursor.
相关推荐
stitch-skills 实战:用 `.stitch/next-prompt.md` 接力棒驱动 Stitch 自动化建站循环
stitch skills 实战:用 .stitch/next prompt.md 接力棒驱动 Stitch 自动化建站循环 本文以 stitch skills
AI 技能AI 插件Angular 设计系统逆向提取实战:基于 stitch-skills 的 extract-design-md 模式指南
Angular 设计系统逆向提取实战:基于 stitch skills 的 extract design md 模式指南 导读 本文面向需要从现有 Angula
AI 技能AI 插件brpc 改造 ubrpc 接入层实战:从 43 台缩容到 8 台的 4 万 QPS 迁移案例与性能对比
brpc 改造 ubrpc 接入层实战:从 43 台缩容到 8 台的 4 万 QPS 迁移案例与性能对比 本篇技术指南以 brpc 仓库官方案例文档 docs/
AI 技能AI 插件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考