@rsc-kit/core 0.16.3 → 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/buildReport.d.ts +6 -1
- package/dist/buildReport.js +16 -18
- package/dist/buildReport.js.map +1 -1
- package/dist/cache.js +8 -4
- package/dist/cache.js.map +1 -1
- package/dist/clientEntries.d.ts +32 -0
- package/dist/clientEntries.js +107 -0
- package/dist/clientEntries.js.map +1 -0
- package/dist/clientImports.d.ts +36 -0
- package/dist/clientImports.js +36 -0
- package/dist/clientImports.js.map +1 -0
- package/dist/devReload.d.ts +19 -0
- package/dist/devReload.js +32 -0
- package/dist/devReload.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/Form.js +54 -6
- package/dist/js/Form.js.map +1 -1
- package/dist/js/Link.d.ts +0 -3
- package/dist/js/Link.js +3 -6
- package/dist/js/Link.js.map +1 -1
- package/dist/js/SegmentBoundary.d.ts +38 -2
- package/dist/js/SegmentBoundary.js +51 -13
- package/dist/js/SegmentBoundary.js.map +1 -1
- package/dist/js/createViteRscApp.js +32 -17
- package/dist/js/createViteRscApp.js.map +1 -1
- package/dist/js/devNotice.d.ts +2 -2
- package/dist/js/devNotice.js +17 -7
- package/dist/js/devNotice.js.map +1 -1
- package/dist/js/fallbackReport.d.ts +2 -0
- package/dist/js/fallbackReport.js +51 -0
- package/dist/js/fallbackReport.js.map +1 -0
- package/dist/js/navigate.d.ts +1 -1
- package/dist/js/navigate.js +55 -23
- package/dist/js/navigate.js.map +1 -1
- 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/segmentStore.d.ts +2 -0
- package/dist/js/segmentStore.js +21 -1
- package/dist/js/segmentStore.js.map +1 -1
- package/dist/js/staleAssets.js +10 -1
- package/dist/js/staleAssets.js.map +1 -1
- package/dist/js/standardSchema.d.ts +6 -0
- package/dist/js/standardSchema.js +19 -1
- package/dist/js/standardSchema.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/useLinkStatus.d.ts +14 -1
- package/dist/js/useLinkStatus.js +15 -1
- package/dist/js/useLinkStatus.js.map +1 -1
- 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/metadata.d.ts +94 -15
- package/dist/metadata.js.map +1 -1
- package/dist/metadataRoutes.d.ts +61 -0
- package/dist/metadataRoutes.js +173 -0
- package/dist/metadataRoutes.js.map +1 -0
- package/dist/prerender.js +44 -10
- package/dist/prerender.js.map +1 -1
- package/dist/reactCache.d.ts +1 -0
- package/dist/reactCache.js +52 -0
- package/dist/reactCache.js.map +1 -0
- package/dist/redirect.d.ts +16 -2
- package/dist/redirect.js +10 -19
- package/dist/redirect.js.map +1 -1
- package/dist/request.d.ts +18 -1
- package/dist/request.js +91 -5
- package/dist/request.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/useSsr.d.ts +19 -0
- package/dist/useSsr.js +104 -0
- package/dist/useSsr.js.map +1 -0
- package/dist/vite.d.ts +86 -9
- package/dist/vite.js +827 -24
- package/dist/vite.js.map +1 -1
- package/package.json +13 -1
package/dist/routing.js
CHANGED
|
@@ -15,12 +15,17 @@ function indexRoutes(routes) {
|
|
|
15
15
|
const scored = [];
|
|
16
16
|
const exact = new Map();
|
|
17
17
|
for (const route of routes) {
|
|
18
|
-
const score = route.segments.reduce((n, s) => n +
|
|
18
|
+
const score = route.segments.reduce((n, s) => n +
|
|
19
|
+
(s.type === "static"
|
|
20
|
+
? 2
|
|
21
|
+
: s.type === "param" || s.type === "host"
|
|
22
|
+
? 1
|
|
23
|
+
: 0), 0);
|
|
19
24
|
scored.push({ route, score });
|
|
20
25
|
// An all-static route matches exactly one path, with the highest score any
|
|
21
26
|
// route can have for that path. First one in wins, as in the scan.
|
|
22
|
-
if (route.segments.every((s) => s.type ===
|
|
23
|
-
const key = route.segments.map((s) => s.value).join(
|
|
27
|
+
if (route.segments.every((s) => s.type === "static")) {
|
|
28
|
+
const key = route.segments.map((s) => s.value).join("/");
|
|
24
29
|
if (!exact.has(key))
|
|
25
30
|
exact.set(key, route);
|
|
26
31
|
}
|
|
@@ -44,9 +49,9 @@ function indexedApis(manifest) {
|
|
|
44
49
|
return found;
|
|
45
50
|
}
|
|
46
51
|
export function matchApiRoute(manifest, pathname) {
|
|
47
|
-
const parts = pathname.split(
|
|
52
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
48
53
|
const { scored, exact } = indexedApis(manifest);
|
|
49
|
-
const direct = exact.get(parts.join(
|
|
54
|
+
const direct = exact.get(parts.join("/"));
|
|
50
55
|
if (direct)
|
|
51
56
|
return { route: direct, params: {} };
|
|
52
57
|
let best = null;
|
|
@@ -68,15 +73,15 @@ export function matchApiRoute(manifest, pathname) {
|
|
|
68
73
|
* HEAD is included whenever GET is, because it is answered by GET.
|
|
69
74
|
*/
|
|
70
75
|
export function allowFor(route) {
|
|
71
|
-
const methods = route.methods.includes(
|
|
76
|
+
const methods = route.methods.includes("HEAD") || !route.methods.includes("GET")
|
|
72
77
|
? route.methods
|
|
73
|
-
: [...route.methods,
|
|
74
|
-
return methods.join(
|
|
78
|
+
: [...route.methods, "HEAD"];
|
|
79
|
+
return methods.join(", ");
|
|
75
80
|
}
|
|
76
|
-
export function matchRoute(manifest, pathname) {
|
|
77
|
-
const parts = pathname.split(
|
|
81
|
+
export function matchRoute(manifest, pathname, host = null) {
|
|
82
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
78
83
|
const { scored, exact } = indexed(manifest);
|
|
79
|
-
const direct = exact.get(parts.join(
|
|
84
|
+
const direct = exact.get(parts.join("/"));
|
|
80
85
|
if (direct)
|
|
81
86
|
return { route: direct, params: {} };
|
|
82
87
|
let best = null;
|
|
@@ -86,7 +91,7 @@ export function matchRoute(manifest, pathname) {
|
|
|
86
91
|
for (const { route, score } of scored) {
|
|
87
92
|
if (score <= bestScore)
|
|
88
93
|
continue;
|
|
89
|
-
const bound = bindSegments(route.segments, parts);
|
|
94
|
+
const bound = bindSegments(route.segments, parts, host);
|
|
90
95
|
if (!bound)
|
|
91
96
|
continue;
|
|
92
97
|
best = { route, params: bound };
|
|
@@ -102,7 +107,7 @@ export function matchRoute(manifest, pathname) {
|
|
|
102
107
|
* modal opens over.
|
|
103
108
|
*/
|
|
104
109
|
export function matchIntercept(manifest, pathname, slot) {
|
|
105
|
-
const parts = pathname.split(
|
|
110
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
106
111
|
let best = null;
|
|
107
112
|
let bestScore = -1;
|
|
108
113
|
for (const intercept of manifest.intercepts) {
|
|
@@ -117,7 +122,7 @@ export function matchIntercept(manifest, pathname, slot) {
|
|
|
117
122
|
// the guard is chosen by one while the content comes from the other, so a
|
|
118
123
|
// url could be checked against the unguarded route and rendered from the
|
|
119
124
|
// guarded one.
|
|
120
|
-
const score = intercept.segments.reduce((n, segment) => n + (segment.type ===
|
|
125
|
+
const score = intercept.segments.reduce((n, segment) => n + (segment.type === "static" ? 2 : segment.type === "param" ? 1 : 0), 0);
|
|
121
126
|
if (score > bestScore) {
|
|
122
127
|
best = { component: intercept.component, params };
|
|
123
128
|
bestScore = score;
|
|
@@ -125,22 +130,35 @@ export function matchIntercept(manifest, pathname, slot) {
|
|
|
125
130
|
}
|
|
126
131
|
return best;
|
|
127
132
|
}
|
|
128
|
-
function bindSegments(segments, parts) {
|
|
133
|
+
function bindSegments(segments, parts, host = null) {
|
|
129
134
|
const params = {};
|
|
130
135
|
for (let i = 0; i < segments.length; i++) {
|
|
131
136
|
const segment = segments[i];
|
|
132
|
-
if (segment.type ===
|
|
137
|
+
if (segment.type === "catchAll") {
|
|
133
138
|
// Swallows the rest, including none of it.
|
|
134
|
-
params[segment.value] = parts.slice(i).join(
|
|
139
|
+
params[segment.value] = parts.slice(i).join("/");
|
|
135
140
|
return params;
|
|
136
141
|
}
|
|
137
142
|
if (i >= parts.length)
|
|
138
143
|
return null;
|
|
139
|
-
|
|
144
|
+
// The leading part is the host's when one was given: a static directory
|
|
145
|
+
// of that name takes it (admin.example.com -> app/admin), and so does a
|
|
146
|
+
// host segment. A path part never binds a host segment, so
|
|
147
|
+
// example.com/nope is not a tenant called "nope".
|
|
148
|
+
const fromHost = i === 0 && host !== null;
|
|
149
|
+
if (segment.type === "static") {
|
|
140
150
|
if (parts[i] !== segment.value)
|
|
141
151
|
return null;
|
|
142
152
|
continue;
|
|
143
153
|
}
|
|
154
|
+
if (segment.type === "host") {
|
|
155
|
+
if (!fromHost)
|
|
156
|
+
return null;
|
|
157
|
+
params[segment.value] = parts[i];
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (fromHost)
|
|
161
|
+
return null;
|
|
144
162
|
params[segment.value] = decodeURIComponent(parts[i]);
|
|
145
163
|
}
|
|
146
164
|
return segments.length === parts.length ? params : null;
|
|
@@ -156,9 +174,11 @@ function bindSegments(segments, parts) {
|
|
|
156
174
|
export function sharedDepth(held, chain) {
|
|
157
175
|
if (!held)
|
|
158
176
|
return 0;
|
|
159
|
-
const mounted = held.split(
|
|
177
|
+
const mounted = held.split(",").filter(Boolean);
|
|
160
178
|
let shared = 0;
|
|
161
|
-
while (shared < mounted.length &&
|
|
179
|
+
while (shared < mounted.length &&
|
|
180
|
+
shared < chain.length &&
|
|
181
|
+
mounted[shared] === chain[shared]) {
|
|
162
182
|
shared++;
|
|
163
183
|
}
|
|
164
184
|
return shared;
|
|
@@ -177,7 +197,9 @@ export function sharedDepth(held, chain) {
|
|
|
177
197
|
*/
|
|
178
198
|
export function retentionKey(path, interceptSlot) {
|
|
179
199
|
const normalised = normalisePath(path);
|
|
180
|
-
return interceptSlot
|
|
200
|
+
return interceptSlot
|
|
201
|
+
? `__intercept:${interceptSlot}:${normalised}`
|
|
202
|
+
: normalised;
|
|
181
203
|
}
|
|
182
204
|
/**
|
|
183
205
|
* The same page written two ways is one key.
|
|
@@ -192,12 +214,12 @@ export function retentionKey(path, interceptSlot) {
|
|
|
192
214
|
function normalisePath(path) {
|
|
193
215
|
// Absolute urls reach this from the initial page, which is identified by
|
|
194
216
|
// href rather than by the path a link would use.
|
|
195
|
-
const withoutOrigin = path.replace(/^[a-z][a-z0-9+.-]*:\/\/[^/]+/i,
|
|
196
|
-
const [pathname, rest =
|
|
197
|
-
return (pathname.replace(/\/+$/,
|
|
217
|
+
const withoutOrigin = path.replace(/^[a-z][a-z0-9+.-]*:\/\/[^/]+/i, "");
|
|
218
|
+
const [pathname, rest = ""] = splitQuery(withoutOrigin);
|
|
219
|
+
return (pathname.replace(/\/+$/, "") || "/") + rest;
|
|
198
220
|
}
|
|
199
221
|
function splitQuery(url) {
|
|
200
222
|
const cut = url.search(/[?#]/);
|
|
201
|
-
return cut === -1 ? [url,
|
|
223
|
+
return cut === -1 ? [url, ""] : [url.slice(0, cut), url.slice(cut)];
|
|
202
224
|
}
|
|
203
225
|
//# sourceMappingURL=routing.js.map
|
package/dist/routing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routing.js","sourceRoot":"","sources":["../src/routing.ts"],"names":[],"mappings":"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","sourcesContent":["// Matching a url to a route, and deciding how much of the page to send.\n//\n// Pure: no Request, no Response, no filesystem. That is what lets the same\n// implementation serve three callers — the request handler, the prerenderer\n// enumerating what to build, and the generated server bundle, which embeds its\n// own route table and answers these without a manifest being passed around at\n// all.\n//\n// Every host would otherwise write this, and would get the same two things\n// wrong: answering /docs/new with [slug], and treating the layout chain as a\n// set rather than a sequence.\n\nimport type { ManifestApiRoute, ManifestRoute, RouteManifest, RouteSegment } from './manifest.js'\n\n/** A matched route and the params its url segments bound. */\nexport interface MatchedRoute {\n route: ManifestRoute\n params: Record<string, string>\n}\n\n/**\n * Match a pathname against the manifest's segments.\n *\n * The manifest stores segments rather than a pattern string, because the\n * pattern is the host's dialect — Laravel writes `{slug}`, Hono writes\n * `:slug`, and neither is the build's business. Matching them directly means\n * no dialect at all.\n *\n * Static segments beat dynamic ones at the same position: /docs/new is the\n * page called new, not the page called [slug] with slug=new.\n */\n/** A `route.ts` the url matched, and the params it bound. */\nexport interface MatchedApiRoute {\n route: ManifestApiRoute\n params: Record<string, string>\n}\n\n/**\n * The api route a url answers, if one does.\n *\n * Exact segment binding only, scored the same way pages are: more static\n * segments wins, a catch-all is weakest. Reusing that is the point — an api\n * route takes `[id]` and `[...rest]` because the matcher was already there.\n */\n/**\n * A manifest's routes, indexed once.\n *\n * Every request used to score every route with a reduce over its segments,\n * which at fifty routes is nothing and at five hundred is the request. The\n * score is a property of the route, so it is computed once; and a path that\n * is entirely static - most of them - is answered from a map without\n * touching the list at all. Keyed by the manifest object, so a host that\n * swaps manifests gets a fresh index and a dropped one is collected.\n */\ninterface Indexed<R extends { segments: RouteSegment[] }> {\n scored: { route: R; score: number }[]\n exact: Map<string, R>\n}\n\nconst indexes = new WeakMap<object, Indexed<ManifestRoute>>()\nconst apiIndexes = new WeakMap<object, Indexed<ManifestApiRoute>>()\n\nfunction indexRoutes<R extends { segments: RouteSegment[] }>(routes: R[]): Indexed<R> {\n const scored: { route: R; score: number }[] = []\n const exact = new Map<string, R>()\n\n for (const route of routes) {\n const score = route.segments.reduce(\n (n, s) => n + (s.type === 'static' ? 2 : s.type === 'param' ? 1 : 0),\n 0,\n )\n\n scored.push({ route, score })\n\n // An all-static route matches exactly one path, with the highest score any\n // route can have for that path. First one in wins, as in the scan.\n if (route.segments.every((s) => s.type === 'static')) {\n const key = route.segments.map((s) => s.value).join('/')\n\n if (!exact.has(key)) exact.set(key, route)\n }\n }\n\n return { scored, exact }\n}\n\nfunction indexed(manifest: RouteManifest): Indexed<ManifestRoute> {\n let found = indexes.get(manifest)\n\n if (!found) {\n found = indexRoutes(manifest.routes)\n indexes.set(manifest, found)\n }\n\n return found\n}\n\nfunction indexedApis(manifest: RouteManifest): Indexed<ManifestApiRoute> {\n let found = apiIndexes.get(manifest)\n\n if (!found) {\n found = indexRoutes(manifest.apis ?? [])\n apiIndexes.set(manifest, found)\n }\n\n return found\n}\n\nexport function matchApiRoute(\n manifest: RouteManifest,\n pathname: string,\n): MatchedApiRoute | null {\n const parts = pathname.split('/').filter(Boolean)\n const { scored, exact } = indexedApis(manifest)\n const direct = exact.get(parts.join('/'))\n\n if (direct) return { route: direct, params: {} }\n\n let best: MatchedApiRoute | null = null\n let bestScore = -1\n\n for (const { route, score } of scored) {\n if (score <= bestScore) continue\n\n const bound = bindSegments(route.segments, parts)\n\n if (!bound) continue\n\n best = { route, params: bound }\n bestScore = score\n }\n\n return best\n}\n\n/**\n * What a 405 should say this route does answer.\n *\n * HEAD is included whenever GET is, because it is answered by GET.\n */\nexport function allowFor(route: ManifestApiRoute): string {\n const methods = route.methods.includes('HEAD') || !route.methods.includes('GET')\n ? route.methods\n : [...route.methods, 'HEAD']\n\n return methods.join(', ')\n}\n\nexport function matchRoute(manifest: RouteManifest, pathname: string): MatchedRoute | null {\n const parts = pathname.split('/').filter(Boolean)\n const { scored, exact } = indexed(manifest)\n const direct = exact.get(parts.join('/'))\n\n if (direct) return { route: direct, params: {} }\n\n let best: MatchedRoute | null = null\n let bestScore = -1\n\n // More static segments wins; a catch-all is the weakest possible match. A\n // route that could not beat the best so far is not worth binding.\n for (const { route, score } of scored) {\n if (score <= bestScore) continue\n\n const bound = bindSegments(route.segments, parts)\n\n if (!bound) continue\n\n best = { route, params: bound }\n bestScore = score\n }\n\n return best\n}\n\n/**\n * The interceptor registered for a slot, if this url has one.\n *\n * Matched against the url being navigated *to*: `(.)posts/[slug]` intercepts\n * /posts/anything, and the params it binds are the target's, not the page the\n * modal opens over.\n */\nexport function matchIntercept(\n manifest: RouteManifest,\n pathname: string,\n slot: string,\n): { component: string; params: Record<string, string> } | null {\n const parts = pathname.split('/').filter(Boolean)\n let best: { component: string; params: Record<string, string> } | null = null\n let bestScore = -1\n\n for (const intercept of manifest.intercepts) {\n if (intercept.slot !== slot) continue\n\n const params = bindSegments(intercept.segments, parts)\n\n if (!params) continue\n\n // Scored exactly as matchRoute scores, and for the same reason: a static\n // segment beats a dynamic one at the same position. Taking the first match\n // instead made the two disagree about which route a url belongs to — and\n // the guard is chosen by one while the content comes from the other, so a\n // url could be checked against the unguarded route and rendered from the\n // guarded one.\n const score = intercept.segments.reduce(\n (n, segment) => n + (segment.type === 'static' ? 2 : segment.type === 'param' ? 1 : 0),\n 0,\n )\n\n if (score > bestScore) {\n best = { component: intercept.component, params }\n bestScore = score\n }\n }\n\n return best\n}\n\nfunction bindSegments(segments: RouteSegment[], parts: string[]): Record<string, string> | null {\n const params: Record<string, string> = {}\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i]\n\n if (segment.type === 'catchAll') {\n // Swallows the rest, including none of it.\n params[segment.value] = parts.slice(i).join('/')\n\n return params\n }\n\n if (i >= parts.length) return null\n\n if (segment.type === 'static') {\n if (parts[i] !== segment.value) return null\n continue\n }\n\n params[segment.value] = decodeURIComponent(parts[i])\n }\n\n return segments.length === parts.length ? params : null\n}\n\n/**\n * How much of the layout chain the client already holds.\n *\n * It sends the chain outermost-first; the shared prefix is what it can keep.\n * Answering with a whole document instead is not merely wasteful — it replaces\n * the root, and replacing the root unmounts every page retained behind the\n * current one, so a half-typed form does not survive going back.\n */\nexport function sharedDepth(held: string | null, chain: string[]): number {\n if (!held) return 0\n\n const mounted = held.split(',').filter(Boolean)\n let shared = 0\n\n while (shared < mounted.length && shared < chain.length && mounted[shared] === chain[shared]) {\n shared++\n }\n\n return shared\n}\n\n/**\n * What a page is remembered as, for retention and for the prefetch cache.\n *\n * The same url intercepted and not intercepted are two different things to go\n * back to — a modal over the feed, and the post on its own page — so they\n * cannot share a key or restoring one returns the other.\n *\n * Shared because both halves compute it: the host stores under this key and\n * the client looks under it. Written out at each of the seven places that\n * needed it, they only had to disagree once, and the symptom is a navigation\n * that silently rebuilds a page it was holding.\n */\nexport function retentionKey(path: string, interceptSlot?: string | null): string {\n const normalised = normalisePath(path)\n\n return interceptSlot ? `__intercept:${interceptSlot}:${normalised}` : normalised\n}\n\n/**\n * The same page written two ways is one key.\n *\n * A static host serves /orders as a directory, so the browser's url ends in a\n * slash while the build wrote the page down as /orders. Left unequal, the\n * entry retained for a page is never the one looked up on the way back: it\n * stays in the document, hidden, while a second copy is fetched and rendered\n * beside it — so the form you were filling in is there, and not the one you\n * are looking at.\n */\nfunction normalisePath(path: string): string {\n // Absolute urls reach this from the initial page, which is identified by\n // href rather than by the path a link would use.\n const withoutOrigin = path.replace(/^[a-z][a-z0-9+.-]*:\\/\\/[^/]+/i, '')\n const [pathname, rest = ''] = splitQuery(withoutOrigin)\n\n return (pathname.replace(/\\/+$/, '') || '/') + rest\n}\n\nfunction splitQuery(url: string): [string, string] {\n const cut = url.search(/[?#]/)\n\n return cut === -1 ? [url, ''] : [url.slice(0, cut), url.slice(cut)]\n}\n"]}
|
|
1
|
+
{"version":3,"file":"routing.js","sourceRoot":"","sources":["../src/routing.ts"],"names":[],"mappings":"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","sourcesContent":["// Matching a url to a route, and deciding how much of the page to send.\n//\n// Pure: no Request, no Response, no filesystem. That is what lets the same\n// implementation serve three callers — the request handler, the prerenderer\n// enumerating what to build, and the generated server bundle, which embeds its\n// own route table and answers these without a manifest being passed around at\n// all.\n//\n// Every host would otherwise write this, and would get the same two things\n// wrong: answering /docs/new with [slug], and treating the layout chain as a\n// set rather than a sequence.\n\nimport type {\n ManifestApiRoute,\n ManifestRoute,\n RouteManifest,\n RouteSegment,\n} from \"./manifest.js\";\n\n/** A matched route and the params its url segments bound. */\nexport interface MatchedRoute {\n route: ManifestRoute;\n params: Record<string, string>;\n}\n\n/**\n * Match a pathname against the manifest's segments.\n *\n * The manifest stores segments rather than a pattern string, because the\n * pattern is the host's dialect — Laravel writes `{slug}`, Hono writes\n * `:slug`, and neither is the build's business. Matching them directly means\n * no dialect at all.\n *\n * Static segments beat dynamic ones at the same position: /docs/new is the\n * page called new, not the page called [slug] with slug=new.\n */\n/** A `route.ts` the url matched, and the params it bound. */\nexport interface MatchedApiRoute {\n route: ManifestApiRoute;\n params: Record<string, string>;\n}\n\n/**\n * The api route a url answers, if one does.\n *\n * Exact segment binding only, scored the same way pages are: more static\n * segments wins, a catch-all is weakest. Reusing that is the point — an api\n * route takes `[id]` and `[...rest]` because the matcher was already there.\n */\n/**\n * A manifest's routes, indexed once.\n *\n * Every request used to score every route with a reduce over its segments,\n * which at fifty routes is nothing and at five hundred is the request. The\n * score is a property of the route, so it is computed once; and a path that\n * is entirely static - most of them - is answered from a map without\n * touching the list at all. Keyed by the manifest object, so a host that\n * swaps manifests gets a fresh index and a dropped one is collected.\n */\ninterface Indexed<R extends { segments: RouteSegment[] }> {\n scored: { route: R; score: number }[];\n exact: Map<string, R>;\n}\n\nconst indexes = new WeakMap<object, Indexed<ManifestRoute>>();\nconst apiIndexes = new WeakMap<object, Indexed<ManifestApiRoute>>();\n\nfunction indexRoutes<R extends { segments: RouteSegment[] }>(\n routes: R[],\n): Indexed<R> {\n const scored: { route: R; score: number }[] = [];\n const exact = new Map<string, R>();\n\n for (const route of routes) {\n const score = route.segments.reduce(\n (n, s) =>\n n +\n (s.type === \"static\"\n ? 2\n : s.type === \"param\" || s.type === \"host\"\n ? 1\n : 0),\n 0,\n );\n\n scored.push({ route, score });\n\n // An all-static route matches exactly one path, with the highest score any\n // route can have for that path. First one in wins, as in the scan.\n if (route.segments.every((s) => s.type === \"static\")) {\n const key = route.segments.map((s) => s.value).join(\"/\");\n\n if (!exact.has(key)) exact.set(key, route);\n }\n }\n\n return { scored, exact };\n}\n\nfunction indexed(manifest: RouteManifest): Indexed<ManifestRoute> {\n let found = indexes.get(manifest);\n\n if (!found) {\n found = indexRoutes(manifest.routes);\n indexes.set(manifest, found);\n }\n\n return found;\n}\n\nfunction indexedApis(manifest: RouteManifest): Indexed<ManifestApiRoute> {\n let found = apiIndexes.get(manifest);\n\n if (!found) {\n found = indexRoutes(manifest.apis ?? []);\n apiIndexes.set(manifest, found);\n }\n\n return found;\n}\n\nexport function matchApiRoute(\n manifest: RouteManifest,\n pathname: string,\n): MatchedApiRoute | null {\n const parts = pathname.split(\"/\").filter(Boolean);\n const { scored, exact } = indexedApis(manifest);\n const direct = exact.get(parts.join(\"/\"));\n\n if (direct) return { route: direct, params: {} };\n\n let best: MatchedApiRoute | null = null;\n let bestScore = -1;\n\n for (const { route, score } of scored) {\n if (score <= bestScore) continue;\n\n const bound = bindSegments(route.segments, parts);\n\n if (!bound) continue;\n\n best = { route, params: bound };\n bestScore = score;\n }\n\n return best;\n}\n\n/**\n * What a 405 should say this route does answer.\n *\n * HEAD is included whenever GET is, because it is answered by GET.\n */\nexport function allowFor(route: ManifestApiRoute): string {\n const methods =\n route.methods.includes(\"HEAD\") || !route.methods.includes(\"GET\")\n ? route.methods\n : [...route.methods, \"HEAD\"];\n\n return methods.join(\", \");\n}\n\nexport function matchRoute(\n manifest: RouteManifest,\n pathname: string,\n host: string | null = null,\n): MatchedRoute | null {\n const parts = pathname.split(\"/\").filter(Boolean);\n const { scored, exact } = indexed(manifest);\n const direct = exact.get(parts.join(\"/\"));\n\n if (direct) return { route: direct, params: {} };\n\n let best: MatchedRoute | null = null;\n let bestScore = -1;\n\n // More static segments wins; a catch-all is the weakest possible match. A\n // route that could not beat the best so far is not worth binding.\n for (const { route, score } of scored) {\n if (score <= bestScore) continue;\n\n const bound = bindSegments(route.segments, parts, host);\n\n if (!bound) continue;\n\n best = { route, params: bound };\n bestScore = score;\n }\n\n return best;\n}\n\n/**\n * The interceptor registered for a slot, if this url has one.\n *\n * Matched against the url being navigated *to*: `(.)posts/[slug]` intercepts\n * /posts/anything, and the params it binds are the target's, not the page the\n * modal opens over.\n */\nexport function matchIntercept(\n manifest: RouteManifest,\n pathname: string,\n slot: string,\n): { component: string; params: Record<string, string> } | null {\n const parts = pathname.split(\"/\").filter(Boolean);\n let best: { component: string; params: Record<string, string> } | null = null;\n let bestScore = -1;\n\n for (const intercept of manifest.intercepts) {\n if (intercept.slot !== slot) continue;\n\n const params = bindSegments(intercept.segments, parts);\n\n if (!params) continue;\n\n // Scored exactly as matchRoute scores, and for the same reason: a static\n // segment beats a dynamic one at the same position. Taking the first match\n // instead made the two disagree about which route a url belongs to — and\n // the guard is chosen by one while the content comes from the other, so a\n // url could be checked against the unguarded route and rendered from the\n // guarded one.\n const score = intercept.segments.reduce(\n (n, segment) =>\n n + (segment.type === \"static\" ? 2 : segment.type === \"param\" ? 1 : 0),\n 0,\n );\n\n if (score > bestScore) {\n best = { component: intercept.component, params };\n bestScore = score;\n }\n }\n\n return best;\n}\n\nfunction bindSegments(\n segments: RouteSegment[],\n parts: string[],\n host: string | null = null,\n): Record<string, string> | null {\n const params: Record<string, string> = {};\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i];\n\n if (segment.type === \"catchAll\") {\n // Swallows the rest, including none of it.\n params[segment.value] = parts.slice(i).join(\"/\");\n\n return params;\n }\n\n if (i >= parts.length) return null;\n\n // The leading part is the host's when one was given: a static directory\n // of that name takes it (admin.example.com -> app/admin), and so does a\n // host segment. A path part never binds a host segment, so\n // example.com/nope is not a tenant called \"nope\".\n const fromHost = i === 0 && host !== null;\n\n if (segment.type === \"static\") {\n if (parts[i] !== segment.value) return null;\n continue;\n }\n\n if (segment.type === \"host\") {\n if (!fromHost) return null;\n\n params[segment.value] = parts[i];\n continue;\n }\n\n if (fromHost) return null;\n\n params[segment.value] = decodeURIComponent(parts[i]);\n }\n\n return segments.length === parts.length ? params : null;\n}\n\n/**\n * How much of the layout chain the client already holds.\n *\n * It sends the chain outermost-first; the shared prefix is what it can keep.\n * Answering with a whole document instead is not merely wasteful — it replaces\n * the root, and replacing the root unmounts every page retained behind the\n * current one, so a half-typed form does not survive going back.\n */\nexport function sharedDepth(held: string | null, chain: string[]): number {\n if (!held) return 0;\n\n const mounted = held.split(\",\").filter(Boolean);\n let shared = 0;\n\n while (\n shared < mounted.length &&\n shared < chain.length &&\n mounted[shared] === chain[shared]\n ) {\n shared++;\n }\n\n return shared;\n}\n\n/**\n * What a page is remembered as, for retention and for the prefetch cache.\n *\n * The same url intercepted and not intercepted are two different things to go\n * back to — a modal over the feed, and the post on its own page — so they\n * cannot share a key or restoring one returns the other.\n *\n * Shared because both halves compute it: the host stores under this key and\n * the client looks under it. Written out at each of the seven places that\n * needed it, they only had to disagree once, and the symptom is a navigation\n * that silently rebuilds a page it was holding.\n */\nexport function retentionKey(\n path: string,\n interceptSlot?: string | null,\n): string {\n const normalised = normalisePath(path);\n\n return interceptSlot\n ? `__intercept:${interceptSlot}:${normalised}`\n : normalised;\n}\n\n/**\n * The same page written two ways is one key.\n *\n * A static host serves /orders as a directory, so the browser's url ends in a\n * slash while the build wrote the page down as /orders. Left unequal, the\n * entry retained for a page is never the one looked up on the way back: it\n * stays in the document, hidden, while a second copy is fetched and rendered\n * beside it — so the form you were filling in is there, and not the one you\n * are looking at.\n */\nfunction normalisePath(path: string): string {\n // Absolute urls reach this from the initial page, which is identified by\n // href rather than by the path a link would use.\n const withoutOrigin = path.replace(/^[a-z][a-z0-9+.-]*:\\/\\/[^/]+/i, \"\");\n const [pathname, rest = \"\"] = splitQuery(withoutOrigin);\n\n return (pathname.replace(/\\/+$/, \"\") || \"/\") + rest;\n}\n\nfunction splitQuery(url: string): [string, string] {\n const cut = url.search(/[?#]/);\n\n return cut === -1 ? [url, \"\"] : [url.slice(0, cut), url.slice(cut)];\n}\n"]}
|
package/dist/useSsr.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const SSR_GUIDE = "https://docs.rsc-kit.dev/guides/emails";
|
|
2
|
+
export declare function hasUseSsr(code: string): boolean;
|
|
3
|
+
export declare class UseSsrError extends Error {
|
|
4
|
+
}
|
|
5
|
+
/** The runtime exports a "use ssr" module can offer: named functions, and a default. */
|
|
6
|
+
export declare function ssrExports(code: string, file: string): {
|
|
7
|
+
named: string[];
|
|
8
|
+
hasDefault: boolean;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* The module the server-components environment gets in place of a "use ssr"
|
|
12
|
+
* one: its exports, as async proxies that call the real module where it
|
|
13
|
+
* runs. Null when the module carries no directive.
|
|
14
|
+
*/
|
|
15
|
+
export declare function ssrProxyModule(code: string, id: string, root?: string): string | null;
|
|
16
|
+
/** Any of react-dom's server renderer entries. */
|
|
17
|
+
export declare const SERVER_RENDERER: RegExp;
|
|
18
|
+
/** What a module gets in place of react-dom/server where server components render. */
|
|
19
|
+
export declare function serverRendererMessage(importer: string | null): string;
|
package/dist/useSsr.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { basename, relative } from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* "use ssr": a module that runs in the ssr environment and is called from
|
|
4
|
+
* where server components render.
|
|
5
|
+
*
|
|
6
|
+
* react-dom/server cannot run where server components render. React's
|
|
7
|
+
* server build refuses to load it, and the refusal is right: the renderer
|
|
8
|
+
* needs the client build's internals, and the components it would render
|
|
9
|
+
* import that same server `react` - no useState, no useContext. A template
|
|
10
|
+
* and the call that renders it have to load together somewhere React DOM's
|
|
11
|
+
* server renderer can: the ssr environment, which every app here already
|
|
12
|
+
* has. plugin-rsc exposes that as a call to write by hand,
|
|
13
|
+
* `import.meta.viteRsc.import('./render', { environment: 'ssr' })`, and
|
|
14
|
+
* nobody should have to. So a directive names the module instead, the way
|
|
15
|
+
* "use client" names one, and this rewrites it for the server-components
|
|
16
|
+
* environment into proxies of its exports that call across. Everything
|
|
17
|
+
* else imports it normally.
|
|
18
|
+
*
|
|
19
|
+
* The exports are functions and the calls are async, because the answer
|
|
20
|
+
* crosses environments as a promise. Anything else is refused with the
|
|
21
|
+
* export named, at build and in dev, rather than proxied into nonsense.
|
|
22
|
+
*/
|
|
23
|
+
const DIRECTIVE = /^\s*(?:\/\/[^\n]*\n|\/\*[\s\S]*?\*\/\s*)*["']use ssr["']/;
|
|
24
|
+
export const SSR_GUIDE = "https://docs.rsc-kit.dev/guides/emails";
|
|
25
|
+
export function hasUseSsr(code) {
|
|
26
|
+
return DIRECTIVE.test(code.slice(0, 2048));
|
|
27
|
+
}
|
|
28
|
+
export class UseSsrError extends Error {
|
|
29
|
+
}
|
|
30
|
+
/** Comments blanked, so an `export` inside one is not read as one. */
|
|
31
|
+
function withoutComments(code) {
|
|
32
|
+
return code
|
|
33
|
+
.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " "))
|
|
34
|
+
.replace(/(^|[^:\\])\/\/[^\n]*/g, (m, lead) => lead + " ".repeat(m.length - lead.length));
|
|
35
|
+
}
|
|
36
|
+
const NAME = "[A-Za-z_$][\\w$]*";
|
|
37
|
+
const NON_FUNCTION = /^\s*(?:new\b|["'`\d[{]|true\b|false\b|null\b)/;
|
|
38
|
+
/** The runtime exports a "use ssr" module can offer: named functions, and a default. */
|
|
39
|
+
export function ssrExports(code, file) {
|
|
40
|
+
const src = withoutComments(code);
|
|
41
|
+
const named = new Set();
|
|
42
|
+
let hasDefault = false;
|
|
43
|
+
for (const m of src.matchAll(new RegExp(`^[ \\t]*export\\s+(async\\s+)?function\\s*\\*?\\s*(${NAME})`, "gm"))) {
|
|
44
|
+
if (!m[1]) {
|
|
45
|
+
throw new UseSsrError(`"use ssr": \`${m[2]}\` in ${file} is called from another environment, and the answer crosses as a promise. Make it an async function.`);
|
|
46
|
+
}
|
|
47
|
+
named.add(m[2]);
|
|
48
|
+
}
|
|
49
|
+
for (const m of src.matchAll(new RegExp(`^[ \\t]*export\\s+(?:const|let|var)\\s+(${NAME})\\s*(?::[^=\\n]*)?=([^\\n]*)`, "gm"))) {
|
|
50
|
+
if (NON_FUNCTION.test(m[2])) {
|
|
51
|
+
throw new UseSsrError(`"use ssr": a module with the directive exports functions only; \`${m[1]}\` in ${file} is a value. Put it in a module without the directive.`);
|
|
52
|
+
}
|
|
53
|
+
named.add(m[1]);
|
|
54
|
+
}
|
|
55
|
+
if (/^[ \t]*export\s+(?:const|let|var)\s*[[{]/m.test(src)) {
|
|
56
|
+
throw new UseSsrError(`"use ssr": ${file} destructures an export. Export each function by its own name; that is what gets called across.`);
|
|
57
|
+
}
|
|
58
|
+
if (/^[ \t]*export\s+(?:abstract\s+)?(?:class|enum)\b/m.test(src)) {
|
|
59
|
+
throw new UseSsrError(`"use ssr": a module with the directive exports functions only; ${file} exports a class or enum.`);
|
|
60
|
+
}
|
|
61
|
+
if (/^[ \t]*export\s*(?:\{|\*)/m.test(src)) {
|
|
62
|
+
throw new UseSsrError(`"use ssr": ${file} re-exports. Export the functions where they are declared; \`export { … }\` and \`export *\` cannot be called across.`);
|
|
63
|
+
}
|
|
64
|
+
const dflt = /^[ \t]*export\s+default\s+(async\s+)?(function\b)?/m.exec(src);
|
|
65
|
+
if (dflt) {
|
|
66
|
+
if (dflt[2] && !dflt[1]) {
|
|
67
|
+
throw new UseSsrError(`"use ssr": the default export of ${file} is called from another environment. Make it an async function.`);
|
|
68
|
+
}
|
|
69
|
+
hasDefault = true;
|
|
70
|
+
}
|
|
71
|
+
return { named: [...named], hasDefault };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The module the server-components environment gets in place of a "use ssr"
|
|
75
|
+
* one: its exports, as async proxies that call the real module where it
|
|
76
|
+
* runs. Null when the module carries no directive.
|
|
77
|
+
*/
|
|
78
|
+
export function ssrProxyModule(code, id, root = process.cwd()) {
|
|
79
|
+
if (!hasUseSsr(code))
|
|
80
|
+
return null;
|
|
81
|
+
const path = id.split("?")[0];
|
|
82
|
+
const file = relative(root, path);
|
|
83
|
+
const { named, hasDefault } = ssrExports(code, file);
|
|
84
|
+
return ([
|
|
85
|
+
`// "use ssr": ${file} runs in the ssr environment, where React DOM's server renderer can. These call across.`,
|
|
86
|
+
`const __rsc_kit_ssr = import.meta.viteRsc.import(${JSON.stringify("./" + basename(path))}, { environment: 'ssr' });`,
|
|
87
|
+
...named.map((name) => `export const ${name} = async (...args) => (await __rsc_kit_ssr).${name}(...args);`),
|
|
88
|
+
...(hasDefault
|
|
89
|
+
? [
|
|
90
|
+
"export default async (...args) => (await __rsc_kit_ssr).default(...args);",
|
|
91
|
+
]
|
|
92
|
+
: []),
|
|
93
|
+
].join("\n") + "\n");
|
|
94
|
+
}
|
|
95
|
+
/** Any of react-dom's server renderer entries. */
|
|
96
|
+
export const SERVER_RENDERER = /^react-dom\/server(?:\.[a-z]+)?$/;
|
|
97
|
+
/** What a module gets in place of react-dom/server where server components render. */
|
|
98
|
+
export function serverRendererMessage(importer) {
|
|
99
|
+
return ("react-dom/server cannot run where server components render: React's server build has no client internals for it, and the components it would render import that same React." +
|
|
100
|
+
(importer ? ` Imported by ${importer}.` : "") +
|
|
101
|
+
' Put the rendering - the template and the call - in a module that starts with "use ssr": it runs in the ssr environment, and its exports are called from here as async functions. ' +
|
|
102
|
+
SSR_GUIDE);
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=useSsr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSsr.js","sourceRoot":"","sources":["../src/useSsr.ts"],"names":[],"mappings":"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","sourcesContent":["import { basename, relative } from \"node:path\";\n\n/**\n * \"use ssr\": a module that runs in the ssr environment and is called from\n * where server components render.\n *\n * react-dom/server cannot run where server components render. React's\n * server build refuses to load it, and the refusal is right: the renderer\n * needs the client build's internals, and the components it would render\n * import that same server `react` - no useState, no useContext. A template\n * and the call that renders it have to load together somewhere React DOM's\n * server renderer can: the ssr environment, which every app here already\n * has. plugin-rsc exposes that as a call to write by hand,\n * `import.meta.viteRsc.import('./render', { environment: 'ssr' })`, and\n * nobody should have to. So a directive names the module instead, the way\n * \"use client\" names one, and this rewrites it for the server-components\n * environment into proxies of its exports that call across. Everything\n * else imports it normally.\n *\n * The exports are functions and the calls are async, because the answer\n * crosses environments as a promise. Anything else is refused with the\n * export named, at build and in dev, rather than proxied into nonsense.\n */\nconst DIRECTIVE = /^\\s*(?:\\/\\/[^\\n]*\\n|\\/\\*[\\s\\S]*?\\*\\/\\s*)*[\"']use ssr[\"']/;\n\nexport const SSR_GUIDE = \"https://docs.rsc-kit.dev/guides/emails\";\n\nexport function hasUseSsr(code: string): boolean {\n return DIRECTIVE.test(code.slice(0, 2048));\n}\n\nexport class UseSsrError extends Error {}\n\n/** Comments blanked, so an `export` inside one is not read as one. */\nfunction withoutComments(code: string): string {\n return code\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, (m) => m.replace(/[^\\n]/g, \" \"))\n .replace(\n /(^|[^:\\\\])\\/\\/[^\\n]*/g,\n (m, lead: string) => lead + \" \".repeat(m.length - lead.length),\n );\n}\n\nconst NAME = \"[A-Za-z_$][\\\\w$]*\";\nconst NON_FUNCTION = /^\\s*(?:new\\b|[\"'`\\d[{]|true\\b|false\\b|null\\b)/;\n\n/** The runtime exports a \"use ssr\" module can offer: named functions, and a default. */\nexport function ssrExports(\n code: string,\n file: string,\n): { named: string[]; hasDefault: boolean } {\n const src = withoutComments(code);\n const named = new Set<string>();\n let hasDefault = false;\n\n for (const m of src.matchAll(\n new RegExp(\n `^[ \\\\t]*export\\\\s+(async\\\\s+)?function\\\\s*\\\\*?\\\\s*(${NAME})`,\n \"gm\",\n ),\n )) {\n if (!m[1]) {\n throw new UseSsrError(\n `\"use ssr\": \\`${m[2]}\\` in ${file} is called from another environment, and the answer crosses as a promise. Make it an async function.`,\n );\n }\n\n named.add(m[2]);\n }\n\n for (const m of src.matchAll(\n new RegExp(\n `^[ \\\\t]*export\\\\s+(?:const|let|var)\\\\s+(${NAME})\\\\s*(?::[^=\\\\n]*)?=([^\\\\n]*)`,\n \"gm\",\n ),\n )) {\n if (NON_FUNCTION.test(m[2])) {\n throw new UseSsrError(\n `\"use ssr\": a module with the directive exports functions only; \\`${m[1]}\\` in ${file} is a value. Put it in a module without the directive.`,\n );\n }\n\n named.add(m[1]);\n }\n\n if (/^[ \\t]*export\\s+(?:const|let|var)\\s*[[{]/m.test(src)) {\n throw new UseSsrError(\n `\"use ssr\": ${file} destructures an export. Export each function by its own name; that is what gets called across.`,\n );\n }\n\n if (/^[ \\t]*export\\s+(?:abstract\\s+)?(?:class|enum)\\b/m.test(src)) {\n throw new UseSsrError(\n `\"use ssr\": a module with the directive exports functions only; ${file} exports a class or enum.`,\n );\n }\n\n if (/^[ \\t]*export\\s*(?:\\{|\\*)/m.test(src)) {\n throw new UseSsrError(\n `\"use ssr\": ${file} re-exports. Export the functions where they are declared; \\`export { … }\\` and \\`export *\\` cannot be called across.`,\n );\n }\n\n const dflt = /^[ \\t]*export\\s+default\\s+(async\\s+)?(function\\b)?/m.exec(src);\n\n if (dflt) {\n if (dflt[2] && !dflt[1]) {\n throw new UseSsrError(\n `\"use ssr\": the default export of ${file} is called from another environment. Make it an async function.`,\n );\n }\n\n hasDefault = true;\n }\n\n return { named: [...named], hasDefault };\n}\n\n/**\n * The module the server-components environment gets in place of a \"use ssr\"\n * one: its exports, as async proxies that call the real module where it\n * runs. Null when the module carries no directive.\n */\nexport function ssrProxyModule(\n code: string,\n id: string,\n root: string = process.cwd(),\n): string | null {\n if (!hasUseSsr(code)) return null;\n\n const path = id.split(\"?\")[0];\n const file = relative(root, path);\n const { named, hasDefault } = ssrExports(code, file);\n\n return (\n [\n `// \"use ssr\": ${file} runs in the ssr environment, where React DOM's server renderer can. These call across.`,\n `const __rsc_kit_ssr = import.meta.viteRsc.import(${JSON.stringify(\"./\" + basename(path))}, { environment: 'ssr' });`,\n ...named.map(\n (name) =>\n `export const ${name} = async (...args) => (await __rsc_kit_ssr).${name}(...args);`,\n ),\n ...(hasDefault\n ? [\n \"export default async (...args) => (await __rsc_kit_ssr).default(...args);\",\n ]\n : []),\n ].join(\"\\n\") + \"\\n\"\n );\n}\n\n/** Any of react-dom's server renderer entries. */\nexport const SERVER_RENDERER = /^react-dom\\/server(?:\\.[a-z]+)?$/;\n\n/** What a module gets in place of react-dom/server where server components render. */\nexport function serverRendererMessage(importer: string | null): string {\n return (\n \"react-dom/server cannot run where server components render: React's server build has no client internals for it, and the components it would render import that same React.\" +\n (importer ? ` Imported by ${importer}.` : \"\") +\n ' Put the rendering - the template and the call - in a module that starts with \"use ssr\": it runs in the ssr environment, and its exports are called from here as async functions. ' +\n SSR_GUIDE\n );\n}\n"]}
|
package/dist/vite.d.ts
CHANGED
|
@@ -6,16 +6,41 @@ export interface RscKitOptions {
|
|
|
6
6
|
/** Directory holding the app/ route tree. Defaults to `src`. */
|
|
7
7
|
sourceDir?: string;
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Say what built the site: `<meta name="generator" content="rsc-kit">` in
|
|
10
|
+
* every document and `X-Powered-By: rsc-kit` on every response. The name
|
|
11
|
+
* only, never the version - a version in every response is what a
|
|
12
|
+
* vulnerability scanner filters on. On by default; `false` for a team
|
|
13
|
+
* whose policy strips every framework identifier.
|
|
10
14
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
15
|
+
* `X-RSC-Kit: stored | rendered | shell` - how a response was answered, the
|
|
16
|
+
* header a developer reads in the Network tab - is not this and is always
|
|
17
|
+
* sent: it names no product, and a CDN rule or a check can key on it.
|
|
18
|
+
*/
|
|
19
|
+
identify?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Unroll named imports from a barrel package on the server, in development.
|
|
22
|
+
*
|
|
23
|
+
* `import { ArrowRight } from 'lucide-react'` reaches a file that re-exports
|
|
24
|
+
* fifteen hundred icons, one module each; the browser gets the package
|
|
25
|
+
* pre-bundled, the server environments do not, so Vite loaded every icon
|
|
26
|
+
* module on the first request. The import is rewritten to the module that
|
|
27
|
+
* owns the name, read from the barrel itself. On by default; `false` to
|
|
28
|
+
* leave imports as written.
|
|
29
|
+
*/
|
|
30
|
+
barrelImports?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* The site's own hosts, beyond the one in the root layout's metadataBase.
|
|
13
33
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
34
|
+
* A request from any other host is routed with the host in front of the
|
|
35
|
+
* path: `admin.example.com/users` reaches `app/admin/users/page.tsx`, the
|
|
36
|
+
* file `example.com/admin/users` reaches by path; `acme.example.com/`
|
|
37
|
+
* reaches `app/[domain]/page.tsx` with `domain: "acme"`, and a custom
|
|
38
|
+
* domain `acme.com/` the same file with `domain: "acme.com"`. The site's
|
|
39
|
+
* own hosts keep path routing, so an app adds tenants without moving a
|
|
40
|
+
* file. `www.` of each is its own too. With no metadataBase and nothing
|
|
41
|
+
* here, every host is the site's own and nothing is prepended.
|
|
17
42
|
*/
|
|
18
|
-
|
|
43
|
+
hosts?: string[];
|
|
19
44
|
/**
|
|
20
45
|
* Serve the app from a service worker, so it survives a reload with no
|
|
21
46
|
* network at all.
|
|
@@ -44,6 +69,18 @@ export interface RscKitOptions {
|
|
|
44
69
|
* regardless, because React expects to find it in the DOM to hydrate.
|
|
45
70
|
*/
|
|
46
71
|
inlineStylesheets?: "auto" | boolean | number;
|
|
72
|
+
/**
|
|
73
|
+
* Run the project's own typecheck as part of `vite build`, and fail the
|
|
74
|
+
* build on an error. On by default when the project has a `tsconfig.json`
|
|
75
|
+
* and `typescript` installed; `false` turns it off.
|
|
76
|
+
*
|
|
77
|
+
* A build that bundles a `<Link href="/nowhere">` and stores every page
|
|
78
|
+
* has produced a broken site that looks finished. The route types exist so
|
|
79
|
+
* that link is an error - but Vite never typechecks, so the error only
|
|
80
|
+
* showed for whoever ran `tsc` separately. `next build` typechecks for the
|
|
81
|
+
* same reason.
|
|
82
|
+
*/
|
|
83
|
+
typecheck?: boolean;
|
|
47
84
|
/**
|
|
48
85
|
* The most a server action body may be, in bytes. 8 MB unless set.
|
|
49
86
|
*
|
|
@@ -93,8 +130,12 @@ export interface RscKitOptions {
|
|
|
93
130
|
* because a second setting that could disagree with the first is a bug
|
|
94
131
|
* waiting to be filed. `false` restores the plain 404.
|
|
95
132
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
133
|
+
* The built server forwards too — the entry that carries this is the one
|
|
134
|
+
* Nitro bundles — with the origin read at build time from the project's
|
|
135
|
+
* .env, while host calls read it from the process at runtime. A build on a
|
|
136
|
+
* machine whose .env names a different backend than production's sets
|
|
137
|
+
* RSC_BACKEND for the build. The name predates that: it was the dev server's
|
|
138
|
+
* alone before Nitro built the server from the same entry.
|
|
98
139
|
*/
|
|
99
140
|
devFallback?: string | false;
|
|
100
141
|
/**
|
|
@@ -245,4 +286,40 @@ export interface KnownAction {
|
|
|
245
286
|
name: string;
|
|
246
287
|
file: string;
|
|
247
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* The worker the dev server serves at /sw.js: it removes the one a production
|
|
291
|
+
* run left on this origin, and the caches with it, then reloads each page it
|
|
292
|
+
* was controlling so they load uncontrolled. Nothing else runs in development.
|
|
293
|
+
*/
|
|
294
|
+
export declare const DEV_WORKER = "self.addEventListener('install', () => self.skipWaiting())\nself.addEventListener('activate', (event) => {\n event.waitUntil((async () => {\n const keys = await caches.keys()\n await Promise.all(keys.filter((k) => k.startsWith('rsc-kit-')).map((k) => caches.delete(k)))\n await self.clients.claim()\n const pages = await self.clients.matchAll({ type: 'window' })\n await self.registration.unregister()\n for (const page of pages) page.navigate(page.url).catch(() => {})\n })())\n})\n";
|
|
295
|
+
/**
|
|
296
|
+
* The project's typecheck, as part of the build.
|
|
297
|
+
*
|
|
298
|
+
* Runs once, in the first environment to start, after the route types have
|
|
299
|
+
* been written by the config hook - so a link to a route that does not exist
|
|
300
|
+
* is an error here and not a 404 found after deploying. Skipped where the
|
|
301
|
+
* project is not doing this checking at all: no tsconfig.json, or no
|
|
302
|
+
* typescript to run. Its own output is the message, because that is what
|
|
303
|
+
* the developer would have read from `tsc`.
|
|
304
|
+
*/
|
|
305
|
+
export type TypecheckOutcome = {
|
|
306
|
+
ran: false;
|
|
307
|
+
because: "no tsconfig" | "no typescript";
|
|
308
|
+
} | {
|
|
309
|
+
ran: true;
|
|
310
|
+
ok: true;
|
|
311
|
+
seconds: number;
|
|
312
|
+
} | {
|
|
313
|
+
ran: true;
|
|
314
|
+
ok: false;
|
|
315
|
+
output: string;
|
|
316
|
+
};
|
|
317
|
+
/**
|
|
318
|
+
* `tsc --noEmit` on the project's own tsconfig, the way `check` runs it.
|
|
319
|
+
*
|
|
320
|
+
* Through package.json rather than a subpath: TypeScript's exports map does
|
|
321
|
+
* not expose bin/tsc, and the bin field is where the name lives. Its output
|
|
322
|
+
* is the message, because that is what the developer would have read.
|
|
323
|
+
*/
|
|
324
|
+
export declare function typecheckProject(root: string): TypecheckOutcome;
|
|
248
325
|
export declare function rscKit(options?: RscKitOptions): PluginOption[];
|