@rsc-kit/core 0.18.0 → 0.18.1
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/js/LoadingBoundary.d.ts +5 -0
- package/dist/js/LoadingBoundary.js +19 -0
- package/dist/js/LoadingBoundary.js.map +1 -0
- package/dist/js/fallbackReport.js +10 -8
- package/dist/js/fallbackReport.js.map +1 -1
- package/dist/js/useEvents.js +9 -7
- package/dist/js/useEvents.js.map +1 -1
- package/dist/js/usePolling.js +38 -37
- package/dist/js/usePolling.js.map +1 -1
- package/dist/prerender.js +1 -0
- package/dist/prerender.js.map +1 -1
- package/dist/routeSchema.d.ts +49 -0
- package/dist/routeSchema.js.map +1 -1
- package/dist/testing.d.ts +8 -0
- package/dist/testing.js +27 -3
- package/dist/testing.js.map +1 -1
- package/dist/vite.d.ts +11 -0
- package/dist/vite.js +175 -38
- package/dist/vite.js.map +1 -1
- package/package.json +3 -3
package/dist/vite.d.ts
CHANGED
|
@@ -101,6 +101,17 @@ export interface RscKitOptions {
|
|
|
101
101
|
* reads this file follows the server instead of guessing at it.
|
|
102
102
|
*/
|
|
103
103
|
hotFile?: string;
|
|
104
|
+
/**
|
|
105
|
+
* Packages the server bundles import at runtime rather than inline.
|
|
106
|
+
*
|
|
107
|
+
* For a dependency with a native binary, one that spawns a process, or one
|
|
108
|
+
* that reads files beside itself - anything a bundle breaks. A default list
|
|
109
|
+
* covers the usual ones (sharp, bcrypt, better-sqlite3, prisma, puppeteer,
|
|
110
|
+
* ...); this adds to it. Nitro traces an external package into
|
|
111
|
+
* .output/server/node_modules with its binaries, so the deployment is still
|
|
112
|
+
* one directory.
|
|
113
|
+
*/
|
|
114
|
+
serverExternalPackages?: string[];
|
|
104
115
|
/**
|
|
105
116
|
* Where `rpc()` goes while `vite dev` is serving.
|
|
106
117
|
*
|
package/dist/vite.js
CHANGED
|
@@ -42,6 +42,47 @@ let resolvedConfig = null;
|
|
|
42
42
|
let clientLibraryImports = [];
|
|
43
43
|
/** Modules a runtime provides and no bundle should try to carry. */
|
|
44
44
|
const RUNTIME_BUILTINS = ["bun", /^bun:/];
|
|
45
|
+
/**
|
|
46
|
+
* Packages the server bundles import rather than inline, by default.
|
|
47
|
+
*
|
|
48
|
+
* Each of these ships a native binary, spawns one, or reads files relative to
|
|
49
|
+
* its own location - none of which survives being rolled into a bundle. Left
|
|
50
|
+
* external, Nitro traces them into .output/server/node_modules with their
|
|
51
|
+
* binaries, and the built server imports them the way the package expects.
|
|
52
|
+
* Next keeps the same list under serverExternalPackages, for the same reason.
|
|
53
|
+
* `rscKit({ serverExternalPackages })` adds to it.
|
|
54
|
+
*/
|
|
55
|
+
const DEFAULT_SERVER_EXTERNALS = [
|
|
56
|
+
"sharp",
|
|
57
|
+
"canvas",
|
|
58
|
+
"bcrypt",
|
|
59
|
+
"argon2",
|
|
60
|
+
"@node-rs/argon2",
|
|
61
|
+
"@node-rs/bcrypt",
|
|
62
|
+
"better-sqlite3",
|
|
63
|
+
"sqlite3",
|
|
64
|
+
"libsql",
|
|
65
|
+
"@libsql/client",
|
|
66
|
+
"@prisma/client",
|
|
67
|
+
"prisma",
|
|
68
|
+
"puppeteer",
|
|
69
|
+
"puppeteer-core",
|
|
70
|
+
"playwright",
|
|
71
|
+
"playwright-core",
|
|
72
|
+
"jsdom",
|
|
73
|
+
"node-pty",
|
|
74
|
+
"onnxruntime-node",
|
|
75
|
+
"@sentry/profiling-node",
|
|
76
|
+
"pdfkit",
|
|
77
|
+
"mongodb",
|
|
78
|
+
"oslo",
|
|
79
|
+
"@resvg/resvg-js",
|
|
80
|
+
"@napi-rs/canvas",
|
|
81
|
+
];
|
|
82
|
+
/** A package name as a rollup external: the package and every subpath of it. */
|
|
83
|
+
function externalPackage(name) {
|
|
84
|
+
return new RegExp("^" + name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?:/|$)");
|
|
85
|
+
}
|
|
45
86
|
function arrayOf(value) {
|
|
46
87
|
return value == null ? [] : Array.isArray(value) ? value : [value];
|
|
47
88
|
}
|
|
@@ -2068,8 +2109,15 @@ function installHostCallsOnce(): void {
|
|
|
2068
2109
|
function instrumentationFile() {
|
|
2069
2110
|
for (const ext of ["ts", "tsx", "mts", "js", "mjs"]) {
|
|
2070
2111
|
const file = join(sourceDir, `instrumentation.${ext}`);
|
|
2071
|
-
if (existsSync(file))
|
|
2072
|
-
|
|
2112
|
+
if (!existsSync(file))
|
|
2113
|
+
continue;
|
|
2114
|
+
// Both halves are optional: a file of imports is a bootstrap, a file
|
|
2115
|
+
// with register() is a hook. The entry must only name `register` when it
|
|
2116
|
+
// exists - a namespace import's missing member is a bundler warning on
|
|
2117
|
+
// every build (IMPORT_IS_UNDEFINED), which is the app being told off for
|
|
2118
|
+
// a file written exactly as documented.
|
|
2119
|
+
const hasRegister = /export\s+(?:async\s+)?(?:function\s+register\b|const\s+register\b|let\s+register\b|\{[^}]*\bregister\b[^}]*\})/.test(readFileSync(file, "utf-8"));
|
|
2120
|
+
return { file, hasRegister };
|
|
2073
2121
|
}
|
|
2074
2122
|
return null;
|
|
2075
2123
|
}
|
|
@@ -2135,10 +2183,13 @@ ${
|
|
|
2135
2183
|
// First, before any page: an import's side effects run in import order,
|
|
2136
2184
|
// and a package configured here has to be configured before a page module
|
|
2137
2185
|
// that reads it at evaluation time.
|
|
2138
|
-
instrumentation
|
|
2139
|
-
? `import * as __instrumentation from ${JSON.stringify(instrumentation)}`
|
|
2140
|
-
:
|
|
2186
|
+
instrumentation?.hasRegister
|
|
2187
|
+
? `import * as __instrumentation from ${JSON.stringify(instrumentation.file)}`
|
|
2188
|
+
: instrumentation
|
|
2189
|
+
? `import ${JSON.stringify(instrumentation.file)}\nconst __instrumentation: { register?: () => unknown } = {}`
|
|
2190
|
+
: "const __instrumentation: { register?: () => unknown } = {}"}
|
|
2141
2191
|
import { SegmentBoundary } from ${JSON.stringify(join(packageDir, "js/SegmentBoundary"))}
|
|
2192
|
+
import { LoadingBoundary } from ${JSON.stringify(join(packageDir, "js/LoadingBoundary"))}
|
|
2142
2193
|
import { DocumentTitle } from ${JSON.stringify(join(packageDir, "js/DocumentTitle"))}
|
|
2143
2194
|
import { SlotBoundary } from ${JSON.stringify(join(packageDir, "js/SlotBoundary"))}
|
|
2144
2195
|
import { RouteErrorBoundary } from ${JSON.stringify(join(packageDir, "js/RouteErrorBoundary"))}
|
|
@@ -2675,9 +2726,17 @@ function buildElement(
|
|
|
2675
2726
|
searchParams: checkedSearchParams(schemas, pageSearchParams()),
|
|
2676
2727
|
})
|
|
2677
2728
|
|
|
2729
|
+
// Through LoadingBoundary when the runtime is shipped, so a server render
|
|
2730
|
+
// can tell the engine's boundary from one the developer wrote by name -
|
|
2731
|
+
// see that file. A page shipping no runtime gets a plain Suspense: a
|
|
2732
|
+
// client component would drag React in for a wrapper nothing can use.
|
|
2678
2733
|
for (let i = loadings.length - 1; i >= 0; i--) {
|
|
2679
2734
|
const Loading = components[loadings[i]]
|
|
2680
|
-
|
|
2735
|
+
const fallback = Loading ? createElement(Loading) : null
|
|
2736
|
+
|
|
2737
|
+
element = bootstrap
|
|
2738
|
+
? createElement(LoadingBoundary, { fallback }, element)
|
|
2739
|
+
: createElement(Suspense, { fallback }, element)
|
|
2681
2740
|
}
|
|
2682
2741
|
|
|
2683
2742
|
// Outside the Suspense boundary, innermost first — the nearest error.tsx to
|
|
@@ -4156,35 +4215,7 @@ export async function handleSsr(
|
|
|
4156
4215
|
const html = await renderToReadableStream(root as any, {
|
|
4157
4216
|
bootstrapScriptContent,
|
|
4158
4217
|
nonce,
|
|
4159
|
-
|
|
4160
|
-
// its digest is what lets the client tell it from a fault on hydration;
|
|
4161
|
-
// returned here so React writes it into the document.
|
|
4162
|
-
onError: onError ?? ((error: unknown, info?: { componentStack?: string }) => {
|
|
4163
|
-
// The consumer cancelled - a browser that left mid-stream, a prefetch
|
|
4164
|
-
// abandoned. React reports it as an error; the page had none.
|
|
4165
|
-
if (cancelledByConsumer(error)) return
|
|
4166
|
-
const digest = (error as { digest?: string } | null)?.digest
|
|
4167
|
-
if (digest === 'rsc-kit:search-params-fallback') {
|
|
4168
|
-
// The component is the first frame of React's stack. Noted on the
|
|
4169
|
-
// request so the build attaches it to the route; printed as one line,
|
|
4170
|
-
// not a stack, so the dev server says which boundary the build wants.
|
|
4171
|
-
const where = /at ([A-Z][\\w$]*)/.exec(info?.componentStack ?? '')?.[1]
|
|
4172
|
-
noteFallback('useSearchParams()' + (where ? ' in ' + where : ''))
|
|
4173
|
-
// Under a boundary the developer wrote, nothing to say. With nothing
|
|
4174
|
-
// closer than a loading.tsx, one line: the whole segment is the
|
|
4175
|
-
// fallback until the query arrives.
|
|
4176
|
-
if (caughtByLoading(info?.componentStack)) {
|
|
4177
|
-
console.error(
|
|
4178
|
-
'[rsc-kit] ' + (where ? where + ': ' : '') +
|
|
4179
|
-
'useSearchParams() was read on the server with nothing closer than a loading.tsx, so the whole ' +
|
|
4180
|
-
'segment shows that fallback until the query arrives. A <Suspense> around the component that reads ' +
|
|
4181
|
-
'keeps the rest of the page painted.',
|
|
4182
|
-
)
|
|
4183
|
-
}
|
|
4184
|
-
return digest
|
|
4185
|
-
}
|
|
4186
|
-
console.error('[rsc-kit:ssr]', error)
|
|
4187
|
-
}),
|
|
4218
|
+
onError: onError ?? reportRenderError('ssr'),
|
|
4188
4219
|
})
|
|
4189
4220
|
|
|
4190
4221
|
return DEV_ORIGIN ? rewriteViteDevUrlStream(html, DEV_ORIGIN) : html
|
|
@@ -4236,6 +4267,46 @@ export async function handleSsrPrerender(
|
|
|
4236
4267
|
}
|
|
4237
4268
|
}
|
|
4238
4269
|
|
|
4270
|
+
/**
|
|
4271
|
+
* What a server render says about an error, first render and resume alike.
|
|
4272
|
+
*
|
|
4273
|
+
* The query-string fallback is the designed path for a stored page, and its
|
|
4274
|
+
* digest is what lets the client tell it from a fault on hydration; returned
|
|
4275
|
+
* so React writes it into the document. Under a boundary the developer wrote
|
|
4276
|
+
* there is nothing to say; with nothing closer than a loading.tsx, one line.
|
|
4277
|
+
*/
|
|
4278
|
+
function reportRenderError(phase: 'ssr' | 'resume') {
|
|
4279
|
+
return (error: unknown, info?: { componentStack?: string }) => {
|
|
4280
|
+
// The consumer cancelled - a browser that left mid-stream, a prefetch
|
|
4281
|
+
// abandoned. React reports it as an error; the page had none.
|
|
4282
|
+
if (cancelledByConsumer(error)) return
|
|
4283
|
+
|
|
4284
|
+
const digest = (error as { digest?: string } | null)?.digest
|
|
4285
|
+
|
|
4286
|
+
if (digest === 'rsc-kit:search-params-fallback') {
|
|
4287
|
+
// The component is the first frame of React's stack. Noted on the
|
|
4288
|
+
// request so the build attaches it to the route; printed as one line,
|
|
4289
|
+
// not a stack, so the server says which boundary it wants.
|
|
4290
|
+
const where = /at ([A-Z][\\w$]*)/.exec(info?.componentStack ?? '')?.[1]
|
|
4291
|
+
|
|
4292
|
+
noteFallback('useSearchParams()' + (where ? ' in ' + where : ''))
|
|
4293
|
+
|
|
4294
|
+
if (caughtByLoading(info?.componentStack)) {
|
|
4295
|
+
console.error(
|
|
4296
|
+
'[rsc-kit] ' + (where ? where + ': ' : '') +
|
|
4297
|
+
'useSearchParams() was read on the server with nothing closer than a loading.tsx, so the whole ' +
|
|
4298
|
+
'segment shows that fallback until the query arrives. A <Suspense> around the component that reads ' +
|
|
4299
|
+
'keeps the rest of the page painted.',
|
|
4300
|
+
)
|
|
4301
|
+
}
|
|
4302
|
+
|
|
4303
|
+
return digest
|
|
4304
|
+
}
|
|
4305
|
+
|
|
4306
|
+
console.error('[rsc-kit:' + phase + ']', error)
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
|
|
4239
4310
|
/**
|
|
4240
4311
|
* Pick a build-time render back up, against data that exists now.
|
|
4241
4312
|
*
|
|
@@ -4255,7 +4326,12 @@ export async function handleSsrResume(
|
|
|
4255
4326
|
|
|
4256
4327
|
const html = await resume(root as any, postponed as any, {
|
|
4257
4328
|
nonce,
|
|
4258
|
-
|
|
4329
|
+
// The same reading of an error the first render has. This used to log
|
|
4330
|
+
// every error raw, so a query read the developer's own boundary caught -
|
|
4331
|
+
// the designed path, on every resume of a shell whose hole holds one -
|
|
4332
|
+
// printed as "[rsc-kit:resume] Error: useSearchParams() was read..." on
|
|
4333
|
+
// every request, with advice the app had already followed.
|
|
4334
|
+
onError: reportRenderError('resume'),
|
|
4259
4335
|
})
|
|
4260
4336
|
|
|
4261
4337
|
return DEV_ORIGIN ? rewriteViteDevUrlStream(html, DEV_ORIGIN) : html
|
|
@@ -4790,8 +4866,51 @@ export default function rscKitStartup() {
|
|
|
4790
4866
|
})
|
|
4791
4867
|
}
|
|
4792
4868
|
`;
|
|
4869
|
+
/**
|
|
4870
|
+
* Where NODE_ENV=development came from, and what to do about it.
|
|
4871
|
+
*
|
|
4872
|
+
* Vite reads NODE_ENV from its env files in this order, later winning, and
|
|
4873
|
+
* only when the process did not already set it - so the last file that
|
|
4874
|
+
* names it is the one that decided. No file naming it means the shell or
|
|
4875
|
+
* `--mode` did.
|
|
4876
|
+
*/
|
|
4877
|
+
function nodeEnvSetIn(mode, envDir) {
|
|
4878
|
+
const files = [".env", ".env.local", `.env.${mode}`, `.env.${mode}.local`];
|
|
4879
|
+
let culprit = null;
|
|
4880
|
+
// The project's root, not Vite's: this plugin points Vite's root at the
|
|
4881
|
+
// build directory, and the app's .env is read from the project root - by
|
|
4882
|
+
// Nitro's dotenv loading into process.env, and by Vite where envDir says
|
|
4883
|
+
// so. Both places are looked at; the project's is where the file is.
|
|
4884
|
+
const dirs = [...new Set([projectRoot, envDir || projectRoot])];
|
|
4885
|
+
for (const dir of dirs)
|
|
4886
|
+
for (const name of files) {
|
|
4887
|
+
const path = join(dir, name);
|
|
4888
|
+
if (!existsSync(path))
|
|
4889
|
+
continue;
|
|
4890
|
+
const lines = readFileSync(path, "utf-8").split("\n");
|
|
4891
|
+
const at = lines.findIndex((line) => /^\s*(?:export\s+)?NODE_ENV\s*=/.test(line));
|
|
4892
|
+
if (at !== -1)
|
|
4893
|
+
culprit = `${path}:${at + 1}`;
|
|
4894
|
+
}
|
|
4895
|
+
return culprit;
|
|
4896
|
+
}
|
|
4897
|
+
function refuseDevelopmentBuild(config) {
|
|
4898
|
+
const culprit = nodeEnvSetIn(config.mode, config.envDir);
|
|
4899
|
+
return ("[rsc-kit] vite build is running as a development build, and the output cannot work: " +
|
|
4900
|
+
"the pages are compiled against React's development JSX runtime (jsxDEV), the server " +
|
|
4901
|
+
"bundles carry React's production build, and every route fails to render.\n\n" +
|
|
4902
|
+
(culprit
|
|
4903
|
+
? `NODE_ENV=development is set in ${culprit}. Remove that line - Vite sets NODE_ENV ` +
|
|
4904
|
+
"itself: development under `vite`, production under `vite build`."
|
|
4905
|
+
: `NODE_ENV is "${process.env.NODE_ENV ?? ""}" from the environment or --mode ` +
|
|
4906
|
+
"(mode: " + JSON.stringify(config.mode) + "). Build with NODE_ENV=production, or unset it."));
|
|
4907
|
+
}
|
|
4793
4908
|
export function rscKit(options = {}) {
|
|
4794
4909
|
resolvePaths(options);
|
|
4910
|
+
const serverExternals = [
|
|
4911
|
+
...RUNTIME_BUILTINS,
|
|
4912
|
+
...[...DEFAULT_SERVER_EXTERNALS, ...(options.serverExternalPackages ?? [])].map(externalPackage),
|
|
4913
|
+
];
|
|
4795
4914
|
const routesPlugin = {
|
|
4796
4915
|
name: "rsc-kit",
|
|
4797
4916
|
// A Nitro module, which Nitro's Vite plugin collects from any plugin
|
|
@@ -5007,7 +5126,7 @@ export function rscKit(options = {}) {
|
|
|
5007
5126
|
build: {
|
|
5008
5127
|
rollupOptions: {
|
|
5009
5128
|
input: { index: join(genDir, "entry.rsc.tsx") },
|
|
5010
|
-
external:
|
|
5129
|
+
external: serverExternals,
|
|
5011
5130
|
},
|
|
5012
5131
|
},
|
|
5013
5132
|
},
|
|
@@ -5015,7 +5134,7 @@ export function rscKit(options = {}) {
|
|
|
5015
5134
|
build: {
|
|
5016
5135
|
rollupOptions: {
|
|
5017
5136
|
input: { index: join(genDir, "entry.ssr.tsx") },
|
|
5018
|
-
external:
|
|
5137
|
+
external: serverExternals,
|
|
5019
5138
|
},
|
|
5020
5139
|
},
|
|
5021
5140
|
},
|
|
@@ -5254,6 +5373,24 @@ export function rscKit(options = {}) {
|
|
|
5254
5373
|
}
|
|
5255
5374
|
},
|
|
5256
5375
|
configResolved(config) {
|
|
5376
|
+
// A build that is not a production build cannot work here, and the
|
|
5377
|
+
// way it fails is opaque: plugin-react emits the development JSX
|
|
5378
|
+
// runtime (jsxDEV), the server bundles resolve React's production
|
|
5379
|
+
// build, and every route fails to render with React's "message
|
|
5380
|
+
// omitted in production builds". The usual cause is NODE_ENV=development
|
|
5381
|
+
// in a .env file, which Vite honours - a line the scaffold itself once
|
|
5382
|
+
// wrote. Named, with the file and line, before any of that happens.
|
|
5383
|
+
//
|
|
5384
|
+
// Refused rather than overridden, because a plugin cannot override it:
|
|
5385
|
+
// Vite notes whether NODE_ENV was set before it loads the config file
|
|
5386
|
+
// and applies the .env value after the config hooks have run, so an
|
|
5387
|
+
// assignment here is overwritten - and patching the resolved config
|
|
5388
|
+
// afterwards leaves the client bundle's process.env.NODE_ENV and
|
|
5389
|
+
// import.meta.env.DEV already decided, which is a production server
|
|
5390
|
+
// serving a development client.
|
|
5391
|
+
if (config.command === "build" && !config.isProduction) {
|
|
5392
|
+
throw new Error(refuseDevelopmentBuild(config));
|
|
5393
|
+
}
|
|
5257
5394
|
isWatch = config.build?.watch != null;
|
|
5258
5395
|
resolvedConfig = config;
|
|
5259
5396
|
const nitroMain = config.plugins.find((p) => p.name === "nitro:main");
|