@rsc-kit/core 0.17.0 → 0.18.0
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/barrelImports.d.ts +6 -0
- package/dist/barrelImports.js +93 -0
- package/dist/barrelImports.js.map +1 -0
- package/dist/events.d.ts +57 -0
- package/dist/events.js +113 -0
- package/dist/events.js.map +1 -0
- package/dist/host.d.ts +16 -6
- package/dist/host.js +219 -124
- package/dist/host.js.map +1 -1
- package/dist/hostCalls.d.ts +27 -0
- package/dist/hostCalls.js +127 -12
- package/dist/hostCalls.js.map +1 -1
- package/dist/hostRouting.d.ts +53 -0
- package/dist/hostRouting.js +100 -0
- package/dist/hostRouting.js.map +1 -0
- package/dist/js/DefaultRouteError.d.ts +3 -0
- package/dist/js/DefaultRouteError.js +61 -0
- package/dist/js/DefaultRouteError.js.map +1 -0
- package/dist/js/queryClient.d.ts +2 -1
- package/dist/js/queryClient.js +0 -12
- package/dist/js/queryClient.js.map +1 -1
- package/dist/js/router.d.ts +2 -2
- package/dist/js/router.js.map +1 -1
- package/dist/js/useEvents.d.ts +25 -0
- package/dist/js/useEvents.js +78 -0
- package/dist/js/useEvents.js.map +1 -0
- package/dist/js/usePolling.d.ts +29 -0
- package/dist/js/usePolling.js +141 -0
- package/dist/js/usePolling.js.map +1 -0
- package/dist/manifest.d.ts +18 -1
- package/dist/manifest.js.map +1 -1
- package/dist/metadataRoutes.d.ts +20 -0
- package/dist/metadataRoutes.js +35 -0
- package/dist/metadataRoutes.js.map +1 -1
- package/dist/prerender.js +1 -0
- package/dist/prerender.js.map +1 -1
- package/dist/redirect.d.ts +16 -2
- package/dist/redirect.js +10 -19
- package/dist/redirect.js.map +1 -1
- package/dist/revalidate.d.ts +6 -4
- package/dist/revalidate.js +6 -5
- package/dist/revalidate.js.map +1 -1
- package/dist/routes.d.ts +16 -3
- package/dist/routes.js +5 -5
- package/dist/routes.js.map +1 -1
- package/dist/routing.d.ts +2 -2
- package/dist/routing.js +47 -25
- package/dist/routing.js.map +1 -1
- package/dist/vite.d.ts +42 -2
- package/dist/vite.js +333 -5
- package/dist/vite.js.map +1 -1
- package/package.json +13 -1
package/dist/vite.js
CHANGED
|
@@ -27,7 +27,9 @@ import { ASSET_BASE, allAppAssets, appAssets, headTags, typeOf, } from "./appAss
|
|
|
27
27
|
import { reactCacheImports } from "./reactCache.js";
|
|
28
28
|
import { clientEntries, clientScanPlugin, engineClientEntries, } from "./clientEntries.js";
|
|
29
29
|
import { serverImportsOfClientPackages } from "./clientImports.js";
|
|
30
|
-
import { METADATA_ROUTES, ROOT_FILES, rootFileType } from "./metadataRoutes.js";
|
|
30
|
+
import { automaticSitemap, METADATA_ROUTES, ROOT_FILES, rootFileType, } from "./metadataRoutes.js";
|
|
31
|
+
import { ownHosts } from "./hostRouting.js";
|
|
32
|
+
import { unrollBarrelImports } from "./barrelImports.js";
|
|
31
33
|
import { serverRendererMessage, SERVER_RENDERER, ssrProxyModule, UseSsrError, } from "./useSsr.js";
|
|
32
34
|
import { httpHostCalls } from "./hostCalls.js";
|
|
33
35
|
// Resolved once per rscKit() call. One build runs in one process, so these are
|
|
@@ -108,6 +110,11 @@ let isWatch = false;
|
|
|
108
110
|
let offline = false;
|
|
109
111
|
let typecheck = true;
|
|
110
112
|
let webManifestOptions = null;
|
|
113
|
+
/** The site's own hosts, from the root layout's metadataBase and rscKit({ hosts }). */
|
|
114
|
+
let siteHosts = [];
|
|
115
|
+
let hostsOption = [];
|
|
116
|
+
let barrelImports = true;
|
|
117
|
+
let identify = true;
|
|
111
118
|
let foundAssets = {
|
|
112
119
|
favicon: null,
|
|
113
120
|
icons: [],
|
|
@@ -270,6 +277,9 @@ function resolvePaths(options) {
|
|
|
270
277
|
prerenderAfterBuild = process.env.RSC_PRERENDER !== "0";
|
|
271
278
|
offline = options.offline === true;
|
|
272
279
|
typecheck = options.typecheck !== false;
|
|
280
|
+
hostsOption = options.hosts ?? [];
|
|
281
|
+
barrelImports = options.barrelImports !== false;
|
|
282
|
+
identify = options.identify !== false;
|
|
273
283
|
inlineStylesheets = options.inlineStylesheets ?? "auto";
|
|
274
284
|
maxActionBody = options.maxActionBody;
|
|
275
285
|
// One place, and it is the file. A plugin option as well would be the same
|
|
@@ -323,7 +333,12 @@ function urlSegments(componentName) {
|
|
|
323
333
|
continue;
|
|
324
334
|
}
|
|
325
335
|
if (part.startsWith("[") && part.endsWith("]")) {
|
|
326
|
-
|
|
336
|
+
// At the top of app/ - the first segment the url has - a parameter is
|
|
337
|
+
// the host's: bound from acme.example.com, never from example.com/acme.
|
|
338
|
+
segments.push({
|
|
339
|
+
type: segments.length === 0 ? "host" : "param",
|
|
340
|
+
value: part.slice(1, -1),
|
|
341
|
+
});
|
|
327
342
|
continue;
|
|
328
343
|
}
|
|
329
344
|
// An interception marker says which url this replaces, not what it is
|
|
@@ -395,7 +410,11 @@ function routeManifest() {
|
|
|
395
410
|
* to whoever knows what they mean.
|
|
396
411
|
*/
|
|
397
412
|
const middlewareIn = (absDir) => {
|
|
398
|
-
|
|
413
|
+
// middleware.ts first: it is the file named for what this is. A guard
|
|
414
|
+
// the engine runs is its default export; the names a host runs are its
|
|
415
|
+
// `middleware` export, and a file may carry either or both. route.ts is
|
|
416
|
+
// read too, for the apps written before middleware.ts could.
|
|
417
|
+
for (const file of ["middleware.ts", "middleware.tsx", "route.ts", "route.tsx"]) {
|
|
399
418
|
const path = join(absDir, file);
|
|
400
419
|
if (!existsSync(path))
|
|
401
420
|
continue;
|
|
@@ -481,7 +500,13 @@ function routeManifest() {
|
|
|
481
500
|
// writing the site out, and knowing which filename the client will ask for.
|
|
482
501
|
return {
|
|
483
502
|
version: 1,
|
|
484
|
-
build: {
|
|
503
|
+
build: {
|
|
504
|
+
output,
|
|
505
|
+
exportPath,
|
|
506
|
+
payloadName: staticPayloads,
|
|
507
|
+
hosts: siteHosts,
|
|
508
|
+
identify,
|
|
509
|
+
},
|
|
485
510
|
routes,
|
|
486
511
|
intercepts,
|
|
487
512
|
apis: [...apiRoutes.values()].map(({ name, methods, generated }) => ({
|
|
@@ -1236,6 +1261,41 @@ async function prerenderAfterBundles(bundle, staticDir, assetsDir, knownActions
|
|
|
1236
1261
|
// places to look for one fact.
|
|
1237
1262
|
const manifest = engine.manifest();
|
|
1238
1263
|
const apis = await prerenderApiRoutes(engine, manifest, writeTo(staticDir));
|
|
1264
|
+
// A sitemap the build writes itself, when the app wrote none: every url
|
|
1265
|
+
// it stored or was told about, minus the guarded ones. It needs the site's
|
|
1266
|
+
// host, which the root layout's metadataBase is for; without one there is
|
|
1267
|
+
// nothing to write and the report says so once.
|
|
1268
|
+
const writesSitemap = manifest.apis?.some((api) => api.name === "app/sitemap.xml/route");
|
|
1269
|
+
const hasSitemapFile = rootFiles.includes("sitemap.xml");
|
|
1270
|
+
if (!writesSitemap && !hasSitemapFile) {
|
|
1271
|
+
const base = rootMetadataBase(join(sourceDir, "app"));
|
|
1272
|
+
if (base) {
|
|
1273
|
+
const xml = automaticSitemap(results, manifest.routes, base);
|
|
1274
|
+
const stored = JSON.stringify({
|
|
1275
|
+
status: 200,
|
|
1276
|
+
headers: [["content-type", "application/xml; charset=utf-8"]],
|
|
1277
|
+
body: xml,
|
|
1278
|
+
varies: false,
|
|
1279
|
+
});
|
|
1280
|
+
await writeTo(staticDir)("sitemap.xml.api.json", stored);
|
|
1281
|
+
pending.push({
|
|
1282
|
+
line: " ○ /sitemap.xml",
|
|
1283
|
+
bytes: null,
|
|
1284
|
+
extra: [
|
|
1285
|
+
` written by the build: ${xml.split("<url>").length - 1} urls; a sitemap.ts beside the root layout replaces it`,
|
|
1286
|
+
],
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
else {
|
|
1290
|
+
pending.push({
|
|
1291
|
+
line: " - /sitemap.xml",
|
|
1292
|
+
bytes: null,
|
|
1293
|
+
extra: [
|
|
1294
|
+
" not written: the root layout has no metadataBase to make the urls absolute",
|
|
1295
|
+
],
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1239
1299
|
for (const api of apis) {
|
|
1240
1300
|
pending.push({
|
|
1241
1301
|
line: ` ${api.type === "frozen" ? "○" : "ƒ"} ${api.url}`,
|
|
@@ -1480,6 +1540,23 @@ function renderRouteTypes(manifest) {
|
|
|
1480
1540
|
const target = relative(typesDir, join(sourceDir, route.component)).replace(/\\/g, "/");
|
|
1481
1541
|
search.set(pattern, target.startsWith(".") ? target : "./" + target);
|
|
1482
1542
|
}
|
|
1543
|
+
// Section names are read from the source - section('orders', …) - the way
|
|
1544
|
+
// generateStaticParams is detected, because this runs before any bundle
|
|
1545
|
+
// exists. A name computed at runtime is not seen and revalidate() falls
|
|
1546
|
+
// back to refusing it at the renderer, as before.
|
|
1547
|
+
const regions = [
|
|
1548
|
+
...new Set([
|
|
1549
|
+
...[...components.values()]
|
|
1550
|
+
.filter((c) => SECTION_FILE.test(c.absPath))
|
|
1551
|
+
.flatMap((c) => [
|
|
1552
|
+
...readFileSync(c.absPath, "utf-8").matchAll(/\bsection\(\s*['"`]([^'"`]+)['"`]/g),
|
|
1553
|
+
].map((m) => m[1])),
|
|
1554
|
+
...[...components.keys()].flatMap((name) => {
|
|
1555
|
+
const slot = name.split("/").find((part) => part.startsWith("@"));
|
|
1556
|
+
return slot ? [slot.slice(1)] : [];
|
|
1557
|
+
}),
|
|
1558
|
+
]),
|
|
1559
|
+
].sort();
|
|
1483
1560
|
return [
|
|
1484
1561
|
"// @generated — do not edit. Written by the RSC build from the route tree.",
|
|
1485
1562
|
"//",
|
|
@@ -1513,6 +1590,14 @@ function renderRouteTypes(manifest) {
|
|
|
1513
1590
|
// Api routes are a separate union, so Link refuses an api url and apiUrl()
|
|
1514
1591
|
// refuses a page. Linking to an api route navigates the browser away to a
|
|
1515
1592
|
// json document, which is the mistake worth catching.
|
|
1593
|
+
// Regions: every section('name', …) the build read, and every @slot
|
|
1594
|
+
// directory. This is what types revalidate().
|
|
1595
|
+
" interface RegisterRegions {",
|
|
1596
|
+
regions.length > 0
|
|
1597
|
+
? " regions:\n" +
|
|
1598
|
+
regions.map((r) => " | " + JSON.stringify(r)).join("\n")
|
|
1599
|
+
: " // No sections or slots found under the source directory.\n regions: never",
|
|
1600
|
+
" }",
|
|
1516
1601
|
" interface RegisterApi {",
|
|
1517
1602
|
apis.length > 0
|
|
1518
1603
|
? " apis:\n" +
|
|
@@ -1714,10 +1799,20 @@ function registerApiRoute(absPath) {
|
|
|
1714
1799
|
}
|
|
1715
1800
|
apiRoutes.set(name, { name, absPath, methods });
|
|
1716
1801
|
}
|
|
1802
|
+
/**
|
|
1803
|
+
* Whether a middleware.ts is a guard the engine runs, or only names guards
|
|
1804
|
+
* the host runs. The engine imports a guard's default export; a file with
|
|
1805
|
+
* none - `export const middleware = ['auth']` and nothing else - has nothing
|
|
1806
|
+
* to import, and registering it would make the build fail on an export that
|
|
1807
|
+
* was never meant to exist.
|
|
1808
|
+
*/
|
|
1809
|
+
function isEngineGuard(absPath) {
|
|
1810
|
+
return /export\s+default\b/.test(readFileSync(absPath, "utf-8"));
|
|
1811
|
+
}
|
|
1717
1812
|
function discover(dir) {
|
|
1718
1813
|
for (const base of ROUTE_FILES) {
|
|
1719
1814
|
const p = findRouteFile(dir, base);
|
|
1720
|
-
if (p)
|
|
1815
|
+
if (p && (base !== "middleware" || isEngineGuard(p)))
|
|
1721
1816
|
register(p);
|
|
1722
1817
|
}
|
|
1723
1818
|
// route.ts — an api endpoint, colocated with the pages it sits among. Read
|
|
@@ -1748,6 +1843,21 @@ function discover(dir) {
|
|
|
1748
1843
|
* pages export only the static object, so referencing both meant that warning
|
|
1749
1844
|
* for almost every route in an app.
|
|
1750
1845
|
*/
|
|
1846
|
+
/**
|
|
1847
|
+
* The root layout's metadataBase, read from the source. Wanted while the
|
|
1848
|
+
* manifest is built, before there is a bundle to execute - the same reason
|
|
1849
|
+
* generateStaticParams is detected by reading. A `new URL('…')` or a string
|
|
1850
|
+
* literal; anything computed is not seen, and rscKit({ hosts }) says it.
|
|
1851
|
+
*/
|
|
1852
|
+
function rootMetadataBase(appDir) {
|
|
1853
|
+
const layout = ["layout.tsx", "layout.jsx", "layout.ts", "layout.js"]
|
|
1854
|
+
.map((name) => join(appDir, name))
|
|
1855
|
+
.find((file) => existsSync(file));
|
|
1856
|
+
if (!layout)
|
|
1857
|
+
return null;
|
|
1858
|
+
const match = /metadataBase\s*:\s*(?:new\s+URL\(\s*)?["'`](https?:\/\/[^"'`]+)["'`]/.exec(readFileSync(layout, "utf-8"));
|
|
1859
|
+
return match ? match[1] : null;
|
|
1860
|
+
}
|
|
1751
1861
|
function metadataExports(absPath) {
|
|
1752
1862
|
const src = readFileSync(absPath, "utf-8");
|
|
1753
1863
|
return {
|
|
@@ -1942,7 +2052,29 @@ function installHostCallsOnce(): void {
|
|
|
1942
2052
|
}
|
|
1943
2053
|
|
|
1944
2054
|
`;
|
|
2055
|
+
/**
|
|
2056
|
+
* The app's process bootstrap, if it has one: `instrumentation.ts` beside
|
|
2057
|
+
* `app/`, the name Next uses so a reader arriving from there knows what it
|
|
2058
|
+
* is.
|
|
2059
|
+
*
|
|
2060
|
+
* Two things make it a framework concern rather than an import the app
|
|
2061
|
+
* adds. Module evaluation order: a page that configures a shared package at
|
|
2062
|
+
* import time runs before any module the app could put first, and only the
|
|
2063
|
+
* generated entry can import something before the pages. And "before the
|
|
2064
|
+
* first request": an async `register()` — connecting, validating env — has
|
|
2065
|
+
* to be awaited by every entry point a host or a prerender can call, which
|
|
2066
|
+
* is a list the app cannot see.
|
|
2067
|
+
*/
|
|
2068
|
+
function instrumentationFile() {
|
|
2069
|
+
for (const ext of ["ts", "tsx", "mts", "js", "mjs"]) {
|
|
2070
|
+
const file = join(sourceDir, `instrumentation.${ext}`);
|
|
2071
|
+
if (existsSync(file))
|
|
2072
|
+
return file;
|
|
2073
|
+
}
|
|
2074
|
+
return null;
|
|
2075
|
+
}
|
|
1945
2076
|
function generateEntryRsc(fallbackOrigin = "") {
|
|
2077
|
+
const instrumentation = instrumentationFile();
|
|
1946
2078
|
// The 404 page, if the app has one, and the layouts it renders inside.
|
|
1947
2079
|
// Computed here rather than looked up at runtime: not-found is not a route,
|
|
1948
2080
|
// so the manifest has no entry to read its chain from.
|
|
@@ -1999,12 +2131,20 @@ function generateEntryRsc(fallbackOrigin = "") {
|
|
|
1999
2131
|
// from src/ in its own repo and from dist/ once published, and Vite resolves
|
|
2000
2132
|
// either. Naming .tsx here builds fine from source and fails after publish.
|
|
2001
2133
|
return `// GENERATED by rscKit() — do not edit.
|
|
2134
|
+
${
|
|
2135
|
+
// First, before any page: an import's side effects run in import order,
|
|
2136
|
+
// and a package configured here has to be configured before a page module
|
|
2137
|
+
// that reads it at evaluation time.
|
|
2138
|
+
instrumentation
|
|
2139
|
+
? `import * as __instrumentation from ${JSON.stringify(instrumentation)}`
|
|
2140
|
+
: "const __instrumentation: { register?: () => unknown } = {}"}
|
|
2002
2141
|
import { SegmentBoundary } from ${JSON.stringify(join(packageDir, "js/SegmentBoundary"))}
|
|
2003
2142
|
import { DocumentTitle } from ${JSON.stringify(join(packageDir, "js/DocumentTitle"))}
|
|
2004
2143
|
import { SlotBoundary } from ${JSON.stringify(join(packageDir, "js/SlotBoundary"))}
|
|
2005
2144
|
import { RouteErrorBoundary } from ${JSON.stringify(join(packageDir, "js/RouteErrorBoundary"))}
|
|
2006
2145
|
import { sectionComponent } from ${JSON.stringify(join(packageDir, "js/section"))}
|
|
2007
2146
|
import { PathnameProvider } from ${JSON.stringify(join(packageDir, "js/PathnameProvider"))}
|
|
2147
|
+
import { DefaultRouteError } from ${JSON.stringify(join(packageDir, "js/DefaultRouteError"))}
|
|
2008
2148
|
import { searchParams as requestSearchParams } from ${JSON.stringify(join(packageDir, "request"))}
|
|
2009
2149
|
import { parseParams, parseSearchParams, parseBody, isSearchParamsError, isBodyError } from ${JSON.stringify(join(packageDir, "routeSchema"))}
|
|
2010
2150
|
import { notFoundDigest, isNotFoundSignal } from ${JSON.stringify(join(packageDir, "notFound"))}
|
|
@@ -2144,6 +2284,7 @@ export async function handleApiRoute(
|
|
|
2144
2284
|
params: Record<string, string>,
|
|
2145
2285
|
allow: string,
|
|
2146
2286
|
): Promise<Response> {
|
|
2287
|
+
await instrumented()
|
|
2147
2288
|
applyHost()
|
|
2148
2289
|
|
|
2149
2290
|
const mod = apiRoutes[name]
|
|
@@ -2251,6 +2392,8 @@ export async function auditActions(ids: string[]): Promise<{ id: string; client:
|
|
|
2251
2392
|
* that asked for everything, or the reverse.
|
|
2252
2393
|
*/
|
|
2253
2394
|
export async function getStaticParams(component: string): Promise<Record<string, string>[] | null> {
|
|
2395
|
+
await instrumented()
|
|
2396
|
+
|
|
2254
2397
|
const generate = staticParamsMap[component]
|
|
2255
2398
|
|
|
2256
2399
|
if (!generate) return null
|
|
@@ -2274,6 +2417,57 @@ const HOST_GLOBAL = ${JSON.stringify(hostGlobal)}
|
|
|
2274
2417
|
*/
|
|
2275
2418
|
const HOST_MIDDLEWARE_FN = '__rsc.middleware'
|
|
2276
2419
|
|
|
2420
|
+
/**
|
|
2421
|
+
* instrumentation.ts's register(), once.
|
|
2422
|
+
*
|
|
2423
|
+
* On a long-lived server it runs at startup: the entry is evaluated when the
|
|
2424
|
+
* process starts, and this begins then, so env that fails validation fails
|
|
2425
|
+
* the boot rather than the first visitor, and the first request does not
|
|
2426
|
+
* pay for it. On a Worker there is no startup — an isolate is created for a
|
|
2427
|
+
* request, and a binding is only readable once one has arrived — so it runs
|
|
2428
|
+
* at the first request of each isolate instead. Every entry point awaits it
|
|
2429
|
+
* either way, which is what makes "before the first render" hold whether the
|
|
2430
|
+
* caller is the built server, the dev server or the prerender.
|
|
2431
|
+
*
|
|
2432
|
+
* A rejection is not memoised. Env that fails validation fails the same way
|
|
2433
|
+
* on every request, which is right; a database that was not up yet gets
|
|
2434
|
+
* asked again rather than leaving the process permanently refusing. In
|
|
2435
|
+
* production a startup failure is reported and the process exits, because a
|
|
2436
|
+
* server that could not bootstrap has nothing correct to serve; the dev
|
|
2437
|
+
* server stays up and reports it on the page instead.
|
|
2438
|
+
*/
|
|
2439
|
+
let __instrumented: Promise<void> | null = null
|
|
2440
|
+
|
|
2441
|
+
function instrumented(): Promise<void> {
|
|
2442
|
+
if (!__instrumented) {
|
|
2443
|
+
__instrumented = Promise.resolve()
|
|
2444
|
+
.then(() => __instrumentation.register?.())
|
|
2445
|
+
.then(
|
|
2446
|
+
() => undefined,
|
|
2447
|
+
(error) => {
|
|
2448
|
+
__instrumented = null
|
|
2449
|
+
throw error
|
|
2450
|
+
},
|
|
2451
|
+
)
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
return __instrumented
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
// Workers name themselves; nothing else does. Where there is a process to
|
|
2458
|
+
// start, start now.
|
|
2459
|
+
const isolateRuntime = typeof navigator !== 'undefined' && navigator.userAgent === 'Cloudflare-Workers'
|
|
2460
|
+
|
|
2461
|
+
if (__instrumentation.register && !isolateRuntime) {
|
|
2462
|
+
instrumented().catch((error) => {
|
|
2463
|
+
console.error('[rsc-kit] instrumentation.ts register() failed:', error)
|
|
2464
|
+
|
|
2465
|
+
if (!import.meta.env.DEV && typeof process !== 'undefined' && typeof process.exit === 'function') {
|
|
2466
|
+
process.exit(1)
|
|
2467
|
+
}
|
|
2468
|
+
})
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2277
2471
|
let currentHost: HostFn | null = null
|
|
2278
2472
|
|
|
2279
2473
|
export function installHostFn(fn: HostFn) {
|
|
@@ -2507,6 +2701,19 @@ function buildElement(
|
|
|
2507
2701
|
)
|
|
2508
2702
|
}
|
|
2509
2703
|
|
|
2704
|
+
// Outermost, for a throw no error.tsx covers - including one in a layout,
|
|
2705
|
+
// which the boundary inside that layout cannot see. Without it React
|
|
2706
|
+
// unmounted the document on hydration: a black page with the cause nowhere
|
|
2707
|
+
// near it. Only with the runtime: it is a client component, and a route
|
|
2708
|
+
// shipping none has nothing to catch with.
|
|
2709
|
+
if (bootstrap) {
|
|
2710
|
+
element = createElement(
|
|
2711
|
+
RouteErrorBoundary,
|
|
2712
|
+
{ fallback: DefaultRouteError as never, resetKey: pageKey || component },
|
|
2713
|
+
element,
|
|
2714
|
+
)
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2510
2717
|
// <title>/<meta> go OUTSIDE the Suspense boundaries so they reach the shell
|
|
2511
2718
|
// immediately — inside, they would be withheld until the page's data
|
|
2512
2719
|
// resolves, delaying the whole document on a slow page.
|
|
@@ -2660,6 +2867,8 @@ async function renderTree(
|
|
|
2660
2867
|
if (bootstrap) head.push(createElement(DocumentTitle, { key: '__ts', title: String(md.title) }))
|
|
2661
2868
|
}
|
|
2662
2869
|
if (md.description != null) head.push(createElement('meta', { key: '__d', name: 'description', content: String(md.description) }))
|
|
2870
|
+
// The name only, never the version - see the identify option.
|
|
2871
|
+
if (manifest().build?.identify) head.push(createElement('meta', { key: '__g', name: 'generator', content: 'rsc-kit' }))
|
|
2663
2872
|
|
|
2664
2873
|
// robots is a string, or the object Next takes: index and follow as
|
|
2665
2874
|
// their no- forms, the flags by name, the limits as name:value. googleBot
|
|
@@ -2961,6 +3170,7 @@ async function runMiddleware(component: string, props: Record<string, unknown> =
|
|
|
2961
3170
|
* throws, exactly as it does mid-render.
|
|
2962
3171
|
*/
|
|
2963
3172
|
export async function runRouteMiddleware(component: string, props: Record<string, unknown> = {}): Promise<void> {
|
|
3173
|
+
await instrumented()
|
|
2964
3174
|
applyHost()
|
|
2965
3175
|
|
|
2966
3176
|
return runMiddleware(component, props)
|
|
@@ -2976,6 +3186,7 @@ export async function handleRscStream(
|
|
|
2976
3186
|
from = 0,
|
|
2977
3187
|
pageKey = '',
|
|
2978
3188
|
): Promise<{ stream: ReadableStream; clientChunks: unknown; segmentDepth: number }> {
|
|
3189
|
+
await instrumented()
|
|
2979
3190
|
applyHost()
|
|
2980
3191
|
|
|
2981
3192
|
// The host proposes how much the client already has; the engine decides what
|
|
@@ -3031,6 +3242,7 @@ export async function handleRscHtmlStream(
|
|
|
3031
3242
|
pageKey = '',
|
|
3032
3243
|
bootstrap = true,
|
|
3033
3244
|
): Promise<{ htmlStream: ReadableStream; rscPayloadPromise: Promise<string>; clientChunks: unknown }> {
|
|
3245
|
+
await instrumented()
|
|
3034
3246
|
applyHost()
|
|
3035
3247
|
await runMiddleware(component, props)
|
|
3036
3248
|
const flight = renderToReadableStream(
|
|
@@ -3066,6 +3278,7 @@ export async function handleRscResume(
|
|
|
3066
3278
|
nonce?: string,
|
|
3067
3279
|
pageKey = '',
|
|
3068
3280
|
): Promise<{ htmlStream: ReadableStream }> {
|
|
3281
|
+
await instrumented()
|
|
3069
3282
|
applyHost()
|
|
3070
3283
|
await runMiddleware(component, props)
|
|
3071
3284
|
|
|
@@ -3216,6 +3429,7 @@ export async function handleQuery(
|
|
|
3216
3429
|
| { status: number; message: string; errors?: Record<string, string[]> }
|
|
3217
3430
|
| null
|
|
3218
3431
|
> {
|
|
3432
|
+
await instrumented()
|
|
3219
3433
|
applyHost()
|
|
3220
3434
|
|
|
3221
3435
|
let fn: unknown
|
|
@@ -3264,6 +3478,7 @@ export async function handleAction(
|
|
|
3264
3478
|
page?: PageContext,
|
|
3265
3479
|
takeRevalidated?: () => string[],
|
|
3266
3480
|
): Promise<{ stream: ReadableStream }> {
|
|
3481
|
+
await instrumented()
|
|
3267
3482
|
applyHost()
|
|
3268
3483
|
|
|
3269
3484
|
// Every body arrives as bytes on its own socket frame — an upload because it
|
|
@@ -3356,6 +3571,8 @@ export async function resolveMetadata(
|
|
|
3356
3571
|
props: Record<string, unknown> = {},
|
|
3357
3572
|
layouts: LayoutEntry[] = [],
|
|
3358
3573
|
): Promise<Record<string, unknown> | null> {
|
|
3574
|
+
await instrumented()
|
|
3575
|
+
|
|
3359
3576
|
const pageEntry = metadataMap[component]
|
|
3360
3577
|
const page: Record<string, unknown> = pageEntry
|
|
3361
3578
|
? (pageEntry.generate
|
|
@@ -3425,6 +3642,7 @@ export async function handleRsc(
|
|
|
3425
3642
|
bootstrap = true,
|
|
3426
3643
|
canReachHost = true,
|
|
3427
3644
|
): Promise<{ body: string; rscPayload: string; clientChunks: unknown; usedDynamicApis: boolean; dynamicBecause: string[]; clientComponents: string[]; serverReferences: boolean }> {
|
|
3645
|
+
await instrumented()
|
|
3428
3646
|
applyHost()
|
|
3429
3647
|
|
|
3430
3648
|
// A build renders this with no host installed, so every rpc() has to suspend
|
|
@@ -3538,6 +3756,7 @@ export async function handleRscRevalidate(
|
|
|
3538
3756
|
target: string,
|
|
3539
3757
|
page: PageContext,
|
|
3540
3758
|
): Promise<{ rscPayload: string }> {
|
|
3759
|
+
await instrumented()
|
|
3541
3760
|
applyHost()
|
|
3542
3761
|
|
|
3543
3762
|
const flight = renderToReadableStream(await renderRevalidated(target, page))
|
|
@@ -3554,6 +3773,7 @@ export async function handleRscPayload(
|
|
|
3554
3773
|
from = 0,
|
|
3555
3774
|
pageKey = '',
|
|
3556
3775
|
): Promise<{ rscPayload: string }> {
|
|
3776
|
+
await instrumented()
|
|
3557
3777
|
applyHost()
|
|
3558
3778
|
|
|
3559
3779
|
const flight = renderToReadableStream(
|
|
@@ -3621,6 +3841,7 @@ export async function handleRscPprShell(
|
|
|
3621
3841
|
// and make every guarded route dynamic for the wrong reason.
|
|
3622
3842
|
let usedDynamicApis = false
|
|
3623
3843
|
|
|
3844
|
+
await instrumented()
|
|
3624
3845
|
applyHost()
|
|
3625
3846
|
|
|
3626
3847
|
const probe = (..._args: unknown[]) => {
|
|
@@ -3790,6 +4011,17 @@ export default async function handler(request: Request): Promise<Response> {
|
|
|
3790
4011
|
}
|
|
3791
4012
|
|
|
3792
4013
|
async function serve(request: Request): Promise<Response> {
|
|
4014
|
+
// The startup plugin's probe. Nitro loads this module at the first request
|
|
4015
|
+
// rather than when the server starts, so a plugin that runs at startup
|
|
4016
|
+
// sends one request to load it; the module's evaluation began register(),
|
|
4017
|
+
// and this waits for it so a failure is reported as the startup failure it
|
|
4018
|
+
// is. No page is rendered and nothing is disclosed.
|
|
4019
|
+
if (request.headers.get('x-rsc-kit-startup') !== null) {
|
|
4020
|
+
await instrumented()
|
|
4021
|
+
|
|
4022
|
+
return new Response(null, { status: 204 })
|
|
4023
|
+
}
|
|
4024
|
+
|
|
3793
4025
|
installHostCallsOnce()
|
|
3794
4026
|
|
|
3795
4027
|
devHandler ??= createRscHandler({
|
|
@@ -4304,6 +4536,46 @@ self.addEventListener('activate', (event) => {
|
|
|
4304
4536
|
})())
|
|
4305
4537
|
})
|
|
4306
4538
|
`;
|
|
4539
|
+
/**
|
|
4540
|
+
* Named imports from a barrel package, unrolled on the server environments.
|
|
4541
|
+
*
|
|
4542
|
+
* The browser gets lucide pre-bundled; the server environments do not, so
|
|
4543
|
+
* `import { ArrowRight } from 'lucide-react'` loaded and transformed every
|
|
4544
|
+
* icon module - 3,700 transforms, twenty-five seconds before the first
|
|
4545
|
+
* document, for thirteen icons. See barrelImports. Development only: a
|
|
4546
|
+
* build tree-shakes the barrel itself.
|
|
4547
|
+
*/
|
|
4548
|
+
function barrelImportsPlugin() {
|
|
4549
|
+
const entries = new Map();
|
|
4550
|
+
return {
|
|
4551
|
+
name: "rsc-kit:barrel-imports",
|
|
4552
|
+
apply: () => barrelImports && process.env.NODE_ENV !== "production",
|
|
4553
|
+
enforce: "pre",
|
|
4554
|
+
applyToEnvironment: (environment) => environment.name === "rsc" || environment.name === "ssr",
|
|
4555
|
+
async transform(code, id) {
|
|
4556
|
+
if (id.includes("/node_modules/") ||
|
|
4557
|
+
!/\.[cm]?[jt]sx?$/.test(id.split("?")[0]))
|
|
4558
|
+
return;
|
|
4559
|
+
if (!code.includes("from"))
|
|
4560
|
+
return;
|
|
4561
|
+
const pending = [];
|
|
4562
|
+
const wanted = new Set();
|
|
4563
|
+
for (const m of code.matchAll(/import\s*(?:type\s+)?\{[^}]*\}\s*from\s*['"]([^'"./][^'"]*)['"]/g)) {
|
|
4564
|
+
wanted.add(m[1]);
|
|
4565
|
+
}
|
|
4566
|
+
for (const specifier of wanted) {
|
|
4567
|
+
if (entries.has(specifier))
|
|
4568
|
+
continue;
|
|
4569
|
+
pending.push(this.resolve(specifier, id).then((resolved) => {
|
|
4570
|
+
entries.set(specifier, resolved && !resolved.external ? resolved.id.split("?")[0] : null);
|
|
4571
|
+
}));
|
|
4572
|
+
}
|
|
4573
|
+
await Promise.all(pending);
|
|
4574
|
+
const out = unrollBarrelImports(code, (specifier) => entries.get(specifier) ?? null);
|
|
4575
|
+
return out === null ? undefined : { code: out, map: null };
|
|
4576
|
+
},
|
|
4577
|
+
};
|
|
4578
|
+
}
|
|
4307
4579
|
/**
|
|
4308
4580
|
* "use ssr" modules, rewritten for the server-components environment into
|
|
4309
4581
|
* proxies that call the real module in the ssr environment. See useSsr.ts.
|
|
@@ -4478,10 +4750,64 @@ function clientImportsAudit() {
|
|
|
4478
4750
|
},
|
|
4479
4751
|
};
|
|
4480
4752
|
}
|
|
4753
|
+
/**
|
|
4754
|
+
* A Nitro runtime plugin, so instrumentation.ts runs when the server starts.
|
|
4755
|
+
*
|
|
4756
|
+
* Nitro loads the rsc service at the first request, not at startup - so
|
|
4757
|
+
* without this, register() would run when the first visitor arrived, and a
|
|
4758
|
+
* bad environment would take down a server that had already reported itself
|
|
4759
|
+
* up. The plugin runs at app init and sends the service one request, which
|
|
4760
|
+
* loads the module; loading it begins register(), and the request waits for
|
|
4761
|
+
* it. Not on a Worker: there is no startup, and the first request is the
|
|
4762
|
+
* first chance to read a binding.
|
|
4763
|
+
*/
|
|
4764
|
+
const STARTUP_PLUGIN = `import { viteServices } from "#nitro/virtual/vite-services"
|
|
4765
|
+
|
|
4766
|
+
export default function rscKitStartup() {
|
|
4767
|
+
if (typeof navigator !== 'undefined' && navigator.userAgent === 'Cloudflare-Workers') return
|
|
4768
|
+
|
|
4769
|
+
// The ssr service is the one Nitro's renderer asks; its fetch imports the
|
|
4770
|
+
// rsc entry and hands the request on. The rsc service's own export is the
|
|
4771
|
+
// handler function, not a { fetch }, so it cannot be probed directly.
|
|
4772
|
+
const ssr = viteServices.ssr
|
|
4773
|
+
if (!ssr) return
|
|
4774
|
+
|
|
4775
|
+
// Two ways to fail, one outcome. register() rejecting is reported by the
|
|
4776
|
+
// module itself. instrumentation.ts throwing at import - an env schema
|
|
4777
|
+
// refusing - means the module never evaluated, so nothing in it can
|
|
4778
|
+
// report; the load rejects here instead. Either way a server that could
|
|
4779
|
+
// not bootstrap has nothing correct to serve, and says so and stops
|
|
4780
|
+
// rather than reporting itself up.
|
|
4781
|
+
ssr
|
|
4782
|
+
.fetch(new Request('http://rsc-kit.internal/_rsc/startup', { headers: { 'x-rsc-kit-startup': '1' } }))
|
|
4783
|
+
.then((response) => {
|
|
4784
|
+
if (!response.ok) throw new Error('the startup probe answered ' + response.status)
|
|
4785
|
+
})
|
|
4786
|
+
.catch((error) => {
|
|
4787
|
+
console.error('[rsc-kit] instrumentation.ts failed at startup:', error)
|
|
4788
|
+
|
|
4789
|
+
if (typeof process !== 'undefined' && typeof process.exit === 'function') process.exit(1)
|
|
4790
|
+
})
|
|
4791
|
+
}
|
|
4792
|
+
`;
|
|
4481
4793
|
export function rscKit(options = {}) {
|
|
4482
4794
|
resolvePaths(options);
|
|
4483
4795
|
const routesPlugin = {
|
|
4484
4796
|
name: "rsc-kit",
|
|
4797
|
+
// A Nitro module, which Nitro's Vite plugin collects from any plugin
|
|
4798
|
+
// that carries one. Only for a built server: the dev server evaluates
|
|
4799
|
+
// the entry through Vite's runner when it is first asked for, and there
|
|
4800
|
+
// is no earlier moment to offer.
|
|
4801
|
+
nitro: {
|
|
4802
|
+
name: "rsc-kit",
|
|
4803
|
+
setup(nitro) {
|
|
4804
|
+
if (nitro.options.dev || !instrumentationFile())
|
|
4805
|
+
return;
|
|
4806
|
+
nitro.options.virtual ??= {};
|
|
4807
|
+
nitro.options.virtual["#rsc-kit/startup"] = STARTUP_PLUGIN;
|
|
4808
|
+
nitro.options.plugins = [...(nitro.options.plugins ?? []), "#rsc-kit/startup"];
|
|
4809
|
+
},
|
|
4810
|
+
},
|
|
4485
4811
|
config(_config, env) {
|
|
4486
4812
|
if (!existsSync(appDir)) {
|
|
4487
4813
|
throw new Error(`[rsc-kit] No app directory at ${appDir} — nothing to build.`);
|
|
@@ -4493,6 +4819,7 @@ export function rscKit(options = {}) {
|
|
|
4493
4819
|
apiRoutes.clear();
|
|
4494
4820
|
discover(appDir);
|
|
4495
4821
|
registerMetadataRoutes(appDir);
|
|
4822
|
+
siteHosts = ownHosts(rootMetadataBase(appDir), hostsOption);
|
|
4496
4823
|
// Silent when it worked. The names were printed on every dev start and
|
|
4497
4824
|
// every build — thirty of them for a middling app, above the output that
|
|
4498
4825
|
// actually says something, and the build's classification table lists
|
|
@@ -4976,6 +5303,7 @@ export function rscKit(options = {}) {
|
|
|
4976
5303
|
clientChunks: (meta) => meta.normalizedId,
|
|
4977
5304
|
...actionEncryptionKey(),
|
|
4978
5305
|
}),
|
|
5306
|
+
barrelImportsPlugin(),
|
|
4979
5307
|
useSsrModules(),
|
|
4980
5308
|
serverRendererInRsc(),
|
|
4981
5309
|
metadataRoutesPlugin(),
|