# next-browser 项目参考分析 > 来源: `/Users/coso/Documents/dev/ai/skills/next-browser` > 项目: @vercel/next-browser - Vercel 官方的浏览器自动化工具 ## 项目概述 `next-browser` 是 Vercel 开发的一个 **CLI 工具**,为 AI Agent 提供对 React DevTools 和 Next.js dev server 的编程访问。它将 GUI 操作(组件树、props、hooks、PPR shells、错误)转换为 **结构化文本输出的 shell 命令**。 ### 核心理念 - **为 AI Agent 设计** - LLM 无法读取 DevTools 面板,但可以运行 `next-browser tree` 并解析输出 - **无状态命令** - 每个命令都是对长期运行的浏览器守护进程的一次性调用 - **结构化输出** - 所有输出都是可解析的文本/JSON,便于 Agent 处理 --- ## 值得 Lime 参考的设计 ### 1. 守护进程 + 客户端架构 ⭐⭐⭐ **设计模式:** ``` CLI 命令 → Unix Socket → 守护进程 → Playwright Browser ``` **实现细节:** ```typescript // daemon.ts - 守护进程 const server = createServer((socket) => { let buffer = ""; socket.on("data", (chunk) => { buffer += chunk; let newline: number; while ((newline = buffer.indexOf("\n")) >= 0) { const line = buffer.slice(0, newline); buffer = buffer.slice(newline + 1); if (line) dispatch(line, socket); } }); }); server.listen(socketPath); // Unix socket async function dispatch(line: string, socket: Socket) { const cmd = JSON.parse(line); const result = await run(cmd).catch((err) => ({ ok: false, error: cleanError(err) })); socket.write(JSON.stringify({ id: cmd.id, ...result }) + "\n"); } ``` **Lime 应用:** - ✅ **替代 Tauri event 的高吞吐方案** - 当事件推送超过 100/s 时,切换到本地 Unix Socket - ✅ **守护进程管理浏览器实例** - 避免每次命令都启动新浏览器 - ✅ **命令-响应模式** - 清晰的请求/响应边界,便于错误处理 --- ### 2. Playwright + React DevTools 集成 ⭐⭐⭐ **核心技术:** ```typescript // browser.ts const extensionPath = resolve(import.meta.dirname, "../extensions/react-devtools-chrome"); // Pre-read the hook script const installHook = readFileSync( join(extensionPath, "build", "installHook.js"), "utf-8", ); async function launch() { const context = await chromium.launchPersistentContext(profileDirPath, { headless: false, args: [ `--disable-extensions-except=${extensionPath}`, `--load-extension=${extensionPath}`, "--auto-open-devtools-for-tabs", ], }); // Pre-inject hook to win the race against extension await context.addInitScript(installHook); return context; } ``` **关键亮点:** 1. **预加载 React DevTools 扩展** - 通过 `--load-extension` 加载 Chrome 扩展 2. **预注入 Hook** - 使用 `addInitScript` 在页面加载前注入 React DevTools hook 3. **自动打开 DevTools** - `--auto-open-devtools-for-tabs` 确保扩展激活 **Lime 应用:** - ✅ **扩展 CDP 能力** - 不仅仅是原始 CDP,还可以加载自定义扩展 - ✅ **预注入脚本** - 在页面加载前注入监控/调试脚本 - ✅ **持久化上下文** - 使用 `launchPersistentContext` 保持会话状态 --- ### 3. 组件树提取 ⭐⭐ **实现原理:** ```typescript // tree.ts export async function tree(page: Page) { // 通过 React DevTools hook 获取组件树 const operations = await page.evaluate(() => { return window.__REACT_DEVTOOLS_GLOBAL_HOOK__.flushInitialOperations(); }); // 解析 operations 构建组件树 const tree = parseOperations(operations); return tree; } export async function inspect(page: Page, nodeId: number) { // 获取组件的 props/hooks/state const data = await page.evaluate((id) => { return window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inspectElement(id); }, nodeId); return { props: data.props, hooks: data.hooks, state: data.state, source: data.source, }; } ``` **Lime 应用:** - ✅ **深度调试能力** - 不仅看到 DOM,还能看到 React 组件结构 - ✅ **状态检查** - 检查组件的 props/hooks/state - ✅ **源码定位** - 通过 source map 定位到原始源码位置 --- ### 4. PPR (Partial Prerendering) 锁定机制 ⭐⭐⭐ **核心概念:** ```typescript // browser.ts let release: (() => void) | null = null; let settled: Promise | null = null; export async function lock() { if (release) return; // Already locked // Use @next/playwright's instant() to set cookie const { promise, resolve } = Promise.withResolvers(); release = resolve; settled = promise; await instant(page!, async () => { await settled; // Block until unlock() }); } export async function unlock() { if (!release) return; // Capture locked state const lockedSnapshot = await captureSuspenseBoundaries(); // Release the lock release(); release = null; settled = null; // Wait for boundaries to settle await page!.waitForTimeout(500); // Capture unlocked state const unlockedSnapshot = await captureSuspenseBoundaries(); // Analyze the diff return analyzePPRShell(lockedSnapshot, unlockedSnapshot); } ``` **工作原理:** 1. **Lock** - 设置 `next-instant-navigation-testing=1` cookie,阻止动态数据加载 2. **Capture** - 截取 PPR shell(静态 HTML + `