Chrome 团队把 Web 最佳实践塞进 Agent:拆解 modern-web-guidance

Chrome 团队把 Web 最佳实践塞进 Agent:拆解 modern-web-guidance

2026 年 3 月 23 日,Google Chrome 团队建了 GoogleChrome/modern-web-guidance。到 11 月 14 日,它累计 12,630 颗星、454 个 fork,最后一次推送停在 11 月 10 日;npm 上刚把版本从 0.0.206 直接跳到 1.0.0,解包 16.5 MB、280 个文件、运行时依赖 0 个。

一句话定位:这是一份给 coding agent 用的 Web 平台技能包。外壳是 90 行 SKILL.md,内核是 208 篇 Markdown 指南加一条本地 CLI。它不是文档站(developer.chrome.com 才是),不是 MCP 服务(不需要常驻进程、不需要 API key、不发请求),也不是 linter——legacy-scan 只负责报疑,定罪交回给 agent。

它要解决的问题很具体:模型训练语料里堆着二十年的旧写法。你说"做个弹窗",它给你手搓 div + z-index: 9999 加一段 document.body.style.overflow = hidden;你说"自定义滚动条",它给你 ::-webkit-scrollbar 三件套,而不是 2025 年已经在 Chrome 里全量可用的 scrollbar-color。这些写法不是错的,是过时的,而 agent 不知道过时这件事。

下面所有数字,凡是我从包里算出来的,我都标了"我算的";仓库没说的,我不替它补。

本文提纲

  1. 它是什么:90 行技能壳加一条零依赖 CLI
  2. 为什么是"技能 + CLI",不是"MCP + 文档站"
  3. 真跑一遍:install、search、retrieve、legacy-scan
  4. 检索层:29,165 个词怎么在断网时找到那篇指南
  5. legacy-scan:静态规则报疑,agent 定罪
  6. 上下文预算:47.5 万 token 的语料,怎么一次只花 5 千
  7. 评测:从 57% 到 88%,这个差值怎么量出来的
  8. 我实测撞到的三个边界
  9. 跟 Context7、手写 CLAUDE.md、MDN 有什么不同
  10. 遥测:本地检索是真的,不上报是假的

它是什么:90 行技能壳加一条零依赖 CLI

把 npm 包解开,结构是这样的:

路径 内容 体量
SKILL.md 给 agent 的流程指令:什么时候查、怎么查、怎么披露 90 行 / 9.7 KB
modern-web.mjs CLI 主体,esbuild 打成单文件 38 KB
search.mjs 混合检索器 6.9 KB
legacy-scan.mjs 33 条检测规则的 bundle 4.99 MB
gen/ 预计算的词表、块向量、索引 8.6 MB
guides/ 17 个分类、208 篇指南(209 个 md 文件) 1.97 MB
watchdog/ 遥测发送子进程 4.4 KB

真正值得看的是第一行和倒数第二行。SKILL.md 里没有一个平台知识——它只写流程:

1. 用 search 把需求变成候选指南 id
2. 用 retrieve 取回最相关的 1~2 篇,完整读完
3. 按指南给的 featuresUsed 核对 Baseline 状态
4. 在最终回答里说明用了哪篇指南、哪些特性有兼容性风险

知识全在 guides/ 里,按需取。这条设计决定了后面所有数字:语料是 47.5 万 token,但 agent 的常驻成本只有 205 token——README 里 --choose 那节自己写的数,modern-web-guidance 技能包注入宿主配置文件时约 205 token,chrome-extensions 约 175 token。

外壳之外是 8 份宿主清单,塞在同一个仓库里:.claude-plugin/、.codex-plugin/、.cursor-plugin/、.grok-plugin/、.agents/、gemini-extension.json、kimi.plugin.json、plugin.json。CLI 内部还认得 23 个 agent 标识符——cursor、cursor-cli、claude、cowork、devin、replit、gemini、codex、antigravity、augment-cli、opencode、github-copilot、v0、fx、eve、amp、kimi、grok、cline、goose、junie、pi、kiro(我在 modern-web.mjs 里抠出来的列表)。这些标识符唯一的用途是填遥测里的 agent 字段——顺便说明它对多宿主的支持是逐个适配过的,不是一句"兼容所有 agent"。

