@rsc-kit/core 0.18.1 → 0.20.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.
Files changed (77) hide show
  1. package/dist/action.d.ts +16 -2
  2. package/dist/action.js +21 -12
  3. package/dist/action.js.map +1 -1
  4. package/dist/apiPrerender.js +25 -7
  5. package/dist/apiPrerender.js.map +1 -1
  6. package/dist/clientPackages.d.ts +30 -0
  7. package/dist/clientPackages.js +234 -0
  8. package/dist/clientPackages.js.map +1 -0
  9. package/dist/compress.d.ts +12 -0
  10. package/dist/compress.js +134 -0
  11. package/dist/compress.js.map +1 -0
  12. package/dist/compressRuntime.d.ts +8 -0
  13. package/dist/compressRuntime.js +62 -0
  14. package/dist/compressRuntime.js.map +1 -0
  15. package/dist/files.d.ts +20 -0
  16. package/dist/files.js +38 -0
  17. package/dist/files.js.map +1 -1
  18. package/dist/formSubmit.d.ts +1 -0
  19. package/dist/formSubmit.js +14 -0
  20. package/dist/formSubmit.js.map +1 -0
  21. package/dist/host.d.ts +23 -0
  22. package/dist/host.js +175 -31
  23. package/dist/host.js.map +1 -1
  24. package/dist/hostCalls.d.ts +15 -0
  25. package/dist/hostCalls.js +75 -8
  26. package/dist/hostCalls.js.map +1 -1
  27. package/dist/js/Form.d.ts +21 -4
  28. package/dist/js/Form.js +110 -83
  29. package/dist/js/Form.js.map +1 -1
  30. package/dist/js/Link.d.ts +5 -5
  31. package/dist/js/Link.js.map +1 -1
  32. package/dist/js/RedirectBoundary.js.map +1 -1
  33. package/dist/js/RouteErrorBoundary.d.ts +6 -2
  34. package/dist/js/RouteErrorBoundary.js +10 -0
  35. package/dist/js/RouteErrorBoundary.js.map +1 -1
  36. package/dist/js/SlotBoundary.d.ts +7 -1
  37. package/dist/js/SlotBoundary.js +9 -1
  38. package/dist/js/SlotBoundary.js.map +1 -1
  39. package/dist/js/createViteRscApp.d.ts +1 -0
  40. package/dist/js/createViteRscApp.js +54 -5
  41. package/dist/js/createViteRscApp.js.map +1 -1
  42. package/dist/js/errors.d.ts +3 -1
  43. package/dist/js/errors.js +26 -3
  44. package/dist/js/errors.js.map +1 -1
  45. package/dist/js/formEncoding.d.ts +72 -3
  46. package/dist/js/formEncoding.js +284 -20
  47. package/dist/js/formEncoding.js.map +1 -1
  48. package/dist/js/navigate.d.ts +6 -3
  49. package/dist/js/navigate.js +57 -7
  50. package/dist/js/navigate.js.map +1 -1
  51. package/dist/js/nuqs.js.map +1 -1
  52. package/dist/js/router.d.ts +3 -3
  53. package/dist/js/router.js.map +1 -1
  54. package/dist/js/updateStore.js +8 -2
  55. package/dist/js/updateStore.js.map +1 -1
  56. package/dist/manifest.d.ts +6 -0
  57. package/dist/manifest.js.map +1 -1
  58. package/dist/openapi.d.ts +74 -0
  59. package/dist/openapi.js +172 -0
  60. package/dist/openapi.js.map +1 -0
  61. package/dist/redirect.d.ts +4 -4
  62. package/dist/redirect.js.map +1 -1
  63. package/dist/request.d.ts +56 -2
  64. package/dist/request.js +68 -4
  65. package/dist/request.js.map +1 -1
  66. package/dist/routes.d.ts +17 -6
  67. package/dist/routes.js.map +1 -1
  68. package/dist/testing.d.ts +14 -0
  69. package/dist/testing.js +56 -2
  70. package/dist/testing.js.map +1 -1
  71. package/dist/useSsr.d.ts +11 -0
  72. package/dist/useSsr.js +15 -0
  73. package/dist/useSsr.js.map +1 -1
  74. package/dist/vite.d.ts +69 -1
  75. package/dist/vite.js +481 -51
  76. package/dist/vite.js.map +1 -1
  77. package/package.json +5 -1
