From 5f479fa526393fbf93882f89ec2f47b1d1fda1b7 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 14:59:51 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix(runner):=20=E6=8A=8A=20vite=20=E5=88=AB?= =?UTF-8?q?=E5=90=8D=E8=A1=A8=E8=A1=A5=E6=88=90=E5=B7=A5=E4=BD=9C=E5=8C=BA?= =?UTF-8?q?=20import=20=E7=9A=84=E5=AE=8C=E6=95=B4=E4=BC=A0=E9=80=92?= =?UTF-8?q?=E9=97=AD=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `packages/runner/vite.config.ts` 只别名了 7 个 `@object-ui/*`,但这些包的 `src` 自己又 import 了另外 8 个工作区包。没被别名的 specifier 退回 Node 解析、 落到 `packages//dist` —— 而 `dist` 只在构建之后存在,所以在只跑过 `pnpm install` 的干净 checkout 里,vite 报 "Failed to run dependency scan", 整条 import 链上的模块全部 500。这让 `content/docs/utilities/runner.mdx` 的 "From Source"(install → dev,没有 build 步骤)按文档操作必然失败。 别名表就是本仓选定的「runner 在 monorepo 内不依赖 dist 即跑」机制,表不完整 是该机制自身不变量的破缺。这里按传递闭包一次补齐,不是只补第一层: 第一层(#3575 已列):i18n / sdui-parser / react-runtime / fields / plugin-detail 第二层(补齐第一层后才可达):providers(<- fields)、permissions(<- plugin-detail) 类型级:data-objectstack(<- react/src/context/AppShellContext.tsx,`import type`, 被 esbuild 抹除,所以依赖扫描从来不报它,但它在闭包里) 表旁留了一行可复跑的闭包重导命令(已验证:当前表下输出为空即到达不动点; 人为拿掉两条时能准确报出缺口),并写明三个过滤器各自压掉的真实误报。 Fixes #3575 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt --- packages/runner/vite.config.ts | 65 ++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/packages/runner/vite.config.ts b/packages/runner/vite.config.ts index f61edcaf72..c4a7118f2f 100644 --- a/packages/runner/vite.config.ts +++ b/packages/runner/vite.config.ts @@ -25,6 +25,50 @@ export default defineConfig({ "@app": path.resolve(__dirname, "./src/app-data"), // ⚡️ DX: Map imports to source code for Hot Module Replacement + // + // This table IS the mechanism that lets runner boot straight from the + // monorepo sources with NO `pnpm -w build` first — exactly what + // `content/docs/utilities/runner.mdx` "From Source" documents + // (`pnpm install` → `pnpm dev`). Any `@object-ui/*` specifier NOT listed + // here falls back to Node resolution and lands on `packages//dist`, + // which does not exist in a fresh install-only checkout — Vite then + // reports "Failed to run dependency scan" and serves HTTP 500 for every + // module on that import chain (objectui#3575). + // + // ⚠️ INVARIANT — the table must be the *transitive* closure, not just + // runner's own direct imports: every `@object-ui/*` specifier imported + // anywhere under the `src` of a package that is itself aliased here must + // also be aliased. Adding only the directly-missing packages just moves + // the hole one layer down (that is how #3575 happened: `plugin-kanban` + // was aliased, but the `fields` / `plugin-detail` it imports were not). + // + // Re-derive the closure — run from the repo root and re-run until it + // prints nothing; every package you add brings its own src into the + // sweep, so this reaches a fixpoint by iteration: + // + // comm -13 \ + // <(grep -oP '"\K@object-ui/[a-z0-9-]+(?=":)' packages/runner/vite.config.ts | sort -u) \ + // <(grep -rhP "(?/dev/null \ + // | grep -vP '^\s*(\*|//)' \ + // | grep -oP "['\"]\K@object-ui/[a-z0-9-]+" | sort -u) + // + // Anything it prints is a package importable from the aliased sources but + // missing below — add it, then run again. Notes on the filters, each of + // which suppresses a real false positive in this repo: + // - the `(? import('@object-ui/plugin-grid')` in react/LazyPluginLoader + // and core/registry/Registry.ts, which are documentation, not edges; + // - `2>/dev/null` tolerates a table entry whose package dir no longer + // exists (`data-objectql` is such a leftover — objectui#3593). + // Type-only imports are invisible to Vite's esbuild dependency scan but + // still belong in the table — see `data-objectstack` below. "@object-ui/components": path.resolve(__dirname, "../../packages/components/src"), "@object-ui/react": path.resolve(__dirname, "../../packages/react/src"), "@object-ui/core": path.resolve(__dirname, "../../packages/core/src"), @@ -32,6 +76,27 @@ export default defineConfig({ "@object-ui/data-objectql": path.resolve(__dirname, "../../packages/data-objectql/src"), "@object-ui/plugin-kanban": path.resolve(__dirname, "../../packages/plugin-kanban/src"), "@object-ui/plugin-charts": path.resolve(__dirname, "../../packages/plugin-charts/src"), + + // Reached transitively through the packages above — see the closure note. + // `@object-ui/i18n` <- react/src/index.ts, components/src/lib/close-label.tsx + "@object-ui/i18n": path.resolve(__dirname, "../../packages/i18n/src"), + // `@object-ui/sdui-parser` <- components/src/renderers/layout/page.tsx + "@object-ui/sdui-parser": path.resolve(__dirname, "../../packages/sdui-parser/src"), + // `@object-ui/react-runtime` <- components/src/renderers/layout/react-page.tsx + "@object-ui/react-runtime": path.resolve(__dirname, "../../packages/react-runtime/src"), + // `@object-ui/fields` <- plugin-kanban/src/ObjectKanban.tsx + "@object-ui/fields": path.resolve(__dirname, "../../packages/fields/src"), + // `@object-ui/plugin-detail` <- plugin-kanban/src/ObjectKanban.tsx + "@object-ui/plugin-detail": path.resolve(__dirname, "../../packages/plugin-detail/src"), + // `@object-ui/providers` <- fields/src/widgets/{FileField,ImageField}.tsx (2nd layer) + "@object-ui/providers": path.resolve(__dirname, "../../packages/providers/src"), + // `@object-ui/permissions` <- plugin-detail/src/DetailView.tsx et al. (2nd layer) + "@object-ui/permissions": path.resolve(__dirname, "../../packages/permissions/src"), + // `@object-ui/data-objectstack` <- react/src/context/AppShellContext.tsx, `import type` + // only. esbuild erases it, so Vite's dependency scan never flags it — it is + // in the closure by the invariant above, and aliased so that the day the + // import turns into a value import the dev server does not break. + "@object-ui/data-objectstack": path.resolve(__dirname, "../../packages/data-objectstack/src"), }, }, }) From 51be84b1bb5f47e61b8d67173426192e540b5630 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 15:13:16 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix(runner):=20=E5=88=AB=E5=90=8D=E7=94=9F?= =?UTF-8?q?=E6=95=88=E4=BA=8E=20build,=E6=95=85=E5=85=B3=E6=8E=89=20module?= =?UTF-8?q?Preload=20=E5=B9=B6=E8=A1=A5=20changeset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 别名表没有按 `command` 分档,所以同样作用于 `vite build`(这是刻意的,与 apps/console 一致:插件包解析到 src 才能保证 ComponentRegistry 单例, 从各自 dist 引入则不保证)。补全闭包后 `@object-ui/fields` 等从 src 打包, `components/src/lib/lazy-icon.tsx` 经 `lucide-react/dynamic.mjs` 产生的 逐图标 chunk 不再被内联 —— 产物从 10 个 asset 变成 1776 个,其中约 1761 个 是 2KB 以下的图标微 chunk。这与 apps/console 的形状一致(其 build 配置注释 里就写着 "1700+ icon chunks")。 问题在于 vite 默认 `modulePreload: true` 会给每个 chunk 发一条 preload: 实测 index.html 从 546 B 涨到 145 KB、含 1765 条 `rel="modulepreload"`, 首屏就把所有图标 chunk 拉下来,懒加载反成劣化。console 正是为此关掉它, runner 同样需要。 关掉之后实测: index.html 546 B -> 542 B(1 条 preload -> 0 条) 首屏急切载荷 4231003 B -> 591795 B(-86%) dist 总大小 5064294 B -> 5343175 B(+5.5%,原先内联的图标变成独立文件) 产物可见变化,故补 patch changeset(runner 已发布,files 含 dist)。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt --- .../runner-vite-alias-transitive-closure.md | 29 +++++++++++++++++++ packages/runner/vite.config.ts | 21 ++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 .changeset/runner-vite-alias-transitive-closure.md diff --git a/.changeset/runner-vite-alias-transitive-closure.md b/.changeset/runner-vite-alias-transitive-closure.md new file mode 100644 index 0000000000..a4bc9864c6 --- /dev/null +++ b/.changeset/runner-vite-alias-transitive-closure.md @@ -0,0 +1,29 @@ +--- +"@object-ui/runner": patch +--- + +Complete `packages/runner/vite.config.ts`'s workspace alias table to the full +transitive import closure, so `@object-ui/runner` boots and builds from the +monorepo sources without a prior `pnpm -w build` (objectui#3575). + +The table aliased 7 `@object-ui/*` specifiers to `packages/*/src`, but those +`src` trees import 8 more workspace packages that were not aliased. Those fell +back to Node resolution and landed on `packages//dist`, which does not +exist in a fresh install-only checkout — so the "From Source" flow documented in +`content/docs/utilities/runner.mdx` (`pnpm install` then `pnpm dev`, no build +step) failed with "Failed to run dependency scan" and served HTTP 500 for every +module on the chain. `pnpm --filter @object-ui/runner build` failed the same way. + +Newly aliased: `i18n`, `sdui-parser`, `react-runtime`, `fields`, `plugin-detail` +(first layer), `providers` and `permissions` (only reachable once the first layer +resolves to src), and `data-objectstack` (a type-only import that esbuild erases, +so the dependency scan never reported it). + +This is user-visible in the published artifact, because the alias table is not +scoped by `command` and therefore applies to `vite build` as well. Bundling the +newly aliased packages from src stops the per-icon `lucide-react/dynamic.mjs` +chunks from being inlined, so the build now emits ~1.7k lazy icon micro-chunks +like `apps/console` does. `build.modulePreload` is disabled to match console, so +those chunks are not all preloaded on first paint: the measured initial eager +payload drops from 4231003 to 591795 bytes, while total `dist` size grows about +5.5% because the previously inlined icons are now separate files. diff --git a/packages/runner/vite.config.ts b/packages/runner/vite.config.ts index c4a7118f2f..f0c21b7b45 100644 --- a/packages/runner/vite.config.ts +++ b/packages/runner/vite.config.ts @@ -99,4 +99,25 @@ export default defineConfig({ "@object-ui/data-objectstack": path.resolve(__dirname, "../../packages/data-objectstack/src"), }, }, + build: { + // The alias table above is NOT scoped by `command`, so it applies to + // `vite build` too. That is deliberate and matches apps/console: resolving + // the plugin packages to src keeps the ComponentRegistry singleton + // single-instanced, which importing their prebuilt dist/ bundles does not + // guarantee (duplicate registries surface as "Unknown component type"). + // + // Measured consequence of completing the table (objectui#3575): once + // `@object-ui/fields` & co. are bundled from src, the per-icon chunks that + // `components/src/lib/lazy-icon.tsx` creates via `lucide-react/dynamic.mjs` + // stop being inlined — this build goes from 10 assets to 1776, ~1761 of + // them sub-2KB icon micro-chunks. That is exactly the shape apps/console + // has ("1700+ icon chunks", see its build config). + // + // Vite's default `modulePreload: true` then emits a preload link for every + // one of them: index.html measured 546 B -> 145 KB with 1765 + // `rel="modulepreload"` links, so the browser eagerly fetches all the icon + // chunks on first paint and the lazy split becomes a pessimisation. + // apps/console disables it for this exact reason; runner needs the same. + modulePreload: false, + }, })