fix(components): div 废弃提示每次模块加载只报一次;catalog 换类型那一半升级待定夺 (#3965) - #3998
Conversation
`DivRenderer` 原先在每一次渲染里 `console.warn`。单个 `div` 的页面看不出问题, `div` 多的页面就被刷成噪声墙:docs 的 schema-catalog 索引页渲染 400+ 个示例缩略图, 实测刷出约 190 条完全相同的提示,把页面真正的报错埋在下面 —— #3903 / PR #3964 的两条 嵌套按钮报错就是从这堆噪声里捞出来的,浏览器实证也因此两次丢掉信号。 废弃本身一字未动:dev 构建照旧提示,文案与迁移建议不变,production 构建照旧静默, 去掉的只是重复。守卫是模块级 `Set`(按 type 记),production 的提前返回发生在标记之前, 所以一次 production 渲染不会吞掉后续 dev 构建的提示。 反向验证(先预判后执行):把无守卫的旧写法改回去,「production 静默」用例按预期保持 绿(旧写法同样按 NODE_ENV 门控),「只报一次」与「后续渲染不再报」两条按预期翻红 (9 条提示 vs 期望 1 条;后续渲染多出 1 条)。 注:本 PR 只完成 #3965 的第二半(warn 去重)。第一半(catalog 48 个示例换掉废弃的 `div`)在现有组件词表下无法完整完成,已在 issue 上以证据升级给维护者定夺,见 PR 正文。 Co-Authored-By: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
|
补两条给「将来真做 catalog sweep 的人」的关键约束,免得踩进去: 1.
它们被 2. 同目录的迁移范本 另: Generated by Claude Code |
|
PM 验收(session_01GTRjn8xBqp75dk7kFupVRt):通过,转 ready 并挂 auto-merge —— 验收的是已落地的 warn 去重半边;catalog 换类型半边的 needs_decision 升级完全正确(83/189 节点无非废弃对等物 + 核验记录(head
Fixes 语义处置(dev 自请改 Refs):PM 不整体重写正文(避免转义风险),改为:合并自动关闭 #3965 后,PM 立即重开并挂 Generated by Claude Code |
Fixes #3965
基 sha
0cbdca888。落地的:
div废弃提示改为每次模块加载只报一次packages/components/src/renderers/basic/div.tsxDivRenderer原先在每一次渲染里console.warn。单个div的页面看不出问题,div多的页面就被刷成噪声墙:docs 的 schema-catalog 索引页渲染 400+ 个示例缩略图,catalog 里共 189 个div节点,于是刷出百来条完全相同的提示,把页面真正的报错埋在下面 —— #3903 / PR #3964 的两条嵌套按钮报错就是从这堆噪声里捞出来的,浏览器实证因此两次丢掉信号。废弃本身一字未动:dev 构建照旧提示,文案与迁移建议逐字节不变,production 构建照旧静默;去掉的只是重复。守卫是模块级
Set(按 type 记,span.tsx将来照抄同一形状即可),production 的提前返回发生在标记之前 —— 所以一次 production 渲染不会吞掉后续 dev 构建的提示。这一条顺序是被测试钉住的,不是注释里的口头承诺。钉子
packages/components/src/__tests__/div-deprecation-warn-once.test.tsx,3 条:stays silent in production builds—— production 构建零提示,且必须跑在最前(守卫是模块级的,会 latch)。它同时钉住上面那条顺序:production 渲染不得标记 Set。warns exactly once however many div nodes render—— 3 次 render、其中一次嵌 3 层共 6 个div节点,只允许 1 条提示;并断言文案里的两条迁移建议原样保留(证明钉的是「去重」而不是「删警告」)。does not warn again on a later render—— 同一模块实例里后续渲染不再报。反向验证(先预判,后执行)
预判:把无守卫的旧写法改回去,用例 1 应当保持绿(旧写法同样按
NODE_ENV门控,production 本来就静默),用例 2、3 应当翻红。实测与预判一致:
旧写法下实测 9 条(不是节点数 6 —— 提示条数取决于 React 的渲染次数,不是 schema 里的节点数)。这正是钉子在修好一侧断言「恰好 1 条」而不是断言某个具体刷屏条数的原因。变异未提交。
第一半为什么没做:现有词表里有一半
div没有非废弃对等物在基 sha 上重新量过(与分诊评论的 47 一致,issue 卡片的 48 是过时计数):47 个文件、189 个
div节点。节点上出现过的键只有type/className/children/body—— 没有id、没有events,它们全是纯样式包装。按 className 里是否真的写着布局意图分类:
flex(不含flex-col)flexspace-y-*stackmax-w-*containergrid/grid-cols-*gridp-4 h-full bg-slate-50、w-full h-12 rounded-md bg-primary mb-2、p-12 border rounded bg-gradient-to-r …)可换的 106 个,换不掉的 83 个、散在 26 个文件里 —— 也就是说即使把能换的全换掉,47 个文件里仍有 26 个含
div。那不是「豁免清单」,那是这一半做不成。为什么废弃提示自己给的建议都不是对等物
提示推荐
card/flex/container/stack/grid。逐个读渲染器:每一个都往 className 前面注入类,div是唯一的透传:layout/container.tsx:21-53—— 注入w-full max-w-xl mx-auto p-2 sm:p-3 md:p-4layout/flex.tsx:22-55—— 注入flex flex-row justify-start items-start gap-1.5 sm:gap-2layout/stack.tsx:25-58—— 注入flex flex-col justify-start items-stretch gap-*layout/grid.tsx:91-103—— 注入grid grid-cols-* gap-*layout/card.tsx:36-56——Card自带 border / shadow / rounded,children 还被塞进CardContent(自带 padding)对 8 个空的颜色色板(
theme/semantic-color-palette.json里w-full h-12 rounded-md bg-primary mb-2这种、无 children)来说,以上每一个都会改变渲染:凭空多出边框、flex 上下文或 max-width。唯一逐字节等价的是
layout/semantic.tsx:13-47——article/aside/main/nav/section/header/footer就是className+children || body的裸透传(baresection/header/footer由它拿走:containers.tsx那三个注册的是page:*且skipFallback: true,不冲突)。但它们是 sectioning / landmark 元素:main标签在一个文档里必须唯一,而 docs 页面是把示例嵌进去渲染的;把 22 个「没有 className 的分组包装」和 8 个空色板写成section,等于把「用 sectioning 元素当通用盒子」这条写进 45 个文档范本。issue 的立论正是「范本的示范价值」—— 用一个错范本换掉另一个错范本,不解决问题。而且
div结构上退不掉kind: 'html'那一层作者面(ADR-0080)会把标签名原样变成 type:sdui-parser/src/parse.ts:85——const node: SchemaElement = { type: tag, ...props }sdui-parser/src/index.ts:33——allowedTags = new Set(Object.keys(manifest.components))components/src/renderers/layout/page.tsx:438-470—— 白名单由ComponentRegistry.getKnownTypes()生成,裸div在内basic/html-elements.tsx:15,36—— 自己的 TAGS 列表故意排除 div,注释写明「div 已在别处注册」所以一个
kind: 'html'页面里写一个 div 标签,编译出的就是{ "type": "div" },照样打到废弃渲染器上。div是 html tier 词表的必需成员,不可退休 —— 于是「零div」在本仓永远不可能是一条全仓不变量,而这条废弃提示会永久地对引擎自己解析器的输出开火。另外查了
@objectstack/spec:element:*词表里既没有div也没有box,div是 objectui 本地词表(ui命名空间)的东西 —— 所以这不是 spec 层的取舍,是本仓自己的公共契约取舍。请维护者定夺的三个选项
A. 补一个中性容器类型(如
box)。 注册表 +@object-ui/typesschema + docs + parser 白名单一并落。之后 189 个节点div→box是一次零渲染差异、机械可验的替换,catalog 才可能真的零div。container(悄悄加 max-width)和section(悄悄加 landmark 语义)之间猜。div的废弃需要重新指向box。B. 收窄这条废弃,承认残余
div合法。 JSON 作者面继续劝退;html tier 的解析产物不再报废弃;catalog 里 106 个明确写着布局意图的节点转成flex/stack/grid/container(那 106 个是纯赚:JSON 手写 Tailwind 复刻一等公民组件的 props,本来就该用 props);余下 83 个保留div。div退不掉,但留下「一个废弃类型永久合法」这一含糊状态。div属于劝退的那批还是合法的那批」。C. 强推 semantic 标签。 83 个残余全换
section/article。div在 html tier 依然会被解析出来 —— 提示照旧刷。推荐:短期 B,长期 A。 两轴一致指向「先把误伤 html tier 的部分收窄 + 把 106 个明确布局意图转成 props(这批与词表决定无关,任何选项下都成立),同时把
box作为把这条废弃真正做完的方向」。明确不推荐 C —— 它在第二轴上是负的,而第二轴正是这个 issue 的立论。选定之后我可以把对应的 sweep 单独接着做;现在不猜,因为三个选项指向的 diff 形状完全不同(A 是 189 个节点一行改一个字;B 是 106 个节点重写成 props;C 是 83 个节点换 sectioning 标签),猜错要全部推倒。
验证
pnpm --workspace-concurrency=2 --filter "@object-ui/components^..." build—— 绿(新 worktree 先建依赖)。pnpm exec vitest run packages/components/src/__tests__/div-deprecation-warn-once.test.tsx packages/components/src/__tests__/basic-renderers.test.tsx examples/schema-catalog --maxWorkers=2——Test Files 6 passed (6) / Tests 1115 passed (1115)。pnpm exec turbo run type-check --filter=@object-ui/components --concurrency=2——Tasks: 8 successful, 8 total。node scripts/check-control-bytes.mjs——OK (scanned 3902 tracked text file(s));改动文件另做了越过门禁盲区的自查,零命中。node scripts/check-changeset-presence.mjs/check-changeset-no-major.mjs—— 均绿。边界
只动了
packages/components/src/renderers/basic/div.tsx+ 一个新测试 + 一个 changeset。没有碰examples/schema-catalog/**(含同批 #3968 的 test/tsconfig/scripts 面)、没有碰 26 个 MDX 文档页、没有碰content/docs/releases/。basic/span.tsx带同族的逐次渲染警告(catalog 里span命中 0),按派发边界未动 —— 它该照抄本 PR 的守卫形状,留作后续。Generated by Claude Code