The storybook project's optimizeDeps now comes from `getStorybookOptimizeDeps()` in `vite-base-config.ts`, beside the dev-server helper it mirrors, so the `force` flag and the base include/exclude it carries are asserted by `vite-base-config.test.ts` rather than living only in an inline object a config refactor could drop. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PsmUAZ6aLUAFqenjJKSSRp Signed-off-by: cliffhall <cliff@futurescale.com>
119 lines
5.4 KiB
TypeScript
119 lines
5.4 KiB
TypeScript
/**
|
|
* Shared Vite config (build externals + optimizeDeps). Consumed by `vite.config.ts`
|
|
* and by `start-vite-dev-server.ts` so the in-process Vite starter can apply the
|
|
* same node-only exclusions without re-loading `vite.config.ts` (Node can't
|
|
* import .ts directly).
|
|
*
|
|
* Aliases and the vitest project setup live in `vite.config.ts` itself — only
|
|
* the parts needed by both the CLI runner and `vite dev` belong here.
|
|
*/
|
|
|
|
import { rmSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
|
|
const NODE_ONLY_OPTIMIZE_DEPS_EXCLUDE = [
|
|
"@modelcontextprotocol/client/stdio",
|
|
// `atomically` is reached only through `core/storage/store-io.ts`,
|
|
// which is imported by `core/mcp/remote/node/server.ts` (the Hono
|
|
// app). The module never lands in the browser graph; excluding it
|
|
// keeps Vite's dev-time scanner from chasing it through the plugin's
|
|
// node-only import chain.
|
|
"atomically",
|
|
// `chokidar` is only loaded inside `core/mcp/remote/node/server.ts`
|
|
// when the lazy mcp.json watcher starts. It transitively imports
|
|
// `readdirp` and core node fs/os modules; excluding it keeps Vite's
|
|
// dep scanner from walking into them during dev startup.
|
|
"chokidar",
|
|
"cross-spawn",
|
|
"which",
|
|
// `@napi-rs/keyring` is loaded only inside
|
|
// `core/auth/node/secret-store.ts` from the Hono `/api/servers`
|
|
// handlers — and lazily even there, via a cached dynamic import, so
|
|
// an unsupported platform degrades instead of crashing at startup
|
|
// (#1905). It's a native-binding package (no browser code path) so
|
|
// excluding it keeps Vite's dep scanner from chasing into the
|
|
// platform-specific binaries during dev startup.
|
|
"@napi-rs/keyring",
|
|
// `proper-lockfile` is reached only through `core/auth/node/file-lock.ts`
|
|
// — the secrets file's cross-process lock (#2082) — which the Hono
|
|
// `/api/servers` handlers pull in via `core/auth/node/file-secret-store.ts`.
|
|
// Same node-only import chain as `atomically` above, and the same reason:
|
|
// it is CJS with a `graceful-fs`/`signal-exit`/`retry` graph that Vite's
|
|
// dev scanner has no business walking. Note the tsup `external` lists do
|
|
// **not** cover this — they configure the production bundles, not `vite dev`.
|
|
"proper-lockfile",
|
|
] as const;
|
|
|
|
export function getViteBaseConfig() {
|
|
return {
|
|
optimizeDeps: {
|
|
exclude: [...NODE_ONLY_OPTIMIZE_DEPS_EXCLUDE],
|
|
// Pre-bundle the browser-safe client barrel up front so Vitest's
|
|
// Storybook (browser) project doesn't discover it lazily and re-optimize
|
|
// mid-run — which reloads the worker and fails in-flight dynamic story
|
|
// imports with "Failed to fetch dynamically imported module". Several
|
|
// 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).
|
|
* Do not use under Vitest.
|
|
*/
|
|
export function getViteDevOptimizeDeps() {
|
|
return {
|
|
exclude: [...NODE_ONLY_OPTIMIZE_DEPS_EXCLUDE],
|
|
force: true,
|
|
ignoreOutdatedRequests: true,
|
|
include: ["ajv", "@modelcontextprotocol/client/validators/ajv"],
|
|
};
|
|
}
|
|
|
|
/** Remove Vite's pre-bundle cache under clients/web before a dev launch. */
|
|
export function clearViteDepsCache(clientWebRoot: string): void {
|
|
rmSync(join(clientWebRoot, "node_modules", ".vite"), {
|
|
recursive: true,
|
|
force: true,
|
|
});
|
|
}
|