语料的分区也能看出重心:ui-behaviors 40 篇、performance 26、visual-design 22、ui-components 22、forms 19、css 18、security 17、ui-atoms 13、html 8,再往下是 js 5、built-in-ai 5、accessibility 4、webmcp 3、user-experience 3、pwa 2、wasm 1、privacy 1。它不是只讲 CSS 技巧——webmcp(把网页能力暴露给 agent 的新协议)和 built-in-ai(端侧翻译、摘要、语言检测)这两块说明它盯的是"正在发生"的能力,而不只是"已经稳定"的能力。

为什么是"技能 + CLI",不是"MCP + 文档站"

这个选择值得掰开看,因为它跟当下主流相反:现在做知识注入,大家默认上 MCP。

三个理由,都能从代码里读出来。

第一,MCP 要一个活着的进程。 检索这件事本身是"读本地文件 + 算余弦相似度",把它做成 server,等于为了读 1.97 MB Markdown 常驻一个 Node 进程,还得处理端口、超时、崩溃重启。CLI 是调用即退出,npx modern-web-guidance@latest search "..." 一条命令,零状态。

第二,@latest 是内容新鲜度的保证。 文档类产品最大的失败模式是"装的时候是对的,三个月后是错的"。每次 npx 都解析最新版本,等于每次检索都拿到当前发布的语料。这个约束反过来解释了为什么版本号敢直接跳到 1.0.0:它卖的不是软件版本,是内容版本。

第三,全量注入塞不进去。 2026 年了,主流 agent 的上下文还是十几万 token 量级,47.5 万 token 的语料直接注入是自杀。所以必须有一个"先缩小范围再展开"的两级结构:索引给候选,指南给细节。

一个有意思的细节:install 命令自己并不写文件。我在 modern-web.mjs 里看到它拼出来的命令是 npx -y skills add GoogleChrome/modern-web-guidance ...——Google 的安装器把活儿转手给了 Vercel 的 skills CLI。这也是这个生态当前的常态:内容的作者和分发的作者是两拨人。

真跑一遍:install、search、retrieve、legacy-scan

不安装也能用。我在一台没装过它的机器上直接跑 CLI:

npx -y modern-web-guidance@latest search "animate a dialog modal backdrop"

返回 10 条候选,id 依次是:animate-to-from-top-layer、light-dismiss-a-dialog、prevent-page-scroll-under-modal-dialog、dialog、navigation-drawer、platform-controls-dismiss-dialog、declarative-dialog-popover-control、persistent-top-layer-ui、html、animate-element-entry-exit。前八条全围绕 dialog 和 top-layer,第九条 html 是明显的噪声,第十条又回到动画。一个"十取八"的命中率,对关键词级检索算是合格,但不能指望它一次就准。

拿到 id 之后取正文:

npx -y modern-web-guidance@latest retrieve "animate-to-from-top-layer"

这条命令的返回不是正文,而是一行提示:

Output (10.7 KB, 244 lines) saved to /tmp/mwg-guide-183191-1794661644078.md
to prevent CLI stdout truncation.
Read ... in full using your file-reading tool (244 lines total)—do not use cat or head.

超过 9.5 KB 的指南会落到临时文件,并明确要求 agent 用文件读取工具整篇读。这是个很小但很硬的工程决定:CLI 的 stdout 经常被宿主截断,截断的指南等于错误的知识。retrieve "dark-mode" 落到文件里是 16.8 KB、347 行。

legacy-scan 是另一个入口:扫现有代码里"用旧写法实现新能力"的地方。我在 /tmp/mwg-test 造了两个文件,一个 CSS 用 ::-webkit-scrollbar 自定义滚动条,一个 JS 里有一句 container.innerHTML = html;,然后:

npx -y modern-web-guidance@latest legacy-scan /tmp/mwg-test --json

结果(关键几行照抄):

Found 2 opportunities across 1 guide (scanned 33 guides with deterministic rules; ~20% of skills corpus covered).
✓ customize-scrollbar-color-and-thickness  style.css:3-7
  Modern CSS properties can customize scrollbar appearance without vendor-specific pseudo-elements.
  Keep when: ... | Drop when: ...

两条命中都落在滚动条那一篇上,innerHTML 那句没有被报出来——这恰好是它设计的一部分,后面细说。

