UNPKG

storybook-framework-cedarjs

Version:
192 lines (191 loc) • 10.1 kB
import fs from "node:fs"; import { createRequire } from "node:module"; import path from "node:path"; import { mergeConfig } from "vite"; import { nodePolyfills } from "vite-plugin-node-polyfills"; import { getPaths } from "@cedarjs/project-config"; import { autoImports } from "./plugins/auto-imports.js"; import { mockAuth } from "./plugins/mock-auth.js"; import { mockRouter } from "./plugins/mock-router.js"; import { reactDocgen } from "./plugins/react-docgen.js"; function getAbsolutePath(input) { const createdRequire = createRequire(import.meta.url); return path.dirname( createdRequire.resolve(path.join(input, "package.json"), { paths: [getPaths().base] }) ); } const core = { builder: getAbsolutePath("@storybook/builder-vite"), renderer: getAbsolutePath("@storybook/react") }; const previewAnnotations = (entries = []) => { const createdRequire = createRequire(import.meta.url); return [...entries, createdRequire.resolve("./preview.js")]; }; const cedarProjectPaths = getPaths(); const viteFinal = async (config) => { const plugins = (config.plugins ?? []).filter( (p) => !p || Array.isArray(p) || p.name !== "cedar-entry-injection" ); plugins.unshift(await reactDocgen()); plugins.unshift(nodePolyfills()); return mergeConfig( { ...config, plugins }, { plugins: [mockRouter(), mockAuth(), autoImports], resolve: { alias: { "~__CEDAR__USER_ROUTES_FOR_MOCK": cedarProjectPaths.web.routes, "~__CEDAR__USER_WEB_SRC": cedarProjectPaths.web.src, // @cedarjs/web imports @apollo/client via explicit .cjs sub-paths // (e.g. `@apollo/client/cache/cache.cjs`). In a Node.js / tsc build // context these resolve fine, but Vite (ESM-first, browser target) // falls back to a ?import CJS interop that can't statically detect // named exports, causing runtime SyntaxErrors. Alias the .cjs paths // to their package-root equivalents so Vite resolves them through // Apollo's package.json `exports` field and picks up the ESM build. "@apollo/client/cache/cache.cjs": "@apollo/client/cache", "@apollo/client/core/core.cjs": "@apollo/client/core", "@apollo/client/link/context/context.cjs": "@apollo/client/link/context", "@apollo/client/link/core/core.cjs": "@apollo/client/link/core", "@apollo/client/link/persisted-queries/persisted-queries.cjs": "@apollo/client/link/persisted-queries", "@apollo/client/react/hooks/hooks.cjs": "@apollo/client/react/hooks", "@apollo/client/react/react.cjs": "@apollo/client/react", "@apollo/client/utilities/utilities.cjs": "@apollo/client/utilities", // graphql ships CJS-only; alias the explicit sub-path to the package // root so Vite resolves it through the exports field (ESM build). "graphql/language/printer.js": "graphql" } }, optimizeDeps: { // Without this, on first run, Vite throws: `The file does not exist at // "{project path}/web/node_modules/.cache/sb-vite/deps/DocsRenderer-NNNQARDV-DEXCJJZJ.js?v=c640a8fa" // which is in the optimize deps directory.` // This refers to @storybook/addon-docs, which is included as part of @storybook/addon-essentials. // the docs addon then includes itself here: https://github.com/storybookjs/storybook/blob/a496ec48c708eed753a5251d55fa07947a869e62/code/addons/docs/src/preset.ts#L198C3-L198C27 // which I believe gets included by the builder here: https://github.com/storybookjs/storybook/blob/a496ec48c708eed753a5251d55fa07947a869e62/code/builders/builder-vite/src/optimizeDeps.ts#L117 // TODO: Figure out why this error is being thrown so that this can be removed. // // Exclude storybook-framework-cedarjs from pre-bundling. Its // MockProviders module has a static import of the // ~__CEDAR__USER_ROUTES_FOR_MOCK alias, which esbuild would try to // resolve during pre-bundling. That leads esbuild into user Cell files // which have no default export (Cedar's Cell transform doesn't run // during esbuild dep scan), causing pre-bundling to fail entirely. // When excluded, Vite serves the package directly through its normal // transform pipeline, which does run the Cell plugin correctly. exclude: ["@storybook/addon-docs", "storybook-framework-cedarjs"], // Force pre-bundling of CJS-only packages that are only reachable through // storybook-framework-cedarjs (excluded above). Without this, Vite serves // them via ?import interop, which can't detect named exports in CJS files. // // Also pre-bundle the Apollo .cjs sub-paths that @cedarjs/web imports. // resolve.alias redirects them in the transform pipeline, but esbuild // (dep optimizer) ignores resolve.alias. Without this, Vite discovers // them on the first page load and triggers a mid-session reload, which // tears down the StorybookProvider's GraphQLHooksProvider context and // breaks nested-Cell stories. These are structural deps of @cedarjs/web // itself, so this list is valid for all Cedar apps, not just the test // project. include: [ "rehackt", "@apollo/client/cache/cache.cjs", "@apollo/client/core/core.cjs", "@apollo/client/link/context/context.cjs", "@apollo/client/link/core/core.cjs", "@apollo/client/link/persisted-queries/persisted-queries.cjs", "@apollo/client/react/hooks/hooks.cjs", "@apollo/client/react/react.cjs", "@apollo/client/utilities/utilities.cjs", "graphql/language/printer.js", // Pre-bundle node-polyfill shims so Vite doesn't discover them // mid-session when storybook-framework-cedarjs (which uses // nodePolyfills()) is first rendered. Without this, each shim // triggers a separate optimized-deps reload that tears down // GraphQLHooksProvider and breaks nested-Cell stories. "vite-plugin-node-polyfills/shims/buffer", "vite-plugin-node-polyfills/shims/global", "vite-plugin-node-polyfills/shims/process", // Pre-bundle @cedarjs/testing and graphql-tag so they don't cause // mid-session reloads when StorybookProvider loads mock files. "@cedarjs/testing/web", // Sub-path explicitly imported by @cedarjs/router mock infrastructure "@cedarjs/testing/web/MockRouter.js", "@cedarjs/testing/auth", "graphql-tag", // Pre-bundle @cedarjs/web so there is only one instance of // GraphQLHooksProvider. Without this, the pre-bundled dep graph and // the Vite transform pipeline can each produce their own copy of // the module, so RedwoodApolloProvider sets context in one copy // while NamedCell reads from the other, causing the // "You must register a useQuery hook via the GraphQLHooksProvider" // error when a Cell is nested inside another story. "@cedarjs/web", "@cedarjs/web/apollo" ], esbuildOptions: { plugins: [ { // Cedar's Cell transform (a Vite plugin) injects `export default // createCell(...)` at transform time, which doesn't run during // esbuild's dep scan. Without a default export, esbuild crashes // the scan with "No matching export ... for import 'default'", // causing Vite to skip pre-bundling entirely. // // We must NOT return the real Cell file contents here. If esbuild // follows the Cell's imports (e.g. createCell from @cedarjs/web), // it pulls GraphQLHooksProvider into its own pre-bundled chunk // separately from the @cedarjs/web chunk. That produces two // distinct GraphQLHooksContext instances, so the context set by // RedwoodApolloProvider is invisible to the Cell's useQuery, // causing "You must register a useQuery hook via the // GraphQLHooksProvider". // // Instead we synthesize a stub module: we scan the source for // exported names and re-export them as empty stubs, plus add a // default export. esbuild treats the Cell as a leaf (no real // imports to follow), keeping @cedarjs/web in a single chunk. // The real Cell transform runs later via Vite's normal pipeline. name: "cedar-cell-stub", setup(build) { build.onLoad({ filter: /Cell\.[jt]sx?$/ }, async (args) => { if (args.path.includes("node_modules")) { return void 0; } const src = await fs.promises.readFile(args.path, "utf-8"); const exportedNames = /* @__PURE__ */ new Set(); for (const m of src.matchAll( /^export\s+(?:const|let|var|function|class)\s+(\w+)/gm )) { exportedNames.add(m[1]); } for (const m of src.matchAll(/^export\s*\{([^}]+)\}/gm)) { for (const part of m[1].split(",")) { const name = (part.split(/\bas\b/).pop() ?? "").trim(); if (name) { exportedNames.add(name); } } } const namedStubs = [...exportedNames].filter((n) => n !== "default").map((n) => `export const ${n} = undefined`).join("\n"); return { contents: `${namedStubs} export default {}`, loader: "js" }; }); } } ] } } } ); }; export { core, previewAnnotations, viteFinal };