From 5b9405fe8cb02d779af206f0d743792ce50cb77f Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 12 Sep 2026 19:11:08 -0400 Subject: [PATCH] fix: force the Storybook dep pre-bundle on every run instead of trusting the cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vite keys the Storybook project's dep cache on the lockfile and the config, never on what the story graph imports. After a story or component gains an import of an already-installed package, the cached bundle is still "valid", Vite skips the scan, and the first request for the new package re-runs the optimizer mid-run: the browser hash is bumped, a full-reload is sent that the Vitest tester iframes ignore, and the `@storybook/react` renderer already loaded in a live iframe keeps the old `?v=` for its lazy `import("@storybook/react-dom-shim")`, so every story that iframe renders from then on fails with "Failed to fetch dynamically imported module". The next run is green because the cache has caught up. `optimizeDeps.force: true` on the storybook project discards the cache up front so the scanner crawls every story entry before the browser opens — the cold path CI already takes on every PR. Measured: stale-cache 6/6 red on the baseline, 6/6 green with the fix, 3/3 red reverted; ~0.8s per run. Closes #2340 Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01PsmUAZ6aLUAFqenjJKSSRp Signed-off-by: cliffhall --- clients/web/server/vite-base-config.ts | 7 +++++++ clients/web/vite.config.ts | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/clients/web/server/vite-base-config.ts b/clients/web/server/vite-base-config.ts index 3d9c1a90..5d4345a3 100644 --- a/clients/web/server/vite-base-config.ts +++ b/clients/web/server/vite-base-config.ts @@ -55,6 +55,13 @@ export function getViteBaseConfig() { // components import it at runtime (e.g. `protocolUtils`'s // `isInputRequiredResult` / `SUBSCRIPTION_ID_META_KEY`), so listing it // here keeps the dep graph stable across story files. + // + // That was one instance of a class, not the class: any package a story + // or component starts importing after the cache was written is + // discovered the same way, and pinning each one here would chase the + // symptom. The `storybook` project in `vite.config.ts` now sets + // `optimizeDeps.force` so every run re-scans the story entries up front + // (#2340); this entry stays as the explicit include it always was. include: ["@modelcontextprotocol/client"], }, }; diff --git a/clients/web/vite.config.ts b/clients/web/vite.config.ts index d6fea376..85ec909f 100644 --- a/clients/web/vite.config.ts +++ b/clients/web/vite.config.ts @@ -391,6 +391,24 @@ export default defineConfig(({ command }) => { configDir: path.join(dirname, ".storybook"), }), ], + optimizeDeps: { + // Re-bundle the browser dep pre-bundle on every run instead of + // trusting the cached one (#2340). Vite keys that cache on the + // lockfile and on this config, never on what the story graph + // imports, so a cached bundle stays "valid" after a story or + // component gains an import of a package that is already + // installed. The first request for it then re-runs the optimizer + // mid-run, which bumps the `?v=` browser hash and rewrites the + // shared chunks; the `@storybook/react` renderer already loaded in + // a live test iframe still holds the old hash for its lazy + // `import("@storybook/react-dom-shim")`, so every story that + // iframe renders from then on fails with "Failed to fetch + // dynamically imported module", and the next run is green because + // the cache has caught up. `force` discards the cache up front, so + // the scanner crawls every story entry before the browser opens — + // the same cold path CI takes on every PR. Cost: ~0.8s per run. + force: true, + }, test: { name: "storybook", // Vitest's default is 5000ms, which is the whole budget a play