首页
/ Material UI 仓库 AGENTS.md 深度解读:为 AI Agent 定制的 Monorepo 开发协作规范

Material UI 仓库 AGENTS.md 深度解读:为 AI Agent 定制的 Monorepo 开发协作规范

2026-09-03 15:23:05作者:庞队千Virginia

Material UI 仓库根目录的 AGENTS.md 是一份面向 AI Agent(同时对人有效)的工程协作指南,规定了 pnpm 工作区命令、测试与代码质量流程、组件结构约定、错误信息规范与无障碍测试机制。本文以该文档为核心骨架,结合仓库源码与配置逐项展开,帮助你在动手修改 MUI 源码前建立完整的操作认知:从正确执行每条 pnpm 命令,到遵循组件目录结构与 minify-error 错误压缩约定,再到理解视觉回归中的 axe 无障碍检查流程。

只允许 pnpm:命令执行的第一原则

文档开宗明义:仅支持 pnpm,yarn/npm 会直接失败。这不是偏好而是硬约束——根 package.json 中声明了 "preinstall": "npx only-allow pnpm",任何非 pnpm 的安装都会在 preinstall 钩子处被拦截;"packageManager": "pnpm@11.22.0"engines 字段进一步锁定了 pnpm 11.22.0、Node >= 22.23.2 的运行时环境。

针对工作区内的包操作,应使用 -F 参数而非 cd 进入包目录(文档明确禁止用 cd 导航):

pnpm -F @mui/material add some-package    # 给某个包添加依赖
pnpm -F @mui/material build               # 构建特定包

这种写法的价值在于:命令始终在仓库根目录执行,工作区解析(workspace 协议、pnpm-workspace.yaml)与 Nx 缓存命中都不受工作目录切换影响。

常用命令速查:从开发到构建

开发

pnpm install                  # 按需安装依赖
pnpm docs:dev                 # 仅启动文档开发服务器

对照 package.json 的 scripts 字段,docs:dev 实际转发为 pnpm --filter docs dev,即只启动文档站而不动其他包。

构建

pnpm release:build            # 构建全部包(不含 docs)
pnpm docs:build               # 构建文档站

源码层面 release:build 展开为 lerna run --concurrency 8 --no-private build --skip-nx-cache——两个关键细节:--no-private 只构建待发布的公开包,--skip-nx-cache 跳过 Nx 缓存强制执行。docs:build 则串联了 pnpm docs:llms:build(生成面向 LLM 的文档产物)与 pnpm --filter docs build

测试

pnpm test:unit                # 运行全部单元测试(jsdom)
pnpm test:unit ComponentName  # 按模式匹配运行测试
pnpm test:unit -t "test name" # 按测试名 grep 过滤
pnpm test:browser             # 在真实浏览器中运行(Chrome、Firefox、WebKit)
pnpm test:e2e                 # 端到端测试
pnpm test:regressions         # 视觉回归测试

实际实现上,test:unitcross-env TZ=UTC vitest,通过固定时区保证日期相关断言的稳定性;test:browser 则切换 TEST_SCOPE=browser 环境变量,利用仓库根与各包下的 vitest.config.browser.mts 双配置在真实浏览器中跑同一套用例;test:e2e 转发到独立的 test/e2e 工作区包(pnpm -F ./test/e2e start)。

代码质量

pnpm prettier                 # 格式化暂存的改动
pnpm eslint                   # 带缓存的 Lint
pnpm typescript               # 对全部包做类型检查

对应实现:prettier 使用 pretty-quick --ignore-path .lintignore --branch master(只对相对 master 分支的暂存改动格式化);eslint 带有 --cache--max-warnings 0(零容忍);typescript 通过 lerna run --no-bail typescript 并行调度所有包各自的类型检查。

API 文档

修改组件 props 或 TypeScript 声明后,需重新生成 API 文档:

pnpm proptypes && pnpm docs:api

proptypes 指向 tsx ./scripts/generateProptypes.ts(基于 packages-internal/scripts/typescript-to-proptypes 从类型声明抽取 proptypes);docs:api 先清理 docs/pages/**/api-docs 旧产物再执行 tsx ./scripts/buildApiDocs/index.ts 重建。

文档 Demo

文档规范:始终编写 TypeScript 版本的 demo,JavaScript 变体由脚本生成:

pnpm docs:typescript:formatted

