@farm.js/plugin 0.1.0-beta.10 → 0.1.0-beta.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/api/index.d.ts +42 -0
  2. package/dist/api/index.d.ts.map +1 -0
  3. package/dist/api/index.js +493 -0
  4. package/dist/context/index.d.ts +61 -0
  5. package/dist/context/index.d.ts.map +1 -0
  6. package/dist/context/index.js +75 -0
  7. package/dist/index.d.ts +43 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +49 -0
  10. package/dist/middleware/index.d.ts +97 -0
  11. package/dist/middleware/index.d.ts.map +1 -0
  12. package/dist/middleware/index.js +469 -0
  13. package/dist/observability/index.d.ts +190 -0
  14. package/dist/observability/index.d.ts.map +1 -0
  15. package/dist/observability/index.js +399 -0
  16. package/dist/rsc/automatic-optimized-boundary.d.ts +11 -0
  17. package/dist/rsc/automatic-optimized-boundary.d.ts.map +1 -0
  18. package/dist/rsc/automatic-optimized-boundary.js +162 -0
  19. package/dist/rsc/build-paths.d.ts +3 -0
  20. package/dist/rsc/build-paths.d.ts.map +1 -0
  21. package/dist/rsc/build-paths.js +8 -0
  22. package/dist/rsc/compatibility.d.ts +8 -0
  23. package/dist/rsc/compatibility.d.ts.map +1 -0
  24. package/dist/rsc/compatibility.js +46 -0
  25. package/dist/rsc/entries/client.d.ts +14 -0
  26. package/dist/rsc/entries/client.d.ts.map +1 -0
  27. package/dist/rsc/entries/client.js +283 -0
  28. package/dist/rsc/entries/rsc.d.ts +13 -0
  29. package/dist/rsc/entries/rsc.d.ts.map +1 -0
  30. package/dist/rsc/entries/rsc.js +932 -0
  31. package/dist/rsc/entries/ssr.d.ts +13 -0
  32. package/dist/rsc/entries/ssr.d.ts.map +1 -0
  33. package/dist/rsc/entries/ssr.js +245 -0
  34. package/dist/rsc/index.d.ts +87 -0
  35. package/dist/rsc/index.d.ts.map +1 -0
  36. package/dist/rsc/index.js +1389 -0
  37. package/dist/rsc/nitro-build.d.ts +36 -0
  38. package/dist/rsc/nitro-build.d.ts.map +1 -0
  39. package/dist/rsc/nitro-build.js +396 -0
  40. package/dist/rsc/optimized-boundary.d.ts +29 -0
  41. package/dist/rsc/optimized-boundary.d.ts.map +1 -0
  42. package/dist/rsc/optimized-boundary.js +243 -0
  43. package/dist/rsc/server-fn-transform.d.ts +6 -0
  44. package/dist/rsc/server-fn-transform.d.ts.map +1 -0
  45. package/dist/rsc/server-fn-transform.js +152 -0
  46. package/dist/rsc/types.d.ts +123 -0
  47. package/dist/rsc/types.d.ts.map +1 -0
  48. package/dist/rsc/types.js +1 -0
  49. package/dist/rsc/vite-plugin-nitro.d.ts +33 -0
  50. package/dist/rsc/vite-plugin-nitro.d.ts.map +1 -0
  51. package/dist/rsc/vite-plugin-nitro.js +163 -0
  52. package/package.json +3 -3