@@ -0,0 +1,234 @@
1
+ // Dependencies with "use client" files that plugin-rsc would leave external.
2
+ //
3
+ // plugin-rsc bundles a dependency into the server graphs - and so sees its
4
+ // directives - only when the dependency declares react as a peer. That is
5
+ // the right signal for a library written for React. It misses the package
6
+ // that imports react and never says so: a component wrapper generated by a
7
+ // tool, a workspace package with react under dependencies, a published
8
+ // package whose author forgot. Left external, its "use client" is a string
9
+ // nobody reads, the rsc environment loads it under the react-server
10
+ // condition, and the first useState inside it fails at render with an error
11
+ // that names React and not the package.
12
+ //
13
+ // So the direct dependencies are read once at config time: any that plugin-rsc
14
+ // would not bundle, and that carries a "use client" directive in one of its
15
+ // files, is bundled here. Direct dependencies only - a directive two levels
16
+ // down is the concern of the package between, which declared it.
17
+ import { existsSync, readdirSync, readFileSync, realpathSync, statSync, openSync, readSync, closeSync } from "node:fs";
18
+ import { dirname, join } from "node:path";
19
+ /** A directive at the top of a file: comments may precede it, code may not. */
20
+ const DIRECTIVE = /^(?:\s|\/\/[^\n]*\n|\/\*[\s\S]*?\*\/)*(?:'use client'|"use client")\s*;?/;
21
+ const SOURCE = /\.(?:m?js|cjs|jsx)$/;
22
+ /** The most files read per package before giving up: a bound, not a budget. */
23
+ const FILE_CAP = 3000;
24
+ /** Enough of a file to hold a licence header and the directive after it. */
25
+ const HEAD_BYTES = 2048;
26
+ /** The packages plugin-rsc handles itself, and this one. */
27
+ const OWN = new Set(["react", "react-dom", "react-server-dom-webpack", "@vitejs/plugin-rsc"]);
28
+ function readJson(path) {
29
+ try {
30
+ return JSON.parse(readFileSync(path, "utf8"));
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ /** Where `name` is installed, walking up from `from` the way resolution does. */
37
+ export function packageDir(name, from) {
38
+ let dir = from;
39
+ for (;;) {
40
+ const candidate = join(dir, "node_modules", name);
41
+ if (existsSync(join(candidate, "package.json")))
42
+ return candidate;
43
+ const parent = dirname(dir);
44
+ if (parent === dir)
45
+ return null;
46
+ dir = parent;
47
+ }
48
+ }
49
+ function realpathOf(path) {
50
+ try {
51
+ return realpathSync(path);
52
+ }
53
+ catch {
54
+ return path;
55
+ }
56
+ }
57
+ function headOf(path) {
58
+ const fd = openSync(path, "r");
59
+ try {
60
+ const buffer = Buffer.alloc(HEAD_BYTES);
61
+ const read = readSync(fd, buffer, 0, HEAD_BYTES, 0);
62
+ return buffer.toString("utf8", 0, read);
63
+ }
64
+ finally {
65
+ closeSync(fd);
66
+ }
67
+ }
68
+ /** Whether any source file under `dir` opens with "use client". */
69
+ export function hasClientDirective(dir) {
70
+ return someSourceFile(dir, (path) => DIRECTIVE.test(headOf(path)));
71
+ }
72
+ /** An import of react-dom's server renderer, static or dynamic, ESM or CJS. */
73
+ const SERVER_RENDERER_IMPORT = /["']react-dom\/server(?:\.[a-z]+)?["']/;
74
+ /** The largest file worth reading whole for an import: a bundled dist, not a data table. */
75
+ const WHOLE_FILE_CAP = 512 * 1024;
76
+ /**
77
+ * Whether any source file under `dir` imports react-dom/server.
78
+ *
79
+ * For a dependency left external where server components render: its
80
+ * imports are never resolved by the build, so the renderer stub that names
81
+ * the app file for a direct import never sees this one. @react-email/render
82
+ * is the usual case - an action imports it, the build says nothing, and the
83
+ * first call throws React's refusal at a visitor.
84
+ */
85
+ export function importsServerRenderer(dir) {
86
+ return someSourceFile(dir, (path) => {
87
+ try {
88
+ if (statSync(path).size > WHOLE_FILE_CAP)
89
+ return false;
90
+ return SERVER_RENDERER_IMPORT.test(readFileSync(path, "utf8"));
91
+ }
92
+ catch {
93
+ return false;
94
+ }
95
+ });
96
+ }
97
+ /** Whether `test` holds for some source file under `dir`, within the cap. */
98
+ function someSourceFile(dir, test) {
99
+ const pending = [dir];
100
+ let seen = 0;
101
+ while (pending.length) {
102
+ const current = pending.pop();
103
+ let entries;
104
+ try {
105
+ entries = readdirSync(current);
106
+ }
107
+ catch {
108
+ continue;
109
+ }
110
+ for (const entry of entries) {
111
+ if (entry === "node_modules" || entry.startsWith("."))
112
+ continue;
113
+ const path = join(current, entry);
114
+ let stat;
115
+ try {
116
+ stat = statSync(path);
117
+ }
118
+ catch {
119
+ continue;
120
+ }
121
+ if (stat.isDirectory()) {
122
+ pending.push(path);
123
+ continue;
124
+ }
125
+ if (!SOURCE.test(entry))
126
+ continue;
127
+ if (++seen > FILE_CAP)
128
+ return false;
129
+ try {
130
+ if (test(path))
131
+ return true;
132
+ }
133
+ catch {
134
+ // Unreadable is neither.
135
+ }
136
+ }
137
+ }
138
+ return false;
139
+ }
140
+ /** Whether a package says it uses React, as a peer or as a dependency. */
141
+ function usesReact(manifest) {
142
+ return !!manifest && ("react" in (manifest.peerDependencies ?? {}) || "react" in (manifest.dependencies ?? {}));
143
+ }
144
+ /**
145
+ * The dependencies of a bundled package that use React, and theirs.
146
+ *
147
+ * Bundling the package that carries the directive is not enough: the
148
+ * runtime it calls into - @stencil/react-output-target under a generated
149
+ * wrapper - is its own package, with react as a peer, and plugin-rsc's
150
+ * crawl never reached it because it only descends through packages that
151
+ * declared React. Left external, that runtime loads React through Node
152
+ * while everything else gets it through Vite: two copies, and the hooks
153
+ * dispatcher is null in one of them. Every React-using dependency under
154
+ * a bundled package is bundled with it.
155
+ */
156
+ function reactDependenciesOf(dir, into, seen) {
157
+ const manifest = readJson(join(dir, "package.json"));
158
+ for (const name of Object.keys(manifest?.dependencies ?? {})) {
159
+ if (OWN.has(name) || seen.has(name))
160
+ continue;
161
+ seen.add(name);
162
+ // From where the package really is: under Bun's store a symlink in the
163
+ // app's node_modules points into .bun/<pkg>@<v>/node_modules/<pkg>, and
164
+ // the dependency sits beside it there, not beside the symlink.
165
+ const depDir = packageDir(name, realpathOf(dir));
166
+ if (!depDir || !usesReact(readJson(join(depDir, "package.json"))))
167
+ continue;
168
+ into.add(name);
169
+ reactDependenciesOf(depDir, into, seen);
170
+ }
171
+ }
172
+ /**
173
+ * Where a package is, anywhere in the project's dependency graph.
174
+ *
175
+ * For a polyfill a dependency of a dependency needs: not hoisted to the
176
+ * project under a strict store, so not resolvable from the project by
177
+ * name, and still what has to be evaluated first. A breadth-first walk of
178
+ * the manifests, from each package's real path, bounded in depth. The
179
+ * entry file is what `main` names, or index.js.
180
+ */
181
+ export function packageEntryInGraph(root, name, depth = 5) {
182
+ const seen = new Set();
183
+ let frontier = [root];
184
+ for (let level = 0; level <= depth && frontier.length; level++) {
185
+ const next = [];
186
+ for (const dir of frontier) {
187
+ const found = packageDir(name, realpathOf(dir));
188
+ if (found) {
189
+ const manifest = readJson(join(found, "package.json"));
190
+ const entry = join(found, manifest?.main ?? "index.js");
191
+ return existsSync(entry) ? entry : existsSync(entry + ".js") ? entry + ".js" : null;
192
+ }
193
+ const manifest = readJson(join(dir, "package.json"));
194
+ for (const dep of Object.keys(manifest?.dependencies ?? {})) {
195
+ if (seen.has(dep))
196
+ continue;
197
+ seen.add(dep);
198
+ const depDir = packageDir(dep, realpathOf(dir));
199
+ if (depDir)
200
+ next.push(depDir);
201
+ }
202
+ }
203
+ frontier = next;
204
+ }
205
+ return null;
206
+ }
207
+ /**
208
+ * The direct dependencies of the project at `root` that plugin-rsc would
209
+ * leave external and that carry a "use client" file - and the React-using
210
+ * dependencies under each, which have to be bundled with it.
211
+ */
212
+ export function clientPackages(root) {
213
+ const own = readJson(join(root, "package.json"));
214
+ if (!own?.dependencies)
215
+ return [];
216
+ const found = new Set();
217
+ for (const name of Object.keys(own.dependencies)) {
218
+ if (OWN.has(name) || name.startsWith("@rsc-kit/"))
219
+ continue;
220
+ const dir = packageDir(name, root);
221
+ if (!dir)
222
+ continue;
223
+ const manifest = readJson(join(dir, "package.json"));
224
+ // plugin-rsc's own rule: react as a peer, and the package is bundled already.
225
+ if (manifest?.peerDependencies && "react" in manifest.peerDependencies)
226
+ continue;
227
+ if (!hasClientDirective(dir))
228
+ continue;
229
+ found.add(name);
230
+ reactDependenciesOf(dir, found, new Set([name]));
231
+ }
232
+ return [...found].sort();
233
+ }
234
+ //# sourceMappingURL=clientPackages.js.map
@@ -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;
@@ -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`;