该命令执行 tsx ./docs/scripts/formattedTSDemos.mjs,将 *.tsx demo 编译为格式化的 *.js 版本供文档站的双语言 Tab 展示(可在 docs/data/material/components 下看到大量成对的 .tsx/.js demo 文件)。

Monorepo 架构:Lerna + Nx 的包组织

文档给出的架构结论是:这是一个由 Lerna 管理、以 Nx 做缓存的 monorepo。仓库证据完全印证:lerna.json 声明 "npmClient": "pnpm""version": "independent"(各包独立版本号),而 package.json 中 devDependencies 同时包含 lerna@10.0.1nx@22.7.6

核心公开包及其职责:

职责 源码位置
@mui/material 核心 Material UI 组件 packages/mui-material/src
@mui/system 样式系统(sx prop、styled、theme) packages/mui-system/src
@mui/lab 实验性组件(新组件先在这里孵化) packages/mui-lab/src
@mui/icons-material Material Design 图标 packages/mui-icons-material/lib
@mui/utils 内部工具函数 packages/mui-utils/src
@mui/styled-engine CSS-in-JS 抽象层(默认 Emotion) packages/mui-styled-engine/src

不发布的内部包以 @mui-internal/*@mui/internal-* 命名,对应 packages-internal/ 目录(如 api-docs-buildercore-docsmarkdown)。从 tsconfig.json 的 paths 别名可以看到内部包与公开包的完整映射关系,例如 "@mui/internal-api-docs-builder": ["./packages-internal/api-docs-builder/src/index.ts"]

TypeScript 约定与路径别名

三条硬性约定:

  1. 组件 props 用 interface 而不是 type
  2. 从组件文件导出 {ComponentName}Props 接口;
  3. 路径别名可用,如 @mui/material./packages/mui-material/src

第 3 点在 tsconfig.json 中有完整落实:"@mui/material": ["./packages/mui-material/src"]"@mui/material/*": ["./packages/mui-material/src/*"] 两级别名让编辑器与测试直接解析到源码,无需预构建即可跨包引用。这也解释了为什么 pnpm test:unit 不需要先 release:build——vitest 环境下各包直接以源码形态互相依赖。

错误信息规范与 minify-error 机制

三段式错误文案

文档要求公开包抛出的每个错误信息必须满足三段式:

  1. 说明发生了什么——清晰描述问题;
  2. 说明为什么是问题——解释后果;
  3. 指向解决方向——给出可操作的指引。

格式约束:以 MUI: 前缀开头;用字符串拼接提升可读性;适用时附文档链接(https://mui.com/r/... 短链)。

minify-error babel 插件

/* minify-error */ 注释激活 babel 插件的压缩能力:

throw /* minify-error */ new Error(
  'MUI: Expected valid input target. ' +
    'Did you use a custom `inputComponent` and forget to forward refs? ' +
    'See https://mui.com/r/input-component-ref-interface for more info.',
);

源码中确有此实践:InputBase.js 的受控输入变更处理中抛出的正是这条 "Expected valid input target" 错误,SelectInput.js 也是同款写法。插件本体是 @mui/internal-babel-plugin-minify-errors(见 package.json devDependencies)。其工作方式:构建时将完整错误消息抽走,运行时只保留一段可定位的短错误码,用户可在文档站的错误码页反查完整文案——从而在不牺牲开发体验的前提下显著压缩产物体积。文档还特别指出该 minifier 同时兼容 ErrorTypeError 两种构造函数。

错误码提取流程

新增或更新错误后运行:

pnpm extract-error-codes