@@ -0,0 +1,162 @@
1
+ import { parseAst } from "vite";
2
+ const JSX_RUNTIME_MODULES = new Set(["react/jsx-runtime", "react/jsx-dev-runtime"]);
3
+ const JSX_FACTORY_EXPORTS = new Set(["jsx", "jsxs", "jsxDEV"]);
4
+ const AUTOMATIC_BOUNDARY_TAGS = new Set([
5
+ "article",
6
+ "aside",
7
+ "div",
8
+ "footer",
9
+ "header",
10
+ "main",
11
+ "nav",
12
+ "section",
13
+ "span",
14
+ ]);
15
+ const AUTOMATIC_BOUNDARY_IMPORT = "@farm.js/plugin/rsc/optimized-boundary";
16
+ function nodeName(node) {
17
+ if (!node || typeof node !== "object")
18
+ return undefined;
19
+ const candidate = node;
20
+ if (typeof candidate.name === "string")
21
+ return candidate.name;
22
+ if (typeof candidate.value === "string")
23
+ return candidate.value;
24
+ return undefined;
25
+ }
26
+ function isClientModule(body) {
27
+ for (const statement of body) {
28
+ if (statement.type === "ImportDeclaration")
29
+ return false;
30
+ if (statement.type !== "ExpressionStatement")
31
+ return false;
32
+ const expression = statement.expression;
33
+ if (expression?.type !== "Literal")
34
+ return false;
35
+ if (expression.value === "use client")
36
+ return true;
37
+ }
38
+ return false;
39
+ }
40
+ function collectJsxFactories(body) {
41
+ const factories = new Set();
42
+ for (const statement of body) {
43
+ if (statement.type !== "ImportDeclaration")
44
+ continue;
45
+ const source = nodeName(statement.source);
46
+ if (!source || !JSX_RUNTIME_MODULES.has(source))
47
+ continue;
48
+ for (const specifier of statement.specifiers || []) {
49
+ if (specifier.type !== "ImportSpecifier")
50
+ continue;
51
+ const imported = nodeName(specifier.imported);
52
+ const local = nodeName(specifier.local);
53
+ if (imported && local && JSX_FACTORY_EXPORTS.has(imported))
54
+ factories.add(local);
55
+ }
56
+ }
57
+ return factories;
58
+ }
59
+ function collectBoundaryCalls(node, factories, calls) {
60
+ if (!node || typeof node !== "object")
61
+ return;
62
+ if (Array.isArray(node)) {
63
+ for (const child of node)
64
+ collectBoundaryCalls(child, factories, calls);
65
+ return;
66
+ }
67
+ const candidate = node;
68
+ if (candidate.type === "CallExpression") {
69
+ const callee = candidate.callee;
70
+ const args = candidate.arguments || [];
71
+ const tag = args[0];
72
+ if (callee?.type === "Identifier" &&
73
+ factories.has(nodeName(callee) || "") &&
74
+ tag?.type === "Literal" &&
75
+ AUTOMATIC_BOUNDARY_TAGS.has(String(tag.value)) &&
76
+ typeof candidate.start === "number" &&
77
+ typeof candidate.end === "number") {
78
+ calls.push(candidate);
79
+ }
80
+ }
81
+ for (const [key, value] of Object.entries(candidate)) {
82
+ if (key === "start" || key === "end" || key === "loc")
83
+ continue;
84
+ collectBoundaryCalls(value, factories, calls);
85
+ }
86
+ }
87
+ function createHelperName(code) {
88
+ const base = "__farmOptimizeBoundary";
89
+ let name = base;
90
+ let suffix = 1;
91
+ while (new RegExp(`\\b${name}\\b`).test(code))
92
+ name = `${base}${suffix++}`;
93
+ return name;
94
+ }
95
+ function importInsertionPoint(body) {
96
+ let position = 0;
97
+ for (const statement of body) {
98
+ if (statement.type === "ImportDeclaration" && typeof statement.end === "number") {
99
+ position = statement.end;
100
+ continue;
101
+ }
102
+ if (statement.type === "ExpressionStatement") {
103
+ const expression = statement.expression;
104
+ if (expression?.type === "Literal" && typeof statement.end === "number") {
105
+ position = statement.end;
106
+ continue;
107
+ }
108
+ }
109
+ break;
110
+ }
111
+ return position;
112
+ }
113
+ /**
114
+ * Wrap React JSX-runtime calls for native host boundaries with Farm's
115
+ * server-only optimizer. Eligibility is decided against the fully evaluated
116
+ * React element at runtime, so unsupported trees retain normal React behavior.
117
+ */
118
+ export function transformAutomaticOptimizedBoundaries(code, id) {
119
+ const cleanId = id.split("?", 1)[0].replace(/\\/g, "/");
120
+ if (!/\.[cm]?[jt]sx?$/.test(cleanId))
121
+ return null;
122
+ if (cleanId.includes("/node_modules/") || cleanId.includes("/.farm/"))
123
+ return null;
124
+ if (!code.includes("react/jsx-runtime") && !code.includes("react/jsx-dev-runtime"))
125
+ return null;
126
+ let ast;
127
+ try {
128
+ ast = parseAst(code);
129
+ }
130
+ catch {
131
+ return null;
132
+ }
133
+ const body = ast.body || [];
134
+ if (isClientModule(body))
135
+ return null;
136
+ const factories = collectJsxFactories(body);
137
+ if (factories.size === 0)
138
+ return null;
139
+ const calls = [];
140
+ collectBoundaryCalls(ast, factories, calls);
141
+ if (calls.length === 0)
142
+ return null;
143
+ const helperName = createHelperName(code);
144
+ const insertions = [];
145
+ for (const call of calls) {
146
+ insertions.push({ position: call.start, text: `${helperName}(`, order: 1 });
147
+ insertions.push({ position: call.end, text: ")", order: 0 });
148
+ }
149
+ insertions.push({
150
+ position: importInsertionPoint(body),
151
+ text: `${importInsertionPoint(body) === 0 ? "" : "\n"}import { _optimizeBoundary as ${helperName} } from ${JSON.stringify(AUTOMATIC_BOUNDARY_IMPORT)};\n`,
152
+ order: 2,
153
+ });
154
+ let transformed = code;
155
+ for (const insertion of insertions.sort((left, right) => right.position - left.position || left.order - right.order)) {
156
+ transformed =
157
+ transformed.slice(0, insertion.position) +
158
+ insertion.text +
159
+ transformed.slice(insertion.position);
160
+ }
161
+ return { code: transformed, boundaryCount: calls.length };
162
+ }
@@ -0,0 +1,3 @@
1
+ /** Resolve a Vite environment outDir without prefixing an already-absolute path. */
2
+ export declare function resolveRscBuildOutputPath(root: string, outDir: string, ...segments: string[]): string;
3
+ //# sourceMappingURL=build-paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-paths.d.ts","sourceRoot":"","sources":["../../src/rsc/build-paths.ts"],"names":[],"mappings":"AAEA,oFAAoF;AACpF,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,GAAG,QAAQ,EAAE,MAAM,EAAE,GACpB,MAAM,CAKR"}
@@ -0,0 +1,8 @@
1
+ import path from "path";
2
+ /** Resolve a Vite environment outDir without prefixing an already-absolute path. */
3
+ export function resolveRscBuildOutputPath(root, outDir, ...segments) {
4
+ const resolvedOutDir = path.isAbsolute(outDir)
5
+ ? path.normalize(outDir)
6
+ : path.resolve(root, outDir);
7
+ return path.join(resolvedOutDir, ...segments);
8
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * React's framework-level RSC APIs do not follow semver. Keep the complete
3
+ * renderer/decoder toolchain on the exact combination exercised by Farm's
4
+ * integration tests instead of accepting a potentially incompatible or
5
+ * vulnerable package resolution.
6
+ */
7
+ export declare function assertRscPackageCompatibility(root: string): void;
8
+ //# sourceMappingURL=compatibility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compatibility.d.ts","sourceRoot":"","sources":["../../src/rsc/compatibility.ts"],"names":[],"mappings":"AA0BA;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAgChE"}
@@ -0,0 +1,46 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ const RSC_PACKAGE_VERSIONS = {
5
+ react: "19.2.8",
6
+ "react-dom": "19.2.8",
7
+ "react-server-dom-webpack": "19.2.8",
8
+ "@vitejs/plugin-rsc": "0.5.32",
9
+ };
10
+ function readPackageVersion(projectRequire, packageName) {
11
+ const manifestPath = projectRequire.resolve(`${packageName}/package.json`);
12
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
13
+ return typeof manifest.version === "string" ? manifest.version : undefined;
14
+ }
15
+ /**
16
+ * React's framework-level RSC APIs do not follow semver. Keep the complete
17
+ * renderer/decoder toolchain on the exact combination exercised by Farm's
18
+ * integration tests instead of accepting a potentially incompatible or
19
+ * vulnerable package resolution.
20
+ */
21
+ export function assertRscPackageCompatibility(root) {
22
+ const projectRequire = createRequire(path.join(path.resolve(root), "__farm_rsc_check__.cjs"));
23
+ const problems = [];
24
+ for (const [packageName, expectedVersion] of Object.entries(RSC_PACKAGE_VERSIONS)) {
25
+ try {
26
+ const actualVersion = readPackageVersion(projectRequire, packageName);
27
+ if (actualVersion !== expectedVersion) {
28
+ problems.push(`${packageName}: expected ${expectedVersion}, found ${actualVersion ?? "unknown"}`);
29
+ }
30
+ }
31
+ catch {
32
+ problems.push(`${packageName}: expected ${expectedVersion}, package is not installed`);
33
+ }
34
+ }
35
+ if (problems.length === 0)
36
+ return;
37
+ throw new Error([
38
+ "[Farm.js] Unsupported React Server Components package combination.",
39
+ ...problems.map((problem) => `- ${problem}`),
40
+ "",
41
+ "Farm pins the framework-level RSC APIs to one supported, integration-tested combination.",
42
+ "Install the supported versions with:",
43
+ "pnpm add react@19.2.8 react-dom@19.2.8 react-server-dom-webpack@19.2.8",
44
+ "pnpm add -D @vitejs/plugin-rsc@0.5.32",
45
+ ].join("\n"));
46
+ }
@@ -0,0 +1,14 @@
1
+ import type { EntryContext } from "../types.js";
2
+ /**
3
+ * Generates the browser entry file.
4
+ *
5
+ * This entry file:
6
+ * - Reads the embedded RSC payload from the HTML (via rsc-html-stream)
7
+ * - Deserializes it to React elements
8
+ * - Sets up client-side navigation (intercepts links, handles popstate)
9
+ * - Registers server action callback if enabled
10
+ * - Hydrates the page
11
+ * - Listens for HMR updates from server components
12
+ */
13
+ export declare function generateClientEntry(ctx: EntryContext): string;
14
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/rsc/entries/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAiR7D"}
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Generates the browser entry file.
3
+ *
4
+ * This entry file:
5
+ * - Reads the embedded RSC payload from the HTML (via rsc-html-stream)
6
+ * - Deserializes it to React elements
7
+ * - Sets up client-side navigation (intercepts links, handles popstate)
8
+ * - Registers server action callback if enabled
9
+ * - Hydrates the page
10
+ * - Listens for HMR updates from server components
11
+ */
12
+ export function generateClientEntry(ctx) {
13
+ const debugLog = `// Debug disabled`;
14
+ const globalStylesheetImport = ctx.globalCssPath
15
+ ? `const farmGlobalStylesheets = import.meta.glob(${JSON.stringify(ctx.globalCssPath)}, {
16
+ eager: true,
17
+ import: 'default',
18
+ query: '?url',
19
+ });
20
+ export const farmGlobalStylesheet = Object.values(farmGlobalStylesheets)[0];
21
+ `
22
+ : "";
23
+ let imports = `${globalStylesheetImport}
24
+ import React from 'react';
25
+ import { hydrateRoot } from 'react-dom/client';
26
+ import { createFromReadableStream } from '@vitejs/plugin-rsc/browser';
27
+ import { rscStream } from 'rsc-html-stream/client';
28
+ import {
29
+ createFarmDeploymentMismatchError,
30
+ createFarmDeploymentRequestHeaders,
31
+ isFarmDeploymentMismatchResponse,
32
+ } from '@farm.js/core/deployment';
33
+ `;
34
+ if (ctx.actionsEnabled) {
35
+ imports += `import { setServerCallback, encodeReply, createTemporaryReferenceSet } from '@vitejs/plugin-rsc/browser';
36
+ import { applyFarmCacheInvalidations } from '@farm.js/core/cache';
37
+ import {
38
+ beginFarmServerQueryAction,
39
+ completeFarmServerQueryAction,
40
+ } from '@farm.js/core/server-query/client';
41
+ `;
42
+ }
43
+ // Module-level ref so the server-action callback can update UI (assigned in BrowserRoot useEffect).
44
+ // Set __viteRscCallServer immediately so it's never undefined when other chunks call it (then replace with real impl).
45
+ let actionSetup = "";
46
+ if (ctx.actionsEnabled) {
47
+ actionSetup = `
48
+ // Ref for payload setter (used by server action callback and refetch)
49
+ const setPayloadRef = { current: null };
50
+
51
+ // Ensure __viteRscCallServer is a function before any other chunk may call it (avoids "is not a function")
52
+ if (typeof globalThis.__viteRscCallServer !== 'function') {
53
+ globalThis.__viteRscCallServer = () => Promise.reject(new Error('Farm.js: server actions not ready'));
54
+ }
55
+ // Register real callback (replaces placeholder above)
56
+ setServerCallback(async (id, args) => {
57
+ debug('Invoking server action:', id);
58
+ const serverQueryInvocation = beginFarmServerQueryAction(id, args);
59
+ const refs = createTemporaryReferenceSet();
60
+ const body = await encodeReply(args, { temporaryReferences: refs });
61
+ const headers = createFarmDeploymentRequestHeaders(farmDeploymentId, {
62
+ 'x-farm-action-id': id,
63
+ 'Accept': 'text/x-component',
64
+ });
65
+ if (typeof body === 'string') headers.set('Content-Type', 'text/plain; charset=utf-8');
66
+ else if (!(body instanceof FormData)) headers.set('Content-Type', 'application/octet-stream');
67
+ const res = await fetch(location.href, {
68
+ method: 'POST',
69
+ headers,
70
+ body,
71
+ cache: 'no-store',
72
+ credentials: 'same-origin',
73
+ redirect: 'error',
74
+ });
75
+ if (isFarmDeploymentMismatchResponse(res, farmDeploymentId)) {
76
+ throw reportDeploymentMismatch(res);
77
+ }
78
+ if (!res.ok) {
79
+ const text = await res.text();
80
+ console.error('[Farm.js] Server action request failed:', res.status, text);
81
+ throw new Error('Server action failed: ' + res.status);
82
+ }
83
+ let p;
84
+ try {
85
+ p = await createFromReadableStream(res.body, { temporaryReferences: refs });
86
+ } catch (e) {
87
+ console.error('[Farm.js] Failed to deserialize action response:', e);
88
+ throw e;
89
+ }
90
+ const hasContent = p && (typeof p.root !== 'undefined' || typeof p.rootContent !== 'undefined');
91
+ if (!hasContent) {
92
+ console.error('[Farm.js] Action response missing payload.root / payload.rootContent');
93
+ return;
94
+ }
95
+ setPayloadRef.current?.(p);
96
+ applyFarmCacheInvalidations(p.returnValue?.invalidations);
97
+ if (!p.returnValue || !p.returnValue.ok) {
98
+ debug('Server action failed:', id);
99
+ const error = new Error(p.returnValue?.data?.message || 'Server function failed');
100
+ error.name = 'ServerActionError';
101
+ throw error;
102
+ }
103
+ return completeFarmServerQueryAction(serverQueryInvocation, p.returnValue.data);
104
+ });
105
+ `;
106
+ }
107
+ else {
108
+ actionSetup = `
109
+ const setPayloadRef = { current: null };
110
+ `;
111
+ }
112
+ return `${imports}
113
+ ${actionSetup}
114
+ const farmDeploymentId = ${JSON.stringify(ctx.deploymentId)};
115
+
116
+ function reportDeploymentMismatch(response) {
117
+ const error = createFarmDeploymentMismatchError(response, farmDeploymentId);
118
+ globalThis.dispatchEvent?.(new CustomEvent('farm:deployment-mismatch', { detail: error }));
119
+ return error;
120
+ }
121
+
122
+ // Debug logging helper
123
+ function debug(...args) {
124
+ ${debugLog}
125
+ }
126
+
127
+ async function main() {
128
+ // Prevent double execution (e.g. script loaded twice)
129
+ if (globalThis.__FARM_RSC_HYDRATED) return;
130
+
131
+ debug('Starting client hydration');
132
+
133
+ // Clean duplicate DOM as early as possible (server may have sent two blocks)
134
+ const rootEl = document.getElementById('root');
135
+ if (rootEl) {
136
+ while (rootEl.children.length > 1) rootEl.lastElementChild.remove();
137
+ while (rootEl.nextElementSibling) rootEl.nextElementSibling.remove();
138
+ }
139
+
140
+ // Deserialize the initial RSC payload embedded in HTML
141
+ // rscStream extracts the payload from <script> tags added by SSR
142
+ let initial;
143
+ try {
144
+ initial = await createFromReadableStream(rscStream);
145
+ debug('Initial RSC payload deserialized');
146
+ } catch (e) {
147
+ console.error('[Farm.js] Failed to deserialize RSC payload:', e);
148
+ return;
149
+ }
150
+
151
+ // Root component that manages RSC state
152
+ function BrowserRoot() {
153
+ const [payload, set] = React.useState(initial);
154
+
155
+ // Expose setter for external updates (navigation, actions, HMR)
156
+ React.useEffect(() => {
157
+ setPayloadRef.current = (p) => React.startTransition(() => set(p));
158
+ }, []);
159
+
160
+ // Keep document metadata in sync when an RSC navigation swaps only #root.
161
+ React.useEffect(() => {
162
+ if (typeof payload.metadata?.title === 'string') {
163
+ document.title = payload.metadata.title;
164
+ } else {
165
+ document.title = '';
166
+ }
167
+ let description = document.querySelector('meta[name="description"]');
168
+ if (typeof payload.metadata?.description === 'string') {
169
+ if (!description) {
170
+ description = document.createElement('meta');
171
+ description.setAttribute('name', 'description');
172
+ document.head.appendChild(description);
173
+ }
174
+ description.setAttribute('content', payload.metadata.description);
175
+ } else {
176
+ description?.remove();
177
+ }
178
+ }, [payload.metadata?.title, payload.metadata?.description]);
179
+
180
+ // Set up client-side navigation
181
+ React.useEffect(() => {
182
+ // Re-fetch RSC when URL changes
183
+ const nav = () => refetch(location.href);
184
+
185
+ // Handle browser back/forward
186
+ window.addEventListener('popstate', nav);
187
+
188
+ // Intercept link clicks for client-side navigation
189
+ const handleClick = (e) => {
190
+ const a = e.target.closest('a');
191
+ if (a?.href && a.origin === location.origin && !a.download && !a.target) {
192
+ // Check for special attributes that should skip SPA navigation
193
+ if (a.hasAttribute('data-native') || a.hasAttribute('data-reload')) {
194
+ return;
195
+ }
196
+
197
+ e.preventDefault();
198
+ history.pushState(null, '', a.href);
199
+ nav();
200
+ }
201
+ };
202
+
203
+ document.addEventListener('click', handleClick, true);
204
+
205
+ return () => {
206
+ window.removeEventListener('popstate', nav);
207
+ document.removeEventListener('click', handleClick, true);
208
+ };
209
+ }, []);
210
+
211
+ // Never render payload.root (full document) on the client - it creates a second visible block (entire page duplicated below).
212
+ // Only render rootContent (layout+page for #root).
213
+ const content = payload.rootContent;
214
+ if (content == null) {
215
+ if (payload.root != null) console.warn('[Farm.js] payload.rootContent missing; not using payload.root to avoid duplicate block. Keys:', Object.keys(payload));
216
+ return null;
217
+ }
218
+ return content;
219
+ }
220
+
221
+ // Fetch new RSC payload for a URL
222
+ async function refetch(url) {
223
+ debug('Fetching RSC for:', url);
224
+
225
+ try {
226
+ // Request RSC format instead of HTML
227
+ const res = await fetch(url, {
228
+ headers: createFarmDeploymentRequestHeaders(farmDeploymentId, {
229
+ Accept: 'text/x-component',
230
+ }),
231
+ });
232
+ if (isFarmDeploymentMismatchResponse(res, farmDeploymentId)) {
233
+ reportDeploymentMismatch(res);
234
+ location.assign(url);
235
+ return;
236
+ }
237
+
238
+ if (!res.ok) {
239
+ console.error('[Farm.js] RSC fetch failed:', res.status);
240
+ // Fall back to full page navigation
241
+ location.href = url;
242
+ return;
243
+ }
244
+
245
+ const newPayload = await createFromReadableStream(res.body);
246
+ setPayloadRef.current?.(newPayload);
247
+ debug('RSC navigation complete');
248
+ } catch (e) {
249
+ console.error('[Farm.js] RSC navigation failed:', e);
250
+ // Fall back to full page navigation
251
+ location.href = url;
252
+ }
253
+ }
254
+ const rootElForHydrate = document.getElementById('root');
255
+ if (!rootElForHydrate) {
256
+ console.error('[Farm.js] #root element not found');
257
+ return;
258
+ }
259
+ // Final cleanup pass before hydrate (in case DOM changed during async)
260
+ while (rootElForHydrate.children.length > 1) rootElForHydrate.lastElementChild.remove();
261
+ while (rootElForHydrate.nextElementSibling) rootElForHydrate.nextElementSibling.remove();
262
+ debug('Hydrating application');
263
+ globalThis.__FARM_RSC_HYDRATED = true;
264
+ hydrateRoot(rootElForHydrate, React.createElement(BrowserRoot), {
265
+ formState: initial.formState,
266
+ });
267
+
268
+ // Handle HMR for server components
269
+ // When server code changes, re-fetch and re-render
270
+ if (import.meta.hot) {
271
+ import.meta.hot.on('rsc:update', () => {
272
+ debug('HMR update received, refetching...');
273
+ refetch(location.href);
274
+ });
275
+ }
276
+ }
277
+
278
+ // Start the application
279
+ main().catch((e) => {
280
+ console.error('[Farm.js] Client initialization failed:', e);
281
+ });
282
+ `;
283
+ }
@@ -0,0 +1,13 @@
1
+ import type { EntryContext } from "../types.js";
2
+ /**
3
+ * Generates the RSC environment entry file.
4
+ *
5
+ * This entry file:
6
+ * - Auto-discovers page files using import.meta.glob
7
+ * - Implements file-based routing by matching URL paths to page files
8
+ * - Handles server actions if enabled (decoding arguments, executing, returning results)
9
+ * - Renders the React tree to an RSC stream
10
+ * - Either returns the stream directly (for client navigation) or delegates to SSR (for initial page load)
11
+ */
12
+ export declare function generateRscEntry(ctx: EntryContext): string;
13
+ //# sourceMappingURL=rsc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rsc.d.ts","sourceRoot":"","sources":["../../../src/rsc/entries/rsc.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAg7B1D"}