Uh oh!
There was an error while loading. Please reload this page.
test(scripts): 仓级 sideEffects 声明与加载期副作用一致性门 (#3943) - #4907
Conversation
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
commented
Aug 17, 2026
PM 验收:ACCEPT(#3943,批次 16,PM 会话 实物核验(已过):diff 与报告逐字对账(+1234/−382);标识 0;releases 0。两处声明外文件的编辑抽查为实: 门的设计
有据扩界(接受):扫描面从字面「barrel」放宽到「barrel + 相对导入可达闭包」—— 反向验证:四变异,M1/M3/M4 预判逐字命中;M2 预判 3 红实测 4 红 —— 多出的红是防空集 floor 按设计触发,dev 按「欠预判」如实报告而非追认,纪律正确。中途自纠 附带产出:新 finding #4902(scripts-type-check 把注释当导入);#4540 追加现状而非立孪生卡。 CI(亲读终态):20 项全 completed,18 success + 2 skipped,零失败。 → undraft + auto-merge (SQUASH)。 Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#3943
按 2026-08-17 晋级评论的 scope 实施:在
scripts/__tests__/落一条派生式仓级门,形状照同目录package-files-exist.test.ts先例。前提验证(先行,
origin/main@97da1b0d6)卡面的两条前提都复核为成立:
components/fields为true(保守claim,不在门的判定面内),core/i18n/react-runtime/sdui-parser/types为false,layout为数组(PR fix(layout): declare the load-time registration insideEffects(#3899) #3940 修正后)。false包自 barrel 可达的全部 157 个模块,顶层副作用语句 0 命中;layout 扫出且仅扫出src/index.ts:285的try { registerLayout(); } catch {},即 packages/layout:sideEffects: falsecontradicts the load-timeregisterLayout()— a side-effect-only import can be tree-shaken away #3899 的原始标本。门落地即绿,没有跑出新的活缺陷。逐包判定表
@object-ui/corefalsedist/index.js,src/index.ts@object-ui/i18nfalsedist/index.js,src/index.ts@object-ui/react-runtimefalsedist/index.js,src/index.tsx@object-ui/sdui-parserfalsedist/index.js,src/index.ts@object-ui/typesfalseexports子路径 +dist/index.cjs+src/index.ts)@object-ui/layoutdist/index.js,dist/index.umd.cjs,src/index.tssrc/index.ts确有顶层副作用@object-ui/componentstruetrue是最保守 claim,不会丢注册)@object-ui/fieldstruereact-runtime的index.tsx是卡面点名「别被index.ts的 glob 漏掉」的那一处 —— 门不靠文件名约定猜,而是从 alias 表解析目录到 barrel,.tsx与.ts走同一条代码路径,并有一条断言与一组探针分别钉住。门做什么
两个方向都判,因为声明可以朝两边说谎:
sideEffects: false+ 加载期副作用 = packages/layout:sideEffects: falsecontradicts the load-timeregisterLayout()— a side-effect-only import can be tree-shaken away #3899 那个谎(注册被整块丢掉,0 字节、退出码 0、无警告)。扫描面按可达性派生,不按文件 glob。
sideEffects: false授权打包器丢掉该包任何导出未被用到的模块,不只是 barrel,所以诚实的扫描面是 barrel + 从它经相对 import 可达的全部模块 —— 恰好就是打包器能丢的那一集。这样还免掉了一份测试文件排除名单:src/**glob 会把同目录的*.test.ts一并扫进来(光packages/core/src就有 28 个顶层describe(...),个个是顶层调用、个个不是打包风险),而按文件名排除等于在门上开一个文件名形状的洞。入口形态含 workspace alias 入口。 仓内有三张打包器 alias 表把
@object-ui/*指到包的src(apps/console、examples/console-starter,以及此前未被点名的packages/fields/vite.config.ts),打包器对这些源文件读的是同一份package.json—— PR #3940 实测过只声明dist/*时 console 的 alias 形态照样 0 字节。三张表都是从配置源文本解析的(照vitest-config-alias-targets-3944.test.ts的理由:只要一串字符串,不该把整个 Vite 插件面 import 起来),新增一张表当天即被覆盖。根vitest.config.mts的第四张表故意不读:Vitest 转译并执行模块,不做 tree-shaking,不是打包面。真打包器只用在静态分析答不了的那一问上。 写门之前先实测过「能不能拿打包器当副作用探测器」——把各包真实源码在镜像的
sideEffects: true下打包(强制保留模块,存活下来的就是打包器认定的不纯部分):rollup 的纯度分析是刻意保守的,「打包器留了东西」≠「这个包有对外可见的副作用」——照这个读法,门第一天就会在三个诚实的包上变红。所以静态判据只问那个可判定的窄问题(裸调用 / 经成员表达式的写 / 纯副作用 import),打包器则负责回答「声明到底改不改变产物」,且每条探针都配
sideEffects: false对照。反向验证(各面先书面预判再跑,变异前已 commit,还原一律
git checkout)false包 barrel(packages/core/src/index.ts)注入顶层裸调用registerEverything();1 failed / 27 passed,报@object-ui/core declares "sideEffects": false, but packages/core/src/index.ts:86 is a top-level call: registerEverything();"./src/index.ts"(#3940 实测过的坑)src/index.ts的 marker 正向探针、真源码 specimen 探针false翻成数组形态(对照的对照)falsebarrel 对照),其余不动8 failed / 20 passed,不多不少正是那 8 条try { registerLayout(); } catch {}(反向/空头声明方向)2 failed / 26 passed② 的预判少了一条,如实记下而不是回头改预判: 第 4 条红是
every declared source entry really does have a load-time side effect里的反空绿地板。抠掉./src/index.ts后,数组里就没有任何源入口了,那条空头声明检查会在空集合上走完并报绿 —— 地板正是为此而设,它按设计先炸。这是地板在干活,不是误报。另外 ② 的 specimen 探针给出的现场是
expected '' to contain 'page-header'—— 字面上的 0 字节 bundle,#3899 那个数字在门里被原样复现。layout 包内 pin 的收敛方式与理由
删除并入(
packages/layout/src/__tests__/side-effects-manifest.test.ts→ 仓级门),理由:该文件 6 条断言里 5 条都是可派生的,并入后自动适用于所有包,而不再只护 layout 一个包——declaresLoadTimeRegistrationsideEffects: false对照最后一条是唯一不可泛化的:marker 探针只能证明「每个声明路径上有某个模块存活」,而 layout 的实际载荷是一组注册键,只有打包真源码才能看到那些具体的键存活 —— 这正是 #3787 需要的、OBJUI-001 报告其缺失的那条断言,因此逐字搬运而非折成 marker。仓级门的反空绿断言按名点住
@object-ui/layout且要求它仍是数组形态,所以 layout 掉出扫描面本身就会变红。一处顺带修正:built-vs-unbuilt 分歧(写门途中实测到)
「哪些入口形态是源码」最初按
fs.existsSync判定。跑turbo type-check(dependsOn: ^build)时packages/layout/dist被生成出来,该判据当场在同一棵树上翻面 ——dist/index.js会被算进静态扫描面,而 CI 的全新 clone 里不会。改为按 git index 判定:git ls-files不会因为你跑了pnpm build而变。这与package-files-exist.test.ts在 #4059 学到的是同一课(它的 producibility 检查最初也写在onDisk上,并在它本该拒绝的状态上报了绿),注释里已互相指认。改完在已 built 的树上重跑 ④,仍只报src/index.ts,不报dist/index.js。相邻缺陷(不扩围,已按纪律处理)
finding,未指派):scripts-type-check.test.ts的 workspace-import 检查按文本 grep,注释里的 import 示例会被判成真 import,且失败信息指引「移动 ci.yml 步骤 / 删掉这条 import」——而现场根本没有 import。本 PR 在check-package-self-import.test.ts里踩到过一次(改写措辞绕开,并留注释指向该卡)。packages/plugin-editor/vite.config.ts.timestamp-*.mjs的.gitignore规则已补上(:99),但文件仍被跟踪。本门因此把打包器配置的匹配收严为 basename 正则/^vite\.config\.(ts|mts|cts|js|mjs|cjs)$/,免得把这份 vite 5 时代的 CI 快照当成第 4 张 alias 表读进来。验证
新门本身 28 tests / 2.3s(9 条静态 + 17 条探针 + 2 条 specimen;探针数与派生出的入口形态数逐条对得上,所以没有探针被静默吞掉)。
changeset 照
.changeset/app-shell-docs-nav-examples.md的 test-only 先例,空 frontmatter、不 bump 任何包(未触碰任何包的运行时源码)。Generated by Claude Code