start extracting parts of the framework out

This commit is contained in:
Alistair Smith
2025-09-08 13:56:42 -07:00
parent 5f8393cc99
commit f55e320f41
18 changed files with 268 additions and 112 deletions

View File

@@ -0,0 +1,18 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"devDependencies": {
"@types/react": "^19.1.12",
"@types/react-dom": "^19.1.9",
},
},
},
"packages": {
"@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="],
"@types/react-dom": ["@types/react-dom@19.1.9", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
}
}

View File

@@ -0,0 +1,7 @@
{
"type": "module",
"devDependencies": {
"@types/react": "^19.1.12",
"@types/react-dom": "^19.1.9"
}
}

View File

@@ -0,0 +1,3 @@
export function Link(props: React.ComponentProps<"a">) {
return <a {...props} />;
}

View File

@@ -2,7 +2,7 @@
// Components integration. It is designed as a minimal base to build RSC
// applications on, and to showcase what features that Bake offers.
/// <reference lib="dom" />
import { onServerSideReload } from "bun:bake/client";
import { onServerSideReload } from "bun:app/client";
import * as React from "react";
import { flushSync } from "react-dom";
import { hydrateRoot } from "react-dom/client";
@@ -11,32 +11,65 @@ import { createFromReadableStream } from "react-server-dom-bun/client.browser";
const te = new TextEncoder();
const td = new TextDecoder();
const windowDebugKey = "$bake";
interface WindowDebugObject {
navigate: (href: string, cacheId?: number) => Promise<void>;
onServerSideReload: (cb: () => void | Promise<void>) => Promise<void>;
readonly currentCssList: string[] | undefined;
}
type WindowWithBakeDebugObject = { [key in typeof windowDebugKey]: WindowDebugObject };
declare global {
interface Window extends WindowWithBakeDebugObject {}
}
// It is the framework's responsibility to ensure that client-side navigation
// loads CSS files. The implementation here loads all CSS files as <link> tags,
// and uses the ".disabled" property to enable/disable them.
const cssFiles = new Map<string, { promise: Promise<void> | null; link: HTMLLinkElement }>();
let currentCssList: string[] | undefined = undefined;
declare global {
interface Window {
__bun_f:
| Array<string | Uint8Array<ArrayBufferLike>>
| {
// it's still an array, but we overwrite the push method to not return
// a number and instead use the `handleChunk` function
push: (chunk: string | Uint8Array<ArrayBufferLike>) => void;
forEach: (callback: (chunk: string | Uint8Array<ArrayBufferLike>) => void) => void;
};
}
}
// The initial RSC payload is put into inline <script> tags that follow the pattern
// `(self.__bun_f ??= []).push(chunk)`, which is converted into a ReadableStream
// here for React hydration. Since inline scripts are executed immediately, and
// this file is loaded asynchronously, the `__bun_f` becomes a clever way to
// stream the arbitrary data while HTML is loading. In a static build, this is
// setup as an array with one string.
let rscPayload: any = createFromReadableStream(
let rscPayload = createFromReadableStream(
new ReadableStream({
start(controller) {
let handleChunk = chunk =>
const handleChunk = (chunk: string | Uint8Array<ArrayBufferLike>) =>
typeof chunk === "string" //
? controller.enqueue(te.encode(chunk))
: controller.enqueue(chunk);
(self.__bun_f ||= []).forEach((__bun_f.push = handleChunk));
const bunF = (self.__bun_f ??= []);
bunF.push = handleChunk;
bunF.forEach(handleChunk);
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
controller.close();
});
document.addEventListener(
"DOMContentLoaded",
() => {
controller.close();
},
{ once: true },
);
} else {
controller.close();
}
@@ -49,7 +82,7 @@ let rscPayload: any = createFromReadableStream(
// This is the same logic that happens on the server, except there is also a
// hook to update the promise when the client navigates. The `Root` component
// also updates CSS files when navigating between routes.
let setPage;
let setPage: React.Dispatch<React.SetStateAction<any>>;
let abortOnRender: AbortController | undefined;
const Root = () => {
setPage = React.useState(rscPayload)[1];
@@ -126,11 +159,12 @@ let lastNavigationId = 0;
let lastNavigationController: AbortController;
// Client side navigation is implemented by updating the app's `useState` with a
// new RSC payload promise. Callers of `goto` are expected to manage history state.
// A navigation id is used
async function goto(href: string, cacheId?: number) {
// new RSC payload promise. Callers of `navigate` are expected to manage history
// state. A navigation id is used
async function navigate(href: string, cacheId?: number) {
const thisNavigationId = ++lastNavigationId;
const olderController = lastNavigationController;
lastNavigationController = new AbortController();
const signal = lastNavigationController.signal;
signal.addEventListener("abort", () => {
@@ -138,11 +172,12 @@ async function goto(href: string, cacheId?: number) {
});
// If the page is cached, use the cached promise instead of fetching it again.
const cached = cacheId && cachedPages.get(cacheId);
const cached = (cacheId !== undefined && cachedPages.get(cacheId)) || undefined;
if (cached) {
currentCssList = cached.css;
await ensureCssIsReady(currentCssList);
setPage?.((rscPayload = cached.element));
rscPayload = cached.element;
setPage(rscPayload);
if (olderController?.signal.aborted === false) abortOnRender = olderController;
return;
}
@@ -314,7 +349,7 @@ document.addEventListener("click", async (event, element = event.target as HTMLA
const href = url.href;
const newId = Date.now();
history.pushState(newId, "", href);
goto(href, newId);
navigate(href, newId);
return event.preventDefault();
}
@@ -330,7 +365,7 @@ window.addEventListener("popstate", event => {
if (typeof state !== "number") {
state = undefined;
}
goto(location.href, state);
navigate(location.href, state);
});
if (import.meta.env.DEV) {
@@ -339,12 +374,12 @@ if (import.meta.env.DEV) {
onServerSideReload(async () => {
const newId = Date.now();
history.replaceState(newId, "", location.href);
await goto(location.href, newId);
await navigate(location.href, newId);
});
// Expose a global in Development mode
(window as any).$bake = {
goto,
window[windowDebugKey] = {
navigate,
onServerSideReload,
get currentCssList() {
return currentCssList;

View File

@@ -1,10 +1,10 @@
import type { Bake } from "bun";
import { renderToHtml, renderToStaticHtml } from "bun-framework-react/ssr.tsx" with { bunBakeGraph: "ssr" };
import { serverManifest } from "bun:bake/server";
import { serverManifest } from "bun:app/server";
import type { AsyncLocalStorage } from "node:async_hooks";
import { PassThrough } from "node:stream";
import { renderToPipeableStream } from "react-server-dom-bun/server.node.unbundled.js";
import type { RequestContext } from "../hmr-runtime-server";
import type { RequestContext } from "../../../../src/bake/hmr-runtime-server.ts";
function assertReactComponent(Component: any) {
if (typeof Component !== "function") {
@@ -37,7 +37,7 @@ function getPage(meta: Bake.RouteMetadata & { request?: Request }, styles: reado
);
}
function component(mod: any, params: Record<string, string> | null, request?: Request) {
function component(mod: any, params: Record<string, string | string[]> | null, request?: Request) {
const Page = mod.default;
let props = {};
if (import.meta.env.DEV) assertReactComponent(Page);

View File

@@ -1,13 +1,13 @@
// This file is loaded in the SSR graph, meaning the `react-server` condition is
// no longer set. This means we can import client components, using `react-dom`
// to perform Server-side rendering (creating HTML) out of the RSC payload.
import { ssrManifest } from "bun:bake/server";
import { ssrManifest } from "bun:app/server";
import { EventEmitter } from "node:events";
import type { Readable } from "node:stream";
import * as React from "react";
import { renderToPipeableStream } from "react-dom/server.node";
import { createFromNodeStream, type Manifest } from "react-server-dom-bun/client.node.unbundled.js";
import type { MiniAbortSignal } from "./server";
import type { MiniAbortSignal } from "./server.tsx";
// Verify that React 19 is being used.
if (!React.use) {

View File

@@ -0,0 +1,11 @@
function isModifiedEvent(event: MouseEvent) {
return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
}
export function shouldProcessLinkClick(event: MouseEvent, target?: string) {
return (
event.button === 0 && // Ignore everything but left clicks
(!target || target === "_self") && // Let browser handle "target=_blank" etc.
!isModifiedEvent(event) // Ignore clicks with modifier keys
);
}

View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"lib": ["ESNext"],
"moduleResolution": "NodeNext",
"module": "NodeNext",
"target": "ESNext",
"strict": true,
"noEmit": true,
"useUnknownInCatchVariables": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": true,
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"isolatedDeclarations": true,
"declaration": true,
"jsx": "react-jsx"
}
}

View File

@@ -0,0 +1,4 @@
{
"name": "bun-react",
"type": "module"
}

View File

@@ -0,0 +1,23 @@
{
"compilerOptions": {
"lib": ["ESNext"],
"moduleResolution": "NodeNext",
"module": "NodeNext",
"target": "ESNext",
"strict": true,
"noEmit": true,
"useUnknownInCatchVariables": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": true,
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"jsx": "react-jsx",
"isolatedDeclarations": true
}
}

View File

@@ -23,6 +23,7 @@
/// <reference path="./experimental.d.ts" />
/// <reference path="./sql.d.ts" />
/// <reference path="./security.d.ts" />
/// <reference path="./rendering.d.ts" />
/// <reference path="./bun.ns.d.ts" />

View File

@@ -7,7 +7,7 @@
declare module "bun" {
type Awaitable<T> = T | Promise<T>;
declare namespace Bake {
namespace Bake {
interface Options {
/**
* Bun provides built-in support for using React as a framework by passing
@@ -19,6 +19,7 @@ declare module "bun" {
* ```
*/
framework: Framework | "react";
// 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
/**
@@ -28,13 +29,16 @@ declare module "bun" {
* @default {}
*/
bundlerOptions?: BundlerOptions | undefined;
/**
* These plugins are applied after `framework.plugins`
*/
plugins?: BunPlugin[] | undefined;
}
/** Bake only allows a subset of options from `Bun.build` */
/**
* Bake only allows a subset of options from `Bun.build`
*/
type BuildConfigSubset = Pick<
BuildConfig,
"conditions" | "define" | "loader" | "ignoreDCEAnnotations" | "drop"
@@ -69,22 +73,22 @@ declare module "bun" {
*/
interface Framework {
/**
* Customize the bundler options. Plugins in this array are merged
* with any plugins the user has.
* Customize the bundler options. Plugins in this array are merged with
* any plugins the user has.
* @default {}
*/
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.
* 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 []
*/
fileSystemRouterTypes?: FrameworkFileSystemRouterType[];
/**
* A list of directories that should be served statically. If the directory
* does not exist in the user's project, it is ignored.
* A list of directories that should be served statically. If the
* directory does not exist in the user's project, it is ignored.
*
* Example: 'public' or 'static'
*
@@ -103,8 +107,8 @@ declare module "bun" {
*/
builtInModules?: BuiltInModule[] | undefined;
/**
* Bun offers integration for React's Server Components with an
* interface that is generic enough to adapt to any framework.
* Bun offers integration for React's Server Components with an interface
* that is generic enough to adapt to any framework.
* @default undefined
*/
serverComponents?: ServerComponentsOptions | undefined;
@@ -115,7 +119,7 @@ declare module "bun" {
*/
reactFastRefresh?: boolean | ReactFastRefreshOptions | undefined;
/** Framework bundler plugins load before the user-provided ones. */
plugins?: BunPlugin[];
plugins?: BunPlugin[] | undefined;
// /**
// * Called after the list of routes is updated. This can be used to
@@ -129,11 +133,11 @@ declare module "bun" {
type BuiltInModule = { import: string; code: string } | { import: string; path: string };
/**
* A high-level overview of what server components means exists
* in the React Docs: https://react.dev/reference/rsc/server-components
* A high-level overview of what server components means exists in the React
* Docs: https://react.dev/reference/rsc/server-components
*
* When enabled, files with "use server" and "use client" directives will get
* special processing according to this object, in combination with the
* When enabled, files with "use server" and "use client" directives will
* get special processing according to this object, in combination with the
* framework-specified entry points for server rendering and browser
* interactivity.
*/
@@ -144,15 +148,14 @@ declare module "bun" {
*
* 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.
* condition. All imports that stem from here get re-bundled for this
* second graph, regardless if they actually differ via this condition.
*
* 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.
* 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.
*
* To cross from the server graph to the SSR graph, use the bun_bake_graph
* import attribute:
@@ -166,9 +169,9 @@ declare module "bun" {
/** Server components runtime for the server */
serverRuntimeImportSource: ImportSource;
/**
* 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.
* 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.
*
* When separateSSRGraph is enabled, the call looks like:
*
@@ -225,12 +228,12 @@ declare module "bun" {
/**
* This import has four exports, mirroring "react-refresh/runtime":
*
* `injectIntoGlobalHook(window): void`
* Called on first startup, before the user entrypoint.
* `injectIntoGlobalHook(window): void` Called on first startup, before
* the user entrypoint.
*
* `register(component, uniqueId: string): void`
* Called on every function that starts with an uppercase letter. These
* may or may not be components, but they are always functions.
* `register(component, uniqueId: string): void` Called on every function
* that starts with an uppercase letter. These may or may not be
* components, but they are always functions.
*
* `createSignatureFunctionForTransform(): ReactRefreshSignatureFunction`
* TODO: document. A passing no-op for this api is `return () => {}`
@@ -256,22 +259,24 @@ declare module "bun" {
*/
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
* 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`
*
* When `serverComponents` is configured, this can access the component
* manifest using the special 'bun:bake/server' import:
* manifest using the special 'bun:app/server' import:
*
* import { serverManifest } from 'bun:bake/server'
* import { serverManifest } from 'bun:app/server'
*/
serverEntryPoint: ImportSource<ServerEntryPoint>;
/**
* This file is the true entrypoint of the client application. If null,
* a client will not be bundled, and the route will not receive bundling
* for client-side interactivity.
* This file is the true entrypoint of the client application. If null, a
* client will not be bundled, and the route will not receive bundling for
* client-side interactivity.
*/
clientEntryPoint?: ImportSource<ClientEntryPoint> | undefined;
/**
* Do not traverse into directories and files that start with an `_`. Do
* not index pages that start with an `_`. Does not prevent stuff like
@@ -279,28 +284,33 @@ declare module "bun" {
* @default false
*/
ignoreUnderscores?: boolean;
/**
* @default ["node_modules", ".git"]
*/
ignoreDirs?: string[];
/**
* Extensions to match on.
* '*' - any extension
* Extensions to match on. '*' - any extension
* @default (set of all valid JavaScript/TypeScript extensions)
*/
extensions?: string[] | "*";
/**
* 'nextjs-app' builds routes out of directories with `page.tsx` and `layout.tsx`
* 'nextjs-pages' builds routes out of any `.tsx` file and layouts with `_layout.tsx`.
* 'nextjs-app' builds routes out of directories with `page.tsx` and
* `layout.tsx` 'nextjs-pages' builds routes out of any `.tsx` file and
* layouts with `_layout.tsx`.
*
* Eventually, an API will be added to add custom styles.
*/
style: "nextjs-pages" | "nextjs-app-ui" | "nextjs-app-routes" | CustomFileSystemRouterFunction;
/**
* If true, this will track route layouts and provide them as an array during SSR.
* @default false
*/
layouts?: boolean | undefined;
// /**
// * If true, layouts act as navigation endpoints. This can be used to
// * implement Remix.run's router design, where `hello._index` and `hello`
@@ -331,8 +341,8 @@ declare module "bun" {
};
/**
* Bun will call this function for every found file. This
* function classifies each file's role in the file system routing.
* Bun will call this function for every found file. This function
* classifies each file's role in the file system routing.
*/
type CustomFileSystemRouterFunction = (candidatePath: string) => CustomFileSystemRouterResult;
@@ -341,8 +351,8 @@ declare module "bun" {
| undefined
| null
/**
* Use this file as a route. Routes may nest, where a framework
* can use parent routes to implement layouts.
* Use this file as a route. Routes may nest, where a framework can use
* parent routes to implement layouts.
*/
| {
/**
@@ -356,8 +366,8 @@ declare module "bun" {
};
/**
* Will be resolved from the point of view of the framework user's project root
* Examples: `react-dom`, `./entry_point.tsx`, `/absolute/path.js`
* Will be resolved from the point of view of the framework user's project
* root Examples: `react-dom`, `./entry_point.tsx`, `/absolute/path.js`
*/
type ImportSource<T = unknown> = string;
@@ -366,8 +376,8 @@ declare module "bun" {
* Bun passes the route's module as an opaque argument `routeModule`. The
* framework implementation decides and enforces the shape of the module.
*
* A common pattern would be to enforce the object is
* `{ default: ReactComponent }`
* A common pattern would be to enforce the object is `{ default:
* ReactComponent }`
*/
render: (request: Request, routeMetadata: RouteMetadata) => Awaitable<Response>;
/**
@@ -376,8 +386,8 @@ declare module "bun" {
* not named `staticRender` as it is invoked during a dynamic build to
* allow deterministic routes to be prerendered.
*
* Note that `import.meta.env.STATIC` will be inlined to true during
* a static build.
* Note that `import.meta.env.STATIC` will be inlined to true during a
* static build.
*/
prerender?: (routeMetadata: RouteMetadata) => Awaitable<PrerenderResult | null>;
// TODO: prerenderWithoutProps (for partial prerendering)
@@ -397,11 +407,11 @@ declare module "bun" {
* "exhaustive" tells Bun that the list is complete. If it is not, a
* static site cannot be generated as it would otherwise be missing
* routes. A non-exhaustive list can speed up build times by only
* specifying a few important pages (such as 10 most recent), leaving
* the rest to be generated on-demand at runtime.
* specifying a few important pages (such as 10 most recent), leaving the
* rest to be generated on-demand at runtime.
*
* To stream results, `getParams` may return an async iterator, which
* Bun will start rendering as more parameters are provided:
* To stream results, `getParams` may return an async iterator, which Bun
* will start rendering as more parameters are provided:
*
* export async function* getParams(meta: Bake.ParamsMetadata) {
* yield { slug: await fetchSlug() };
@@ -410,9 +420,10 @@ declare module "bun" {
* }
*/
getParams?: (paramsMetadata: ParamsMetadata) => Awaitable<GetParamIterator>;
/**
* When a dynamic build uses static assets, Bun can map content types in the
* user's `Accept` header to the different static files.
* When a dynamic build uses static assets, Bun can map content types in
* the user's `Accept` header to the different static files.
*/
contentTypeToStaticFile?: Record<string, string>;
}
@@ -477,18 +488,24 @@ declare module "bun" {
* }
*/
readonly layouts: ReadonlyArray<any>;
/** Received route params. `null` if the route does not take params */
/**
* Received route params. `null` if the route does not take params
*/
readonly params: null | Record<string, string | string[]>;
/**
* A list of js files that the route will need to be interactive.
*/
readonly modules: ReadonlyArray<string>;
/**
* A list of js files that should be preloaded.
*
* <link rel="modulepreload" href="..." />
*/
readonly modulepreload: ReadonlyArray<string>;
/**
* A list of css files that the route will need to be styled.
*/
@@ -529,8 +546,10 @@ declare module "bun" {
}
}
/** Available in server-side files only. */
declare module "bun:bake/server" {
/**
* Available in server-side files only
*/
declare module "bun:app/server" {
// NOTE: The format of these manifests will likely be customizable in the future.
/**
@@ -541,6 +560,7 @@ declare module "bun:bake/server" {
* To perform SSR with client components, see `ssrManifest`
*/
declare const serverManifest: ServerManifest;
/**
* Entries in this manifest map from client-side files to their respective SSR
* bundles. They can be loaded by `await import()` or `require()`.
@@ -548,7 +568,7 @@ declare module "bun:bake/server" {
declare const ssrManifest: SSRManifest;
/** (insert teaser trailer) */
declare const actionManifest: never;
declare const actionManifest: null;
declare interface ServerManifest {
/**
@@ -566,12 +586,16 @@ declare module "bun:bake/server" {
* Correlates but is not required to be the filename
*/
id: string;
/**
* The `name` in ReactServerManifest
* Correlates but is not required to be the export name
*/
name: string;
/** Currently not implemented; always an empty array */
/**
* Currently not implemented; always an empty array
*/
chunks: [];
}
@@ -591,17 +615,17 @@ declare module "bun:bake/server" {
}
}
/** Available in client-side files. */
declare module "bun:bake/client" {
/**
* Available in client-side files.
*/
declare module "bun:app/client" {
/**
* Callback is invoked when server-side code is changed. This can be used to
* fetch a non-html version of the updated page to perform a faster reload. If
* not provided, the client will perform a hard reload.
*
* Only one callback can be set. This function overwrites the previous one.
* Only one callback can be set. Calling this function will overwrite the
* previous callback, if set.
*/
export function onServerSideReload(cb: () => void | Promise<void>): Promise<void>;
}
/** Available during development */
declare module "bun:bake/dev" {}

View File

@@ -88,7 +88,7 @@ declare module "react-server-dom-bun/client.browser" {
}
declare module "react-server-dom-bun/client.node.unbundled.js" {
import type { ReactClientManifest } from "bun:bake/server";
import type { ReactClientManifest } from "bun:app/server";
import type { Readable } from "node:stream";
export interface Manifest {
moduleMap: ReactClientManifest;
@@ -107,7 +107,7 @@ declare module "react-server-dom-bun/client.node.unbundled.js" {
}
declare module "react-server-dom-bun/server.node.unbundled.js" {
import type { ReactServerManifest } from "bun:bake/server";
import type { ReactServerManifest } from "bun:app/server";
import type { ReactElement } from "react";
export interface PipeableStream<T> {

View File

@@ -52,7 +52,7 @@ function parseV8OrIE(stack: string): Frame[] {
return stack
.split("\n")
.filter(line => !!line.match(CHROME_IE_STACK_REGEXP) && !line.includes("Bun HMR Runtime"))
.map(function (line) {
.map((line): Frame => {
let sanitizedLine = line
.replace(/^\s+/, "")
.replace(/\(eval code/g, "(")
@@ -71,12 +71,14 @@ function parseV8OrIE(stack: string): Frame[] {
let functionName = (loc && sanitizedLine) || undefined;
let fileName = ["eval", "<anonymous>"].indexOf(locationParts[0]) > -1 ? undefined : locationParts[0];
return {
const frame: Frame = {
fn: functionName || "unknown",
file: fileName,
line: 0 | locationParts[1],
col: 0 | locationParts[2],
} satisfies Frame;
};
return frame;
});
}

View File

@@ -22,9 +22,9 @@ import { type SourceMapURL, derefMapping } from "#stack-trace";
const registry = new Map<Id, HMRModule>();
const registrySourceMapIds = new Map<string, SourceMapURL>();
/** Server */
export const serverManifest = {};
export const serverManifest: Bun.App.ServerManifest = {};
/** Server */
export const ssrManifest = {};
export const ssrManifest: Bun.App.SSRManifest = {};
/** Client */
export let onServerSideReload: (() => Promise<void>) | null = null;
const eventHandlers: Record<HMREvent | string, HotEventHandler[] | undefined> = {};
@@ -170,19 +170,19 @@ export class HMRModule {
// not destructed.
accept(
arg1?: string | readonly string[] | HotAcceptFunction,
arg2?: HotAcceptFunction | HotArrayAcceptFunction | undefined,
specifiedOrAcceptFunctionOrFunctions?: string | readonly string[] | HotAcceptFunction,
acceptFunctionOrFunctions?: HotAcceptFunction | HotArrayAcceptFunction | undefined,
) {
if (arg2 == undefined) {
if (arg1 == undefined) {
if (acceptFunctionOrFunctions == undefined) {
if (specifiedOrAcceptFunctionOrFunctions == undefined) {
this.selfAccept = implicitAcceptFunction;
return;
}
if (typeof arg1 !== "function") {
if (typeof specifiedOrAcceptFunctionOrFunctions !== "function") {
throw new Error("import.meta.hot.accept requires a callback function");
}
// Self-accept function
this.selfAccept = arg1;
this.selfAccept = specifiedOrAcceptFunctionOrFunctions;
} else {
throw new Error(
'"import.meta.hot.accept" must be directly called with string literals for ' +
@@ -941,7 +941,7 @@ declare global {
}
}
// bun:bake/server, bun:bake/client, and bun:wrap are
// bun:app/server, bun:app/client, and bun:wrap are
// provided by this file instead of the bundler
registerSynthetic("bun:wrap", {
__name,
@@ -953,7 +953,7 @@ registerSynthetic("bun:wrap", {
});
if (side === "server") {
registerSynthetic("bun:bake/server", {
registerSynthetic("bun:app/server", {
serverManifest,
ssrManifest,
actionManifest: null,
@@ -961,7 +961,11 @@ if (side === "server") {
}
if (side === "client") {
registerSynthetic("bun:bake/client", {
registerSynthetic("bun:app/client", {
onServerSideReload: cb => (onServerSideReload = cb),
});
}
// `bun:app` exports types only, but we don't want
// to throw a module not found error if it's accessed at runtime
registerSynthetic("bun:app", {});

View File

@@ -3,7 +3,7 @@
"compilerOptions": {
"lib": ["ESNext", "DOM", "DOM.Iterable", "DOM.AsyncIterable"],
"paths": {
"bun-framework-react/*": ["./bun-framework-react/*"],
"bun-framework-react/*": ["../../packages/bun-framework-react/*"],
"bindgen": ["../codegen/bindgen-lib"]
},
"jsx": "react-jsx",

View File

@@ -1058,7 +1058,7 @@ pub const BundleV2 = struct {
}
}
/// This generates the two asts for 'bun:bake/client' and 'bun:bake/server'. Both are generated
/// This generates the two asts for 'bun:app/client' and 'bun:app/server'. Both are generated
/// at the same time in one pass over the SCB list.
pub fn processServerComponentManifestFiles(this: *BundleV2) OOM!void {
// If a server components is not configured, do nothing