From c1931c11feb31c06bac2f179cb10e4c7f6def956 Mon Sep 17 00:00:00 2001 From: Alistair Smith Date: Wed, 24 Sep 2025 20:24:06 -0700 Subject: [PATCH] lots of Framework interface documentation --- packages/bun-types/app.d.ts | 1150 +++++++++++++++++++++++++++++++---- 1 file changed, 1017 insertions(+), 133 deletions(-) diff --git a/packages/bun-types/app.d.ts b/packages/bun-types/app.d.ts index 9a5827d439..1e3dbff191 100644 --- a/packages/bun-types/app.d.ts +++ b/packages/bun-types/app.d.ts @@ -13,11 +13,37 @@ declare module "bun:app" { interface Config { /** - * The framework definition + * Specifies the framework configuration for this Bake application. + * + * This is THE CORE PROPERTY that determines how your app is structured and bundled. + * It can be: + * - A Framework object with full configuration (for advanced customization) + * - A string package name prefixed with "bun-framework-" (e.g., "bun-framework-react") + * - Any npm package name that exports a Framework configuration object + * + * When a string is provided: + * 1. Bun first attempts to resolve "bun-framework-{name}" + * 2. If that fails, it tries resolving "{name}" directly + * 3. The resolved module MUST export a Framework object as default export + * 4. The framework module is loaded and evaluated synchronously at config time + * + * The framework controls: + * - File system routing behavior (how files map to routes) + * - Server Components configuration + * - React Fast Refresh settings + * - Built-in module replacements + * - Default bundler options * * @example * ```ts + * // Using a pre-built framework * export default {app: {framework: "bun-framework-react"}}; + * + * // Using a custom framework object + * export default {app: {framework: customFrameworkConfig}}; + * + * // Using a custom npm package + * export default {app: {framework: "my-custom-bake-framework"}}; * ``` */ framework: FrameworkDefinitionLike; @@ -25,21 +51,102 @@ declare module "bun:app" { // Note: To contribute to 'bun-framework-react', it can be run from this file: // https://github.com/oven-sh/bun/blob/main/src/bake/bun-framework-react/index.ts /** - * A subset of the options from Bun.build can be configured. While the framework - * can also set these options, this property overrides and merges with them. + * Overrides and extends the bundler options provided by the framework. * - * @default {} + * This property allows fine-tuning of the bundler behavior beyond what the framework sets. + * Options specified here OVERRIDE and MERGE with framework defaults using the following rules: + * - Primitive values (booleans, strings) override framework values + * - Objects (define, loader) merge with framework values + * - Arrays (conditions, drop) concatenate with framework values + * + * You can configure different options for: + * - Top-level: applies to both client and server builds + * - `client`: only affects browser bundle generation + * - `server`: only affects server-side bundle generation + * - `ssr`: only affects SSR graph when separateSSRGraph is enabled + * + * Hierarchy: client/server/ssr options override top-level options + * + * @default {} (uses framework defaults) + * + * @example + * ```ts + * bundlerOptions: { + * // Applied to all builds + * define: { "process.env.API_URL": "\"https://api.example.com\"" }, + * + * // Client-only minification + * client: { minify: true }, + * + * // Server-only conditions + * server: { conditions: ["node", "production"] } + * } + * ``` */ bundlerOptions?: BundlerOptions | undefined; /** - * These plugins are applied after `framework.plugins` + * Additional Bun build plugins to apply during bundling. + * + * These plugins are executed AFTER framework-provided plugins, allowing you to: + * - Override framework plugin behavior + * - Add custom transformations + * - Implement project-specific build logic + * + * Plugins are executed in the following order: + * 1. Framework plugins (framework.plugins) + * 2. User plugins (this property) + * + * Each plugin must have: + * - A unique `name` property (non-empty string) + * - A `setup()` function that configures the plugin behavior + * + * Plugin setup can be async - Bun will wait for all plugin promises to resolve + * before starting the build process. + * + * @default undefined (no additional plugins) + * + * @example + * ```ts + * plugins: [{ + * name: "my-custom-plugin", + * setup(build) { + * build.onLoad({ filter: /\.svg$/ }, async (args) => { + * // Custom SVG handling + * }); + * } + * }] + * ``` */ plugins?: Bun.BunPlugin[] | undefined; } /** - * Bake only allows a subset of options from `Bun.build` + * Subset of Bun.build options available for Bake configuration. + * + * Only specific build options are exposed because Bake manages many aspects + * of the build process internally for optimal hot-reloading and SSR support. + * + * Available options: + * - `conditions`: Package.json export conditions for module resolution + * - `define`: Global constant replacements at compile time + * - `loader`: File extension to loader mappings + * - `ignoreDCEAnnotations`: Disable dead code elimination annotations + * - `drop`: Remove specific code patterns (console.*, debugger) + * + * Explicitly NOT available (and why): + * - `format`: Locked to "internal_bake_dev" in dev, "esm" in production + * - `entrypoints/outfile/outdir`: Managed by Bake's routing system + * - `sourcemap`: Always "external" in dev for debugging, configurable in production + * - `minifyIdentifiers`: Not allowed in dev (breaks generated code) + * - `publicPath`: Set via framework configuration + * - `emitDCEAnnotations`: Not useful for app bundles + * - `banner/footer`: Not compatible with multi-file builds + * - `external`: Would break module imports + * - `plugins`: Use framework.plugins or top-level plugins instead + * + * @internal Implementation note: These restrictions ensure consistent behavior + * across dev/prod and client/server boundaries */ type BuildConfigSubset = Pick< Bun.BuildConfig, @@ -59,33 +166,211 @@ declare module "bun:app" { >; type BundlerOptions = BuildConfigSubset & { - /** Customize the build options of the client-side build */ + /** + * Client-specific bundler configuration. + * + * Controls how JavaScript/TypeScript is bundled for browser execution. + * These settings OVERRIDE the top-level bundler options for client builds. + * + * Client bundles: + * - Target browser environments + * - Include HMR runtime in development + * - Support React Fast Refresh when configured + * - Generate code splitting chunks in production + * - Use "browser" condition in package.json exports + * - Minify by default in production + * + * Common use cases: + * - Adding browser-specific polyfills via `define` + * - Removing server-only code via `drop` + * - Setting browser-specific module conditions + * + * @example + * ```ts + * client: { + * define: { "process.env.IS_CLIENT": "true" }, + * drop: ["console"], // Remove console.logs in client + * } + * ``` + */ client?: BuildConfigSubset; - /** Customize the build options of the server build */ + + /** + * Server-specific bundler configuration. + * + * Controls how code is bundled for server-side execution (SSR and API routes). + * These settings OVERRIDE the top-level bundler options for server builds. + * + * Server bundles: + * - Target Bun runtime (Node.js compatible) + * - Include "node" and "bun" conditions + * - Support server components when configured + * - Never include HMR runtime + * - Can access file system and Node.js APIs + * - Include "react-server" condition when server components enabled + * + * Common use cases: + * - Setting server-only environment variables + * - Including Node.js-specific modules + * - Configuring database connection strings + * + * @example + * ```ts + * server: { + * define: { "process.env.DATABASE_URL": '"postgresql://..."' }, + * conditions: ["node", "production"] + * } + * ``` + */ server?: BuildConfigSubset; - /** Customize the build options of the separated SSR graph */ + + /** + * SSR-specific bundler configuration (Server-Side Rendering). + * + * ONLY USED when `serverComponents.separateSSRGraph` is true. + * Controls bundling for the separate SSR module graph. + * + * When separateSSRGraph is enabled: + * - SSR uses a different React version than server components + * - Client components are bundled separately for SSR + * - Allows server components and SSR to coexist with different React versions + * - SSR graph does NOT include "react-server" condition + * + * This separation enables: + * - Server components using React's async components + * - SSR using standard React for client component rendering + * - Both running in the same process without conflicts + * + * If separateSSRGraph is false, these options are IGNORED. + * + * @example + * ```ts + * ssr: { + * conditions: ["node"], // No "react-server" for SSR + * define: { "process.env.IS_SSR": "true" } + * } + * ``` + */ ssr?: BuildConfigSubset; }; /** - * A "Framework" in our eyes is simply a set of bundler options that a - * framework author would set in order to integrate framework code with the - * application. Many of the configuration options are paths, which are - * resolved as import specifiers. + * Framework configuration object that defines how Bake processes your application. + * + * A Framework is a set of conventions and configurations that tell Bake: + * - How to discover and route files (file system routing) + * - How to handle server/client boundaries (server components) + * - How to enable hot reloading features (React Fast Refresh) + * - What bundler settings to apply + * + * Framework authors use this to create reusable configurations that work + * with specific UI libraries (React, Vue, Svelte, etc.) or implement + * custom routing conventions. + * + * All path properties are resolved as import specifiers, meaning they can be: + * - Relative paths ("./my-module") + * - Node modules ("react-refresh/runtime") + * - Built-in modules (defined in builtInModules) + * + * Resolution happens in this order: + * 1. Check builtInModules for the exact path + * 2. Resolve as a normal import from the project root + * + * @example + * ```ts + * const myFramework: Framework = { + * fileSystemRouterTypes: [{ + * root: "src/pages", + * style: "nextjs-pages", + * // ... other routing config + * }], + * reactFastRefresh: { importSource: "react-refresh/runtime" }, + * serverComponents: { ... } + * } + * ``` */ interface Framework { /** - * Customize the bundler options. Plugins in this array are merged with - * any plugins the user has. - * @default {} + * Default bundler options provided by the framework. + * + * These options serve as BASE DEFAULTS that users can override via + * their own bundlerOptions in the app config. + * + * Merging behavior: + * - User's top-level bundlerOptions OVERRIDE framework bundlerOptions + * - User's client/server/ssr options OVERRIDE framework's respective options + * - Objects (define, loader) are MERGED (user values win) + * - Arrays (conditions, drop) are CONCATENATED + * - Primitives are REPLACED + * + * Framework authors should set sensible defaults here that make their + * framework "just work" without requiring user configuration. + * + * Common framework defaults: + * - React frameworks: set JSX runtime to automatic + * - Node frameworks: add "node" condition + * - SSR frameworks: configure server and client differently + * + * @default {} (no framework-specific bundler options) + * + * @example + * ```ts + * bundlerOptions: { + * // React framework defaults + * define: { "process.env.NODE_ENV": '"development"' }, + * client: { + * conditions: ["browser"], + * }, + * server: { + * conditions: ["node"], + * } + * } + * ``` */ bundlerOptions?: BundlerOptions | undefined; /** - * The translation of files to routes is unopinionated and left to framework - * authors. This interface allows most flexibility between the already - * established conventions while allowing new ideas to be explored too. - * @default [] + * Defines how the file system maps to application routes. + * + * This is THE CORE of how Bake discovers and bundles your application pages. + * Each entry defines a separate routing root with its own conventions. + * + * Multiple router types can coexist: + * - Pages router at "/pages" with Next.js conventions + * - API routes at "/api" with different conventions + * - Admin panel at "/admin" with custom routing + * + * Each router type specifies: + * - Where to look for files (root directory) + * - How to interpret file names as routes (style) + * - What files to bundle for client/server + * - What to ignore + * + * The array is processed in order, with first match wins for overlapping routes. + * + * Empty array means no file-system routing (you handle routing manually). + * + * @default [] (no file-system routing) + * + * @example + * ```ts + * fileSystemRouterTypes: [ + * { + * root: 'src/pages', + * style: 'nextjs-pages', + * prefix: '/', + * serverEntryPoint: './server.tsx', + * clientEntryPoint: './client.tsx' + * }, + * { + * root: 'src/api', + * style: 'nextjs-app-routes', + * prefix: '/api', + * serverEntryPoint: './api-server.tsx', + * clientEntryPoint: null // API routes are server-only + * } + * ] + * ``` */ fileSystemRouterTypes?: FrameworkFileSystemRouterType[]; @@ -112,20 +397,131 @@ declare module "bun:app" { // builtInModules?: BuiltInModule[] | undefined; /** - * Bun offers integration for React's Server Components with an interface - * that is generic enough to adapt to any framework. - * @default undefined + * Enables React Server Components (RSC) or similar server/client component boundaries. + * + * When enabled, Bake processes "use client" and "use server" directives to create + * boundaries between server and client code. This enables: + * - Components that only run on the server (async components, direct DB access) + * - Automatic code splitting at component boundaries + * - Streaming server rendering with Suspense + * - Server Actions (server-side functions callable from client) + * + * The bundler creates THREE distinct module graphs: + * 1. Server graph: Contains server components and server-only code + * 2. Client graph: Contains client components and browser code + * 3. SSR graph (optional): Separate graph for SSR when separateSSRGraph=true + * + * Files with "use client" become client component boundaries: + * - Bundled separately for the browser + * - On server, replaced with reference stubs that call registerClientReference + * - Props are serialized when crossing the boundary + * + * Files with "use server" become server component boundaries: + * - Only run on the server + * - Can be async and use server-only APIs + * - Results are streamed to the client + * + * undefined means server components are DISABLED. + * + * @default undefined (server components disabled) + * + * @example + * ```ts + * serverComponents: { + * separateSSRGraph: true, // Use different React for SSR vs RSC + * serverRuntimeImportSource: 'react-server-dom/server', + * serverRegisterClientReferenceExport: 'registerClientReference' + * } + * ``` */ serverComponents?: ServerComponentsOptions | undefined; /** - * While it is unlikely that Fast Refresh is useful outside of React, it can - * be enabled regardless. - * @default false + * Enables React Fast Refresh for hot module replacement with state preservation. + * + * Fast Refresh provides a superior development experience by: + * - Preserving component state during code changes + * - Only re-rendering changed components + * - Recovering from runtime errors gracefully + * - Providing clear error boundaries + * + * Three ways to configure: + * - `true`: Use default React Fast Refresh ("react-refresh/runtime") + * - `false` or undefined: Disable Fast Refresh + * - Object: Customize the runtime import source + * + * How it works: + * 1. In development, Bake injects refresh registration calls + * 2. Every React component is registered with a unique ID + * 3. On hot update, components are patched in-place + * 4. State and refs are preserved across updates + * + * Only functions starting with uppercase letters are registered + * (React component convention). + * + * While designed for React, the transform could theoretically work with + * other frameworks that follow similar component conventions. + * + * @default false (Fast Refresh disabled) + * + * @example + * ```ts + * // Use default React Fast Refresh + * reactFastRefresh: true + * + * // Use custom Fast Refresh implementation + * reactFastRefresh: { + * importSource: '@my/custom-refresh-runtime' + * } + * + * // Disable Fast Refresh + * reactFastRefresh: false + * ``` */ reactFastRefresh?: boolean | ReactFastRefreshOptions | undefined; - /** Framework bundler plugins load before the user-provided ones. */ + /** + * Framework-provided bundler plugins. + * + * These plugins are executed BEFORE user plugins, establishing the base + * transformation pipeline for the framework. + * + * Execution order: + * 1. Framework plugins (this property) - run first + * 2. User plugins (from app config) - run second + * + * This order ensures: + * - Framework establishes core behavior + * - Users can override or extend framework behavior + * - Framework plugins can't accidentally break user customizations + * + * Common framework plugin uses: + * - Transform framework-specific file types + * - Inject framework runtime code + * - Handle special imports (e.g., "virtual:framework") + * - Set up framework-specific optimizations + * + * Each plugin must have: + * - Unique `name` property + * - `setup()` function + * + * Plugins are initialized synchronously during config parsing. + * Async operations in setup() will block the build start. + * + * @default undefined (no framework plugins) + * + * @example + * ```ts + * plugins: [{ + * name: 'framework-mdx', + * setup(build) { + * build.onLoad({ filter: /\.mdx$/ }, async (args) => { + * // Transform MDX to JSX + * }); + * } + * }] + * ``` + */ plugins?: Bun.BunPlugin[] | undefined; // /** @@ -150,72 +546,105 @@ declare module "bun:app" { */ interface ServerComponentsOptions { /** - * If you are unsure what to set this to for a custom server components - * framework, choose 'false'. + * Controls whether SSR uses a separate module graph from server components. * - * When set `true`, bundling "use client" components for SSR will be - * placed in a separate bundling graph without the `react-server` - * condition. All imports that stem from here get re-bundled for this - * second graph, regardless if they actually differ via this condition. + * THIS IS A CRITICAL DECISION that affects your entire application architecture. * - * The built in framework config for React enables this flag so that - * server components and client components utilize their own versions of - * React, despite running in the same process. This facilitates different - * aspects of the server and client react runtimes, such as `async` - * components only being available on the server. + * When `true` (React's approach): + * - THREE separate module graphs: Server, Client, and SSR + * - Server components use "react-server" condition (async React) + * - SSR uses standard React (no "react-server" condition) + * - Client components are bundled TWICE (once for client, once for SSR) + * - Allows React 19+ async components on server, standard React for SSR + * - More complex but enables full React Server Components features + * - Higher memory usage (multiple React versions in memory) * - * To cross from the server graph to the SSR graph, use the bun_bake_graph - * import attribute: + * When `false` (simpler approach): + * - TWO module graphs: Server+SSR combined, Client separate + * - Both server components and SSR use the same React + * - Client components bundled once, used for both client and SSR + * - Cannot use React async components (they require separation) + * - Simpler mental model and less memory usage + * - Works for basic "use client" boundaries without full RSC * - * import * as ReactDOM from 'react-dom/server' with { bunBakeGraph: 'ssr' }; + * To cross between graphs when true, use import attributes: + * ```ts + * import * as ReactDOM from 'react-dom/server' with { bunBakeGraph: 'ssr' }; + * ``` * - * Since these models are so subtley different, there is no default value - * provided for this. + * NO DEFAULT PROVIDED - you must explicitly choose based on your needs. + * If unsure, choose `false` for simplicity unless you need React async components. */ separateSSRGraph: boolean; - /** Server components runtime for the server */ + /** + * Import source for the server components runtime. + * + * This module provides the functions that handle client/server boundaries: + * - `registerClientReference`: Marks client components on the server + * - `registerServerReference`: Marks server actions + * - Component serialization/deserialization logic + * - Streaming protocols for RSC payloads + * + * The module MUST export the functions specified in: + * - `serverRegisterClientReferenceExport` (default: "registerClientReference") + * - (Future) `serverRegisterServerReferenceExport` for server actions + * + * Common values: + * - React: "react-server-dom-webpack/server" or "react-server-dom-bun/server" + * - Custom: Your own RSC runtime implementation + * + * This import is resolved at build time and bundled into the server. + * Resolution follows normal module resolution rules (node_modules, relative paths). + * + * @example "react-server-dom-webpack/server" + * @example "./my-rsc-runtime" + * @example "@my-org/rsc-runtime" + */ serverRuntimeImportSource: string; /** - * When server code imports client code, a stub module is generated, where - * every export calls this export from `serverRuntimeImportSource`. This - * is used to implement client components on the server. + * Name of the export from serverRuntimeImportSource that registers client components. * - * When separateSSRGraph is enabled, the call looks like: + * This function is called in generated stub modules when server code imports client code. + * Every "use client" component gets wrapped with this function on the server. * - * export const ClientComp = registerClientReference( - * // A function which may be passed through, it throws an error - * function () { throw new Error('Cannot call client-component on the server') }, + * The function signature differs based on `separateSSRGraph`: * - * // The file path. In production, these use hashed strings for - * // compactness and code privacy. - * "src/components/Client.tsx", + * When `separateSSRGraph: true` (opaque references): + * ```ts + * export const Button = registerClientReference( + * function() { throw new Error('Cannot call client component on server') }, + * "src/Button.tsx", // Source file ID (minified in prod: "a1") + * "Button" // Export name (minified in prod: "b") + * ); + * ``` + * The last two params are OPAQUE STRINGS looked up in the manifest. * - * // The instance id. This is not guaranteed to match the export - * // name the user has given. - * "ClientComp", - * ); + * When `separateSSRGraph: false` (direct references): + * ```ts + * export const Button = registerClientReference( + * function() { ... }, + * "/_bun/client-123.js", // Actual client bundle URL + * "Button" // Actual export name in bundle + * ); + * ``` + * The last two params are DIRECT REFERENCES for client loading. * - * When separateSSRGraph is disabled, the call looks like: + * The difference is crucial: + * - With SSR graph: Abstract references requiring manifest lookup + * - Without SSR graph: Concrete URLs for immediate loading * - * export const ClientComp = registerClientReference( - * function () { ... original user implementation here ... }, + * Your runtime must implement this function to handle the boundary. * - * // The file path of the client-side file to import in the browser. - * "/_bun/d41d8cd0.js", - * - * // The export within the client-side file to load. This is - * // not guaranteed to match the export name the user has given. - * "ClientComp", - * ); - * - * While subtle, the parameters in `separateSSRGraph` mode are opaque - * strings that have to be looked up in the server manifest. While when - * there isn't a separate SSR graph, the two parameters are the actual - * URLs to load on the client; The manifest is not required for anything. - * - * Additionally, the bundler will assemble a component manifest to be used - * during rendering. * @default "registerClientReference" + * + * @example + * ```ts + * // React's implementation + * serverRegisterClientReferenceExport: "registerClientReference" + * + * // Custom implementation + * serverRegisterClientReferenceExport: "createClientBoundary" + * ``` */ serverRegisterClientReferenceExport?: string | undefined; // /** @@ -257,68 +686,346 @@ declare module "bun:app" { /** This API is similar, but unrelated to `Bun.FileSystemRouter` */ interface FrameworkFileSystemRouterType { /** - * Relative to project root. For example: `src/pages`. + * Root directory to scan for route files, relative to project root. + * + * This is WHERE Bake looks for files that become routes. + * The path is relative to your project root (where package.json lives). + * + * Bake recursively scans this directory for files matching: + * - Extensions specified in `extensions` property + * - Style conventions specified in `style` property + * - Excluding directories in `ignoreDirs` + * - Excluding underscored files if `ignoreUnderscores` is true + * + * The directory structure maps to URL structure based on `style`. + * For example, with "nextjs-pages" style: + * - `src/pages/index.tsx` → `/` + * - `src/pages/about.tsx` → `/about` + * - `src/pages/blog/[slug].tsx` → `/blog/:slug` + * + * This directory MUST exist at build time or Bake will error. + * + * @example "src/pages" + * @example "app" + * @example "routes" */ root: string; /** - * The prefix to serve this directory on. + * URL prefix for all routes from this router. + * + * This prepends a path segment to all discovered routes. + * Useful for mounting route groups at specific paths. + * + * How it works: + * - Route: `pages/users.tsx` + * - Without prefix: `/users` + * - With prefix "/api": `/api/users` + * - With prefix "/v2": `/v2/users` + * + * Rules: + * - Must start with "/" + * - No trailing slash (use "/api", not "/api/") + * - Empty string treated as "/" + * - Prefixes stack when using multiple routers + * + * Common patterns: + * - API versioning: prefix: "/api/v1" + * - Admin routes: prefix: "/admin" + * - Localization: prefix: "/en-US" + * * @default "/" + * + * @example + * ```ts + * // Mount API routes at /api + * { root: "src/api", prefix: "/api" } + * + * // Version your API + * { root: "src/api/v2", prefix: "/api/v2" } + * ``` */ prefix?: string | undefined; /** - * This file is the entrypoint of the server application. This module must - * `export default` a fetch function, which takes a request and the - * bundled route module, and returns a response. See `ServerEntryPoint` + * Path to the server-side rendering entry point. * - * When `serverComponents` is configured, this can access the component - * manifest using the special 'bun:app/server' import: + * This file orchestrates HOW routes are rendered on the server. + * It must export a ServerEntryPoint object with a `render` function. * - * import { serverManifest } from 'bun:app/server' + * The render function receives: + * 1. The incoming Request + * 2. RouteMetadata (matched route module, params, layouts) + * 3. Optional AsyncLocalStorage for request context + * + * And must return a Response (usually HTML). + * + * This is where you: + * - Call your framework's SSR function (e.g., ReactDOMServer.renderToString) + * - Inject the rendered HTML into an HTML template + * - Add