@rsc-kit/core 0.18.1 → 0.19.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/action.d.ts +16 -2
- package/dist/action.js +21 -12
- package/dist/action.js.map +1 -1
- package/dist/apiPrerender.js +24 -6
- package/dist/apiPrerender.js.map +1 -1
- package/dist/clientPackages.d.ts +30 -0
- package/dist/clientPackages.js +234 -0
- package/dist/clientPackages.js.map +1 -0
- package/dist/compress.d.ts +12 -0
- package/dist/compress.js +134 -0
- package/dist/compress.js.map +1 -0
- package/dist/compressRuntime.d.ts +8 -0
- package/dist/compressRuntime.js +62 -0
- package/dist/compressRuntime.js.map +1 -0
- package/dist/files.d.ts +20 -0
- package/dist/files.js +38 -0
- package/dist/files.js.map +1 -1
- package/dist/formSubmit.d.ts +1 -0
- package/dist/formSubmit.js +14 -0
- package/dist/formSubmit.js.map +1 -0
- package/dist/host.d.ts +23 -0
- package/dist/host.js +175 -31
- package/dist/host.js.map +1 -1
- package/dist/hostCalls.d.ts +15 -0
- package/dist/hostCalls.js +75 -8
- package/dist/hostCalls.js.map +1 -1
- package/dist/js/Form.d.ts +19 -2
- package/dist/js/Form.js +110 -83
- package/dist/js/Form.js.map +1 -1
- package/dist/js/createViteRscApp.d.ts +1 -0
- package/dist/js/createViteRscApp.js +34 -5
- package/dist/js/createViteRscApp.js.map +1 -1
- package/dist/js/errors.d.ts +3 -1
- package/dist/js/errors.js +26 -3
- package/dist/js/errors.js.map +1 -1
- package/dist/js/formEncoding.d.ts +72 -3
- package/dist/js/formEncoding.js +284 -20
- package/dist/js/formEncoding.js.map +1 -1
- package/dist/js/navigate.d.ts +3 -0
- package/dist/js/navigate.js +56 -6
- package/dist/js/navigate.js.map +1 -1
- package/dist/js/updateStore.js +8 -2
- package/dist/js/updateStore.js.map +1 -1
- package/dist/openapi.d.ts +74 -0
- package/dist/openapi.js +172 -0
- package/dist/openapi.js.map +1 -0
- package/dist/redirect.d.ts +2 -2
- package/dist/redirect.js.map +1 -1
- package/dist/request.d.ts +56 -2
- package/dist/request.js +68 -4
- package/dist/request.js.map +1 -1
- package/dist/routes.d.ts +15 -1
- package/dist/routes.js.map +1 -1
- package/dist/testing.d.ts +14 -0
- package/dist/testing.js +56 -2
- package/dist/testing.js.map +1 -1
- package/dist/vite.d.ts +69 -1
- package/dist/vite.js +451 -45
- package/dist/vite.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clientPackages.js","sourceRoot":"","sources":["../src/clientPackages.ts"],"names":[],"mappings":"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","sourcesContent":["// Dependencies with \"use client\" files that plugin-rsc would leave external.\n//\n// plugin-rsc bundles a dependency into the server graphs - and so sees its\n// directives - only when the dependency declares react as a peer. That is\n// the right signal for a library written for React. It misses the package\n// that imports react and never says so: a component wrapper generated by a\n// tool, a workspace package with react under dependencies, a published\n// package whose author forgot. Left external, its \"use client\" is a string\n// nobody reads, the rsc environment loads it under the react-server\n// condition, and the first useState inside it fails at render with an error\n// that names React and not the package.\n//\n// So the direct dependencies are read once at config time: any that plugin-rsc\n// would not bundle, and that carries a \"use client\" directive in one of its\n// files, is bundled here. Direct dependencies only - a directive two levels\n// down is the concern of the package between, which declared it.\n\nimport { existsSync, readdirSync, readFileSync, realpathSync, statSync, openSync, readSync, closeSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\n/** A directive at the top of a file: comments may precede it, code may not. */\nconst DIRECTIVE = /^(?:\\s|\\/\\/[^\\n]*\\n|\\/\\*[\\s\\S]*?\\*\\/)*(?:'use client'|\"use client\")\\s*;?/;\n\nconst SOURCE = /\\.(?:m?js|cjs|jsx)$/;\n\n/** The most files read per package before giving up: a bound, not a budget. */\nconst FILE_CAP = 3000;\n\n/** Enough of a file to hold a licence header and the directive after it. */\nconst HEAD_BYTES = 2048;\n\n/** The packages plugin-rsc handles itself, and this one. */\nconst OWN = new Set([\"react\", \"react-dom\", \"react-server-dom-webpack\", \"@vitejs/plugin-rsc\"]);\n\ninterface PackageJson {\n name?: string;\n dependencies?: Record<string, string>;\n peerDependencies?: Record<string, string>;\n}\n\nfunction readJson(path: string): PackageJson | null {\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as PackageJson;\n } catch {\n return null;\n }\n}\n\n/** Where `name` is installed, walking up from `from` the way resolution does. */\nexport function packageDir(name: string, from: string): string | null {\n let dir = from;\n\n for (;;) {\n const candidate = join(dir, \"node_modules\", name);\n\n if (existsSync(join(candidate, \"package.json\"))) return candidate;\n\n const parent = dirname(dir);\n\n if (parent === dir) return null;\n\n dir = parent;\n }\n}\n\nfunction realpathOf(path: string): string {\n try {\n return realpathSync(path);\n } catch {\n return path;\n }\n}\n\nfunction headOf(path: string): string {\n const fd = openSync(path, \"r\");\n\n try {\n const buffer = Buffer.alloc(HEAD_BYTES);\n const read = readSync(fd, buffer, 0, HEAD_BYTES, 0);\n\n return buffer.toString(\"utf8\", 0, read);\n } finally {\n closeSync(fd);\n }\n}\n\n/** Whether any source file under `dir` opens with \"use client\". */\nexport function hasClientDirective(dir: string): boolean {\n return someSourceFile(dir, (path) => DIRECTIVE.test(headOf(path)));\n}\n\n/** An import of react-dom's server renderer, static or dynamic, ESM or CJS. */\nconst SERVER_RENDERER_IMPORT = /[\"']react-dom\\/server(?:\\.[a-z]+)?[\"']/;\n\n/** The largest file worth reading whole for an import: a bundled dist, not a data table. */\nconst WHOLE_FILE_CAP = 512 * 1024;\n\n/**\n * Whether any source file under `dir` imports react-dom/server.\n *\n * For a dependency left external where server components render: its\n * imports are never resolved by the build, so the renderer stub that names\n * the app file for a direct import never sees this one. @react-email/render\n * is the usual case - an action imports it, the build says nothing, and the\n * first call throws React's refusal at a visitor.\n */\nexport function importsServerRenderer(dir: string): boolean {\n return someSourceFile(dir, (path) => {\n try {\n if (statSync(path).size > WHOLE_FILE_CAP) return false;\n\n return SERVER_RENDERER_IMPORT.test(readFileSync(path, \"utf8\"));\n } catch {\n return false;\n }\n });\n}\n\n/** Whether `test` holds for some source file under `dir`, within the cap. */\nfunction someSourceFile(dir: string, test: (path: string) => boolean): boolean {\n const pending = [dir];\n let seen = 0;\n\n while (pending.length) {\n const current = pending.pop()!;\n let entries: string[];\n\n try {\n entries = readdirSync(current);\n } catch {\n continue;\n }\n\n for (const entry of entries) {\n if (entry === \"node_modules\" || entry.startsWith(\".\")) continue;\n\n const path = join(current, entry);\n let stat;\n\n try {\n stat = statSync(path);\n } catch {\n continue;\n }\n\n if (stat.isDirectory()) {\n pending.push(path);\n continue;\n }\n\n if (!SOURCE.test(entry)) continue;\n if (++seen > FILE_CAP) return false;\n\n try {\n if (test(path)) return true;\n } catch {\n // Unreadable is neither.\n }\n }\n }\n\n return false;\n}\n\n/** Whether a package says it uses React, as a peer or as a dependency. */\nfunction usesReact(manifest: PackageJson | null): boolean {\n return !!manifest && (\"react\" in (manifest.peerDependencies ?? {}) || \"react\" in (manifest.dependencies ?? {}));\n}\n\n/**\n * The dependencies of a bundled package that use React, and theirs.\n *\n * Bundling the package that carries the directive is not enough: the\n * runtime it calls into - @stencil/react-output-target under a generated\n * wrapper - is its own package, with react as a peer, and plugin-rsc's\n * crawl never reached it because it only descends through packages that\n * declared React. Left external, that runtime loads React through Node\n * while everything else gets it through Vite: two copies, and the hooks\n * dispatcher is null in one of them. Every React-using dependency under\n * a bundled package is bundled with it.\n */\nfunction reactDependenciesOf(dir: string, into: Set<string>, seen: Set<string>): void {\n const manifest = readJson(join(dir, \"package.json\"));\n\n for (const name of Object.keys(manifest?.dependencies ?? {})) {\n if (OWN.has(name) || seen.has(name)) continue;\n\n seen.add(name);\n\n // From where the package really is: under Bun's store a symlink in the\n // app's node_modules points into .bun/<pkg>@<v>/node_modules/<pkg>, and\n // the dependency sits beside it there, not beside the symlink.\n const depDir = packageDir(name, realpathOf(dir));\n\n if (!depDir || !usesReact(readJson(join(depDir, \"package.json\")))) continue;\n\n into.add(name);\n reactDependenciesOf(depDir, into, seen);\n }\n}\n\n/**\n * Where a package is, anywhere in the project's dependency graph.\n *\n * For a polyfill a dependency of a dependency needs: not hoisted to the\n * project under a strict store, so not resolvable from the project by\n * name, and still what has to be evaluated first. A breadth-first walk of\n * the manifests, from each package's real path, bounded in depth. The\n * entry file is what `main` names, or index.js.\n */\nexport function packageEntryInGraph(root: string, name: string, depth = 5): string | null {\n const seen = new Set<string>();\n let frontier = [root];\n\n for (let level = 0; level <= depth && frontier.length; level++) {\n const next: string[] = [];\n\n for (const dir of frontier) {\n const found = packageDir(name, realpathOf(dir));\n\n if (found) {\n const manifest = readJson(join(found, \"package.json\")) as (PackageJson & { main?: string }) | null;\n const entry = join(found, manifest?.main ?? \"index.js\");\n\n return existsSync(entry) ? entry : existsSync(entry + \".js\") ? entry + \".js\" : null;\n }\n\n const manifest = readJson(join(dir, \"package.json\"));\n\n for (const dep of Object.keys(manifest?.dependencies ?? {})) {\n if (seen.has(dep)) continue;\n\n seen.add(dep);\n\n const depDir = packageDir(dep, realpathOf(dir));\n\n if (depDir) next.push(depDir);\n }\n }\n\n frontier = next;\n }\n\n return null;\n}\n\n/**\n * The direct dependencies of the project at `root` that plugin-rsc would\n * leave external and that carry a \"use client\" file - and the React-using\n * dependencies under each, which have to be bundled with it.\n */\nexport function clientPackages(root: string): string[] {\n const own = readJson(join(root, \"package.json\"));\n\n if (!own?.dependencies) return [];\n\n const found = new Set<string>();\n\n for (const name of Object.keys(own.dependencies)) {\n if (OWN.has(name) || name.startsWith(\"@rsc-kit/\")) continue;\n\n const dir = packageDir(name, root);\n\n if (!dir) continue;\n\n const manifest = readJson(join(dir, \"package.json\"));\n\n // plugin-rsc's own rule: react as a peer, and the package is bundled already.\n if (manifest?.peerDependencies && \"react\" in manifest.peerDependencies) continue;\n\n if (!hasClientDirective(dir)) continue;\n\n found.add(name);\n reactDependenciesOf(dir, found, new Set([name]));\n }\n\n return [...found].sort();\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Whether this answer is one to compress at all. */
|
|
2
|
+
export declare function shouldCompress(request: Request, response: Response): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* The response, gzipped if the request accepts it and the answer is worth it.
|
|
5
|
+
*
|
|
6
|
+
* `storedKey` names an answer the build wrote - the same bytes for every
|
|
7
|
+
* visitor - so it is compressed once and kept; everything else is streamed
|
|
8
|
+
* through the compressor as it is produced.
|
|
9
|
+
*/
|
|
10
|
+
export declare function compressed(request: Request, response: Response, storedKey?: string): Promise<Response>;
|
|
11
|
+
/** For a test, or a redeploy that reuses the process: forget the stored bytes. */
|
|
12
|
+
export declare function forgetCompressed(): void;
|
package/dist/compress.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Compression for what the host answers, where nothing in front of it will.
|
|
2
|
+
//
|
|
3
|
+
// Behind Cloudflare, Vercel or nginx the platform compresses and this never
|
|
4
|
+
// runs. A bun or node server answering the internet by itself sent every
|
|
5
|
+
// byte raw - a 90 kB document and 145 kB of stylesheet at their full size -
|
|
6
|
+
// and that alone was the mobile Lighthouse regression a port measured:
|
|
7
|
+
// first paint at 3.8 s where 1.2 s was the baseline. Public assets are
|
|
8
|
+
// precompressed by the build and served by Nitro with their encoding; the
|
|
9
|
+
// pages, payloads, stored answers and api routes come through here.
|
|
10
|
+
//
|
|
11
|
+
// gzip, flushed on every chunk. A streamed page is a shell followed by the
|
|
12
|
+
// holes as they fill, and a compressor left to its own buffer would hold
|
|
13
|
+
// the shell back until enough had accumulated - so each write is flushed
|
|
14
|
+
// out, which costs a little ratio and keeps the stream a stream.
|
|
15
|
+
import { deflate } from "./compressRuntime.js";
|
|
16
|
+
/** What is worth compressing: text of every kind, and the formats that are text. */
|
|
17
|
+
const COMPRESSIBLE = /^(?:text\/|application\/(?:json|javascript|xml|manifest\+json|rss\+xml|atom\+xml)|image\/svg\+xml)/i;
|
|
18
|
+
/** Answers too small to be worth a compressor's frame. */
|
|
19
|
+
const MIN_BYTES = 1024;
|
|
20
|
+
/** Stored answers, compressed once: the same bytes go to every visitor who accepts gzip. */
|
|
21
|
+
const stored = new Map();
|
|
22
|
+
const STORED_CAP = 1000;
|
|
23
|
+
function acceptsGzip(request) {
|
|
24
|
+
const accept = request.headers.get("accept-encoding") ?? "";
|
|
25
|
+
return /(?:^|,)\s*(?:gzip|\*)\s*(?:;\s*q=(?!0(?:\.0*)?\s*(?:,|$)))?/i.test(accept);
|
|
26
|
+
}
|
|
27
|
+
/** Whether this answer is one to compress at all. */
|
|
28
|
+
export function shouldCompress(request, response) {
|
|
29
|
+
if (response.body === null || request.method === "HEAD")
|
|
30
|
+
return false;
|
|
31
|
+
if (response.status === 204 || response.status === 304 || response.status < 200)
|
|
32
|
+
return false;
|
|
33
|
+
if (response.headers.has("content-encoding"))
|
|
34
|
+
return false;
|
|
35
|
+
if (/\bno-transform\b/i.test(response.headers.get("cache-control") ?? ""))
|
|
36
|
+
return false;
|
|
37
|
+
if (!COMPRESSIBLE.test(response.headers.get("content-type") ?? ""))
|
|
38
|
+
return false;
|
|
39
|
+
const length = Number(response.headers.get("content-length"));
|
|
40
|
+
if (Number.isFinite(length) && length > 0 && length < MIN_BYTES)
|
|
41
|
+
return false;
|
|
42
|
+
return acceptsGzip(request);
|
|
43
|
+
}
|
|
44
|
+
function withEncoding(response, body) {
|
|
45
|
+
const headers = new Headers(response.headers);
|
|
46
|
+
headers.set("Content-Encoding", "gzip");
|
|
47
|
+
headers.delete("Content-Length");
|
|
48
|
+
const vary = headers.get("Vary");
|
|
49
|
+
if (!vary)
|
|
50
|
+
headers.set("Vary", "Accept-Encoding");
|
|
51
|
+
else if (!/\baccept-encoding\b/i.test(vary) && vary.trim() !== "*")
|
|
52
|
+
headers.set("Vary", vary + ", Accept-Encoding");
|
|
53
|
+
return new Response(body, { status: response.status, statusText: response.statusText, headers });
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The response, gzipped if the request accepts it and the answer is worth it.
|
|
57
|
+
*
|
|
58
|
+
* `storedKey` names an answer the build wrote - the same bytes for every
|
|
59
|
+
* visitor - so it is compressed once and kept; everything else is streamed
|
|
60
|
+
* through the compressor as it is produced.
|
|
61
|
+
*/
|
|
62
|
+
export async function compressed(request, response, storedKey) {
|
|
63
|
+
if (!shouldCompress(request, response))
|
|
64
|
+
return response;
|
|
65
|
+
const gzip = await deflate();
|
|
66
|
+
if (!gzip)
|
|
67
|
+
return response;
|
|
68
|
+
if (storedKey !== undefined) {
|
|
69
|
+
let bytes = stored.get(storedKey);
|
|
70
|
+
if (!bytes) {
|
|
71
|
+
bytes = await gzip.whole(new Uint8Array(await response.arrayBuffer()));
|
|
72
|
+
if (stored.size >= STORED_CAP)
|
|
73
|
+
stored.clear();
|
|
74
|
+
stored.set(storedKey, bytes);
|
|
75
|
+
}
|
|
76
|
+
return withEncoding(response, bytes);
|
|
77
|
+
}
|
|
78
|
+
// A Response made from a string or JSON carries no Content-Length for the
|
|
79
|
+
// check above to read, so the body is peeked: what arrives before the
|
|
80
|
+
// first kilobyte decides. An answer that ends inside it - /api/health, a
|
|
81
|
+
// 15-byte "ok" - goes out as it was; anything longer is compressed from
|
|
82
|
+
// its first byte, the peeked part included, and keeps streaming.
|
|
83
|
+
const reader = response.body.getReader();
|
|
84
|
+
const peeked = [];
|
|
85
|
+
let size = 0;
|
|
86
|
+
let ended = false;
|
|
87
|
+
while (size < MIN_BYTES) {
|
|
88
|
+
const { done, value } = await reader.read();
|
|
89
|
+
if (done) {
|
|
90
|
+
ended = true;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
peeked.push(value);
|
|
94
|
+
size += value.byteLength;
|
|
95
|
+
}
|
|
96
|
+
if (ended) {
|
|
97
|
+
return new Response(size === 0 ? null : concat(peeked, size), {
|
|
98
|
+
status: response.status,
|
|
99
|
+
statusText: response.statusText,
|
|
100
|
+
headers: response.headers,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
const rest = new ReadableStream({
|
|
104
|
+
start(controller) {
|
|
105
|
+
for (const chunk of peeked)
|
|
106
|
+
controller.enqueue(chunk);
|
|
107
|
+
},
|
|
108
|
+
async pull(controller) {
|
|
109
|
+
const { done, value } = await reader.read();
|
|
110
|
+
if (done)
|
|
111
|
+
controller.close();
|
|
112
|
+
else
|
|
113
|
+
controller.enqueue(value);
|
|
114
|
+
},
|
|
115
|
+
cancel(reason) {
|
|
116
|
+
return reader.cancel(reason);
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
return withEncoding(response, gzip.stream(rest));
|
|
120
|
+
}
|
|
121
|
+
function concat(chunks, size) {
|
|
122
|
+
const out = new Uint8Array(size);
|
|
123
|
+
let at = 0;
|
|
124
|
+
for (const chunk of chunks) {
|
|
125
|
+
out.set(chunk, at);
|
|
126
|
+
at += chunk.byteLength;
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
/** For a test, or a redeploy that reuses the process: forget the stored bytes. */
|
|
131
|
+
export function forgetCompressed() {
|
|
132
|
+
stored.clear();
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=compress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compress.js","sourceRoot":"","sources":["../src/compress.ts"],"names":[],"mappings":"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","sourcesContent":["// Compression for what the host answers, where nothing in front of it will.\n//\n// Behind Cloudflare, Vercel or nginx the platform compresses and this never\n// runs. A bun or node server answering the internet by itself sent every\n// byte raw - a 90 kB document and 145 kB of stylesheet at their full size -\n// and that alone was the mobile Lighthouse regression a port measured:\n// first paint at 3.8 s where 1.2 s was the baseline. Public assets are\n// precompressed by the build and served by Nitro with their encoding; the\n// pages, payloads, stored answers and api routes come through here.\n//\n// gzip, flushed on every chunk. A streamed page is a shell followed by the\n// holes as they fill, and a compressor left to its own buffer would hold\n// the shell back until enough had accumulated - so each write is flushed\n// out, which costs a little ratio and keeps the stream a stream.\n\nimport { deflate } from \"./compressRuntime.js\";\n\n/** What is worth compressing: text of every kind, and the formats that are text. */\nconst COMPRESSIBLE = /^(?:text\\/|application\\/(?:json|javascript|xml|manifest\\+json|rss\\+xml|atom\\+xml)|image\\/svg\\+xml)/i;\n\n/** Answers too small to be worth a compressor's frame. */\nconst MIN_BYTES = 1024;\n\n/** Stored answers, compressed once: the same bytes go to every visitor who accepts gzip. */\nconst stored = new Map<string, Uint8Array>();\n\nconst STORED_CAP = 1000;\n\nfunction acceptsGzip(request: Request): boolean {\n const accept = request.headers.get(\"accept-encoding\") ?? \"\";\n\n return /(?:^|,)\\s*(?:gzip|\\*)\\s*(?:;\\s*q=(?!0(?:\\.0*)?\\s*(?:,|$)))?/i.test(accept);\n}\n\n/** Whether this answer is one to compress at all. */\nexport function shouldCompress(request: Request, response: Response): boolean {\n if (response.body === null || request.method === \"HEAD\") return false;\n if (response.status === 204 || response.status === 304 || response.status < 200) return false;\n if (response.headers.has(\"content-encoding\")) return false;\n if (/\\bno-transform\\b/i.test(response.headers.get(\"cache-control\") ?? \"\")) return false;\n if (!COMPRESSIBLE.test(response.headers.get(\"content-type\") ?? \"\")) return false;\n\n const length = Number(response.headers.get(\"content-length\"));\n\n if (Number.isFinite(length) && length > 0 && length < MIN_BYTES) return false;\n\n return acceptsGzip(request);\n}\n\nfunction withEncoding(response: Response, body: BodyInit): Response {\n const headers = new Headers(response.headers);\n\n headers.set(\"Content-Encoding\", \"gzip\");\n headers.delete(\"Content-Length\");\n\n const vary = headers.get(\"Vary\");\n\n if (!vary) headers.set(\"Vary\", \"Accept-Encoding\");\n else if (!/\\baccept-encoding\\b/i.test(vary) && vary.trim() !== \"*\") headers.set(\"Vary\", vary + \", Accept-Encoding\");\n\n return new Response(body, { status: response.status, statusText: response.statusText, headers });\n}\n\n/**\n * The response, gzipped if the request accepts it and the answer is worth it.\n *\n * `storedKey` names an answer the build wrote - the same bytes for every\n * visitor - so it is compressed once and kept; everything else is streamed\n * through the compressor as it is produced.\n */\nexport async function compressed(\n request: Request,\n response: Response,\n storedKey?: string,\n): Promise<Response> {\n if (!shouldCompress(request, response)) return response;\n\n const gzip = await deflate();\n\n if (!gzip) return response;\n\n if (storedKey !== undefined) {\n let bytes = stored.get(storedKey);\n\n if (!bytes) {\n bytes = await gzip.whole(new Uint8Array(await response.arrayBuffer()));\n\n if (stored.size >= STORED_CAP) stored.clear();\n\n stored.set(storedKey, bytes);\n }\n\n return withEncoding(response, bytes as unknown as BodyInit);\n }\n\n // A Response made from a string or JSON carries no Content-Length for the\n // check above to read, so the body is peeked: what arrives before the\n // first kilobyte decides. An answer that ends inside it - /api/health, a\n // 15-byte \"ok\" - goes out as it was; anything longer is compressed from\n // its first byte, the peeked part included, and keeps streaming.\n const reader = response.body!.getReader();\n const peeked: Uint8Array[] = [];\n let size = 0;\n let ended = false;\n\n while (size < MIN_BYTES) {\n const { done, value } = await reader.read();\n\n if (done) {\n ended = true;\n break;\n }\n\n peeked.push(value);\n size += value.byteLength;\n }\n\n if (ended) {\n return new Response(size === 0 ? null : (concat(peeked, size) as unknown as BodyInit), {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n }\n\n const rest = new ReadableStream<Uint8Array>({\n start(controller) {\n for (const chunk of peeked) controller.enqueue(chunk);\n },\n async pull(controller) {\n const { done, value } = await reader.read();\n\n if (done) controller.close();\n else controller.enqueue(value);\n },\n cancel(reason) {\n return reader.cancel(reason);\n },\n });\n\n return withEncoding(response, gzip.stream(rest));\n}\n\nfunction concat(chunks: Uint8Array[], size: number): Uint8Array {\n const out = new Uint8Array(size);\n let at = 0;\n\n for (const chunk of chunks) {\n out.set(chunk, at);\n at += chunk.byteLength;\n }\n\n return out;\n}\n\n/** For a test, or a redeploy that reuses the process: forget the stored bytes. */\nexport function forgetCompressed(): void {\n stored.clear();\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface Deflater {
|
|
2
|
+
/** One buffer in, one out: for an answer known whole. */
|
|
3
|
+
whole(bytes: Uint8Array): Promise<Uint8Array>;
|
|
4
|
+
/** A stream through the compressor, every chunk flushed as it arrives. */
|
|
5
|
+
stream(body: ReadableStream<Uint8Array>): ReadableStream<Uint8Array>;
|
|
6
|
+
}
|
|
7
|
+
/** The runtime's compressor, or null where there is none. */
|
|
8
|
+
export declare function deflate(): Promise<Deflater | null>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// The compressor, from whichever runtime has one.
|
|
2
|
+
//
|
|
3
|
+
// node:zlib on Node and Bun - imported once, lazily, so the host loads on a
|
|
4
|
+
// Worker (which has no zlib and needs none: the platform compresses) and
|
|
5
|
+
// simply answers uncompressed there. Not the web CompressionStream: neither
|
|
6
|
+
// runtime lets a caller flush it per chunk, and a streamed page held back
|
|
7
|
+
// in a compressor's buffer is a page that stopped streaming.
|
|
8
|
+
let loading = null;
|
|
9
|
+
function onWorkers() {
|
|
10
|
+
return typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers";
|
|
11
|
+
}
|
|
12
|
+
function deflaterFrom(zlib) {
|
|
13
|
+
return {
|
|
14
|
+
whole(bytes) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
zlib.gzip(bytes, (error, out) => (error ? reject(error) : resolve(new Uint8Array(out))));
|
|
17
|
+
});
|
|
18
|
+
},
|
|
19
|
+
stream(body) {
|
|
20
|
+
const gz = zlib.createGzip({ flush: zlib.constants.Z_SYNC_FLUSH });
|
|
21
|
+
const reader = body.getReader();
|
|
22
|
+
return new ReadableStream({
|
|
23
|
+
start(controller) {
|
|
24
|
+
gz.on("data", (chunk) => controller.enqueue(new Uint8Array(chunk)));
|
|
25
|
+
gz.on("end", () => controller.close());
|
|
26
|
+
gz.on("error", (error) => controller.error(error));
|
|
27
|
+
void (async () => {
|
|
28
|
+
try {
|
|
29
|
+
for (;;) {
|
|
30
|
+
const { done, value } = await reader.read();
|
|
31
|
+
if (done)
|
|
32
|
+
break;
|
|
33
|
+
// Back-pressure from the compressor: wait for drain rather
|
|
34
|
+
// than queueing an unbounded amount in front of it.
|
|
35
|
+
if (!gz.write(value))
|
|
36
|
+
await new Promise((resolve) => gz.once("drain", resolve));
|
|
37
|
+
}
|
|
38
|
+
gz.end();
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
gz.destroy(error);
|
|
42
|
+
}
|
|
43
|
+
})();
|
|
44
|
+
},
|
|
45
|
+
cancel(reason) {
|
|
46
|
+
gz.destroy();
|
|
47
|
+
return reader.cancel(reason);
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** The runtime's compressor, or null where there is none. */
|
|
54
|
+
export function deflate() {
|
|
55
|
+
if (loading)
|
|
56
|
+
return loading;
|
|
57
|
+
loading = onWorkers()
|
|
58
|
+
? Promise.resolve(null)
|
|
59
|
+
: import("node:zlib").then((zlib) => deflaterFrom(zlib), () => null);
|
|
60
|
+
return loading;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=compressRuntime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compressRuntime.js","sourceRoot":"","sources":["../src/compressRuntime.ts"],"names":[],"mappings":"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","sourcesContent":["// The compressor, from whichever runtime has one.\n//\n// node:zlib on Node and Bun - imported once, lazily, so the host loads on a\n// Worker (which has no zlib and needs none: the platform compresses) and\n// simply answers uncompressed there. Not the web CompressionStream: neither\n// runtime lets a caller flush it per chunk, and a streamed page held back\n// in a compressor's buffer is a page that stopped streaming.\n\nexport interface Deflater {\n /** One buffer in, one out: for an answer known whole. */\n whole(bytes: Uint8Array): Promise<Uint8Array>;\n /** A stream through the compressor, every chunk flushed as it arrives. */\n stream(body: ReadableStream<Uint8Array>): ReadableStream<Uint8Array>;\n}\n\ntype Zlib = typeof import(\"node:zlib\");\n\nlet loading: Promise<Deflater | null> | null = null;\n\nfunction onWorkers(): boolean {\n return typeof navigator !== \"undefined\" && navigator.userAgent === \"Cloudflare-Workers\";\n}\n\nfunction deflaterFrom(zlib: Zlib): Deflater {\n return {\n whole(bytes) {\n return new Promise((resolve, reject) => {\n zlib.gzip(bytes, (error, out) => (error ? reject(error) : resolve(new Uint8Array(out))));\n });\n },\n\n stream(body) {\n const gz = zlib.createGzip({ flush: zlib.constants.Z_SYNC_FLUSH });\n const reader = body.getReader();\n\n return new ReadableStream<Uint8Array>({\n start(controller) {\n gz.on(\"data\", (chunk: Buffer) => controller.enqueue(new Uint8Array(chunk)));\n gz.on(\"end\", () => controller.close());\n gz.on(\"error\", (error: Error) => controller.error(error));\n\n void (async () => {\n try {\n for (;;) {\n const { done, value } = await reader.read();\n\n if (done) break;\n\n // Back-pressure from the compressor: wait for drain rather\n // than queueing an unbounded amount in front of it.\n if (!gz.write(value)) await new Promise<void>((resolve) => gz.once(\"drain\", resolve));\n }\n\n gz.end();\n } catch (error) {\n gz.destroy(error as Error);\n }\n })();\n },\n cancel(reason) {\n gz.destroy();\n\n return reader.cancel(reason);\n },\n });\n },\n };\n}\n\n/** The runtime's compressor, or null where there is none. */\nexport function deflate(): Promise<Deflater | null> {\n if (loading) return loading;\n\n loading = onWorkers()\n ? Promise.resolve(null)\n : import(\"node:zlib\").then(\n (zlib) => deflaterFrom(zlib),\n () => null,\n );\n\n return loading;\n}\n"]}
|
package/dist/files.d.ts
CHANGED
|
@@ -24,7 +24,27 @@ export declare function prerenderedFrom(dir: string): (name: string) => Promise<
|
|
|
24
24
|
* this host has no filesystem to have frozen it on. Every page renders live,
|
|
25
25
|
* which is what happens today everywhere.
|
|
26
26
|
*/
|
|
27
|
+
/**
|
|
28
|
+
* Where a compiled binary puts the frozen pages.
|
|
29
|
+
*
|
|
30
|
+
* `bun build --compile` embeds what is imported statically and nothing a
|
|
31
|
+
* computed import names, so the walk below finds no directory and no
|
|
32
|
+
* module inside a binary. The build writes `compile.mjs` beside the
|
|
33
|
+
* server: it imports the inline module by name - which embeds it - and
|
|
34
|
+
* hands the pages over here before importing the server. Shared as a
|
|
35
|
+
* global under a registered symbol, because the wrapper and this module
|
|
36
|
+
* are bundled apart.
|
|
37
|
+
*/
|
|
38
|
+
export declare const EMBEDDED_PAGES: unique symbol;
|
|
27
39
|
export declare function prerenderedBeside(moduleUrl: string, dirName: string, levels?: number): (name: string) => Promise<string | null>;
|
|
40
|
+
/**
|
|
41
|
+
* The entry a binary is compiled from, written beside the server.
|
|
42
|
+
*
|
|
43
|
+
* Two imports, in this order: the inline module first, so its pages are
|
|
44
|
+
* handed over before the server's first request; the server second. Bun
|
|
45
|
+
* embeds both. `bun build --compile .output/server/compile.mjs`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function compileEntrySource(dirName: string): string;
|
|
28
48
|
/** The name the build writes the inline module under, beside the stored pages' directory. */
|
|
29
49
|
export declare function inlineModuleName(dirName: string): string;
|
|
30
50
|
/**
|
package/dist/files.js
CHANGED
|
@@ -96,9 +96,28 @@ export function prerenderedFrom(dir) {
|
|
|
96
96
|
* this host has no filesystem to have frozen it on. Every page renders live,
|
|
97
97
|
* which is what happens today everywhere.
|
|
98
98
|
*/
|
|
99
|
+
/**
|
|
100
|
+
* Where a compiled binary puts the frozen pages.
|
|
101
|
+
*
|
|
102
|
+
* `bun build --compile` embeds what is imported statically and nothing a
|
|
103
|
+
* computed import names, so the walk below finds no directory and no
|
|
104
|
+
* module inside a binary. The build writes `compile.mjs` beside the
|
|
105
|
+
* server: it imports the inline module by name - which embeds it - and
|
|
106
|
+
* hands the pages over here before importing the server. Shared as a
|
|
107
|
+
* global under a registered symbol, because the wrapper and this module
|
|
108
|
+
* are bundled apart.
|
|
109
|
+
*/
|
|
110
|
+
export const EMBEDDED_PAGES = Symbol.for('rsc-kit.embedded-pages');
|
|
111
|
+
function embeddedPages() {
|
|
112
|
+
const pages = globalThis[EMBEDDED_PAGES];
|
|
113
|
+
return pages && typeof pages === 'object' ? pages : null;
|
|
114
|
+
}
|
|
99
115
|
export function prerenderedBeside(moduleUrl, dirName, levels = 4) {
|
|
100
116
|
let reader = null;
|
|
101
117
|
const resolveDir = async () => {
|
|
118
|
+
const embedded = embeddedPages();
|
|
119
|
+
if (embedded)
|
|
120
|
+
return async (name) => embedded[name] ?? null;
|
|
102
121
|
let dir = null;
|
|
103
122
|
try {
|
|
104
123
|
const { fileURLToPath } = await import('node:url');
|
|
@@ -170,6 +189,25 @@ function safeUrl(relative, base) {
|
|
|
170
189
|
return null;
|
|
171
190
|
}
|
|
172
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* The entry a binary is compiled from, written beside the server.
|
|
194
|
+
*
|
|
195
|
+
* Two imports, in this order: the inline module first, so its pages are
|
|
196
|
+
* handed over before the server's first request; the server second. Bun
|
|
197
|
+
* embeds both. `bun build --compile .output/server/compile.mjs`.
|
|
198
|
+
*/
|
|
199
|
+
export function compileEntrySource(dirName) {
|
|
200
|
+
return ('// The entry to compile into one binary: bun build --compile .output/server/compile.mjs\n' +
|
|
201
|
+
'//\n' +
|
|
202
|
+
'// A binary carries what is imported by name. The server reads its frozen pages\n' +
|
|
203
|
+
'// through a computed import, which a compile cannot see - imported here, they\n' +
|
|
204
|
+
'// travel inside the binary and are handed to the server before it starts.\n' +
|
|
205
|
+
`import pages from "./${inlineModuleName(dirName)}";\n` +
|
|
206
|
+
'\n' +
|
|
207
|
+
`globalThis[Symbol.for("rsc-kit.embedded-pages")] = pages;\n` +
|
|
208
|
+
'\n' +
|
|
209
|
+
'await import("./index.mjs");\n');
|
|
210
|
+
}
|
|
173
211
|
/** The name the build writes the inline module under, beside the stored pages' directory. */
|
|
174
212
|
export function inlineModuleName(dirName) {
|
|
175
213
|
return `${dirName}-inline.mjs`;
|
package/dist/files.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files.js","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"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","sourcesContent":["// Reading built output off a disk.\n//\n// Kept apart from the host on purpose: it is the only part of serving an RSC\n// app that assumes a filesystem, and plenty of places to run one do not have\n// one. Nothing in `@rsc-kit/core/host` imports this module, so a bundle for a\n// host without a filesystem carries no reference to `node:fs` at all.\n//\n// Assets are not here any more — Nitro publishes and serves those. What is\n// left is the prerendered output: written by the build, read by the server it\n// generates, and copied by a static export.\n\nimport { cp, mkdir, readdir, readFile, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\n\n/**\n * Read what the prerenderer wrote, from a directory.\n *\n * Missing is an answer, not an error: a partial prerender is a valid state,\n * and anything not found here is rendered on demand instead.\n */\nexport function prerenderedFrom(dir: string) {\n /**\n * What is on disk, listed once.\n *\n * A miss used to be a thrown ENOENT, and misses are the common case: the\n * host asks for `{url}.html`, then `{url}.ppr.html`, then the route's\n * pattern — so every request to a page served by a pattern shell threw and\n * caught two exceptions before finding anything. Measured at 3,000 req/s\n * against 28,000 for the same page read from memory.\n *\n * Contents are cached for the life of the process. A build's output does\n * not change under a running server - a deploy restarts it, and a server\n * that outlived a rebuild would be serving a stale engine against new\n * pages anyway - so the first read is the only one. Under load the same\n * page used to be opened, read and closed thousands of times a second,\n * which a profile showed as the one cost on a stored page that was ours\n * rather than React's. Capped by bytes, so a site with more pages than\n * memory holds the hot ones and re-reads the rest.\n */\n let present: Promise<Set<string>> | null = null\n\n const CONTENT_CAP_BYTES = 64 * 1024 * 1024\n const contents = new Map<string, string>()\n let held = 0\n\n const remember = (name: string, text: string): void => {\n const size = text.length * 2\n\n if (size > CONTENT_CAP_BYTES / 4) return\n\n while (held + size > CONTENT_CAP_BYTES && contents.size > 0) {\n const oldest = contents.keys().next().value as string\n held -= contents.get(oldest)!.length * 2\n contents.delete(oldest)\n }\n\n contents.set(name, text)\n held += size\n }\n\n const listing = async (): Promise<Set<string>> => {\n present ??= readdir(dir, { recursive: true })\n .then((names) => new Set(names.map((n) => String(n).replace(/\\\\/g, '/'))))\n // An absent directory is an empty one: a partial prerender is a valid\n // state, and everything falls through to being rendered.\n .catch(() => new Set<string>())\n\n return await present\n }\n\n return async (name: string): Promise<string | null> => {\n if (name.includes('..')) return null\n\n if (!(await listing()).has(name)) return null\n\n const cached = contents.get(name)\n\n if (cached !== undefined) return cached\n\n try {\n const text = await readFile(join(dir, name), 'utf-8')\n\n remember(name, text)\n\n return text\n } catch {\n return null\n }\n }\n}\n\n/**\n * The same reader, for a directory whose absolute path is not known until the\n * server runs.\n *\n * Nitro bundles the generated entry to a different depth depending on the\n * preset — `.output/server/index.mjs` for one, `.output/server/_ssr/rsc.mjs`\n * for another — so neither a fixed relative path nor a path baked in at build\n * time survives: the first is wrong for half the presets, and the second stops\n * being true the moment `.output/` is copied to the machine that serves it.\n *\n * So the caller passes its own `import.meta.url` and the directory is found by\n * walking up from it. A few levels, bounded, and resolved once.\n *\n * prerendered: prerenderedBeside(import.meta.url, 'rsc-static')\n *\n * Not finding it is a valid state and not an error: nothing was frozen, or\n * this host has no filesystem to have frozen it on. Every page renders live,\n * which is what happens today everywhere.\n */\nexport function prerenderedBeside(moduleUrl: string, dirName: string, levels = 4) {\n let reader: Promise<(name: string) => Promise<string | null>> | null = null\n\n const resolveDir = async (): Promise<(name: string) => Promise<string | null>> => {\n let dir: string | null = null\n\n try {\n const { fileURLToPath } = await import('node:url')\n\n dir = dirname(fileURLToPath(moduleUrl))\n } catch {\n // No filesystem, or no file: url to speak of. The inline module below\n // is the only place the pages can be.\n }\n\n if (dir !== null) {\n for (let i = 0; i <= levels; i++) {\n const candidate = join(dir, ...Array(i).fill('..'), dirName)\n\n try {\n await readdir(candidate)\n\n return prerenderedFrom(candidate)\n } catch {\n // Not at this level. Keep walking.\n }\n }\n }\n\n const inline = await inlinePrerendered(moduleUrl, `${dirName}-inline.mjs`, levels)\n\n return inline ? async (name) => inline[name] ?? null : async () => null\n }\n\n return async (name: string): Promise<string | null> => {\n reader ??= resolveDir()\n\n return await (await reader)(name)\n }\n}\n\n/**\n * The frozen pages as one module, for a runtime with no filesystem.\n *\n * A Worker cannot readdir. Its modules are what wrangler uploaded, and a\n * sibling .mjs beside the bundle is one of them - so the build writes every\n * stored file into `rsc-static-inline.mjs` as strings, and this walks up from\n * the caller the way the disk reader does, importing rather than reading.\n * The specifier is computed, and marked for the bundler to leave alone: at\n * bundle time the file does not exist yet, because it is written by the\n * prerender that runs the bundle.\n *\n * Two forms of the specifier, because runtimes disagree. workerd resolves a\n * relative string against the importing module's name and refuses a url;\n * Node and Bun want the url, since a relative string would resolve against\n * this file rather than the caller's.\n */\nasync function inlinePrerendered(\n moduleUrl: string,\n fileName: string,\n levels: number,\n): Promise<Record<string, string> | null> {\n for (let i = 0; i <= levels; i++) {\n const relative = './' + '../'.repeat(i) + fileName\n\n for (const specifier of [relative, safeUrl(relative, moduleUrl)]) {\n if (!specifier) continue\n\n try {\n const loaded = (await import(/* @vite-ignore */ specifier)) as { default?: Record<string, string> }\n\n if (loaded.default && typeof loaded.default === 'object') return loaded.default\n } catch {\n // Not there, or not at this level.\n }\n }\n }\n\n return null\n}\n\nfunction safeUrl(relative: string, base: string): string | null {\n try {\n return new URL(relative, base).href\n } catch {\n return null\n }\n}\n\n/** The name the build writes the inline module under, beside the stored pages' directory. */\nexport function inlineModuleName(dirName: string): string {\n return `${dirName}-inline.mjs`\n}\n\n/**\n * Every stored file under `dir`, as the source of the inline module.\n * Strings only: a frozen page, its flight payload, its meta, an api answer.\n */\nexport async function inlineModuleSource(dir: string): Promise<string> {\n const { readdir: list, readFile: read } = await import('node:fs/promises')\n const entries: Record<string, string> = {}\n\n const walk = async (sub: string): Promise<void> => {\n for (const entry of await list(join(dir, sub), { withFileTypes: true })) {\n const rel = sub ? `${sub}/${entry.name}` : entry.name\n\n if (entry.isDirectory()) await walk(rel)\n else entries[rel] = await read(join(dir, rel), 'utf-8')\n }\n }\n\n await walk('')\n\n return (\n '// @generated by the RSC build: the stored pages, for a runtime with no filesystem.\\n' +\n 'export default ' +\n JSON.stringify(entries) +\n '\\n'\n )\n}\n\nfunction contentTypeOf(pathname: string): string {\n if (pathname.endsWith('.js')) return 'text/javascript; charset=utf-8'\n if (pathname.endsWith('.css')) return 'text/css; charset=utf-8'\n if (pathname.endsWith('.map')) return 'application/json; charset=utf-8'\n if (pathname.endsWith('.svg')) return 'image/svg+xml'\n if (pathname.endsWith('.woff2')) return 'font/woff2'\n\n return 'application/octet-stream'\n}\n\n/**\n * Write build output into a directory.\n *\n * The sink `prerender` and `exportSite` take. Names are relative and may\n * contain directories — `docs/index.html` — so each one's parent is created\n * as it goes.\n */\nexport function writeTo(dir: string): ((name: string, contents: string) => Promise<void>) & { dir: string } {\n const write = async (name: string, contents: string): Promise<void> => {\n const path = join(dir, name)\n\n await mkdir(dirname(path), { recursive: true })\n await writeFile(path, contents)\n }\n\n // Where it wrote, for a caller that has to read it back. The embeddable\n // module has to list these files, and being told the directory a second time\n // is a second place for it to be wrong.\n return Object.assign(write, { dir })\n}\n\n/**\n * Copy the built browser bundle into an exported site.\n *\n * Separate from the export itself because copying a tree of files is a\n * filesystem operation by nature — a deploy that uploads them to a bucket\n * has its own way to do that, and passes its own callback instead.\n */\nexport function copyAssets(from: string, to: string, url = '/assets/') {\n return async (): Promise<void> => {\n const at = url.replace(/^\\/+|\\/+$/g, '')\n\n if (at === '') return\n\n const target = join(to, at)\n\n await mkdir(dirname(target), { recursive: true })\n await cp(from, target, { recursive: true })\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"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","sourcesContent":["// Reading built output off a disk.\n//\n// Kept apart from the host on purpose: it is the only part of serving an RSC\n// app that assumes a filesystem, and plenty of places to run one do not have\n// one. Nothing in `@rsc-kit/core/host` imports this module, so a bundle for a\n// host without a filesystem carries no reference to `node:fs` at all.\n//\n// Assets are not here any more — Nitro publishes and serves those. What is\n// left is the prerendered output: written by the build, read by the server it\n// generates, and copied by a static export.\n\nimport { cp, mkdir, readdir, readFile, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\n\n/**\n * Read what the prerenderer wrote, from a directory.\n *\n * Missing is an answer, not an error: a partial prerender is a valid state,\n * and anything not found here is rendered on demand instead.\n */\nexport function prerenderedFrom(dir: string) {\n /**\n * What is on disk, listed once.\n *\n * A miss used to be a thrown ENOENT, and misses are the common case: the\n * host asks for `{url}.html`, then `{url}.ppr.html`, then the route's\n * pattern — so every request to a page served by a pattern shell threw and\n * caught two exceptions before finding anything. Measured at 3,000 req/s\n * against 28,000 for the same page read from memory.\n *\n * Contents are cached for the life of the process. A build's output does\n * not change under a running server - a deploy restarts it, and a server\n * that outlived a rebuild would be serving a stale engine against new\n * pages anyway - so the first read is the only one. Under load the same\n * page used to be opened, read and closed thousands of times a second,\n * which a profile showed as the one cost on a stored page that was ours\n * rather than React's. Capped by bytes, so a site with more pages than\n * memory holds the hot ones and re-reads the rest.\n */\n let present: Promise<Set<string>> | null = null\n\n const CONTENT_CAP_BYTES = 64 * 1024 * 1024\n const contents = new Map<string, string>()\n let held = 0\n\n const remember = (name: string, text: string): void => {\n const size = text.length * 2\n\n if (size > CONTENT_CAP_BYTES / 4) return\n\n while (held + size > CONTENT_CAP_BYTES && contents.size > 0) {\n const oldest = contents.keys().next().value as string\n held -= contents.get(oldest)!.length * 2\n contents.delete(oldest)\n }\n\n contents.set(name, text)\n held += size\n }\n\n const listing = async (): Promise<Set<string>> => {\n present ??= readdir(dir, { recursive: true })\n .then((names) => new Set(names.map((n) => String(n).replace(/\\\\/g, '/'))))\n // An absent directory is an empty one: a partial prerender is a valid\n // state, and everything falls through to being rendered.\n .catch(() => new Set<string>())\n\n return await present\n }\n\n return async (name: string): Promise<string | null> => {\n if (name.includes('..')) return null\n\n if (!(await listing()).has(name)) return null\n\n const cached = contents.get(name)\n\n if (cached !== undefined) return cached\n\n try {\n const text = await readFile(join(dir, name), 'utf-8')\n\n remember(name, text)\n\n return text\n } catch {\n return null\n }\n }\n}\n\n/**\n * The same reader, for a directory whose absolute path is not known until the\n * server runs.\n *\n * Nitro bundles the generated entry to a different depth depending on the\n * preset — `.output/server/index.mjs` for one, `.output/server/_ssr/rsc.mjs`\n * for another — so neither a fixed relative path nor a path baked in at build\n * time survives: the first is wrong for half the presets, and the second stops\n * being true the moment `.output/` is copied to the machine that serves it.\n *\n * So the caller passes its own `import.meta.url` and the directory is found by\n * walking up from it. A few levels, bounded, and resolved once.\n *\n * prerendered: prerenderedBeside(import.meta.url, 'rsc-static')\n *\n * Not finding it is a valid state and not an error: nothing was frozen, or\n * this host has no filesystem to have frozen it on. Every page renders live,\n * which is what happens today everywhere.\n */\n/**\n * Where a compiled binary puts the frozen pages.\n *\n * `bun build --compile` embeds what is imported statically and nothing a\n * computed import names, so the walk below finds no directory and no\n * module inside a binary. The build writes `compile.mjs` beside the\n * server: it imports the inline module by name - which embeds it - and\n * hands the pages over here before importing the server. Shared as a\n * global under a registered symbol, because the wrapper and this module\n * are bundled apart.\n */\nexport const EMBEDDED_PAGES = Symbol.for('rsc-kit.embedded-pages')\n\nfunction embeddedPages(): Record<string, string> | null {\n const pages = (globalThis as Record<symbol, unknown>)[EMBEDDED_PAGES]\n\n return pages && typeof pages === 'object' ? (pages as Record<string, string>) : null\n}\n\nexport function prerenderedBeside(moduleUrl: string, dirName: string, levels = 4) {\n let reader: Promise<(name: string) => Promise<string | null>> | null = null\n\n const resolveDir = async (): Promise<(name: string) => Promise<string | null>> => {\n const embedded = embeddedPages()\n\n if (embedded) return async (name) => embedded[name] ?? null\n\n let dir: string | null = null\n\n try {\n const { fileURLToPath } = await import('node:url')\n\n dir = dirname(fileURLToPath(moduleUrl))\n } catch {\n // No filesystem, or no file: url to speak of. The inline module below\n // is the only place the pages can be.\n }\n\n if (dir !== null) {\n for (let i = 0; i <= levels; i++) {\n const candidate = join(dir, ...Array(i).fill('..'), dirName)\n\n try {\n await readdir(candidate)\n\n return prerenderedFrom(candidate)\n } catch {\n // Not at this level. Keep walking.\n }\n }\n }\n\n const inline = await inlinePrerendered(moduleUrl, `${dirName}-inline.mjs`, levels)\n\n return inline ? async (name) => inline[name] ?? null : async () => null\n }\n\n return async (name: string): Promise<string | null> => {\n reader ??= resolveDir()\n\n return await (await reader)(name)\n }\n}\n\n/**\n * The frozen pages as one module, for a runtime with no filesystem.\n *\n * A Worker cannot readdir. Its modules are what wrangler uploaded, and a\n * sibling .mjs beside the bundle is one of them - so the build writes every\n * stored file into `rsc-static-inline.mjs` as strings, and this walks up from\n * the caller the way the disk reader does, importing rather than reading.\n * The specifier is computed, and marked for the bundler to leave alone: at\n * bundle time the file does not exist yet, because it is written by the\n * prerender that runs the bundle.\n *\n * Two forms of the specifier, because runtimes disagree. workerd resolves a\n * relative string against the importing module's name and refuses a url;\n * Node and Bun want the url, since a relative string would resolve against\n * this file rather than the caller's.\n */\nasync function inlinePrerendered(\n moduleUrl: string,\n fileName: string,\n levels: number,\n): Promise<Record<string, string> | null> {\n for (let i = 0; i <= levels; i++) {\n const relative = './' + '../'.repeat(i) + fileName\n\n for (const specifier of [relative, safeUrl(relative, moduleUrl)]) {\n if (!specifier) continue\n\n try {\n const loaded = (await import(/* @vite-ignore */ specifier)) as { default?: Record<string, string> }\n\n if (loaded.default && typeof loaded.default === 'object') return loaded.default\n } catch {\n // Not there, or not at this level.\n }\n }\n }\n\n return null\n}\n\nfunction safeUrl(relative: string, base: string): string | null {\n try {\n return new URL(relative, base).href\n } catch {\n return null\n }\n}\n\n/**\n * The entry a binary is compiled from, written beside the server.\n *\n * Two imports, in this order: the inline module first, so its pages are\n * handed over before the server's first request; the server second. Bun\n * embeds both. `bun build --compile .output/server/compile.mjs`.\n */\nexport function compileEntrySource(dirName: string): string {\n return (\n '// The entry to compile into one binary: bun build --compile .output/server/compile.mjs\\n' +\n '//\\n' +\n '// A binary carries what is imported by name. The server reads its frozen pages\\n' +\n '// through a computed import, which a compile cannot see - imported here, they\\n' +\n '// travel inside the binary and are handed to the server before it starts.\\n' +\n `import pages from \"./${inlineModuleName(dirName)}\";\\n` +\n '\\n' +\n `globalThis[Symbol.for(\"rsc-kit.embedded-pages\")] = pages;\\n` +\n '\\n' +\n 'await import(\"./index.mjs\");\\n'\n )\n}\n\n/** The name the build writes the inline module under, beside the stored pages' directory. */\nexport function inlineModuleName(dirName: string): string {\n return `${dirName}-inline.mjs`\n}\n\n/**\n * Every stored file under `dir`, as the source of the inline module.\n * Strings only: a frozen page, its flight payload, its meta, an api answer.\n */\nexport async function inlineModuleSource(dir: string): Promise<string> {\n const { readdir: list, readFile: read } = await import('node:fs/promises')\n const entries: Record<string, string> = {}\n\n const walk = async (sub: string): Promise<void> => {\n for (const entry of await list(join(dir, sub), { withFileTypes: true })) {\n const rel = sub ? `${sub}/${entry.name}` : entry.name\n\n if (entry.isDirectory()) await walk(rel)\n else entries[rel] = await read(join(dir, rel), 'utf-8')\n }\n }\n\n await walk('')\n\n return (\n '// @generated by the RSC build: the stored pages, for a runtime with no filesystem.\\n' +\n 'export default ' +\n JSON.stringify(entries) +\n '\\n'\n )\n}\n\nfunction contentTypeOf(pathname: string): string {\n if (pathname.endsWith('.js')) return 'text/javascript; charset=utf-8'\n if (pathname.endsWith('.css')) return 'text/css; charset=utf-8'\n if (pathname.endsWith('.map')) return 'application/json; charset=utf-8'\n if (pathname.endsWith('.svg')) return 'image/svg+xml'\n if (pathname.endsWith('.woff2')) return 'font/woff2'\n\n return 'application/octet-stream'\n}\n\n/**\n * Write build output into a directory.\n *\n * The sink `prerender` and `exportSite` take. Names are relative and may\n * contain directories — `docs/index.html` — so each one's parent is created\n * as it goes.\n */\nexport function writeTo(dir: string): ((name: string, contents: string) => Promise<void>) & { dir: string } {\n const write = async (name: string, contents: string): Promise<void> => {\n const path = join(dir, name)\n\n await mkdir(dirname(path), { recursive: true })\n await writeFile(path, contents)\n }\n\n // Where it wrote, for a caller that has to read it back. The embeddable\n // module has to list these files, and being told the directory a second time\n // is a second place for it to be wrong.\n return Object.assign(write, { dir })\n}\n\n/**\n * Copy the built browser bundle into an exported site.\n *\n * Separate from the export itself because copying a tree of files is a\n * filesystem operation by nature — a deploy that uploads them to a bucket\n * has its own way to do that, and passes its own callback instead.\n */\nexport function copyAssets(from: string, to: string, url = '/assets/') {\n return async (): Promise<void> => {\n const at = url.replace(/^\\/+|\\/+$/g, '')\n\n if (at === '') return\n\n const target = join(to, at)\n\n await mkdir(dirname(target), { recursive: true })\n await cp(from, target, { recursive: true })\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function submitForm(action: (formData: FormData) => Promise<unknown>, _previous: unknown, formData: FormData): Promise<unknown>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
// What a <Form> posts to before it has a runtime.
|
|
3
|
+
//
|
|
4
|
+
// React seats the answer to a form posted without javascript into the form
|
|
5
|
+
// that posted it - through useActionState, which hands the action a
|
|
6
|
+
// (previousState, formData) pair. A Form's action takes the FormData alone,
|
|
7
|
+
// and every action written against the guide does; so the form is registered
|
|
8
|
+
// with this, bound to its real action, and this is the pair-shaped function
|
|
9
|
+
// React calls. The action is a server reference travelling as a bound
|
|
10
|
+
// argument, decoded back to itself on the way in.
|
|
11
|
+
export async function submitForm(action, _previous, formData) {
|
|
12
|
+
return await action(formData);
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=formSubmit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formSubmit.js","sourceRoot":"","sources":["../src/formSubmit.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,kDAAkD;AAClD,EAAE;AACF,2EAA2E;AAC3E,oEAAoE;AACpE,4EAA4E;AAC5E,6EAA6E;AAC7E,4EAA4E;AAC5E,sEAAsE;AACtE,kDAAkD;AAElD,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAAgD,EAChD,SAAkB,EAClB,QAAkB;IAElB,OAAO,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;AAChC,CAAC","sourcesContent":["\"use server\";\n\n// What a <Form> posts to before it has a runtime.\n//\n// React seats the answer to a form posted without javascript into the form\n// that posted it - through useActionState, which hands the action a\n// (previousState, formData) pair. A Form's action takes the FormData alone,\n// and every action written against the guide does; so the form is registered\n// with this, bound to its real action, and this is the pair-shaped function\n// React calls. The action is a server reference travelling as a bound\n// argument, decoded back to itself on the way in.\n\nexport async function submitForm(\n action: (formData: FormData) => Promise<unknown>,\n _previous: unknown,\n formData: FormData,\n): Promise<unknown> {\n return await action(formData);\n}\n"]}
|
package/dist/host.d.ts
CHANGED
|
@@ -32,6 +32,18 @@ export interface RscEngine {
|
|
|
32
32
|
}[], loadings?: string[], parallelSlots?: Record<string, string>, slotOverrides?: Record<string, unknown>, nonce?: string, pageKey?: string, bootstrap?: boolean): Promise<{
|
|
33
33
|
htmlStream: ReadableStream;
|
|
34
34
|
}>;
|
|
35
|
+
/**
|
|
36
|
+
* A form posted to the page's own url before the page had a runtime to
|
|
37
|
+
* catch it: React wrote the action's id into the form, and this runs that
|
|
38
|
+
* action from the posted fields, then renders the page with the result.
|
|
39
|
+
* Optional: an engine built by an older plugin answers such a post 404.
|
|
40
|
+
*/
|
|
41
|
+
handleRscFormPost?(component: string, props: Record<string, unknown>, layouts: {
|
|
42
|
+
component: string;
|
|
43
|
+
props: Record<string, unknown>;
|
|
44
|
+
}[], loadings: string[], parallelSlots: Record<string, string>, slotOverrides: Record<string, unknown>, nonce: string | undefined, pageKey: string, bootstrap: boolean, formData: FormData): Promise<{
|
|
45
|
+
htmlStream: ReadableStream;
|
|
46
|
+
}>;
|
|
35
47
|
/**
|
|
36
48
|
* Finish a shell frozen at build time, against data that exists now.
|
|
37
49
|
*
|
|
@@ -148,6 +160,17 @@ export interface RscHostOptions {
|
|
|
148
160
|
* through actions; a proxy in front usually has its own limit too.
|
|
149
161
|
*/
|
|
150
162
|
maxActionBody?: number;
|
|
163
|
+
/**
|
|
164
|
+
* gzip what this host answers, for a request that accepts it.
|
|
165
|
+
*
|
|
166
|
+
* On by default where the runtime has a compressor - Node and Bun - and
|
|
167
|
+
* never on a Worker, where the platform compresses. Behind a CDN or a
|
|
168
|
+
* proxy that compresses, the proxy sees an already-encoded answer and
|
|
169
|
+
* leaves it; a bun or node server answering the internet by itself sent
|
|
170
|
+
* every byte raw, which was a port's whole mobile performance story. Off
|
|
171
|
+
* for a deployment that would rather its proxy did it.
|
|
172
|
+
*/
|
|
173
|
+
compress?: boolean;
|
|
151
174
|
}
|
|
152
175
|
/**
|
|
153
176
|
* Whether a browser was told it could post this action.
|