Merge pull request #2342 from modelcontextprotocol/v2/fix/2340-storybook-optimizer-race
fix: force the Storybook dep pre-bundle on every run (#2340)
This commit is contained in:
commit
856729fb4b
3 changed files with 66 additions and 0 deletions
|
|
@ -55,11 +55,47 @@ 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. `getStorybookOptimizeDeps` below adds `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"],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Storybook (browser Vitest project) optimizeDeps: the base set, re-bundled
|
||||
* on every run instead of read from the cache (#2340).
|
||||
*
|
||||
* Vite keys that cache on the lockfile and on the 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. With a
|
||||
* valid cache Vite skips the scan, and the first request for the new package
|
||||
* re-runs the optimizer mid-run: the shared chunks are rewritten, the `?v=`
|
||||
* browser hash is bumped, and Vite sends a `full-reload` that the Vitest
|
||||
* tester iframes do not act on. The `@storybook/react` renderer already loaded
|
||||
* in a live 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.
|
||||
*
|
||||
* Spread from `getViteBaseConfig()` rather than left to Vitest's project merge
|
||||
* so the include/exclude the base set carries are the helper's own claim, and
|
||||
* tested as such.
|
||||
*/
|
||||
export function getStorybookOptimizeDeps() {
|
||||
return {
|
||||
...getViteBaseConfig().optimizeDeps,
|
||||
force: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Dev-server optimizeDeps: rebuild from scratch each launch — no stale
|
||||
* pre-bundles carried across restarts (avoids 504 Outdated Optimize Dep).
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { join } from "node:path";
|
|||
import { tmpdir } from "node:os";
|
||||
import {
|
||||
clearViteDepsCache,
|
||||
getStorybookOptimizeDeps,
|
||||
getViteBaseConfig,
|
||||
getViteDevOptimizeDeps,
|
||||
} from "../../../../server/vite-base-config.js";
|
||||
|
|
@ -54,6 +55,30 @@ describe("getViteDevOptimizeDeps", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("getStorybookOptimizeDeps", () => {
|
||||
it("forces a re-bundle on every run so a stale cache cannot re-optimize mid-run (#2340)", () => {
|
||||
const config = getStorybookOptimizeDeps();
|
||||
expect(config.force).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps the base include and exclude sets", () => {
|
||||
const config = getStorybookOptimizeDeps();
|
||||
const base = getViteBaseConfig().optimizeDeps;
|
||||
expect(config.include).toEqual(base.include);
|
||||
expect(config.exclude).toEqual(base.exclude);
|
||||
expect(config.include).toContain("@modelcontextprotocol/client");
|
||||
});
|
||||
|
||||
it("does not carry the dev server's stale-request tolerance", () => {
|
||||
// `ignoreOutdatedRequests` belongs to `vite dev`, where a full page reload
|
||||
// follows a re-optimization; under Vitest a request for an outdated dep
|
||||
// must fail loudly, since nothing reloads the tester iframe.
|
||||
expect(getStorybookOptimizeDeps()).not.toHaveProperty(
|
||||
"ignoreOutdatedRequests",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("clearViteDepsCache", () => {
|
||||
let tempRoot: string;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
|
|||
import { playwright } from "@vitest/browser-playwright";
|
||||
import { honoMiddlewarePlugin } from "./server/vite-hono-plugin";
|
||||
import {
|
||||
getStorybookOptimizeDeps,
|
||||
getViteBaseConfig,
|
||||
getViteDevOptimizeDeps,
|
||||
} from "./server/vite-base-config";
|
||||
|
|
@ -391,6 +392,10 @@ export default defineConfig(({ command }) => {
|
|||
configDir: path.join(dirname, ".storybook"),
|
||||
}),
|
||||
],
|
||||
// Re-bundled on every run rather than read from the cache — the
|
||||
// stale-cache failure it prevents, and why `force` is the lever, are
|
||||
// on the helper (#2340).
|
||||
optimizeDeps: getStorybookOptimizeDeps(),
|
||||
test: {
|
||||
name: "storybook",
|
||||
// Vitest's default is 5000ms, which is the whole budget a play
|
||||
|
|
|
|||
Loading…
Reference in a new issue