@farm.js/plugin 0.1.0-beta.93 → 0.1.0-beta.94
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.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/rsc/dev-response.d.ts +3 -0
- package/dist/rsc/dev-response.d.ts.map +1 -0
- package/dist/rsc/dev-response.js +10 -0
- package/dist/rsc/entries/error-fallback.d.ts +3 -0
- package/dist/rsc/entries/error-fallback.d.ts.map +1 -0
- package/dist/rsc/entries/error-fallback.js +12 -0
- package/dist/rsc/entries/rsc.d.ts.map +1 -1
- package/dist/rsc/entries/rsc.js +165 -179
- package/dist/rsc/entries/ssr.d.ts.map +1 -1
- package/dist/rsc/entries/ssr.js +44 -74
- package/dist/rsc/index.d.ts +4 -0
- package/dist/rsc/index.d.ts.map +1 -1
- package/dist/rsc/index.js +56 -33
- package/dist/rsc/nitro-build.d.ts.map +1 -1
- package/dist/rsc/nitro-build.js +14 -3
- package/dist/rsc/types.d.ts +4 -0
- package/dist/rsc/types.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-response.d.ts","sourceRoot":"","sources":["../../src/rsc/dev-response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGhD,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,IAAI,CAAC,CAYf"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { sendWebResponse } from "@farm.js/core/server";
|
|
2
|
+
export async function sendRscDevelopmentResponse(res, response) {
|
|
3
|
+
const headers = new Headers(response.headers);
|
|
4
|
+
headers.delete("transfer-encoding");
|
|
5
|
+
await sendWebResponse(res, new Response(response.body, {
|
|
6
|
+
status: response.status,
|
|
7
|
+
statusText: response.statusText,
|
|
8
|
+
headers,
|
|
9
|
+
}));
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-fallback.d.ts","sourceRoot":"","sources":["../../../src/rsc/entries/error-fallback.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,wBAAgB,0BAA0B,IAAI,MAAM,CAUnD"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Client-owned reset callbacks must not be serialized from the RSC server. */
|
|
2
|
+
export function generateErrorFallbackEntry() {
|
|
3
|
+
return `"use client";
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
export default function ServerErrorFallback({ Fallback, message, fallbackProps }) {
|
|
7
|
+
const error = new Error(message);
|
|
8
|
+
const reset = () => window.location.reload();
|
|
9
|
+
return React.createElement(Fallback, { ...fallbackProps, error, reset });
|
|
10
|
+
}
|
|
11
|
+
`;
|
|
12
|
+
}
|
|
@@ -1 +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,
|
|
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,CA06B1D"}
|
package/dist/rsc/entries/rsc.js
CHANGED
|
@@ -25,6 +25,8 @@ export function generateRscEntry(ctx) {
|
|
|
25
25
|
const debugLog = `// Debug disabled`;
|
|
26
26
|
let code = `
|
|
27
27
|
import React from 'react';
|
|
28
|
+
import ServerErrorFallback from '/.farm/rsc-entries/error-fallback.tsx';
|
|
29
|
+
import { registerAPIRouteShape } from '@farm.js/core/api/runtime';
|
|
28
30
|
import {
|
|
29
31
|
renderToReadableStream,
|
|
30
32
|
`;
|
|
@@ -49,11 +51,24 @@ import {
|
|
|
49
51
|
applyProductionMiddlewareHeaders,
|
|
50
52
|
createProductionMiddlewareRunner,
|
|
51
53
|
} from '@farm.js/core/middleware';
|
|
52
|
-
import {
|
|
54
|
+
import {
|
|
55
|
+
getAllowedAPIRouteMethods,
|
|
56
|
+
invokeAPIRouteEndpoint,
|
|
57
|
+
isFarmAPIPathname,
|
|
58
|
+
matchAPIRouteAtBasePath,
|
|
59
|
+
} from '@farm.js/core/api/runtime';
|
|
53
60
|
import { _runWithAfterRequest } from '@farm.js/core/after';
|
|
54
61
|
import { _runWithCurrentRequest, searchParamsToObject } from '@farm.js/core/internal/production-runtime';
|
|
62
|
+
import { getFarmRedirectError, isFarmNotFoundError } from '@farm.js/core/internal/production-runtime';
|
|
63
|
+
import {
|
|
64
|
+
parseRoutePath,
|
|
65
|
+
matchFarmPageRoute,
|
|
66
|
+
compareRouteSpecificity,
|
|
67
|
+
getRoutePatternSpecificity,
|
|
68
|
+
} from '@farm.js/core/internal/production-runtime';
|
|
55
69
|
|
|
56
70
|
const farmDeploymentId = ${JSON.stringify(ctx.deploymentId)};
|
|
71
|
+
const farmApiBasePath = ${JSON.stringify(ctx.apiBasePath ?? "/api")};
|
|
57
72
|
`;
|
|
58
73
|
if (ctx.actionsEnabled) {
|
|
59
74
|
code += `import {
|
|
@@ -76,6 +91,9 @@ function debug(...args) {
|
|
|
76
91
|
}
|
|
77
92
|
|
|
78
93
|
function applyActionResponseHeaders(headers, request) {
|
|
94
|
+
// The same page URL serves HTML or Flight depending on Accept.
|
|
95
|
+
const vary = (headers.get('vary') || '').split(',').map(value => value.trim().toLowerCase());
|
|
96
|
+
if (!vary.includes('*') && !vary.includes('accept')) headers.append('vary', 'Accept');
|
|
79
97
|
headers.set('x-farm-deployment-id', farmDeploymentId);
|
|
80
98
|
const accept = request.headers.get('accept') || '';
|
|
81
99
|
if (request.method === 'GET' && !accept.includes('text/x-component')) {
|
|
@@ -165,20 +183,30 @@ const routeDefinitionModules = collectRouteModuleEntries([${routeSourceRoots
|
|
|
165
183
|
.map((root, index) => `{ name: ${JSON.stringify(root.name)}, base: ${JSON.stringify(root.sourceGlob)}, modules: routeDefinitionModules${index} }`)
|
|
166
184
|
.join(", ")}]);
|
|
167
185
|
|
|
168
|
-
const apiRouteMethods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'];
|
|
186
|
+
const apiRouteMethods = ['GET', 'HEAD', 'QUERY', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'];
|
|
169
187
|
const apiRouteMap = new Map();
|
|
188
|
+
const apiRouteShapes = new Map();
|
|
170
189
|
|
|
171
|
-
function registerApiEndpoint(routePath, filePath, method, endpoint) {
|
|
190
|
+
function registerApiEndpoint(routePath, filePath, method, endpoint, sourceIndex) {
|
|
172
191
|
if (!routePath || typeof endpoint !== 'function') return;
|
|
192
|
+
const replacedPath = registerAPIRouteShape(apiRouteShapes, routePath, filePath, sourceIndex);
|
|
193
|
+
if (replacedPath) apiRouteMap.delete(replacedPath);
|
|
173
194
|
const normalizedMethod = String(method || 'GET').toUpperCase();
|
|
174
195
|
let route = apiRouteMap.get(routePath);
|
|
175
196
|
if (!route) {
|
|
176
|
-
route = { path: routePath, methods: [], handlers: {}, files: {} };
|
|
197
|
+
route = { path: routePath, methods: [], handlers: {}, files: {}, sources: {} };
|
|
177
198
|
apiRouteMap.set(routePath, route);
|
|
178
199
|
}
|
|
200
|
+
if (route.sources[normalizedMethod] === sourceIndex) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
'Duplicate API route for ' + normalizedMethod + ' ' + routePath + ': ' +
|
|
203
|
+
route.files[normalizedMethod] + ' conflicts with ' + filePath
|
|
204
|
+
);
|
|
205
|
+
}
|
|
179
206
|
if (!route.methods.includes(normalizedMethod)) route.methods.push(normalizedMethod);
|
|
180
207
|
route.handlers[normalizedMethod] = endpoint;
|
|
181
208
|
route.files[normalizedMethod] = filePath;
|
|
209
|
+
route.sources[normalizedMethod] = sourceIndex;
|
|
182
210
|
}
|
|
183
211
|
|
|
184
212
|
function getProgrammaticApiRoutes(routeModule) {
|
|
@@ -206,7 +234,7 @@ function registerApiRouteSources(fileModules, definitionModules, sourceCount) {
|
|
|
206
234
|
const routePath = relativePath.replace(/\\/route\\.[tj]sx?$/i, '') || '/api';
|
|
207
235
|
for (const method of apiRouteMethods) {
|
|
208
236
|
if (typeof routeModule?.[method] === 'function') {
|
|
209
|
-
registerApiEndpoint(routePath, filePath, method, routeModule[method]);
|
|
237
|
+
registerApiEndpoint(routePath, filePath, method, routeModule[method], sourceIndex);
|
|
210
238
|
}
|
|
211
239
|
}
|
|
212
240
|
}
|
|
@@ -216,13 +244,13 @@ function registerApiRouteSources(fileModules, definitionModules, sourceCount) {
|
|
|
216
244
|
const { filePath, module: routeModule } = entry;
|
|
217
245
|
for (const endpoint of Object.values(routeModule)) {
|
|
218
246
|
if (typeof endpoint === 'function' && endpoint.__path) {
|
|
219
|
-
registerApiEndpoint(endpoint.__path, filePath, endpoint.__method || 'GET', endpoint);
|
|
247
|
+
registerApiEndpoint(endpoint.__path, filePath, endpoint.__method || 'GET', endpoint, sourceIndex);
|
|
220
248
|
}
|
|
221
249
|
}
|
|
222
250
|
|
|
223
251
|
for (const route of getProgrammaticApiRoutes(routeModule)) {
|
|
224
252
|
for (const [method, endpoint] of Object.entries(route.methods || {})) {
|
|
225
|
-
registerApiEndpoint(route.path, filePath, method, endpoint);
|
|
253
|
+
registerApiEndpoint(route.path, filePath, method, endpoint, sourceIndex);
|
|
226
254
|
}
|
|
227
255
|
}
|
|
228
256
|
}
|
|
@@ -233,15 +261,19 @@ registerApiRouteSources(apiRouteModules, routeDefinitionModules, ${routeSourceRo
|
|
|
233
261
|
|
|
234
262
|
async function handleAPIRequest(request) {
|
|
235
263
|
const url = new URL(request.url);
|
|
236
|
-
const match =
|
|
264
|
+
const match = matchAPIRouteAtBasePath(apiRouteMap, url.pathname, farmApiBasePath);
|
|
237
265
|
if (!match) return null;
|
|
238
266
|
|
|
239
267
|
const method = request.method.toUpperCase();
|
|
240
|
-
const endpoint = match.route.handlers[method]
|
|
268
|
+
const endpoint = match.route.handlers[method] ??
|
|
269
|
+
(method === 'HEAD' ? match.route.handlers.GET : undefined);
|
|
241
270
|
if (!endpoint) {
|
|
242
271
|
return new Response(JSON.stringify({ error: 'Method Not Allowed' }), {
|
|
243
272
|
status: 405,
|
|
244
|
-
headers: {
|
|
273
|
+
headers: {
|
|
274
|
+
'Allow': getAllowedAPIRouteMethods(match.route).join(', '),
|
|
275
|
+
'Content-Type': 'application/json',
|
|
276
|
+
},
|
|
245
277
|
});
|
|
246
278
|
}
|
|
247
279
|
|
|
@@ -272,57 +304,6 @@ debug('Discovered errors:', Object.keys(errors));
|
|
|
272
304
|
debug('Discovered middlewares:', Object.keys(middlewares));
|
|
273
305
|
debug('Discovered API routes:', Array.from(apiRouteMap.keys()));
|
|
274
306
|
|
|
275
|
-
/**
|
|
276
|
-
* Convert loading/error file path to route pattern (segment the boundary applies to).
|
|
277
|
-
* Inlined to avoid runtime dependency on plugin package; logic is simple and covered by e2e.
|
|
278
|
-
*/
|
|
279
|
-
function boundaryPathToRoute(filePath, globVal, kind) {
|
|
280
|
-
const re = kind === 'loading' ? /\\/loading\\.[tj]sx?$/i : /\\/error\\.[tj]sx?$/i;
|
|
281
|
-
let route = filePath.replace(globVal, '').replace(re, '').replace(/\\\\/g, '/') || '/';
|
|
282
|
-
route = route.replace(/\\[([^\\]]+)\\]/g, ':$1');
|
|
283
|
-
return route;
|
|
284
|
-
}
|
|
285
|
-
function getMatchingLoading(pathname, globVal) {
|
|
286
|
-
const normalized = pathname.replace(/\\/$/, '') || '/';
|
|
287
|
-
const pathParts = normalized.split('/').filter(Boolean);
|
|
288
|
-
let best = null, bestLength = -1;
|
|
289
|
-
for (const filePath of Object.keys(loadings)) {
|
|
290
|
-
const pattern = boundaryPathToRoute(filePath, globVal, 'loading');
|
|
291
|
-
const patternParts = pattern === '/' ? [] : pattern.split('/').filter(Boolean);
|
|
292
|
-
if (patternParts.length > pathParts.length) continue;
|
|
293
|
-
let matches = true;
|
|
294
|
-
for (let i = 0; i < patternParts.length; i++) {
|
|
295
|
-
const p = patternParts[i], seg = pathParts[i];
|
|
296
|
-
if (!seg || (p.startsWith(':') && p !== ':...') || p === ':...') continue;
|
|
297
|
-
if (!p.startsWith(':') && p !== seg) { matches = false; break; }
|
|
298
|
-
}
|
|
299
|
-
if (matches && patternParts.length > bestLength && loadings[filePath]?.default) {
|
|
300
|
-
best = loadings[filePath].default; bestLength = patternParts.length;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return best;
|
|
304
|
-
}
|
|
305
|
-
function getMatchingError(pathname, globVal) {
|
|
306
|
-
const normalized = pathname.replace(/\\/$/, '') || '/';
|
|
307
|
-
const pathParts = normalized.split('/').filter(Boolean);
|
|
308
|
-
let best = null, bestLength = -1;
|
|
309
|
-
for (const filePath of Object.keys(errors)) {
|
|
310
|
-
const pattern = boundaryPathToRoute(filePath, globVal, 'error');
|
|
311
|
-
const patternParts = pattern === '/' ? [] : pattern.split('/').filter(Boolean);
|
|
312
|
-
if (patternParts.length > pathParts.length) continue;
|
|
313
|
-
let matches = true;
|
|
314
|
-
for (let i = 0; i < patternParts.length; i++) {
|
|
315
|
-
const p = patternParts[i], seg = pathParts[i];
|
|
316
|
-
if (!seg || (p.startsWith(':') && p !== ':...') || p === ':...') continue;
|
|
317
|
-
if (!p.startsWith(':') && p !== seg) { matches = false; break; }
|
|
318
|
-
}
|
|
319
|
-
if (matches && patternParts.length > bestLength && errors[filePath]?.default) {
|
|
320
|
-
best = errors[filePath].default; bestLength = patternParts.length;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
return best;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
307
|
/**
|
|
327
308
|
* Convert middleware file path to route path
|
|
328
309
|
* e.g., '/src/middleware.ts' -> '/'
|
|
@@ -345,7 +326,7 @@ async function executeMiddleware(request) {
|
|
|
345
326
|
/**
|
|
346
327
|
* Convert file path to route pattern
|
|
347
328
|
* e.g., '/src/about/page.tsx' -> '/about'
|
|
348
|
-
* e.g., '/
|
|
329
|
+
* e.g., '/blog/[slug]/page.tsx' -> '/blog/[slug]'
|
|
349
330
|
*/
|
|
350
331
|
function filePathToRoute(filePath) {
|
|
351
332
|
let route = filePath
|
|
@@ -353,9 +334,6 @@ function filePathToRoute(filePath) {
|
|
|
353
334
|
.replace(/\\/page\\.[tj]sx?$/, '')
|
|
354
335
|
.replace(/\\/page$/, '') || '/';
|
|
355
336
|
|
|
356
|
-
// Convert [param] to :param for matching
|
|
357
|
-
route = route.replace(/\\[([^\\]]+)\\]/g, ':$1');
|
|
358
|
-
|
|
359
337
|
return route;
|
|
360
338
|
}
|
|
361
339
|
|
|
@@ -385,52 +363,14 @@ const RouteErrorBoundary = React.Component
|
|
|
385
363
|
return children;
|
|
386
364
|
};
|
|
387
365
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
// Special case for root
|
|
397
|
-
if (pattern === '/' && pathname === '/') {
|
|
398
|
-
return { params: {} };
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
if (patternParts.length !== pathParts.length) {
|
|
402
|
-
// Check for catch-all
|
|
403
|
-
const lastPattern = patternParts[patternParts.length - 1];
|
|
404
|
-
if (!lastPattern?.startsWith(':...')) {
|
|
405
|
-
return null;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const params = {};
|
|
410
|
-
|
|
411
|
-
for (let i = 0; i < patternParts.length; i++) {
|
|
412
|
-
const patternPart = patternParts[i];
|
|
413
|
-
const pathPart = pathParts[i];
|
|
414
|
-
|
|
415
|
-
if (patternPart.startsWith(':...')) {
|
|
416
|
-
// Catch-all segment
|
|
417
|
-
const paramName = patternPart.slice(4);
|
|
418
|
-
params[paramName] = pathParts.slice(i).join('/');
|
|
419
|
-
return { params };
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
if (patternPart.startsWith(':')) {
|
|
423
|
-
// Dynamic segment
|
|
424
|
-
const paramName = patternPart.slice(1);
|
|
425
|
-
params[paramName] = pathPart;
|
|
426
|
-
} else if (patternPart !== pathPart) {
|
|
427
|
-
// Static segment mismatch
|
|
428
|
-
return null;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
return { params };
|
|
433
|
-
}
|
|
366
|
+
// Parse and rank once per entry initialization, not on every request. Use the
|
|
367
|
+
// same validation, decoding and specificity rules as Farm's ordinary router.
|
|
368
|
+
const pageRoutes = Object.entries(pages).map(([filePath, module]) => ({
|
|
369
|
+
filePath,
|
|
370
|
+
module,
|
|
371
|
+
segments: parseRoutePath(filePath).segments,
|
|
372
|
+
specificity: getRoutePatternSpecificity(filePathToRoute(filePath)),
|
|
373
|
+
})).sort((left, right) => compareRouteSpecificity(left.specificity, right.specificity));
|
|
434
374
|
|
|
435
375
|
/**
|
|
436
376
|
* Simple file-based router
|
|
@@ -439,17 +379,16 @@ function matchPath(pattern, pathname) {
|
|
|
439
379
|
function matchRoute(pathname) {
|
|
440
380
|
const normalized = pathname.replace(/\\/$/, '') || '/';
|
|
441
381
|
|
|
442
|
-
for (const filePath of
|
|
443
|
-
const
|
|
444
|
-
const match = matchPath(pattern, normalized);
|
|
382
|
+
for (const { filePath, module, segments } of pageRoutes) {
|
|
383
|
+
const match = matchFarmPageRoute(normalized, segments);
|
|
445
384
|
|
|
446
|
-
if (match) {
|
|
447
|
-
debug('Matched route:',
|
|
385
|
+
if (match.matches) {
|
|
386
|
+
debug('Matched route:', filePath, 'for path:', normalized);
|
|
448
387
|
return {
|
|
449
|
-
Page:
|
|
388
|
+
Page: module.default,
|
|
450
389
|
pattern: filePath,
|
|
451
390
|
params: match.params,
|
|
452
|
-
pageMetadata:
|
|
391
|
+
pageMetadata: module.metadata,
|
|
453
392
|
};
|
|
454
393
|
}
|
|
455
394
|
}
|
|
@@ -468,14 +407,14 @@ function mergeDocumentMetadata(...sources) {
|
|
|
468
407
|
}
|
|
469
408
|
|
|
470
409
|
/**
|
|
471
|
-
* Find
|
|
472
|
-
*
|
|
473
|
-
*
|
|
410
|
+
* Find modules along the selected page's actual file ancestry, root to leaf.
|
|
411
|
+
* Keep route groups and catch-all folders; matching URL prefixes can pick
|
|
412
|
+
* boundaries from a sibling page that the router did not select.
|
|
474
413
|
*/
|
|
475
|
-
function
|
|
414
|
+
function getRouteModules(pageFilePath, modules, kind) {
|
|
476
415
|
const tryKeys = (...keys) => {
|
|
477
416
|
for (const k of keys) {
|
|
478
|
-
if (
|
|
417
|
+
if (modules[k]?.default) return modules[k];
|
|
479
418
|
}
|
|
480
419
|
return null;
|
|
481
420
|
};
|
|
@@ -487,26 +426,41 @@ function getLayoutModules(pageFilePath) {
|
|
|
487
426
|
for (let depth = 0; depth <= parts.length; depth++) {
|
|
488
427
|
const relativeDir = parts.slice(0, depth).join('/');
|
|
489
428
|
const absoluteDir = relativeDir ? '/' + relativeDir : '';
|
|
490
|
-
let
|
|
429
|
+
let matchedModule = null;
|
|
491
430
|
for (const ext of extensions) {
|
|
492
|
-
|
|
493
|
-
absoluteDir + '/
|
|
494
|
-
relativeDir ? relativeDir + '/
|
|
431
|
+
matchedModule = tryKeys(
|
|
432
|
+
absoluteDir + '/' + kind + '.' + ext,
|
|
433
|
+
relativeDir ? relativeDir + '/' + kind + '.' + ext : kind + '.' + ext,
|
|
495
434
|
);
|
|
496
|
-
if (
|
|
435
|
+
if (matchedModule) break;
|
|
497
436
|
}
|
|
498
|
-
if (
|
|
437
|
+
if (matchedModule) matches.push(matchedModule);
|
|
499
438
|
}
|
|
500
439
|
|
|
501
440
|
return matches;
|
|
502
441
|
}
|
|
503
442
|
|
|
443
|
+
function getLayoutModules(pageFilePath) {
|
|
444
|
+
return getRouteModules(pageFilePath, layouts, 'layout');
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function getMatchingBoundary(pageFilePath, modules, kind) {
|
|
448
|
+
// Before page selection (for example, middleware failure), only the root
|
|
449
|
+
// boundary applies. Do not guess a route from a partially processed URL.
|
|
450
|
+
const matches = getRouteModules(pageFilePath ?? '/page.tsx', modules, kind);
|
|
451
|
+
return matches.at(-1)?.default ?? null;
|
|
452
|
+
}
|
|
453
|
+
|
|
504
454
|
/**
|
|
505
455
|
* Main request handler - entry point for both dev and production (Nitro).
|
|
506
456
|
* Exported as { fetch: handler } for the RSC/Nitro contract (see vite-plugin-rsc-deploy-example).
|
|
507
457
|
*/
|
|
508
458
|
async function handleFarmRequest(request) {
|
|
509
459
|
let url = new URL(request.url);
|
|
460
|
+
let matchedPage = null;
|
|
461
|
+
let errorData = new Map();
|
|
462
|
+
let errorContext = new Map();
|
|
463
|
+
let errorHeaders = new Headers();
|
|
510
464
|
try {
|
|
511
465
|
debug('Handling request:', request.method, url.pathname);
|
|
512
466
|
|
|
@@ -515,9 +469,9 @@ async function handleFarmRequest(request) {
|
|
|
515
469
|
return createFarmDeploymentMismatchResponse(deploymentMismatch);
|
|
516
470
|
}
|
|
517
471
|
|
|
518
|
-
const initialApiMatch =
|
|
472
|
+
const initialApiMatch = matchAPIRouteAtBasePath(apiRouteMap, url.pathname, farmApiBasePath);
|
|
519
473
|
const isInitialApiRequest = Boolean(initialApiMatch) ||
|
|
520
|
-
url.pathname
|
|
474
|
+
isFarmAPIPathname(url.pathname, farmApiBasePath) || isFarmAPIPathname(url.pathname);
|
|
521
475
|
|
|
522
476
|
${ctx.actionsEnabled
|
|
523
477
|
? `if (request.method === 'POST' && !isInitialApiRequest) {
|
|
@@ -553,6 +507,9 @@ async function handleFarmRequest(request) {
|
|
|
553
507
|
const middlewareData = Object.fromEntries(middlewareResult.data);
|
|
554
508
|
const middlewareContext = middlewareResult.context;
|
|
555
509
|
const middlewareHeaders = new Headers(middlewareResult.headers);
|
|
510
|
+
errorData = middlewareResult.data;
|
|
511
|
+
errorContext = middlewareContext;
|
|
512
|
+
errorHeaders = middlewareHeaders;
|
|
556
513
|
if (middlewareResult.data.size || middlewareContext.size) {
|
|
557
514
|
middlewareHeaders.set('cache-control', 'private, no-store');
|
|
558
515
|
}
|
|
@@ -568,7 +525,7 @@ async function handleFarmRequest(request) {
|
|
|
568
525
|
if (apiResponse) {
|
|
569
526
|
return applyProductionMiddlewareHeaders(apiResponse, middlewareHeaders);
|
|
570
527
|
}
|
|
571
|
-
if (url.pathname
|
|
528
|
+
if (isFarmAPIPathname(url.pathname, farmApiBasePath) || isFarmAPIPathname(url.pathname)) {
|
|
572
529
|
return applyProductionMiddlewareHeaders(new Response(
|
|
573
530
|
JSON.stringify({ error: 'API route not found', pathname: url.pathname }),
|
|
574
531
|
{ status: 404, headers: { 'Content-Type': 'application/json' } },
|
|
@@ -699,6 +656,7 @@ async function handleFarmRequest(request) {
|
|
|
699
656
|
code += `
|
|
700
657
|
// Match the URL to a page component
|
|
701
658
|
const matched = matchRoute(url.pathname);
|
|
659
|
+
matchedPage = matched;
|
|
702
660
|
|
|
703
661
|
if (!matched) {
|
|
704
662
|
debug('404 - No route found for:', url.pathname);
|
|
@@ -707,8 +665,6 @@ async function handleFarmRequest(request) {
|
|
|
707
665
|
|
|
708
666
|
const { Page, pattern, params, pageMetadata } = matched;
|
|
709
667
|
const LayoutModules = getLayoutModules(pattern);
|
|
710
|
-
const LayoutModule = LayoutModules[LayoutModules.length - 1];
|
|
711
|
-
const Layout = LayoutModule?.default || (function PassThrough({ children }) { return children; });
|
|
712
668
|
const metadata = mergeDocumentMetadata(
|
|
713
669
|
...LayoutModules.map((layoutModule) => layoutModule.metadata),
|
|
714
670
|
pageMetadata,
|
|
@@ -731,23 +687,25 @@ async function handleFarmRequest(request) {
|
|
|
731
687
|
// Helper to create elements without JSX
|
|
732
688
|
const h = React.createElement;
|
|
733
689
|
|
|
734
|
-
// Render page content -
|
|
690
|
+
// Render page content - only native async functions need direct awaiting.
|
|
691
|
+
// Synchronous components must retain React's hook dispatcher. Source text
|
|
692
|
+
// containing the word async does not change how a component executes.
|
|
735
693
|
let pageContent;
|
|
736
|
-
if (Page.constructor.name === 'AsyncFunction'
|
|
694
|
+
if (Page.constructor.name === 'AsyncFunction') {
|
|
737
695
|
pageContent = await Page(pageProps);
|
|
738
696
|
} else {
|
|
739
697
|
pageContent = h(Page, pageProps);
|
|
740
698
|
}
|
|
741
699
|
|
|
742
700
|
// Route-level loading boundary: wrap in Suspense so async content shows fallback
|
|
743
|
-
const LoadingComponent =
|
|
701
|
+
const LoadingComponent = getMatchingBoundary(pattern, loadings, 'loading');
|
|
744
702
|
if (LoadingComponent) {
|
|
745
703
|
const loadingFallback = h(LoadingComponent, { params, path: url.pathname });
|
|
746
704
|
pageContent = h(React.Suspense, { fallback: loadingFallback }, pageContent);
|
|
747
705
|
}
|
|
748
706
|
|
|
749
707
|
// Route-level error boundary (Next.js error.tsx): catches render errors in this segment
|
|
750
|
-
const ErrorComponent =
|
|
708
|
+
const ErrorComponent = getMatchingBoundary(pattern, errors, 'error');
|
|
751
709
|
if (ErrorComponent) {
|
|
752
710
|
pageContent = h(RouteErrorBoundary, {
|
|
753
711
|
Fallback: ErrorComponent,
|
|
@@ -756,12 +714,16 @@ async function handleFarmRequest(request) {
|
|
|
756
714
|
});
|
|
757
715
|
}
|
|
758
716
|
|
|
759
|
-
// Render
|
|
760
|
-
let layoutContent;
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
717
|
+
// Render layouts inside out so every ancestor wraps its descendants.
|
|
718
|
+
let layoutContent = pageContent;
|
|
719
|
+
for (let index = LayoutModules.length - 1; index >= 0; index--) {
|
|
720
|
+
const Layout = LayoutModules[index].default;
|
|
721
|
+
const layoutProps = { params: pageProps.params, children: layoutContent };
|
|
722
|
+
if (Layout.constructor.name === 'AsyncFunction') {
|
|
723
|
+
layoutContent = await Layout(layoutProps);
|
|
724
|
+
} else {
|
|
725
|
+
layoutContent = h(Layout, layoutProps);
|
|
726
|
+
}
|
|
765
727
|
}
|
|
766
728
|
|
|
767
729
|
// Single wrapper so #root has exactly one child (avoids duplicate block / "two pages" in DOM).
|
|
@@ -861,6 +823,9 @@ async function handleFarmRequest(request) {
|
|
|
861
823
|
)
|
|
862
824
|
);
|
|
863
825
|
} catch (err) {
|
|
826
|
+
const redirect = getFarmRedirectError(err);
|
|
827
|
+
if (redirect) return new Response(null, { status: redirect.status, headers: { location: redirect.url, 'cache-control': 'no-store' } });
|
|
828
|
+
if (isFarmNotFoundError(err)) return new Response('Not Found', { status: 404, headers: { 'cache-control': 'no-store' } });
|
|
864
829
|
console.error('[RSC] Handler error:', err);
|
|
865
830
|
if (request.method === 'POST') {
|
|
866
831
|
if (request.signal.aborted) {
|
|
@@ -875,50 +840,71 @@ async function handleFarmRequest(request) {
|
|
|
875
840
|
},
|
|
876
841
|
});
|
|
877
842
|
}
|
|
878
|
-
//
|
|
843
|
+
// Render outside the failed page/layout tree, but retain request context.
|
|
879
844
|
const pathname = url.pathname.replace(/\\/$/, '') || '/';
|
|
880
|
-
const ErrorComponent =
|
|
845
|
+
const ErrorComponent = getMatchingBoundary(matchedPage?.pattern, errors, 'error');
|
|
881
846
|
if (ErrorComponent) {
|
|
882
847
|
try {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
848
|
+
return await _runWithCurrentRequest(request, () =>
|
|
849
|
+
_runWithMiddlewareData(errorData, () =>
|
|
850
|
+
_runWithMiddlewareContext(errorContext, async () => {
|
|
851
|
+
const h = React.createElement;
|
|
852
|
+
const errSearchParams = searchParamsToObject(url.searchParams);
|
|
853
|
+
const fallbackProps = {
|
|
854
|
+
params: matchedPage?.params || {},
|
|
855
|
+
path: pathname,
|
|
856
|
+
search: url.search,
|
|
857
|
+
searchParams: errSearchParams,
|
|
858
|
+
middlewareData: Object.fromEntries(errorData),
|
|
859
|
+
};
|
|
860
|
+
// Preserve the original failure in logs, not production payloads.
|
|
861
|
+
const message = ${ctx.development === true ? "true" : "false"} && err instanceof Error
|
|
862
|
+
? err.message : 'Internal Server Error';
|
|
863
|
+
const isClientBoundary = ErrorComponent.$$typeof === Symbol.for('react.client.reference');
|
|
864
|
+
const errorElement = isClientBoundary
|
|
865
|
+
? h(ServerErrorFallback, { Fallback: ErrorComponent, message, fallbackProps })
|
|
866
|
+
: h(ErrorComponent, { ...fallbackProps, error: new Error(message), reset: () => {} });
|
|
867
|
+
const rootInner = h('div', { 'data-farm-root': 'true' }, errorElement);
|
|
868
|
+
const payload = {
|
|
869
|
+
root: h('html', null,
|
|
870
|
+
h('head', null, h('meta', { charSet: 'utf-8' }), h('title', null, 'Error')),
|
|
871
|
+
h('body', null, h('div', { id: 'root' }, rootInner))),
|
|
872
|
+
rootContent: rootInner,
|
|
873
|
+
metadata: { title: 'Error' },
|
|
874
|
+
};
|
|
875
|
+
const rscStream = renderToReadableStream(payload);
|
|
876
|
+
const headers = new Headers(errorHeaders);
|
|
877
|
+
headers.set('cache-control', 'private, no-store');
|
|
878
|
+
headers.set('x-content-type-options', 'nosniff');
|
|
879
|
+
applyActionResponseHeaders(headers, request);
|
|
880
|
+
if ((request.headers.get('accept') || '').includes('text/x-component')) {
|
|
881
|
+
headers.set('content-type', 'text/x-component');
|
|
882
|
+
return new Response(rscStream, { status: 500, headers });
|
|
883
|
+
}
|
|
884
|
+
let ssr;
|
|
885
|
+
if (typeof import.meta.viteRsc?.loadModule === 'function') {
|
|
886
|
+
ssr = await import.meta.viteRsc.loadModule('ssr', 'index');
|
|
887
|
+
} else if (typeof globalThis.__VITE_RSC_LOAD_SSR__ === 'function') {
|
|
888
|
+
ssr = await globalThis.__VITE_RSC_LOAD_SSR__();
|
|
889
|
+
}
|
|
890
|
+
if (!ssr) {
|
|
891
|
+
void rscStream.cancel().catch(() => {});
|
|
892
|
+
throw new Error('SSR environment unavailable');
|
|
893
|
+
}
|
|
894
|
+
const html = await ssr.renderHTML({ payload, rscStream });
|
|
895
|
+
headers.set('content-type', 'text/html; charset=utf-8');
|
|
896
|
+
return new Response(html, { status: 500, headers });
|
|
897
|
+
})
|
|
898
|
+
)
|
|
910
899
|
);
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
return new Response(html, { status: 500, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
|
|
914
|
-
} catch (e) {
|
|
915
|
-
console.error('[RSC] Error boundary render failed:', e);
|
|
900
|
+
} catch (fallbackError) {
|
|
901
|
+
console.error('[RSC] Error boundary render failed:', fallbackError);
|
|
916
902
|
}
|
|
917
903
|
}
|
|
918
904
|
const message = 'Internal Server Error';
|
|
919
905
|
return new Response(JSON.stringify({ error: true, url: request.url, status: 500, message }), {
|
|
920
906
|
status: 500,
|
|
921
|
-
headers: { 'Content-Type': 'application/json' },
|
|
907
|
+
headers: { 'Content-Type': 'application/json', 'Cache-Control': 'private, no-store' },
|
|
922
908
|
});
|
|
923
909
|
}
|
|
924
910
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr.d.ts","sourceRoot":"","sources":["../../../src/rsc/entries/ssr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"ssr.d.ts","sourceRoot":"","sources":["../../../src/rsc/entries/ssr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CA+M1D"}
|
package/dist/rsc/entries/ssr.js
CHANGED
|
@@ -40,43 +40,57 @@ const cssLinkTag = resolvedCssHref
|
|
|
40
40
|
? '<link rel="stylesheet" href="' + escapeHtmlAttribute(resolvedCssHref) + '" as="style" data-precedence="default">'
|
|
41
41
|
: '';
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
// Work on bytes: chunk boundaries need not align with UTF-8 code points.
|
|
44
|
+
function injectBeforeStream(markerText, tag) {
|
|
44
45
|
const encoder = new TextEncoder();
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
let buffer =
|
|
46
|
+
const marker = encoder.encode(markerText);
|
|
47
|
+
const insertion = encoder.encode(tag);
|
|
48
|
+
let buffer = new Uint8Array(0);
|
|
48
49
|
let injected = false;
|
|
49
50
|
|
|
50
51
|
return new TransformStream({
|
|
51
52
|
transform(chunk, controller) {
|
|
52
|
-
const
|
|
53
|
+
const bytes = typeof chunk === 'string' ? encoder.encode(chunk) : chunk;
|
|
53
54
|
if (injected || !tag) {
|
|
54
|
-
controller.enqueue(
|
|
55
|
+
controller.enqueue(bytes);
|
|
55
56
|
return;
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
buffer
|
|
59
|
-
|
|
59
|
+
const pending = new Uint8Array(buffer.length + bytes.length);
|
|
60
|
+
pending.set(buffer);
|
|
61
|
+
pending.set(bytes, buffer.length);
|
|
62
|
+
let index = -1;
|
|
63
|
+
outer: for (let i = 0; i <= pending.length - marker.length; i++) {
|
|
64
|
+
for (let j = 0; j < marker.length; j++) {
|
|
65
|
+
if (pending[i + j] !== marker[j]) continue outer;
|
|
66
|
+
}
|
|
67
|
+
index = i;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
60
70
|
if (index >= 0) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
controller.enqueue(
|
|
64
|
-
buffer =
|
|
71
|
+
if (index) controller.enqueue(pending.subarray(0, index));
|
|
72
|
+
controller.enqueue(insertion);
|
|
73
|
+
controller.enqueue(pending.subarray(index));
|
|
74
|
+
buffer = new Uint8Array(0);
|
|
65
75
|
injected = true;
|
|
66
76
|
return;
|
|
67
77
|
}
|
|
68
78
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
// Retain only a possible marker prefix, never the whole response.
|
|
80
|
+
const emitLength = Math.max(0, pending.length - marker.length + 1);
|
|
81
|
+
if (emitLength) controller.enqueue(pending.subarray(0, emitLength));
|
|
82
|
+
buffer = pending.slice(emitLength);
|
|
73
83
|
},
|
|
74
84
|
flush(controller) {
|
|
75
|
-
if (buffer) controller.enqueue(
|
|
85
|
+
if (buffer.length) controller.enqueue(buffer);
|
|
76
86
|
},
|
|
77
87
|
});
|
|
78
88
|
}
|
|
79
89
|
|
|
90
|
+
function injectIntoHeadStream(tag) {
|
|
91
|
+
return injectBeforeStream('</head>', tag);
|
|
92
|
+
}
|
|
93
|
+
|
|
80
94
|
/** Collect Node stream (from renderToPipeableStream) into a single string. Supports Suspense. */
|
|
81
95
|
function pipeableStreamToString(stream) {
|
|
82
96
|
return new Promise((resolve, reject) => {
|
|
@@ -125,13 +139,19 @@ export async function renderHTML(firstArg, options = {}) {
|
|
|
125
139
|
if (!bootstrap && PROD_BOOTSTRAP !== "__FARM_BOOTSTRAP_SCRIPT__") bootstrap = PROD_BOOTSTRAP;
|
|
126
140
|
|
|
127
141
|
// Use renderToPipeableStream so Suspense streams: fallback (loading.tsx) first, then resolved content.
|
|
128
|
-
const { pipe } = renderToPipeableStream(React.createElement(Root), {
|
|
129
|
-
...(bootstrap && { bootstrapScriptContent: bootstrap }),
|
|
130
|
-
formState: options.formState,
|
|
131
|
-
});
|
|
132
142
|
const { PassThrough, Readable } = await import('stream');
|
|
133
143
|
const passThrough = new PassThrough();
|
|
134
|
-
|
|
144
|
+
// Do not commit an empty response when the shell cannot render. Reject back
|
|
145
|
+
// to the request handler so it can render error.tsx through RSC and SSR.
|
|
146
|
+
await new Promise((resolve, reject) => {
|
|
147
|
+
const { pipe } = renderToPipeableStream(React.createElement(Root), {
|
|
148
|
+
...(bootstrap && { bootstrapScriptContent: bootstrap }),
|
|
149
|
+
formState: options.formState,
|
|
150
|
+
onShellReady() { pipe(passThrough); resolve(); },
|
|
151
|
+
onShellError: reject,
|
|
152
|
+
onError(error) { console.error('[RSC] SSR render error:', error); },
|
|
153
|
+
});
|
|
154
|
+
});
|
|
135
155
|
|
|
136
156
|
const CLIENT_ENTRY_HREF = "__FARM_CLIENT_ENTRY_HREF__";
|
|
137
157
|
const PLACEHOLDER_STR = "__FARM_CLIENT_ENTRY_HREF__";
|
|
@@ -141,34 +161,7 @@ export async function renderHTML(firstArg, options = {}) {
|
|
|
141
161
|
if (hasPayload) {
|
|
142
162
|
// Stream HTML to the client while preserving the RSC payload for hydration.
|
|
143
163
|
debug('Streaming HTML (loading fallback then content)');
|
|
144
|
-
const
|
|
145
|
-
const TAG_LEN = BODY_HTML_END.length;
|
|
146
|
-
const encoder = new TextEncoder();
|
|
147
|
-
let streamBuf = '';
|
|
148
|
-
const streamingClientScriptInjector = new TransformStream({
|
|
149
|
-
transform(chunk, controller) {
|
|
150
|
-
const str = typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk);
|
|
151
|
-
if (!injectClientScriptInStream || !clientScriptTag) {
|
|
152
|
-
controller.enqueue(typeof chunk === "string" ? encoder.encode(str) : chunk);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
streamBuf += str;
|
|
156
|
-
while (streamBuf.length > TAG_LEN) {
|
|
157
|
-
const tail = streamBuf.slice(-TAG_LEN);
|
|
158
|
-
if (tail === BODY_HTML_END) {
|
|
159
|
-
const before = streamBuf.slice(0, streamBuf.length - TAG_LEN);
|
|
160
|
-
controller.enqueue(encoder.encode(before + clientScriptTag + BODY_HTML_END));
|
|
161
|
-
streamBuf = '';
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
controller.enqueue(encoder.encode(streamBuf.slice(0, streamBuf.length - TAG_LEN)));
|
|
165
|
-
streamBuf = streamBuf.slice(-TAG_LEN);
|
|
166
|
-
}
|
|
167
|
-
},
|
|
168
|
-
flush(controller) {
|
|
169
|
-
if (streamBuf) controller.enqueue(encoder.encode(streamBuf));
|
|
170
|
-
}
|
|
171
|
-
});
|
|
164
|
+
const streamingClientScriptInjector = injectBeforeStream('</body></html>', clientScriptTag);
|
|
172
165
|
const webStream = typeof Readable.toWeb === 'function'
|
|
173
166
|
? Readable.toWeb(passThrough)
|
|
174
167
|
: new ReadableStream({
|
|
@@ -207,30 +200,7 @@ export async function renderHTML(firstArg, options = {}) {
|
|
|
207
200
|
controller.close();
|
|
208
201
|
}
|
|
209
202
|
});
|
|
210
|
-
|
|
211
|
-
let clientScriptInjected = false;
|
|
212
|
-
const injectClientScriptStream = new TransformStream({
|
|
213
|
-
transform(chunk, controller) {
|
|
214
|
-
const str = typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk);
|
|
215
|
-
if (!injectClientScriptInStream || clientScriptInjected) {
|
|
216
|
-
controller.enqueue(chunk);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
clientBuffer += str;
|
|
220
|
-
if (clientBuffer.includes("</body></html>")) {
|
|
221
|
-
const href = (typeof CLIENT_ENTRY_HREF === "string" && CLIENT_ENTRY_HREF.length > 0 && CLIENT_ENTRY_HREF.indexOf(PLACEHOLDER_STR) < 0) ? CLIENT_ENTRY_HREF : "";
|
|
222
|
-
const out = href ? clientBuffer.replace("</body></html>", '<script type="module" src="' + href + '"></script></body></html>') : clientBuffer;
|
|
223
|
-
controller.enqueue(new TextEncoder().encode(out));
|
|
224
|
-
clientBuffer = "";
|
|
225
|
-
clientScriptInjected = true;
|
|
226
|
-
}
|
|
227
|
-
},
|
|
228
|
-
flush(controller) {
|
|
229
|
-
if (clientBuffer) {
|
|
230
|
-
controller.enqueue(new TextEncoder().encode(clientBuffer));
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
});
|
|
203
|
+
const injectClientScriptStream = injectBeforeStream('</body></html>', clientScriptTag);
|
|
234
204
|
|
|
235
205
|
let out = streamFromHtml.pipeThrough(injectRSCPayload(s2));
|
|
236
206
|
out = out.pipeThrough(injectClientScriptStream);
|
package/dist/rsc/index.d.ts
CHANGED
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
import { type Plugin, type UserConfig } from "vite";
|
|
25
25
|
import type { FarmRscPluginOptions, EntryContext } from "./types.js";
|
|
26
26
|
import type { FarmServerActionsConfig } from "@farm.js/core/server-action-security";
|
|
27
|
+
import type { FarmAPIConfig } from "@farm.js/core/api";
|
|
28
|
+
import type { FarmServerConfig } from "@farm.js/core/internal/production-runtime";
|
|
27
29
|
import type { FarmLayerEntry, ResolvedFarmLayer } from "@farm.js/core/server";
|
|
28
30
|
export type { FarmRscPluginOptions, EntryContext };
|
|
29
31
|
export { buildRscNitro, waitForRscManifest, waitForRscOutputs } from "./nitro-build.js";
|
|
@@ -36,6 +38,8 @@ export interface FarmRscConfig {
|
|
|
36
38
|
layers?: readonly ResolvedFarmLayer[];
|
|
37
39
|
outDir?: string;
|
|
38
40
|
basePath?: string;
|
|
41
|
+
api?: FarmAPIConfig;
|
|
42
|
+
server?: FarmServerConfig;
|
|
39
43
|
port?: number;
|
|
40
44
|
experimental?: {
|
|
41
45
|
/**
|
package/dist/rsc/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rsc/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAA4B,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAE9E,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rsc/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAA4B,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAE9E,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAClF,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAmD9E,YAAY,EAAE,oBAAoB,EAAE,YAAY,EAAE,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAExF,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAGjE,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACpC,MAAM,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE;QACb;;;WAGG;QACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAC3B;;;WAGG;QACH,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB;;;;;;;WAOG;QACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;KAC7B,CAAC;IACF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE;QACR,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,gHAAgH;IAChH,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,GAAE,aAAkB,GAAG,UAAU,CAuDnE;AA8TD;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,OAAO,GAAE,oBAAyB,GAAG,MAAM,EAAE,CAksC5E"}
|
package/dist/rsc/index.js
CHANGED
|
@@ -26,12 +26,14 @@ import { init as initModuleLexer, parse as parseModuleImports } from "es-module-
|
|
|
26
26
|
import { farmEnvironmentFunctionsPlugin } from "@farm.js/core/environment/vite";
|
|
27
27
|
import { generateRscEntry } from "./entries/rsc.js";
|
|
28
28
|
import { generateSsrEntry } from "./entries/ssr.js";
|
|
29
|
+
import { generateErrorFallbackEntry } from "./entries/error-fallback.js";
|
|
29
30
|
import { generateClientEntry, serverFnTransportErrorClientRuntime } from "./entries/client.js";
|
|
30
31
|
import { transformFarmRouteActionClients } from "./route-action-transform.js";
|
|
31
32
|
import { transformFarmServerFns } from "./server-fn-transform.js";
|
|
32
33
|
import { transformAutomaticOptimizedBoundaries } from "./automatic-optimized-boundary.js";
|
|
33
34
|
import { resolveRscBuildOutputPath } from "./build-paths.js";
|
|
34
35
|
import { assertRscPackageCompatibility } from "./compatibility.js";
|
|
36
|
+
import { sendRscDevelopmentResponse } from "./dev-response.js";
|
|
35
37
|
import fs from "fs/promises";
|
|
36
38
|
import path from "path";
|
|
37
39
|
import { devServableFileExists } from "../dev-static.js";
|
|
@@ -40,10 +42,13 @@ import { createRequire } from "node:module";
|
|
|
40
42
|
// Use API and middleware plugins from @farm.js/core (require so CJS build resolves when ESM .mjs is missing)
|
|
41
43
|
const require_ = createRequire(import.meta.url);
|
|
42
44
|
const { farmApiPlugin, farmMiddlewarePlugin } = require_("@farm.js/core");
|
|
43
|
-
const { resolveServerActionsConfig } = require_("@farm.js/core/server-action-security");
|
|
45
|
+
const { resolveServerActionsConfig, validateServerActionRequest, createServerActionRequestErrorResponse, } = require_("@farm.js/core/server-action-security");
|
|
44
46
|
const { normalizeFarmDeploymentId } = require_("@farm.js/core/deployment");
|
|
45
47
|
const { getFarmLayerAliases, getFarmSourceRoots, resolveFarmLayers } = require_("@farm.js/core/server");
|
|
46
|
-
const { searchParamsToObject } = require_("@farm.js/core/internal/production-runtime");
|
|
48
|
+
const { searchParamsToObject, readNodeRequestBody, resolveFarmServerConfig, createFarmRequestBodyErrorResponse, } = require_("@farm.js/core/internal/production-runtime");
|
|
49
|
+
const { isFarmAPIPathname } = require_("@farm.js/core/api/runtime");
|
|
50
|
+
const { resolveFarmAPIConfig, resolveFarmAPIServerBasePath } = require_("@farm.js/core/api");
|
|
51
|
+
const { getResolvedEnv } = require_("@farm.js/core/env");
|
|
47
52
|
export { buildRscNitro, waitForRscManifest, waitForRscOutputs } from "./nitro-build.js";
|
|
48
53
|
import { registerRscNitroRuntimePackage } from "./nitro-build.js";
|
|
49
54
|
export { default as nitro } from "./vite-plugin-nitro.js";
|
|
@@ -66,11 +71,13 @@ export function defineConfig(config = {}) {
|
|
|
66
71
|
layers: config.layers,
|
|
67
72
|
outDir: config.outDir ?? "dist",
|
|
68
73
|
basePath: config.basePath ?? "/",
|
|
74
|
+
api: config.api,
|
|
69
75
|
serverActions: config.serverActions,
|
|
70
76
|
deploymentId: config.deploymentId,
|
|
71
77
|
generateBuildId: config.generateBuildId,
|
|
72
78
|
// Vite server configuration
|
|
73
79
|
server: {
|
|
80
|
+
...config.server,
|
|
74
81
|
port,
|
|
75
82
|
strictPort: false,
|
|
76
83
|
},
|
|
@@ -83,7 +90,11 @@ export function defineConfig(config = {}) {
|
|
|
83
90
|
},
|
|
84
91
|
plugins: [
|
|
85
92
|
farmMiddlewarePlugin({ srcDir: config.srcDir ?? "src", debug }),
|
|
86
|
-
farmApiPlugin({
|
|
93
|
+
farmApiPlugin({
|
|
94
|
+
srcDir: config.srcDir ?? "src",
|
|
95
|
+
debug,
|
|
96
|
+
bodySizeLimit: config.server?.bodySizeLimit,
|
|
97
|
+
}),
|
|
87
98
|
farmRsc({
|
|
88
99
|
debug,
|
|
89
100
|
encryptActions: config.encryptActions,
|
|
@@ -375,6 +386,7 @@ export default function farmRsc(options = {}) {
|
|
|
375
386
|
let rscEnabled = false;
|
|
376
387
|
let actionsEnabled = false;
|
|
377
388
|
let optimizedBoundaryEnabled = false;
|
|
389
|
+
let bodySizeLimit = resolveFarmServerConfig(undefined).bodySizeLimit;
|
|
378
390
|
// Context passed to entry generators
|
|
379
391
|
let entryContext;
|
|
380
392
|
/** Set in config when RSC enabled; used by build plugin to run Nitro after client writeBundle. */
|
|
@@ -474,6 +486,7 @@ export default function farmRsc(options = {}) {
|
|
|
474
486
|
// Check if user enabled RSC in their config
|
|
475
487
|
rscEnabled = c.experimental?.serverComponents === true;
|
|
476
488
|
actionsEnabled = c.experimental?.serverActions === true;
|
|
489
|
+
bodySizeLimit = resolveFarmServerConfig(c.server).bodySizeLimit;
|
|
477
490
|
optimizedBoundaryEnabled = c.experimental?.optimizedBoundary === true;
|
|
478
491
|
logInfo(`RSC enabled: ${rscEnabled}`);
|
|
479
492
|
logInfo(`Actions enabled: ${actionsEnabled}`);
|
|
@@ -498,6 +511,11 @@ export default function farmRsc(options = {}) {
|
|
|
498
511
|
Object.assign(c, layerResolution.config);
|
|
499
512
|
}
|
|
500
513
|
// Read user's directory configuration
|
|
514
|
+
const api = await resolveFarmAPIConfig(c.api, {
|
|
515
|
+
root,
|
|
516
|
+
mode: env.command === "build" ? "production" : "development",
|
|
517
|
+
env: getResolvedEnv(),
|
|
518
|
+
});
|
|
501
519
|
const srcDir = c.srcDir ?? "src";
|
|
502
520
|
const outDir = c.outDir ?? "dist";
|
|
503
521
|
const deploymentId = normalizeFarmDeploymentId(options.deploymentId ||
|
|
@@ -538,6 +556,7 @@ export default function farmRsc(options = {}) {
|
|
|
538
556
|
srcDir,
|
|
539
557
|
outDir,
|
|
540
558
|
basePath: c.basePath ?? "/",
|
|
559
|
+
apiBasePath: resolveFarmAPIServerBasePath(api),
|
|
541
560
|
routesDir: options.routesDir,
|
|
542
561
|
globalCssPath,
|
|
543
562
|
routeRoots,
|
|
@@ -549,6 +568,7 @@ export default function farmRsc(options = {}) {
|
|
|
549
568
|
}),
|
|
550
569
|
deploymentId,
|
|
551
570
|
debug,
|
|
571
|
+
development: env.command === "serve",
|
|
552
572
|
};
|
|
553
573
|
logInfo(`srcDir: ${entryContext.srcDir}, outDir: ${entryContext.outDir}`);
|
|
554
574
|
// Write real entry files so @vitejs/plugin-rsc can use file-based entries.
|
|
@@ -559,6 +579,7 @@ export default function farmRsc(options = {}) {
|
|
|
559
579
|
await fs.writeFile(entryRscPath, generateRscEntry(entryContext));
|
|
560
580
|
await fs.writeFile(entrySsrPath, generateSsrEntry(entryContext));
|
|
561
581
|
await fs.writeFile(entryClientPath, generateClientEntry(entryContext));
|
|
582
|
+
await fs.writeFile(path.join(entriesDir, "error-fallback.tsx"), generateErrorFallbackEntry());
|
|
562
583
|
logInfo(`Wrote RSC entries to ${entriesDir}`);
|
|
563
584
|
// User must add rsc({ entries: { rsc, ssr, client } }) to config.plugins so the RSC plugin
|
|
564
585
|
// runs for every environment and client build can resolve virtual:vite-rsc/client-references.
|
|
@@ -585,6 +606,9 @@ export default function farmRsc(options = {}) {
|
|
|
585
606
|
return {
|
|
586
607
|
appType: "custom",
|
|
587
608
|
builder: { sharedConfigBuild: true },
|
|
609
|
+
define: {
|
|
610
|
+
__FARM_API_BASE_URL__: JSON.stringify(api.baseURL),
|
|
611
|
+
},
|
|
588
612
|
ssr: {
|
|
589
613
|
external: [
|
|
590
614
|
"react",
|
|
@@ -1000,19 +1024,26 @@ if (document.readyState === 'loading') {
|
|
|
1000
1024
|
globalThis.__FARM_VITE_RSC_LOAD_BOOTSTRAP__ = async () => bootstrapPrefix +
|
|
1001
1025
|
`import("/@react-refresh").then(m=>{m.default.injectIntoGlobalHook(window);window.$RefreshReg$=()=>{};window.$RefreshSig$=()=>type=>type;window.__vite_plugin_react_preamble_installed__=true;return import("/@vite/client");}).then(()=>import(${JSON.stringify(clientEntryUrl)}));`;
|
|
1002
1026
|
const base = `http://${req.headers.host || "localhost:3000"}`;
|
|
1027
|
+
const requestUrl = new URL(url, base);
|
|
1028
|
+
const isAction = actionsEnabled &&
|
|
1029
|
+
method === "POST" &&
|
|
1030
|
+
!isFarmAPIPathname(requestUrl.pathname, entryContext.apiBasePath) &&
|
|
1031
|
+
!isFarmAPIPathname(requestUrl.pathname);
|
|
1032
|
+
if (isAction) {
|
|
1033
|
+
validateServerActionRequest(new Request(requestUrl, {
|
|
1034
|
+
method,
|
|
1035
|
+
headers: req.headers,
|
|
1036
|
+
}), entryContext.serverActions);
|
|
1037
|
+
}
|
|
1003
1038
|
let body;
|
|
1004
|
-
if (method
|
|
1005
|
-
|
|
1006
|
-
for await (const chunk of req)
|
|
1007
|
-
chunks.push(chunk);
|
|
1008
|
-
body = Buffer.concat(chunks);
|
|
1009
|
-
// Keep as buffer so request.formData() / request.text() in RSC handler work (multipart must not be UTF-8 decoded)
|
|
1039
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
1040
|
+
body = await readNodeRequestBody(req, isAction ? entryContext.serverActions.bodySizeLimit : bodySizeLimit);
|
|
1010
1041
|
}
|
|
1011
|
-
const request = new Request(
|
|
1042
|
+
const request = new Request(requestUrl, {
|
|
1012
1043
|
method,
|
|
1013
1044
|
headers: req.headers,
|
|
1014
|
-
body:
|
|
1015
|
-
? body
|
|
1045
|
+
body: body && body.length > 0
|
|
1046
|
+
? body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength)
|
|
1016
1047
|
: undefined,
|
|
1017
1048
|
});
|
|
1018
1049
|
// Load the RSC entry in the "rsc" environment so @vitejs/plugin-rsc transforms run with this.environment.name === "rsc".
|
|
@@ -1046,32 +1077,24 @@ if (document.readyState === 'loading') {
|
|
|
1046
1077
|
if (!rscEntry?.default?.fetch)
|
|
1047
1078
|
throw new Error("[Farm.js] Could not load RSC entry in rsc environment");
|
|
1048
1079
|
const response = await rscEntry.default.fetch(request);
|
|
1049
|
-
res
|
|
1050
|
-
response.headers.forEach((value, key) => {
|
|
1051
|
-
if (key.toLowerCase() !== "transfer-encoding")
|
|
1052
|
-
res.setHeader(key, value);
|
|
1053
|
-
});
|
|
1054
|
-
if (response.body) {
|
|
1055
|
-
const reader = response.body.getReader();
|
|
1056
|
-
const pump = async () => {
|
|
1057
|
-
while (true) {
|
|
1058
|
-
const { done, value } = await reader.read();
|
|
1059
|
-
if (done)
|
|
1060
|
-
break;
|
|
1061
|
-
res.write(Buffer.from(value));
|
|
1062
|
-
}
|
|
1063
|
-
res.end();
|
|
1064
|
-
};
|
|
1065
|
-
await pump();
|
|
1066
|
-
}
|
|
1067
|
-
else {
|
|
1068
|
-
res.end();
|
|
1069
|
-
}
|
|
1080
|
+
await sendRscDevelopmentResponse(res, response);
|
|
1070
1081
|
const duration = Date.now() - startTime;
|
|
1071
1082
|
logResponse(method, pathname, response.status, duration);
|
|
1072
1083
|
return;
|
|
1073
1084
|
}
|
|
1074
1085
|
catch (rscError) {
|
|
1086
|
+
const rejection = createFarmRequestBodyErrorResponse(rscError) ??
|
|
1087
|
+
createServerActionRequestErrorResponse(rscError);
|
|
1088
|
+
if (rejection && !res.headersSent && !res.writableEnded && !res.destroyed) {
|
|
1089
|
+
await sendRscDevelopmentResponse(res, rejection);
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
if (res.headersSent || res.writableEnded || res.destroyed) {
|
|
1093
|
+
console.error("[Farm.js] RSC dev response error:", rscError);
|
|
1094
|
+
if (!res.destroyed)
|
|
1095
|
+
res.destroy(rscError instanceof Error ? rscError : new Error(String(rscError)));
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1075
1098
|
// RSC pipeline failed; fall back to legacy SSR for GET only
|
|
1076
1099
|
if (method !== "GET") {
|
|
1077
1100
|
const duration = Date.now() - startTime;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nitro-build.d.ts","sourceRoot":"","sources":["../../src/rsc/nitro-build.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwBH;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,IAAI,CAKN;AAwFD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,OAAO,CAAC,IAAI,CAAC,CAgCf;
|
|
1
|
+
{"version":3,"file":"nitro-build.d.ts","sourceRoot":"","sources":["../../src/rsc/nitro-build.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwBH;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,IAAI,CAKN;AAwFD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,OAAO,CAAC,IAAI,CAAC,CAgCf;AA0ED,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8MhF"}
|
package/dist/rsc/nitro-build.js
CHANGED
|
@@ -219,9 +219,20 @@ function createRscRequest(event) {
|
|
|
219
219
|
return new Request(event.req, { signal: controller.signal });
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
export default defineEventHandler((event) =>
|
|
223
|
-
|
|
224
|
-
|
|
222
|
+
export default defineEventHandler(async (event) => {
|
|
223
|
+
const response = await handler(createRscRequest(event), {
|
|
224
|
+
waitUntil: (promise) => event.waitUntil(promise),
|
|
225
|
+
});
|
|
226
|
+
// H3's prepared headers override Response headers. Keep both the runtime's
|
|
227
|
+
// fields (e.g. Accept-Encoding from static middleware) and the app's fields.
|
|
228
|
+
const vary = [event.res.headers.get('vary'), response.headers.get('vary')]
|
|
229
|
+
.filter(Boolean).join(',').split(',').map(value => value.trim()).filter(Boolean);
|
|
230
|
+
if (vary.length) {
|
|
231
|
+
const fields = new Map(vary.map(value => [value.toLowerCase(), value]));
|
|
232
|
+
event.res.headers.set('vary', fields.has('*') ? '*' : [...fields.values()].join(', '));
|
|
233
|
+
}
|
|
234
|
+
return response;
|
|
235
|
+
});
|
|
225
236
|
`.trim();
|
|
226
237
|
const entryPath = path.join(buildDir, "rsc-entry.mjs");
|
|
227
238
|
mkdirSync(buildDir, { recursive: true });
|
package/dist/rsc/types.d.ts
CHANGED
|
@@ -54,6 +54,8 @@ export interface EntryContext {
|
|
|
54
54
|
outDir: string;
|
|
55
55
|
/** Base URL path, e.g. '/' */
|
|
56
56
|
basePath: string;
|
|
57
|
+
/** Resolved same-origin mount for canonical /api routes. */
|
|
58
|
+
apiBasePath?: string;
|
|
57
59
|
/** Route files subdirectory within srcDir */
|
|
58
60
|
routesDir?: string;
|
|
59
61
|
/** Root-relative Vite URL for an existing global stylesheet. */
|
|
@@ -72,6 +74,8 @@ export interface EntryContext {
|
|
|
72
74
|
deploymentId: string;
|
|
73
75
|
/** Whether debug mode is enabled */
|
|
74
76
|
debug: boolean;
|
|
77
|
+
/** True only for the Vite serve command, never for a production build. */
|
|
78
|
+
development?: boolean;
|
|
75
79
|
}
|
|
76
80
|
/**
|
|
77
81
|
* Matched route information from the file-based router.
|
package/dist/rsc/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/rsc/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,+BAA+B,EAChC,MAAM,sCAAsC,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,yDAAyD;IACzD,aAAa,CAAC,EAAE,uBAAuB,CAAC;IAExC,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,OAAO,CAAC,EAAE;QACR,wCAAwC;QACxC,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,wCAAwC;QACxC,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,2CAA2C;QAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IAEF;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IAEf,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IAEf,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;IAEjB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,uFAAuF;IACvF,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IAEH,yCAAyC;IACzC,cAAc,EAAE,OAAO,CAAC;IAExB,wDAAwD;IACxD,aAAa,EAAE,+BAA+B,CAAC;IAE/C,8EAA8E;IAC9E,YAAY,EAAE,MAAM,CAAC;IAErB,oCAAoC;IACpC,KAAK,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/rsc/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,+BAA+B,EAChC,MAAM,sCAAsC,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,yDAAyD;IACzD,aAAa,CAAC,EAAE,uBAAuB,CAAC;IAExC,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,OAAO,CAAC,EAAE;QACR,wCAAwC;QACxC,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,wCAAwC;QACxC,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,2CAA2C;QAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IAEF;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IAEf,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IAEf,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;IAEjB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,uFAAuF;IACvF,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IAEH,yCAAyC;IACzC,cAAc,EAAE,OAAO,CAAC;IAExB,wDAAwD;IACxD,aAAa,EAAE,+BAA+B,CAAC;IAE/C,8EAA8E;IAC9E,YAAY,EAAE,MAAM,CAAC;IAErB,oCAAoC;IACpC,KAAK,EAAE,OAAO,CAAC;IAEf,0EAA0E;IAC1E,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IAE/B,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAEhB,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,0DAA0D;IAC1D,IAAI,EAAE,KAAK,CAAC,YAAY,CAAC;IAEzB,wFAAwF;IACxF,WAAW,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC;IAEjC,yEAAyE;IACzE,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IAEF,0DAA0D;IAC1D,WAAW,CAAC,EAAE;QACZ,EAAE,EAAE,OAAO,CAAC;QACZ,IAAI,EAAE,OAAO,CAAC;KACf,CAAC;IAEF,6CAA6C;IAC7C,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,UAAU;QAClB,QAAQ,CAAC,OAAO,EAAE;YAChB,qCAAqC;YACrC,OAAO,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;YAEhC,6CAA6C;YAC7C,UAAU,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YAEhE,kDAAkD;YAClD,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;SAC5D,CAAC;KACH;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farm.js/plugin",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.94",
|
|
4
4
|
"description": "Official plugins for Farm.js framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Farm.js Team",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"nitro": "3.0.1-alpha.0",
|
|
62
62
|
"picocolors": "^1.0.0",
|
|
63
63
|
"rsc-html-stream": "^0.0.4",
|
|
64
|
-
"@farm.js/core": "0.1.0-beta.
|
|
64
|
+
"@farm.js/core": "0.1.0-beta.94"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@types/node": "^20.19.21",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"react-dom": "19.2.8",
|
|
83
83
|
"react-server-dom-webpack": "19.2.8",
|
|
84
84
|
"vite": ">=6.0.0",
|
|
85
|
-
"@farm.js/core": "0.1.0-beta.
|
|
85
|
+
"@farm.js/core": "0.1.0-beta.94"
|
|
86
86
|
},
|
|
87
87
|
"peerDependenciesMeta": {
|
|
88
88
|
"@vitejs/plugin-rsc": {
|