还有一个 --choose 分支和 .mwgrc 工程配置:allowOriginTrials、allowVolatileFeatures 两个开关控制"能不能推荐还没全量发布的特性"。默认策略是保守的:origin trial 和 volatile 特性默认不给,想用要显式打开。这个门禁比大多数"AI 推荐代码"产品都诚实。

检索层:29,165 个词怎么在断网时找到那篇指南

这是整个项目里最像工程的部分。它没有用任何 embedding API,也没有塞一个 ONNX 模型,而是预计算了一份静态词向量表:

文件 尺寸 换算
query-words.json 316 KB 词表
query-words.i8 7,466,240 B 29,165 词 × 256 维 int8(我算的)
query-words.scale.f32 116,672 B 每词一个 float32 缩放因子
guide-chunks.i8 516,096 B 2,016 块 × 256 维 int8
guide-chunks.json.gz 652 KB 块原文

检索流程是这样的:

MERMAID_BLOCK_0

具体三步。

词级向量:每个英文词有一个 256 维 int8 向量,读出来乘 scale 还原成浮点,然后对 query 里所有词做加权平均,再 L2 归一化。连字符词会先拆开分别取向量再平均(scrollbar-color → scrollbar + color)。我把 search.mjs 完整读了一遍,一共 6.9 KB,没有任何网络调用。

BM25:标准公式,k1 = 1.2、b = 0.75,配一份 33 个词的停用词表和一套简易词干规则(复数、-ing、-ed 那几刀)。

RRF 融合:两路排序用 Reciprocal Rank Fusion 合并,常数 k = 60——也就是两路各按名次给 1/(60 + rank) 的分再加起来,取前十。这是搜索引擎里常见的手法,好处是不用管两路分数尺度不一致。

还有一个兜底:如果最高余弦相似度低于 0.3 且 BM25 一个词都没命中,就返回空数组,而不是硬凑十条。

这套东西的取舍很清楚:用"查词表"换"零依赖 + 零延迟 + 断网可用"。代价是语义能力有上限,而且它只认英文——第 8 节我拿实测数据说话。

legacy-scan:静态规则报疑,agent 定罪

legacy-scan.mjs 有 4.99 MB,比 CLI 主体大两个数量级。砸开看,里面是 33 条规则的 matcher 加每条的复核清单(keepWhen / dropWhen),外加一个被打包的解析器。

为什么只有 33 条?208 篇指南里,能把"旧写法"写成确定性匹配的只有 16%(我按 bundle 里的 guideId 去重数出来的,CLI 输出也自称约 20%)。剩下的指南是"该用什么"而不是"你正在用错的什么",没法静态匹配。

关键在输出格式。它报的每一条都不是结论:

✓ customize-scrollbar-color-and-thickness  style.css:3-7
  Modern CSS properties can customize scrollbar appearance
  without vendor-specific pseudo-elements.
  Keep when: <条件>  |  Drop when: <条件>

Keep when / Drop when 是一份给 agent 的复核清单。静态扫描负责"全量扫、高精度",agent 负责"读上下文、排除误报"。CLI 输出末尾那句 Note: Static analysis results are provisional — verify against Keep/Drop criteria 就是把责任划清。

这个分工也能解释我那句 container.innerHTML = html; 为什么没被报出来:html 是个变量,静态分析证明不了它的来源,硬报就是误报。规则在这里选择了"宁可漏,不可吵"——因为吵的结果是 agent 把整份扫描报告当噪音丢掉。

顺带一个判断:这套"确定性检测 + LLM 复核"的组合,比"让 agent 直接读整个仓库找旧写法"便宜得多,也比"纯正则 linter"准确得多。它是这份仓库里最值得抄走的设计,比指南本身更值得抄。

上下文预算:47.5 万 token 的语料,怎么一次只花 5 千

把 208 篇指南的 tokenCount 加起来是 474,620 token(我算的),中位数 1,731,最贵的一篇是 accessibility,13,341 token。

分层之后,一次查询的成本是这样:

层 谁在用 字节 约 token
技能包注入(--choose 报的数) 常驻 — 约 205
gen/guides.json 索引(208 条 id + 描述) 检索时读 69,108 约 1.7 万
search 返回的 10 条描述 进窗口 约 1.5 KB 约 400
单篇指南正文 进窗口 中位数约 7 KB 约 1,700
单次查询合计 进窗口 — 约 5 千

