Uh oh!
There was an error while loading. Please reload this page.
feat(console): OBJECTSTACK_SPEC_DIST hook so a framework build can bundle its own @objectstack/spec (#4854) - #4927
Conversation
…ndle its own @objectstack/spec (#4854) apps/console/vite.config.ts 增加 OBJECTSTACK_SPEC_DIST 环境钩子,与既有的 OBJECTSTACK_CLIENT_DIST 对称:设了就把 @objectstack/spec 的解析指向给定的本地 spec 包,未设时完全惰性(alias 表、optimizeDeps.include、vendor-objectstack 分 块判据、dev server 的 fs.allow 全部保持基线值)。 client 钩子不能原样搬:@objectstack/client 的 exports map 只有 1 条,而 @objectstack/spec 有 18 条且每条都重定向进 dist/,Vite 的字符串 alias 是前缀替 换、不读 exports map。所以映射从**覆盖目标自己的 exports map** 派生,逐条生成 alias(subpath 在前、裸 specifier 在后),而不是按「dist/NAME/index.mjs」这类手 写规则——后者对 ./openapi.json 就是错的(它指向 json-schema/openapi.json)。 任一失败路径都抛错(路径不存在、不是 spec 包、exports 条目指向构建产物里缺失的 文件、通配 pattern),不做宽容回退:静默落回已安装的 spec 正是这个钩子要消灭的 偏差类。 Co-authored-by: Claude <noreply@anthropic.com>
18 条 exports map 逐条与 Node 自己的解析器(import.meta.resolve)对账;仓内实 际写出的每个 @objectstack/spec specifier 都走一遍别名表并落到存在的文件;裸 specifier 必须排在最后(未声明的 subpath 由此重写成不存在的路径,响亮报错而不 是静默落回已安装的 spec)。 四个标记面在 env 未设时逐个钉在基线值上(alias 表无 @objectstack 键、 optimizeDeps.include 七条原样、vendor-objectstack 判据源码逐字、server.fs 不 存在),读的是真实 console config 而不是 helper;env 设了时同一 config 以带 query 的模块 id 再求值一次,四面同时到位。 外加两条真实 vite build:注入后的别名表能把 bare + /ui + /data + /package.json 全部打进产物且 chunk 无外部 import;而字面照搬 client 钩子的单条前缀别名会直接 构建失败——卡面前提由此从断言变成实测。 Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
commented
Aug 17, 2026
PM 验收:ACCEPT(#4854,批次 17,PM 会话 实物核验(已过):5 文件 +799/−14 逐字对账(vite.config +82/−14、helper 240、测试 465、turbo.json 单行 env、changeset 空 frontmatter 主动声明);标识 0;releases 0;与最新 main merge-tree 干净。 机制裁量(接受,且有实证):映射从覆盖目标自己的 exports map 派生而非按卡面草案手写 —— 草案对 四面判定表(逐面显式,全部带证据): subpath 对账:18 条 exports ↔ 18 条别名,用 反向验证:两变异预判先行命中;M1 的反直觉形态(被抠 subpath 表现为裸条目前缀吞并的 有据偏离(全部接受):派生式映射(实证)、测试落 scripts/tests(被测对象性质 + #3943 门互动已确认不进扫描面)、运行时拼接 specifier 绕 TS5097(共享 tsconfig 未动,缺口立 #4926)、空 frontmatter changeset(照先例)。 CI(亲读终态):20 项全 completed,18 success + 2 skipped,零失败。 给 framework 侧的边界注记已在 PR:钩子是打包器层,tsc 不读 alias,类型半边归 os#8134 自决 —— 未扩围,正确。os#8134 的 → undraft + auto-merge (SQUASH)。 Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#4854
apps/console/vite.config.ts增加OBJECTSTACK_SPEC_DIST环境钩子,与既有的OBJECTSTACK_CLIENT_DIST对称:设了就把@objectstack/spec的解析指向给定的本地 spec 包;未设时完全惰性,现行为零变化。5311163356)执行:本卡先落,framework 的.objectui-shapin 随后推进,再落 objectstack#8134 —— 正文的Blocked-by:行不适用。前提验证(先做,后写码)
5ffcc1432)OBJECTSTACK_SPEC_DIST仓内出现次数@objectstack/specexports map 条目数17.0.0-rc.6)/ui214 处、/data87 处OBJECTSTACK_CLIENT_DIST钩子形状:161、:167)前提成立,
premise_still_valid: true。只有一处卡面表述需要修正,而且它恰好是不能照抄的那处:卡面给的映射草案是「
@objectstack/spec/NAME映到SPEC_PKG/dist/NAME/index.mjs,openapi.json与package.json作两个非目录例外」。实测./openapi.json的目标是./json-schema/openapi.json—— 不在dist/下。按草案手写表会得到dist/openapi.json/index.mjs,一个不存在的路径。所以本 PR 不写死规则,而是从覆盖目标自己的 exports map 派生每一条别名。卡面也明说了「shape is yours to choose」。机制
新增
scripts/vite-objectstack-spec-dist.ts(放在scripts/vite-*.ts,turbo.json的 build inputs 与apps/console/tsconfig.node.json的 include 都已按该 glob 覆盖,不需要新增声明)。dist// 其中的入口文件三种写法,向上走到最近的package.json并要求它就是@objectstack/spec,再取 realpath。import→module→browser→default取叶子(不取types—— 它在每个条件对象里排第一,按声明顺序遍历会把每条 subpath 别名到.d.mts)。find命中规则是「相等,或以find+/开头」,先命中先赢,裸条目排前面会吞掉所有 subpath。排最后则它还兼任兜底 —— 覆盖目标没有声明的 subpath 会被重写成一个不可能存在的路径,构建响亮报错,而不是静默落回已安装的 spec。四个标记面逐个判定
server.fs.allowfs.allow会 403(表现为空白页,不是构建错误)。client 推两条(dirname与其父)是因为它拿到的可能是文件;spec 侧解析出的就是包目录本身,一条即可。未设任一 override 时server.fs整个键不存在,与基线逐字一致。optimizeDepsinclude摘掉。依据是 vite 8 源码实测:pre-alias 插件对「别名到node_modules之外」的解析,只有optimizeDeps.include点名该 specifier 时才注册为 dep(node_modules/vite/dist/node/chunks/node.js:27820,(isInNodeModules(resolvedId) || optimizeDeps.include?.includes(id)))。留着 = 把注入的 spec 塞进node_modules/.vite预打包缓存,而该缓存 key 不会因为 framework 原地重建 spec 而变化 —— 陈旧预打包供着昨天的 schema,正是本钩子要消灭的偏差类。代价只是 dev 冷启动略慢。另需记明:build 期预打包在 Vite 5.1 已移除(node.js:37128的告警文案;depsOptimizerEnabled要求!isBuild,node.js:32743),所以 framework 的vite build路径根本不读这张表,这条处理是给pnpm dev用的。manualChunks(本仓已是 rolldown 的advancedChunks.groups)vendor-objectstack会散进各自 importer 的 chunk。注入构建应当与发布构建只差 spec 内容、不差 chunk 布局,所以把覆盖目录并进该 group 的 test(基线.source | 转义后的包目录 + 分隔符),只加不换:原来的node_modules/@objectstack/与 pnpm@objectstack+两条臂都保留。未设 env 时该 group 的 test 就是原来那个字面量对象本身(identity 相同)。turbo.json的 env 声明tasks.build.env加OBJECTSTACK_SPEC_DIST。turbo v2 严格 env 模式会剥掉未声明的变量,那会让钩子读到undefined而调用方以为自己注入成功 —— 正是要防的静默失效。卡面提到 framework 直接调 console 的 build 脚本、但 deps 构建走 turbo;根pnpm build也走 turbo,所以这条是必需的。subpath 对账读数
scripts/__tests__/vite-objectstack-spec-dist.test.ts把派生表与 Node 自己的解析器(import.meta.resolve,即算法本身,不是对 map 的第二次解读)逐条对账:/ui→dist/ui/index.mjs(目录型)、/openapi.json→json-schema/openapi.json(非dist例外)、/package.json→package.json。packages/apps/examples,排除node_modules、dist):实际写出的 spec specifier 全部走一遍别名表并落到存在的文件,未解析:0。这条随仓库生长自动收紧 —— 将来某个 subpath 覆盖目标没有,它会红。.../dist/index.mjs/not-a-real-subpath(实测不存在)。另加两条真实 vite build(约 0.3s,不落盘):注入后的别名表能把裸 +
/ui+/data+/package.json全部打进产物,chunk 的外部 import 列表为空;而字面照搬 client 钩子的单条前缀别名会直接构建失败(2 个解析错误)。卡面「client hook 不能原样搬」由此从断言变成实测。反向验证(先书面预判,后跑;变异前已 commit,还原用
git checkout)预判两条都是普通 RED(派生表是 case 1 的唯一输入,
null分支是 case 2 的唯一输入,变异只能增加发现),既不是计数型也不是反转型。实测:… && s !== '@objectstack/spec/ui')→ 5 红。这里有一处值得记下的反直觉:被抠掉的 specifier 不会表现为「无别名」。它会前缀命中裸条目,被报成@objectstack/spec/ui -> …/dist/index.mjs/ui(不可能存在的路径),所以发现落在仓内扫描的unresolved里,而missing保持为空;对账用例更早一步就在 18-vs-17 上失败了。@objectstack键;optimizeDeps.include从 7 条掉到 3 条。惰性证明本身读的是真实 console config(以带 query 的模块 id 二次求值拿到 env 设了的那份),不是 helper 的返回值 —— helper 正确但没接线,是本仓付过学费的失败形状。
边界(留给 objectstack#8134 的一句)
这个钩子是打包器层的,
tsc不读resolve.alias。console 的 build 是tsc && vite build,所以类型仍然按 lockfile 里的 spec 声明检查。如果注入的 spec 带了尚未发布的类型,framework 侧需要自己决定怎么处理那一半(路径映射,或接受类型按已发布版本校验)。本卡按维护者裁定只做打包器半边,不在此扩围。验证
#3943 的 side-effects 门按要求连带跑过:它的别名解析只匹配
@object-ui/作用域 +path.resolve(…)形状,本 PR 的@objectstack/spec条目是运行时Object.assign进去的,不进它的扫描面,aliasEntries计数不变。changeset:空 frontmatter(纯构建工具改动,没有任何发版包的
src/被触及;check-changeset-presence也确认「无 changeset 义务」,这份是主动声明)。相邻发现(未在本 PR 修)
tsconfig.scripts.json的头注声称与apps/console/tsconfig.node.json选项集对齐,但缺allowImportingTsExtensions,导致两边共享的scripts/vite-*.ts在一个 program 绿、另一个红。今天休眠(scripts/下没有.ts后缀 specifier),本 PR 用运行时拼 specifier 绕开而不是改共享 tsconfig。finding标签,无pm:queue。Generated by Claude Code