Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "veryfront",
"version": "0.1.1125",
"version": "0.1.1126",
"license": "Apache-2.0",
Comment thread
kojiwakayama marked this conversation as resolved.
Resolved
"nodeModulesDir": "auto",
"minimumDependencyAge": {
Expand Down
9 changes: 5 additions & 4 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/api-reference/veryfront/index.client.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "veryfront/index.client"
description: "Client and SSR safe mirror of the `veryfront` root barrel. Use this import surface in code that can run in a browser bundle. It exposes the same browser-safe helpers as `veryfront` while omitting server bootstrap functions such as `createHandler`, `startServer`, and `toNodeHandler`."
description: "Browser- and SSR-safe helpers from the `veryfront` package. This entrypoint exposes the root package's client-safe configuration, platform, routing, data, and security helpers without server bootstrap functions. Most app code can import these helpers from `veryfront`; use this explicit entrypoint when a browser or SSR module needs to declare that boundary directly."
order: 12
---

Expand Down
1 change: 1 addition & 0 deletions extensions/ext-observability-opentelemetry/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"@opentelemetry/semantic-conventions": "npm:@opentelemetry/semantic-conventions@1.43.0",
"@std/assert": "jsr:@std/assert@1.0.19",
"@std/testing/bdd": "jsr:@std/testing@1.0.17/bdd",
"protobufjs": "npm:protobufjs@7.6.5",
"veryfront/extensions": "../../src/extensions/index.ts",
"veryfront/extensions/observability": "../../src/extensions/observability/index.ts",
"veryfront/utils": "../../src/utils/index.ts"
Expand Down
2 changes: 2 additions & 0 deletions scripts/storybook/storybook-workbench.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,8 @@ describe("Storybook UI workbench", () => {
assertStringIncludes(main, "createVeryfrontAliases");
assertStringIncludes(aliases, "veryfront\\/chat");
assertStringIncludes(aliases, "#veryfront");
assertStringIncludes(aliases, "#deno-config");
assertStringIncludes(aliases, "deno.json");
assertStringIncludes(
aliases,
"storybook/.storybook/shims/veryfront-utils.ts",
Expand Down
27 changes: 6 additions & 21 deletions src/index.client.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,11 @@
/**
* Client/SSR-safe mirror of the `veryfront` root barrel ({@link file://./index.ts}).
* Browser- and SSR-safe helpers from the `veryfront` package.
*
* The root barrel re-exports the server bootstrap surface (`createHandler`,
* `startServer`, `toNodeHandler`) from `#veryfront/server`. Because the browser
* and SSR pipelines transform modules per-file (no cross-module tree-shaking),
* an ESM re-export eagerly loads its source module, so pulling the root barrel
* into a client chunk drags the entire server graph in, including
* `src/server/production-server.ts`, which has module top-level `await` and
* cannot be transformed to the es2020 browser target (HTTP 500 on that chunk,
* which aborts hydration).
*
* A client-reachable module doing a used value import from the barrel, for
* example `import { getEnv } from "veryfront"`, is not dead-stripped, so it
* keeps the barrel and the leak. This barrel exposes exactly the same
* browser-safe surface minus the server bootstrap functions, which no
* client/SSR page code legitimately calls. The import rewriter redirects
* `veryfront` to this module for the `browser` and `ssr` targets (see
* `src/transforms/import-rewriter/strategies/veryfront-strategy.ts`), the same
* mechanism `veryfront/workflow` already uses.
*
* Keep the exports below in sync with {@link file://./index.ts}: everything
* except the `createHandler` / `startServer` / `toNodeHandler` value exports.
* This entrypoint exposes the root package's client-safe configuration,
* platform, routing, data, and security helpers without server bootstrap
* functions. Most app code can import these helpers from `veryfront`; use this
* explicit entrypoint when a browser or SSR module needs to declare that
* boundary directly.
*
* @example
* ```ts
Expand Down
4 changes: 2 additions & 2 deletions src/proxy/routing-invalidation-redis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ describe("proxy routing invalidation Redis bus", () => {
redisUrl: "redis://example.test:6379",
expectedReplicas: 2,
replicaId: "replica-a",
acknowledgementTimeoutMs: 10,
acknowledgementTimeoutMs: 100,
createClient: redis.createClient,
integritySecret,
onInvalidate: () => {},
Expand All @@ -176,7 +176,7 @@ describe("proxy routing invalidation Redis bus", () => {
redisUrl: "redis://example.test:6379",
expectedReplicas: 2,
replicaId: "replica-b",
acknowledgementTimeoutMs: 10,
acknowledgementTimeoutMs: 100,
createClient: redis.createClient,
integritySecret,
onInvalidate: () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createRoot } from "react-dom/client";
import { JSDOM } from "npm:jsdom@28.0.0";
import { assert, assertEquals } from "#veryfront/testing/assert.ts";
import { describe, it } from "#veryfront/testing/bdd.ts";
import { waitFor } from "#veryfront/testing/deno-compat.ts";
import { InlineCitation } from "./inline-citation.tsx";

function installDomGlobals(dom: JSDOM): () => void {
Expand Down Expand Up @@ -74,10 +75,10 @@ describe("InlineCitation", () => {
assert(trigger.className.includes("vf-citation-trigger"));

trigger.dispatchEvent(new MouseEvent("mouseover", { bubbles: true }));
await new Promise((resolve) => setTimeout(resolve, 170));
flushSync(() => {
// Flush the hover timer's state update before inspecting the DOM.
});
await waitFor(
() => document.querySelector(".vf-citation-card") !== null,
{ interval: 10, message: "Citation card did not render after hover" },
);

const card = document.querySelector(".vf-citation-card");
assert(card, "Expected citation card to render after hover");
Expand Down
6 changes: 5 additions & 1 deletion src/react/runtime/router-provider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { renderToStaticMarkup } from "react-dom/server";
import { JSDOM } from "npm:jsdom@28.0.0";
import { assertEquals, assertStringIncludes } from "#veryfront/testing/assert.ts";
import { describe, it } from "#veryfront/testing/bdd.ts";
import { waitFor } from "#veryfront/testing/deno-compat.ts";
import {
PageContextProvider,
type PageContextValue,
Expand Down Expand Up @@ -395,7 +396,10 @@ describe("react/runtime/RouterProvider (reactive)", () => {
</RouterProvider>,
);
});
await tick();
await waitFor(
() => (rootElement.textContent ?? "").includes("m:true"),
{ interval: 10, message: "RouterProvider did not finish its client mount" },
);
assertStringIncludes(rootElement.textContent ?? "", "m:true");
root.unmount();
} finally {
Expand Down
23 changes: 23 additions & 0 deletions src/server/services/rsc/endpoints/handler-registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,29 @@ describe("server/services/rsc/endpoints/handler-registry", () => {
__destroyRSCHandlerForTests();
});

it("does not inspect project dependencies until a handler is used", async () => {
const cache = createStubCache();
__injectCacheForTests(cache);
const originalStat = Deno.stat;
const projectDir = "/project/lazy-react-version";
let statCalls = 0;

Deno.stat = (path: string | URL) => {
if (String(path).includes(projectDir)) statCalls++;
return originalStat(path);
};
Comment thread
kojiwakayama marked this conversation as resolved.
Resolved

try {
getRSCHandler(projectDir);
await Deno.stat(".");
await new Promise((resolve) => setTimeout(resolve, 0));

assertEquals(statCalls, 0);
} finally {
Deno.stat = originalStat;
}
});

describe("invalidateRSCHandlersForProject", () => {
it("evicts every handler variant for only the changed project", () => {
const cache = createStubCache();
Expand Down
22 changes: 14 additions & 8 deletions src/server/services/rsc/orchestrators/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class RSCDevServerHandler {
private renderer: RSCRenderer | null = null;
private clientManifest: Map<string, ClientComponentMeta> | null = null;
private rendererPromise: Promise<void> | null = null;
private reactVersionPromise: Promise<string> | null = null;
private invalidationGeneration = 0;

private readonly manifestHandler: ManifestHandler;
Expand All @@ -53,10 +54,6 @@ export class RSCDevServerHandler {
fs: options.adapter?.fs,
contentSourceId,
});
this.reactVersionPromise = resolveProjectReactVersion({
projectDir,
config: options.config,
});
this.renderHandler = new RenderHandler(
projectDir,
() => this.renderer,
Expand All @@ -67,21 +64,22 @@ export class RSCDevServerHandler {
projectId: options.projectId,
projectSlug: options.projectSlug,
contentSourceId,
reactVersion: this.reactVersionPromise,
reactVersion: () => this.getReactVersion(),
},
);
this.streamHandler = new StreamHandler(this.renderHandler);
this.appDir = appDir;
this.isLocalProject = isLocalProject;
this.mode = mode;
this.fs = options.adapter?.fs;
this.config = options.config;
}

private readonly appDir: string;
private readonly isLocalProject: boolean;
private readonly mode: "development" | "production";
private readonly reactVersionPromise: Promise<string>;
private readonly fs?: RuntimeAdapter["fs"];
private readonly config?: VeryfrontConfig;

handleManifest(): Promise<Response> {
return this.manifestHandler.handle(this.clientManifest);
Expand Down Expand Up @@ -109,7 +107,7 @@ export class RSCDevServerHandler {
if (!this.pageHandler) {
this.pageHandler = new PageHandler(
this.mode === "development",
await this.reactVersionPromise,
await this.getReactVersion(),
this.isLocalProject ? "fs" : "rsc-module",
);
}
Expand Down Expand Up @@ -140,7 +138,7 @@ export class RSCDevServerHandler {

private async initializeRenderer(generation: number): Promise<void> {
const clientManifest = await buildClientManifest(this.projectDir, this.appDir, this.fs);
const reactVersion = await this.reactVersionPromise;
const reactVersion = await this.getReactVersion();
if (generation !== this.invalidationGeneration) return;

this.clientManifest = clientManifest;
Expand All @@ -153,4 +151,12 @@ export class RSCDevServerHandler {
reactVersion,
});
}

private getReactVersion(): Promise<string> {
this.reactVersionPromise ??= resolveProjectReactVersion({
projectDir: this.projectDir,
config: this.config,
});
return this.reactVersionPromise;
}
}
6 changes: 3 additions & 3 deletions src/server/services/rsc/orchestrators/render-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ interface RenderHandlerModuleOptions {
projectId?: string;
projectSlug?: string;
contentSourceId?: string;
reactVersion?: Promise<string>;
reactVersion?: () => Promise<string>;
}

const logger = serverLogger.component("rsc");
Expand Down Expand Up @@ -115,7 +115,7 @@ export class RenderHandler {
this.projectDir,
this.moduleOptions.projectSlug,
this.moduleOptions.contentSourceId,
await this.moduleOptions.reactVersion,
await this.moduleOptions.reactVersion?.(),
) as Record<string, unknown>;
}

Expand All @@ -130,7 +130,7 @@ export class RenderHandler {
contentSourceId: this.moduleOptions.contentSourceId,
dev: this.mode === "development",
mode: this.mode === "development" ? "preview" : "production",
reactVersion: await this.moduleOptions.reactVersion,
reactVersion: await this.moduleOptions.reactVersion?.(),
});
}

Expand Down
Loading