它调用 code-infra extract-error-codes,把各包源码中带 minify-error 标注的消息抽取到 docs/public/static/error-codes.json。当前该文件首条记录正是上面 InputBase 的完整文案:"1": "MUI: Expected valid input target. Did you use a custom \inputComponent` and forget to forward refs? ..."`。

一个容易踩坑的细节:如果新消息与原消息参数个数相同且语义未变,应直接更新原错误码的文案,而不是新增一个编码——错误码编号是对外稳定契约,随意增码会让旧版本用户的排查链接失效。

组件目录结构约定

文档给出的标准组件目录布局:

packages/mui-material/src/Button/
├── Button.tsx           # 组件实现
├── Button.d.ts          # TypeScript 声明(供 JSDoc API 文档使用)
├── Button.test.js       # 单元测试
├── buttonClasses.ts     # CSS 类名
└── index.ts             # 公开导出

对照实际的 packages/mui-material/src/Button/ 目录:Button.d.tsButton.spec.tsx(类型测试)、Button.test.jsbuttonClasses.tsindex.d.tsindex.js 一应俱全(实现文件在此仓库版本中为 Button.js,文档中的 .tsx 命名代表约定方向)。

其中 buttonClasses.ts 展示了该约定的完整形态:ButtonClasses 接口为每个类名键(roottextcontainedloading 等 27 个)提供 JSDoc 注释,getButtonUtilityClass(slot) 委托给 @mui/utils/generateUtilityClasses 统一生成 MuiButton-* 类名。这套 CSS 类同时是用户通过 componentsProps/sx 覆样式的官方钩子,因此每个类名都必须有可检索的文档注释。

单元测试约定

  • 使用 @mui/internal-test-utilscreateRenderer() 创建渲染器;
  • 使用 Chai BDD 风格断言(expect(x).to.equal(y));
  • 自定义断言 toErrorDev()toWarnDev() 用于捕获开发期 console 输出;
  • 优先用 user.* 方法做完整交互测试,尽量避免 fireEventsetProps
  • 需要浏览器布局测量的用例,用 it.skipIf(isJsdom()) / describe.skipIf(isJsdom()) 限定到 Chromium 环境(不确定时先搜索其他测试的用法)。

文档附带的示例:

import { createRenderer } from '@mui/internal-test-utils';

describe('Button', () => {
  const { render } = createRenderer();

  it('renders children', async () => {
    const handleClick = vi.fn();
    const { getByRole, user } = render(<Button onClick={handleClick}>Hello</Button>);

    const button = getByRole('button');
    expect(button).to.have.text('Hello');

    await user.click(button);
    expect(handleClick).toHaveBeenCalledTimes(1);
  });
});

这个模式可直接在 Button.test.js 等既有测试中找到同构写法:createRenderer() 返回 render 及其作用域内的查询 API 与 user 句柄,断言库则是 vitest 环境下混入 Chai 风格 matcher 的组合,配合仓库根 test/setupVitest.ts 完成全局初始化。

无障碍(a11y)测试:截图与 axe 双轨并行

运行机制

axe-core 的无障碍检查运行在视觉回归的 Playwright 循环内test/regressions/index.test.js),不单独开浏览器会话。截图与 a11y 相互独立:一个 demo 可以退出其中一项而保留另一项。

三个关键文件

  • test/regressions/demoMeta.tsSCREENSHOT_RULESA11Y_RULES 两个独立规则数组,按最后匹配生效(last-wins)原则对 docs/data/material/components/{slug}/{Demo} 路径做 minimatch glob 匹配。规则间没有继承——覆盖规则必须重述它关心的每个字段。
  • test/regressions/a11y/axe.ts:默认断言 color-contrastlink-in-text-block 两条 axe 规则;当匹配的 a11y 规则设置 assertions: 'all' 时,断言该 demo 触发的全部 axe 规则;skipAssertions 可屏蔽指定规则。
  • test/regressions/a11y/a11yReporter.ts:按 slug 每个组件写一个 docs/data/material/components/{slug}/{slug}.a11y.json,内部以 demo 名为一级键、axe 规则 ID 为二级键,每条规则记录 statuspass/fail/incomplete)与 WCAG 标签。

demoMeta.ts 源码可以看到 A11yRule 接口的精确形状:test(glob)、enabledassertions: 'visual' | 'all'(默认 'visual')、skipAssertions: string[]ScreenshotRule 额外支持 waitForSelector(Playwright 在导航后等待该选择器出现,再执行 axe + 截图)与 viewportWidth(仅宽度可配,因为截图截取的是 testcase 元素全高,只有宽度影响结果)。

接入一个组件

按 slug 整体纳入,或用 brace-glob 收窄范围:

// test/regressions/demoMeta.ts
{ test: 'docs/data/material/components/alert/*', enabled: true, skipAssertions: ['color-contrast'] },
{ test: 'docs/data/material/components/buttons/{BasicButtons,ColorButtons}', enabled: true, assertions: 'all' },

覆盖特定 demo:在 slug 级规则之后追加 per-demo 规则(last-match-wins,且覆盖必须重述所有字段):

{ test: 'docs/data/material/components/popover/AnchorPlayground', enabled: false }, // Redux isolation

规则作用的对象就是 docs/data/material/components/ 下的 demo 文件(如 alert/ 目录里的 BasicAlerts.tsxActionAlerts.tsx 等),glob 匹配的是文档数据路径而非源码路径。

刷新结果与 CI 校验

pnpm test:regressions    # 刷新所有 *.a11y.json;CI 会因文件过期(stale)而失败

本地迭代时用 vitest 的 -t 名称过滤缩小范围(匹配 it() 字符串中携带的路由,不匹配的测试体根本不执行,浏览器不会导航到那些路由):

# 终端一
pnpm test:regressions:server

# 终端二 —— 注意没有 `--`,pnpm 直接转发参数
pnpm test:regressions:run -t '/docs-components-buttons/'               # 单个 slug
pnpm test:regressions:run -t '/docs-components-buttons/BasicButtons$' # 单个 demo
pnpm test:regressions:run -t '/docs-components-(buttons|chips)/'      # 多个 slug

注意:过滤后的运行只会刷新匹配到 slug 的 *.a11y.json,推送前务必跑一次无过滤的 pnpm test:regressions。对照 package.jsontest:regressions 的完整编排是:cross-env NODE_ENV=production pnpm test:regressions:build(vite 构建回归站点)+ concurrently 并行拉起 run 与 server + 最后 prettier --write 归一化所有 *.a11y.json

导入约定:一级深度导入

包内代码必须使用一级深度导入,避免把整个包打进依赖者的 bundle:

import Button from '@mui/material/Button'; // 正确
import { Button } from '@mui/material';   // 包内避免

这与 packages/mui-material/src/Buttonindex.js 逐文件导出结构、以及 tsconfig.json@mui/material/* 二级别名共同保证了深层导入在源码态与构建态都可用。

Agent Skills:按主题分包的集成指南

文档指出,面向常见集成主题的分包指南存放在 skills/ 目录下,每个 skill 是一个自包含目录:

Skill 主题
skills/material-ui-styling sxstyled()、主题覆写、slots、全局 CSS
skills/material-ui-theming createTheme、设计令牌、colorSchemes、CSS 变量
skills/material-ui-nextjs App/Pages Router、Emotion 缓存、next/fontLink、SSR
skills/material-ui-tailwind Tailwind v4 @layerenableCssLayer、v3 互操作

处理这些主题时应读取对应 skill 的 AGENTS.md。按 skills/README.md,每个 skill 目录遵循固定布局:AGENTS.md(完整指南,规范事实源)、SKILL.md(入口与索引)、README.md(人读概览)、metadata.json(机器可读元数据)、reference.md(速查表)。发现机制正是根 AGENTS.md 的这张表格——任何会读取 AGENTS.md 的 Agent 都能据此跳转到细分技能。

Pre-PR 检查清单与 PR 标题格式

文档结尾给出了七步提交前检查:

  1. pnpm prettier — 格式化代码
  2. pnpm eslint — 通过 Lint
  3. pnpm typescript — 通过类型检查
  4. pnpm test:unit — 通过单元测试
  5. 若改了 API:pnpm proptypes && pnpm docs:api
  6. 若改了 demo:pnpm docs:typescript:formatted
  7. 若改了 .md 文件:pnpm vale <file1> <file2> ... — 检查行文风格与语法

第 7 步的 vale 规则在 package.json 中实现为 code-infra vale(版本锁定 3.18.0),仅对 .Level=="error" 级别报错。

PR 标题格式为 [component] Imperative description,示例:

  • [button] Add loading state
  • [docs] Fix typo in Grid documentation

小结

AGENTS.md 本质上把 MUI 仓库的"隐性工程知识"显性化为一份可被 Agent 与新人直接执行的契约:命令层只认 pnpm -F 与 lerna/nx 编排,规范层锁定 interface props、三段式 MUI: 错误文案与 minify-error 压缩、一级深度导入,测试层则以 createRenderer() + user.* 交互、jsdom/浏览器双轨、以及截图/axe 独立开关的回归体系兜底。对照仓库中 lerna.jsonpackage.jsontsconfig.jsondemoMeta.ts 等真实实现,每一条约定都有落点;按 Pre-PR 清单走完七步,即可保证一次改动在格式、类型、单测、API 文档与文档站 demo 之间的一致性。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
503
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384