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 不知道过时这件事。
下面所有数字,凡是我从包里算出来的,我都标了"我算的";仓库没说的,我不替它补。
本文提纲
- 它是什么:90 行技能壳加一条零依赖 CLI
- 为什么是"技能 + CLI",不是"MCP + 文档站"
- 真跑一遍:install、search、retrieve、legacy-scan
- 检索层:29,165 个词怎么在断网时找到那篇指南
- legacy-scan:静态规则报疑,agent 定罪
- 上下文预算:47.5 万 token 的语料,怎么一次只花 5 千
- 评测:从 57% 到 88%,这个差值怎么量出来的
- 我实测撞到的三个边界
- 跟 Context7、手写 CLAUDE.md、MDN 有什么不同
- 遥测:本地检索是真的,不上报是假的
它是什么: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 团队自己也在每天改。真正值得带走的是它的三层账:
- 知识写成文件,不做成服务。文件可以被 diff、被 review、被 vendor 到自己的仓库里。
- 检索留在本地,用一份 7.4 MB 的 int8 词表换掉 embedding API。牺牲语义精度,换掉网络、密钥、延迟、成本四个变量。
- 检测和判断分开。静态规则只产出候选和复核清单,结论必须由读过上下文的 agent 下。这条对任何"AI + 静态分析"的组合都成立。
至于它会不会成为前端 agent 的标配,取决于两件事:指南的更新能不能跟上 Chrome 的发布节奏(x.y.z 版本号加 @latest 说明他们知道这是命门),以及中文这类非英语查询的缺口补不补。前者是工程问题,后者是产品问题——而产品问题的答案,通常比工程问题来得慢。
参考文档与链接
- GitHub: GoogleChrome/modern-web-guidance — 主仓库,README 里有完整指南清单、Skill Packs、评测章节和遥测说明
- skills/modern-web-guidance/SKILL.md — 90 行流程指令,检索、复核、披露四步
- skills/modern-web-guidance/guides/ — 17 个分类、208 篇指南原文
- guides/ui-components/navigation-drawer.md — README 亲自推荐的样例指南
- npm: modern-web-guidance — 1.0.0 发布页,含 Node 版本要求与包体量
- Vercel skills CLI —
install命令实际转手的目标,多 agent 技能分发的通用工具 - web.dev: Baseline — 指南里
featuresUsed对应的兼容性口径 - MDN: CSS scrollbar-color —
legacy-scan报出的那条指南对应的标准属性 - MDN: View Transitions API — 指南里 top-layer 动画相关场景的标准入口
- Chrome for Developers — 指南的原始知识来源之一,人工阅读用
- Google Clearcut 日志端点说明(源码内常量) — 遥测实现,
LOG_SOURCE = 2921、1,000 条缓冲、10 秒超时 - Context7 — 对照组:云端库文档 MCP,与本文方案互补
你上一次看到 agent 给你写
::-webkit-scrollbar是什么时候?如果它还在给你手搓 modal,那 200 token 的技能包大概是这天性价比最高的一次安装。评论区说说你在哪个场景被旧写法坑过。觉得有用就点个赞。
作者: itech001
来源: 公众号:AI人工智能时代(the-ai-era)
网站: https://www.theaiera.top/
关注每日最新AI新闻和技术博客,主页有更多的文章的AI 技术参考:https://www.theaiera.top
本文首发于 AI人工智能时代,转载请注明出处。

浙公网安备 33010602011771号