注意索引本身从来不进窗口——它只被 CLI 读。agent 看到的只有"10 条描述 + 1 篇正文"。这就是 47.5 万到 5 千的那条线,约 95 倍压缩,代价是每次只看得见一篇。

这个预算意味着一个隐含的使用约束:一次任务里连着取 3~4 篇指南(比如同时要 dialog、scrollbar、dark-mode),就是 1.5 万 token 上下。SKILL.md 里明确写了"取 1~2 篇",不是客套,是预算。

评测:从 57% 到 88%,这个差值怎么量出来的

README 里有评测一节,口径比我预期干净:跑分是端到端的——agent 在真实应用里改代码,Playwright 断言验收。

任务集是两个小应用,daily-grind(咖啡店点单)和 devtools-times(新闻站)。提示词刻意不提 API 名和特性名,只说人话,比如"让我的图片加载更快"——考的是 agent 能不能自己找到对应的指南,而不是被提示词喂答案。

两个关键定义:opportunity = 100% - 无指南通过率,即"这里本来有多少分是可以拿到的";uplift = 有指南通过率 - 无指南通过率。这套定义有个很实用的副作用:模型本来就会做的任务,opportunity 低,指南就是废话,会被剪掉;只有机会大的地方才值得写指南。

校准是硬门槛:golden 解必须让所有断言 100% 通过,未改动的基线应用必须 0% 通过。两边任何一边不满足,任务进废件箱——这条规则防的是"断言本身写错了"和"不给指南也能过"这两种污染。

2026 年 11 月 8 日那轮的分数(原文照抄):

Agent / 模型 任务 / 断言 无指南 → 有指南 提升
antigravity_cli(gemini-4-argon) 214 / 1,673 58% → 94% +36pp
codex_cli(gpt-6.1-sol) 215 / 1,795 54% → 82% +28pp
claude_code(opus-5-5) 215 / 1,801 57% → 88% +31pp

单看一轮容易当过拟合。往前后拉八天,表里一共十轮(11 月 2 日到 8 日,三个 agent 交替跑),提升区间一直卡在 +27pp 到 +36pp:antigravity 稳定在 +36,claude_code 在 +31 到 +34,codex 在 +27 到 +33。同时任务数从 205 缓涨到 215——每轮都在加题,分数没掉,这比"某一轮峰值 94%"更有说服力。

要挑刺的话:任务数在涨,说明每轮的卷子不完全一样,所以看 uplift,别跨行比绝对分;另外只有两个 demo 应用、214 个任务,覆盖面还谈不上广。

我实测撞到的三个边界

一、中文查询直接返回空数组。

npx -y modern-web-guidance@latest search "暗黑模式 滚动条 颜色"
# → []

原因是分词只保留字母、数字和连字符,中文一个 token 都切不出来,向量全零,BM25 零命中,直接走兜底返回空。这不是 bug,是设计——语料是英文的,检索也是英文的。中文用户要先把需求自己翻成英文关键词,比如 scrollbar-color dark mode,再查。这个摩擦在中文社区里会一直被撞到。

二、0.3 阈值基本是装饰。

npx -y modern-web-guidance@latest search "kubernetes pod autoscaling"
# → menu / declarative-dialog-popover-control / sanitize-untrusted-html / persistent-top-layer-ui ...

完全不相干的查询照样返回十条,因为词级静态向量的余弦在任意英文词上都容易越过 0.3。SKILL.md 里因此写了"搜索结果不可信,要靠描述判断、必要时换词重查"——它把判断权交给了 agent,而不是靠阈值。这跟"检索器要自己保证准确"的思路相反,也算一种诚实:CLI 只负责排序,不负责说"我不知道"。

三、legacy-scan 覆盖窄,且保守。

33 篇 / 208 篇 = 16%。我那两句 innerHTML 没被报出来。如果你的目的是"全仓库清扫旧写法",这个入口不够用,只能当"高置信度线索"。

顺带一条不算 bug 但会影响体感的:retrieve 的输出经常是 10~17 KB,一次连取三篇就吃掉半个窗口。

跟 Context7、手写 CLAUDE.md、MDN 有什么不同

方案 知识来源 交付方式 覆盖 主要短板
modern-web-guidance Chrome 团队维护的 208 篇 Web 平台指南 本地 CLI + 技能包 Web 平台特性、Baseline 状态、旧写法检测 只有英文、只覆盖前端平台
Context7 第三方库文档,云端抓取 MCP 服务 任意 npm / Python 库 需要联网与服务进程;不管 Baseline
手写 CLAUDE.md / AGENTS.md 规则 团队自己总结 常驻上下文 团队约定 常驻成本高、无法按需取、不会更新
MDN / web.dev 网页 人工文档 agent 自己搜网页 极全 非结构化、无"该不该用"判断、无评测
Baseline 数据(web-features) 浏览器兼容数据 JSON 特性可用性 只回答"能不能用",不回答"怎么写"

我的选型结论:

  • 写 Web 前端:装。收益最明显的是"弹窗、滚动条、容器查询、dark mode、表单校验"这些有明确新旧写法的场景,正好是模型最容易返回 2018 年答案的区域。
  • 写后端 / CLI / 基础设施:不装。208 篇全是浏览器侧知识,装了只是多 200 token。
  • 需要第三方库文档:modern-web-guidance 不管库,这块是 Context7 的地盘,两者互补,可以同时装。
  • 想统一团队代码风格:还是自己写 CLAUDE.md。指南是"平台该用什么",不是"我们团队怎么命名"。

遥测:本地检索是真的,不上报是假的

README 有一节 Telemetry & Privacy,写得很直白:默认收集安装与使用事件、agent 名称、检索到的指南 id、以及 CLI 的 search 查询文本;原始对话内容不收集;DISABLE_TELEMETRY=1 可完全关闭。

我在源码里核对了实现:watchdog/main.js 起一个独立子进程,把事件缓冲起来(上限 1,000 条),POST 到 https://play.googleapis.com/log?format=json_proto,也就是 Google 的 Clearcut 日志端点,LOG_SOURCE = 2921,请求超时 10 秒、退出超时 5 秒。isTelemetryEnabled() 在没有 DISABLE_TELEMETRY 且不在测试上下文时返回 true——默认开。install 类命令会先打印一段提示,把 opt-out 方式讲清楚。

这件事值得分开说两句,因为容易被混成一句:

  • 检索本身确实离线。 search.mjs 里没有任何网络调用,词表和块向量都躺在包内,跑一次检索不需要出网,也不需要任何 key。
  • 但"隐私"只对代码成立,对 query 文本不成立。 你让 agent 查了什么,会以明文关键词上报。如果查询里带了内部项目名、客户名,这些词会跟着出去。这条在 README 里写了,但很多人只看"本地、离线、零依赖"这三个词就装了。

要给团队落地,我的建议是装的时候就设 DISABLE_TELEMETRY=1,写进 .envrc 或 CI 的 env。功能一点不损失,检索全在本地算。

一个值得抄的设计

看完这份仓库,最值得带走的不是 208 篇指南——内容会过时,Chrome 团队自己也在每天改。真正值得带走的是它的三层账:

  1. 知识写成文件,不做成服务。文件可以被 diff、被 review、被 vendor 到自己的仓库里。
  2. 检索留在本地,用一份 7.4 MB 的 int8 词表换掉 embedding API。牺牲语义精度,换掉网络、密钥、延迟、成本四个变量。
  3. 检测和判断分开。静态规则只产出候选和复核清单,结论必须由读过上下文的 agent 下。这条对任何"AI + 静态分析"的组合都成立。

至于它会不会成为前端 agent 的标配,取决于两件事:指南的更新能不能跟上 Chrome 的发布节奏(x.y.z 版本号加 @latest 说明他们知道这是命门),以及中文这类非英语查询的缺口补不补。前者是工程问题,后者是产品问题——而产品问题的答案,通常比工程问题来得慢。

参考文档与链接

你上一次看到 agent 给你写 ::-webkit-scrollbar 是什么时候?如果它还在给你手搓 modal,那 200 token 的技能包大概是这天性价比最高的一次安装。评论区说说你在哪个场景被旧写法坑过。觉得有用就点个赞。


作者: itech001
来源: 公众号:AI人工智能时代(the-ai-era)
网站: https://www.theaiera.top/
关注每日最新AI新闻和技术博客,主页有更多的文章的AI 技术参考:https://www.theaiera.top

本文首发于 AI人工智能时代,转载请注明出处。

posted @ 2026-11-14 21:14  iTech  阅读(2)  评论(0)    收藏  举报