@rsc-kit/core 0.30.38 → 0.30.42
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/cache.d.ts +79 -2
- package/dist/cache.js +151 -5
- package/dist/cache.js.map +1 -1
- package/dist/changed.js +2 -1
- package/dist/changed.js.map +1 -1
- package/dist/conformance.d.ts +1 -0
- package/dist/conformance.js +27 -0
- package/dist/conformance.js.map +1 -1
- package/dist/flash.d.ts +18 -0
- package/dist/flash.js +29 -0
- package/dist/flash.js.map +1 -0
- package/dist/flashCookie.d.ts +17 -0
- package/dist/flashCookie.js +36 -0
- package/dist/flashCookie.js.map +1 -0
- package/dist/host.d.ts +28 -17
- package/dist/host.js +109 -31
- package/dist/host.js.map +1 -1
- package/dist/hostCalls.js +27 -11
- package/dist/hostCalls.js.map +1 -1
- package/dist/js/ActivityRouter.js +3 -3
- package/dist/js/ActivityRouter.js.map +1 -1
- package/dist/js/Changes.js +1 -1
- package/dist/js/Changes.js.map +1 -1
- package/dist/js/DefaultRouteError.d.ts +1 -1
- package/dist/js/DefaultRouteError.js.map +1 -1
- package/dist/js/Form.d.ts +2 -2
- package/dist/js/Form.js +5 -5
- package/dist/js/Form.js.map +1 -1
- package/dist/js/Link.js +2 -2
- package/dist/js/Link.js.map +1 -1
- package/dist/js/RedirectBoundary.js +2 -2
- package/dist/js/RedirectBoundary.js.map +1 -1
- package/dist/js/SegmentBoundary.js +5 -5
- package/dist/js/SegmentBoundary.js.map +1 -1
- package/dist/js/SlotBoundary.js +3 -3
- package/dist/js/SlotBoundary.js.map +1 -1
- package/dist/js/changesStore.js +1 -1
- package/dist/js/changesStore.js.map +1 -1
- package/dist/js/createViteRscApp.js +16 -17
- package/dist/js/createViteRscApp.js.map +1 -1
- package/dist/js/errors.d.ts +3 -1
- package/dist/js/errors.js +5 -2
- package/dist/js/errors.js.map +1 -1
- package/dist/js/navigate.d.ts +17 -2
- package/dist/js/navigate.js +50 -19
- package/dist/js/navigate.js.map +1 -1
- package/dist/js/nuqs.js +2 -2
- package/dist/js/nuqs.js.map +1 -1
- package/dist/js/pagePayload.js +2 -2
- package/dist/js/pagePayload.js.map +1 -1
- package/dist/js/payloadKey.d.ts +32 -0
- package/dist/js/payloadKey.js +61 -0
- package/dist/js/payloadKey.js.map +1 -0
- package/dist/js/refreshOn.js +3 -3
- package/dist/js/refreshOn.js.map +1 -1
- package/dist/js/section.d.ts +4 -4
- package/dist/js/section.js +2 -2
- package/dist/js/section.js.map +1 -1
- package/dist/js/updateStore.js +6 -2
- package/dist/js/updateStore.js.map +1 -1
- package/dist/js/useAppUpdate.js +1 -1
- package/dist/js/useAppUpdate.js.map +1 -1
- package/dist/js/useFlash.d.ts +16 -0
- package/dist/js/useFlash.js +56 -0
- package/dist/js/useFlash.js.map +1 -0
- package/dist/js/useOnline.js +1 -1
- package/dist/js/useOnline.js.map +1 -1
- package/dist/js/usePathname.js +1 -1
- package/dist/js/usePathname.js.map +1 -1
- package/dist/manifest.d.ts +7 -0
- package/dist/manifest.js.map +1 -1
- package/dist/pageCache.d.ts +47 -0
- package/dist/pageCache.js +177 -0
- package/dist/pageCache.js.map +1 -0
- package/dist/prerender.js +4 -0
- package/dist/prerender.js.map +1 -1
- package/dist/request.d.ts +36 -0
- package/dist/request.js +80 -0
- package/dist/request.js.map +1 -1
- package/dist/testing.d.ts +9 -0
- package/dist/testing.js +14 -2
- package/dist/testing.js.map +1 -1
- package/dist/vite.d.ts +27 -0
- package/dist/vite.js +120 -14
- package/dist/vite.js.map +1 -1
- package/package.json +11 -3
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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","sourcesContent":["// The whole app as a function, for tests.\n//\n// import { createTestApp } from '@rsc-kit/core/testing'\n//\n// const app = await createTestApp()\n// const res = await app.fetch('/api/orders', { headers: { Cookie: 'user=ada' } })\n//\n// No port, no browser, no server process. The built entry already exports the\n// same Request → Response handler the dev server and the production server\n// both call, so a test can hand it a Request and read the Response — through\n// the real router, the real middleware, the real api routes, and the pages the\n// build stored.\n//\n// That is the tier between a unit test and a browser. An action or a route is\n// a function and can be imported and called; a browser test proves the page\n// works; this proves the app answers a url the way it is deployed, which is\n// where a route that renders fine and is served wrong shows up.\n\nimport { spawnSync } from 'node:child_process'\nimport { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'\nimport { extname, join, resolve, sep } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { httpHostCalls } from './hostCalls.js'\nimport { configureChanged } from './changed.js'\nimport { testHostFetch, type TestHost } from './testHost.js'\n\nexport { hostReply, testChanges, HOST_MIDDLEWARE, type HostCallInput, type HostHandler, type TestHost } from './testHost.js'\n\nexport interface TestApp {\n /** A path, not a url. The origin is whatever the app was told it is. */\n fetch(path: string, init?: RequestInit): Promise<Response>\n /**\n * What each region of the page at `path` refreshes on, rendered for this\n * request: `{ page: ['ledger'], repos: ['team:1:repos'] }` - `page` for the\n * page's own `refreshOn`, a section's name for each section's. A region\n * whose refreshOn gave no names is there with `[]`; one with no refreshOn\n * is not there at all.\n *\n * expect(await app.watched('/teams/1')).toMatchObject({ repos: ['team:1:repos'] })\n *\n * The names a tab would be handed, without reading them out of the payload.\n */\n watched(path: string, init?: RequestInit): Promise<Record<string, string[]>>\n /**\n * The page at `path` as markup: the whole streamed document, with every\n * `<script>` taken out.\n *\n * The document carries the page's payload for the browser to hydrate from,\n * and the payload holds everything the page was given - a client\n * component's props included, shown or not. A test asserting that something\n * is NOT on the page reads this, not `fetch`:\n *\n * expect(await app.markup('/teams/1')).not.toContain('internal note')\n *\n * Regions that streamed in later are included where they arrived; a\n * Suspense fallback they replaced is still in the markup too, as the\n * server sent it.\n */\n markup(path: string, init?: RequestInit): Promise<string>\n /** Where the build was read from, for a test that wants to look. */\n bundle: string\n}\n\nexport interface TestAppOptions {\n /** The project. Defaults to the working directory. */\n root?: string\n /**\n * Whether to build first.\n *\n * `true` builds when the source is newer than the last build, which is what\n * a test run wants: the first run pays for it, the rest do not, and an edit\n * is picked up. `false` never builds and fails loudly if there is nothing to\n * read — for a ci step that already built.\n */\n build?: boolean\n /** The origin requests are made against. Nothing reads it; it is a url. */\n origin?: string\n /**\n * The backend, answered in the test: rpc() calls and host guards by name.\n * The app reaches it through the same client it uses against a real one,\n * so nothing listens and RSC_BACKEND is not called. See `hostReply`.\n */\n host?: TestHost\n /**\n * What answers a url this app does not own - a Go route, a Laravel page,\n * /login - in place of the backend the app forwards it to. Handed the\n * request as the backend would receive it. Without one, such a url is\n * forwarded to RSC_BACKEND, and in a test that is usually a 502.\n */\n backend?: (request: Request) => Response | Promise<Response>\n}\n\n/**\n * Where the build put the server bundle.\n *\n * Two layouts, because two ways of building. Under Nitro — every scaffolded\n * app — it is inside Nitro's own directory. On its own the plugin writes\n * <outDir>/dist/rsc. The first that exists wins.\n */\nfunction findBundle(root: string): string | null {\n const candidates = [\n join(root, 'node_modules/.nitro/vite/services/rsc/index.js'),\n // The default outDir since 0.29.8; .rsc before it.\n join(root, '.rsc-kit/dist/rsc/index.js'),\n join(root, '.rsc/dist/rsc/index.js'),\n join(root, 'build/dist/rsc/index.js'),\n ]\n\n return candidates.find((path) => existsSync(path)) ?? null\n}\n\n/**\n * When anything a build is made from last changed: the app's source, its\n * Vite config, its dependencies - package.json, the lockfile, and the\n * installed engine itself. Only src/ used to count, so an app that upgraded\n * @rsc-kit/core kept testing the build the old one made, and passed.\n */\nfunction newestInput(root: string): number {\n const files = [\n 'vite.config.ts', 'vite.config.mts', 'vite.config.js', 'vite.config.mjs',\n 'package.json', 'bun.lock', 'bun.lockb', 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock',\n ]\n let latest = newest(join(root, 'src'))\n\n for (const file of files) {\n const path = join(root, file)\n\n if (existsSync(path)) latest = Math.max(latest, statSync(path).mtimeMs)\n }\n\n // The engine as installed: a workspace link changes without the lockfile.\n const engine = join(root, 'node_modules/@rsc-kit/core/dist')\n\n return Math.max(latest, newest(engine))\n}\n\n/** The newest mtime under a directory, for deciding whether a build is stale. */\nfunction newest(dir: string): number {\n if (!existsSync(dir)) return 0\n\n let latest = 0\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue\n\n const path = join(dir, entry.name)\n const time = entry.isDirectory() ? newest(path) : statSync(path).mtimeMs\n\n if (time > latest) latest = time\n }\n\n return latest\n}\n\n/** One build per process per root, however many test files ask. */\nconst built = new Map<string, Promise<string>>()\n\n/** The content types a test is likely to assert on; anything else is octet-stream. */\nconst CONTENT_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.webmanifest': 'application/manifest+json',\n '.xml': 'application/xml',\n '.txt': 'text/plain; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.webp': 'image/webp',\n '.avif': 'image/avif',\n '.gif': 'image/gif',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.wasm': 'application/wasm',\n '.map': 'application/json',\n}\n\n/**\n * A file the build put in .output/public, answered the way Nitro's static\n * layer would - which the handler this harness loads does not include. The\n * handler is the rsc service: routing, pages, actions, api routes. Assets,\n * the service worker, the manifest and the icons are files Nitro serves in\n * production from .output/public, and a test that asked for /sw.js used to\n * get the router's 404 for a file the deployment serves fine - the one\n * shape this harness exists to catch, in the other direction.\n *\n * Files only, for GET and HEAD, within the directory: a path that escapes it\n * is a request for the handler, not a file.\n */\n/** @internal Exported for its test. */\nexport function staticFile(root: string, request: Request): Response | null {\n if (request.method !== 'GET' && request.method !== 'HEAD') return null\n\n const publicDir = join(root, '.output/public')\n const pathname = decodeURIComponent(new URL(request.url).pathname)\n const file = resolve(publicDir, '.' + pathname)\n\n if (!file.startsWith(publicDir + sep) || !existsSync(file) || !statSync(file).isFile()) return null\n\n const type = CONTENT_TYPES[extname(file).toLowerCase()] ?? 'application/octet-stream'\n const body = request.method === 'HEAD' ? null : readFileSync(file)\n\n return new Response(body, {\n status: 200,\n headers: { 'Content-Type': type, 'Cache-Control': 'no-store', 'X-RSC-Kit-Test': 'static' },\n })\n}\n\n/**\n * How this project builds: its own `build` script, run by the package manager\n * this test is running under. Without a script, Vite directly - on Bun's\n * runtime when the tests are, since a bin's node shebang would otherwise\n * start it under Node.\n */\n/** @internal Exported for its test. */\nexport function buildCommand(root: string): [string, string[]] {\n const onBun = typeof process.versions.bun === 'string'\n\n let scripts: Record<string, string> = {}\n\n try {\n scripts = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8')).scripts ?? {}\n } catch {\n // No package.json, or not JSON: there is no script to run.\n }\n\n if (typeof scripts.build === 'string') {\n return onBun ? ['bun', ['run', 'build']] : ['npm', ['run', 'build']]\n }\n\n return onBun ? ['bun', ['--bun', 'vite', 'build']] : ['npx', ['vite', 'build']]\n}\n\nasync function ensureBuilt(root: string, build: boolean): Promise<string> {\n const existing = findBundle(root)\n\n if (!build) {\n if (!existing) {\n throw new Error(\n `[rsc-kit] No build to test against under ${root}. Run the build first, or let createTestApp() do it by leaving \\`build\\` on.`,\n )\n }\n\n return existing\n }\n\n const fresh = existing && statSync(existing).mtimeMs > newestInput(root)\n\n if (fresh) return existing\n\n // The user's own build command, so what is tested is what ships. A test\n // that built some other way would pass against a bundle nobody deploys -\n // and used to: this ran `npx vite build` whatever package.json said, which\n // on a Bun project built under Node and failed on the first `import 'bun'`.\n const [command, args] = buildCommand(root)\n const run = spawnSync(command, args, {\n cwd: root,\n stdio: ['ignore', 'pipe', 'pipe'],\n env: { ...process.env, NODE_ENV: 'production' },\n })\n\n if (run.status !== 0) {\n throw new Error(`[rsc-kit] The build failed, so there is nothing to test:\\n${run.stderr}`)\n }\n\n const bundle = findBundle(root)\n\n if (!bundle) {\n throw new Error(\n `[rsc-kit] The build finished but no server bundle was found under ${root}. Is rscKit() in vite.config?`,\n )\n }\n\n return bundle\n}\n\n/**\n * Build the app if it needs it, load it, and hand back something to fetch from.\n *\n * Memoised per root: every test file in a run shares one build and one loaded\n * module, which is both the fast path and the correct one — two copies of the\n * server bundle in one process would be two client-reference registries.\n */\n/**\n * Whether Request is the runtime's, told apart from a browser's by behaviour.\n *\n * A browser forbids a script from setting Cookie, so happy-dom's Request drops\n * it from an init - and every signed-in request a test makes arrives with no\n * session, bounces to sign-in, and fails far from the cause. Behaviour rather\n * than identity, because identity depends on which file loaded first.\n */\nfunction runtimeRequest(): boolean {\n try {\n return new Request('http://probe.test/', { headers: { cookie: 'probe=1' } }).headers.has('cookie')\n } catch {\n return false\n }\n}\n\nexport async function createTestApp(options: TestAppOptions = {}): Promise<TestApp> {\n if (!runtimeRequest()) {\n throw new Error(\n \"[rsc-kit] createTestApp is running against a browser's Request: a DOM is registered globally \" +\n '(happy-dom, in a preload or an earlier test file), and it replaces Request, Response, Headers and fetch. ' +\n 'A browser cannot set Cookie, so every signed-in request would arrive with no session. ' +\n 'Register the DOM only in the component test file and `GlobalRegistrator.unregister()` after it, ' +\n 'and run the suites isolated (`bun test --isolate`). See the testing guide, \"Components\".',\n )\n }\n\n const root = resolve(options.root ?? process.cwd())\n const origin = options.origin ?? 'https://app.test'\n\n built.set(root, built.get(root) ?? ensureBuilt(root, options.build ?? true))\n\n // The app is built for production, which refuses to sign refreshOn names\n // without a key. One process, one key: a fixed one, unless the test set its own.\n if (!process.env.RSC_SIGNING_SECRET) {\n configureChanged({ secret: 'rsc-kit-test' })\n }\n\n const bundle = await built.get(root)!\n const entry = (await import(pathToFileURL(bundle).href)) as {\n default: (request: Request) => Promise<Response>\n installHostFn?: (fn: (name: string, ...args: unknown[]) => Promise<unknown>) => () => void\n installBackendForward?: (fn: ((request: Request) => Response | Promise<Response>) | null) => void\n }\n\n if (typeof entry.default !== 'function') {\n throw new Error(`[rsc-kit] ${bundle} does not export a request handler.`)\n }\n\n const host = options.host\n ? httpHostCalls({ endpoint: 'http://test-host/__rsc/host-call', secret: 'test', fetch: testHostFetch(options.host) })\n : null\n\n if (host && !entry.installHostFn) {\n throw new Error(`[rsc-kit] ${bundle} cannot take a host. Rebuild against the current @rsc-kit/core.`)\n }\n\n const fetch: TestApp['fetch'] = async (path, init) => {\n const request = new Request(new URL(path, origin), init)\n\n // Per request, not once: the loaded bundle is shared by every app a\n // run creates, and the host is this app's.\n if (host) entry.installHostFn!(host)\n if (options.backend && !entry.installBackendForward) {\n throw new Error(`[rsc-kit] ${bundle} cannot take a backend. Rebuild against the current @rsc-kit/core.`)\n }\n entry.installBackendForward?.(options.backend ?? null)\n\n return staticFile(root, request) ?? entry.default(request)\n }\n\n return {\n bundle,\n fetch,\n markup: async (path, init) => {\n const html = await (await fetch(path, init)).text()\n\n // A script's body never holds its own closing tag: the payload writes\n // \"<\" as \\u003c, and React's scripts are its own.\n return html.replace(/<script\\b[^>]*>[\\s\\S]*?<\\/script>/gi, '')\n },\n watched: async (path, init) => {\n // The render records what each region resolves to while this is set:\n // the app's bundle runs in this process, so it sees the same global.\n const recording: Record<string, string[]> = {}\n const into = globalThis as { [WATCHED]?: Record<string, string[]> }\n\n into[WATCHED] = recording\n\n try {\n // Read to the end: the names resolve under their own Suspense, so\n // they are in the stream's tail, not its head.\n await (await fetch(path, init)).text()\n } finally {\n delete into[WATCHED]\n }\n\n return recording\n },\n }\n}\n\n/** Where a render records what its regions refresh on, while watched() asks (js/refreshOn). */\nconst WATCHED = Symbol.for('rsc-kit.test-watched')\n"]}
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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","sourcesContent":["// The whole app as a function, for tests.\n//\n// import { createTestApp } from '@rsc-kit/core/testing'\n//\n// const app = await createTestApp()\n// const res = await app.fetch('/api/orders', { headers: { Cookie: 'user=ada' } })\n//\n// No port, no browser, no server process. The built entry already exports the\n// same Request → Response handler the dev server and the production server\n// both call, so a test can hand it a Request and read the Response — through\n// the real router, the real middleware, the real api routes, and the pages the\n// build stored.\n//\n// That is the tier between a unit test and a browser. An action or a route is\n// a function and can be imported and called; a browser test proves the page\n// works; this proves the app answers a url the way it is deployed, which is\n// where a route that renders fine and is served wrong shows up.\n\nimport { spawnSync } from 'node:child_process'\nimport { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'\nimport { extname, join, resolve, sep } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { httpHostCalls } from './hostCalls.js'\nimport { configureChanged } from './changed.js'\nimport { testHostFetch, type TestHost } from './testHost.js'\n\nexport { hostReply, testChanges, HOST_MIDDLEWARE, type HostCallInput, type HostHandler, type TestHost } from './testHost.js'\n\nexport interface TestApp {\n /** A path, not a url. The origin is whatever the app was told it is. */\n fetch(path: string, init?: RequestInit): Promise<Response>\n /**\n * What each region of the page at `path` refreshes on, rendered for this\n * request: `{ page: ['ledger'], repos: ['team:1:repos'] }` - `page` for the\n * page's own `refreshOn`, a section's name for each section's. A region\n * whose refreshOn gave no names is there with `[]`; one with no refreshOn\n * is not there at all.\n *\n * expect(await app.watched('/teams/1')).toMatchObject({ repos: ['team:1:repos'] })\n *\n * The names a tab would be handed, without reading them out of the payload.\n */\n watched(path: string, init?: RequestInit): Promise<Record<string, string[]>>\n /**\n * The page at `path` as markup: the whole streamed document, with every\n * `<script>` taken out.\n *\n * The document carries the page's payload for the browser to hydrate from,\n * and the payload holds everything the page was given - a client\n * component's props included, shown or not. A test asserting that something\n * is NOT on the page reads this, not `fetch`:\n *\n * expect(await app.markup('/teams/1')).not.toContain('internal note')\n *\n * Regions that streamed in later are included where they arrived; a\n * Suspense fallback they replaced is still in the markup too, as the\n * server sent it.\n */\n markup(path: string, init?: RequestInit): Promise<string>\n /** Where the build was read from, for a test that wants to look. */\n bundle: string\n}\n\nexport interface TestAppOptions {\n /** The project. Defaults to the working directory. */\n root?: string\n /**\n * Whether to build first.\n *\n * `true` builds when the source is newer than the last build, which is what\n * a test run wants: the first run pays for it, the rest do not, and an edit\n * is picked up. `false` never builds and fails loudly if there is nothing to\n * read — for a ci step that already built.\n */\n build?: boolean\n /** The origin requests are made against. Nothing reads it; it is a url. */\n origin?: string\n /**\n * The backend, answered in the test: rpc() calls and host guards by name.\n * The app reaches it through the same client it uses against a real one,\n * so nothing listens and RSC_BACKEND is not called. See `hostReply`.\n */\n host?: TestHost\n /**\n * What answers a url this app does not own - a Go route, a Laravel page,\n * /login - in place of the backend the app forwards it to. Handed the\n * request as the backend would receive it. Without one, such a url is\n * forwarded to RSC_BACKEND, and in a test that is usually a 502.\n */\n backend?: (request: Request) => Response | Promise<Response>\n}\n\n/**\n * Where the build put the server bundle.\n *\n * Two layouts, because two ways of building. Under Nitro — every scaffolded\n * app — it is inside Nitro's own directory. On its own the plugin writes\n * <outDir>/dist/rsc. The first that exists wins.\n */\nfunction findBundle(root: string): string | null {\n const candidates = [\n join(root, 'node_modules/.nitro/vite/services/rsc/index.js'),\n // The default outDir since 0.29.8; .rsc before it.\n join(root, '.rsc-kit/dist/rsc/index.js'),\n join(root, '.rsc/dist/rsc/index.js'),\n join(root, 'build/dist/rsc/index.js'),\n ]\n\n return candidates.find((path) => existsSync(path)) ?? null\n}\n\n/**\n * When anything a build is made from last changed: the app's source, its\n * Vite config, its dependencies - package.json, the lockfile, and the\n * installed engine itself. Only src/ used to count, so an app that upgraded\n * @rsc-kit/core kept testing the build the old one made, and passed.\n */\nfunction newestInput(root: string): number {\n const files = [\n 'vite.config.ts', 'vite.config.mts', 'vite.config.js', 'vite.config.mjs',\n 'package.json', 'bun.lock', 'bun.lockb', 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock',\n ]\n let latest = newest(join(root, 'src'))\n\n for (const file of files) {\n const path = join(root, file)\n\n if (existsSync(path)) latest = Math.max(latest, statSync(path).mtimeMs)\n }\n\n // The engine as installed: a workspace link changes without the lockfile.\n const engine = join(root, 'node_modules/@rsc-kit/core/dist')\n\n return Math.max(latest, newest(engine))\n}\n\n/** The newest mtime under a directory, for deciding whether a build is stale. */\nfunction newest(dir: string): number {\n if (!existsSync(dir)) return 0\n\n let latest = 0\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue\n\n const path = join(dir, entry.name)\n const time = entry.isDirectory() ? newest(path) : statSync(path).mtimeMs\n\n if (time > latest) latest = time\n }\n\n return latest\n}\n\n/** One build per process per root, however many test files ask. */\nconst built = new Map<string, Promise<string>>()\n\n/** The content types a test is likely to assert on; anything else is octet-stream. */\nconst CONTENT_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.webmanifest': 'application/manifest+json',\n '.xml': 'application/xml',\n '.txt': 'text/plain; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.webp': 'image/webp',\n '.avif': 'image/avif',\n '.gif': 'image/gif',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.wasm': 'application/wasm',\n '.map': 'application/json',\n}\n\n/**\n * A file the build put in .output/public, answered the way Nitro's static\n * layer would - which the handler this harness loads does not include. The\n * handler is the rsc service: routing, pages, actions, api routes. Assets,\n * the service worker, the manifest and the icons are files Nitro serves in\n * production from .output/public, and a test that asked for /sw.js used to\n * get the router's 404 for a file the deployment serves fine - the one\n * shape this harness exists to catch, in the other direction.\n *\n * Files only, for GET and HEAD, within the directory: a path that escapes it\n * is a request for the handler, not a file.\n */\n/** @internal Exported for its test. */\nexport function staticFile(root: string, request: Request): Response | null {\n if (request.method !== 'GET' && request.method !== 'HEAD') return null\n\n const publicDir = join(root, '.output/public')\n const pathname = decodeURIComponent(new URL(request.url).pathname)\n const file = resolve(publicDir, '.' + pathname)\n\n if (!file.startsWith(publicDir + sep) || !existsSync(file) || !statSync(file).isFile()) return null\n\n const type = CONTENT_TYPES[extname(file).toLowerCase()] ?? 'application/octet-stream'\n const body = request.method === 'HEAD' ? null : readFileSync(file)\n\n return new Response(body, {\n status: 200,\n headers: { 'Content-Type': type, 'Cache-Control': 'no-store', 'X-RSC-Kit-Test': 'static' },\n })\n}\n\n/**\n * How this project builds: its own `build` script, run by the package manager\n * this test is running under. Without a script, Vite directly - on Bun's\n * runtime when the tests are, since a bin's node shebang would otherwise\n * start it under Node.\n */\n/** @internal Exported for its test. */\nexport function buildCommand(root: string): [string, string[]] {\n const onBun = typeof process.versions.bun === 'string'\n\n let scripts: Record<string, string> = {}\n\n try {\n scripts = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8')).scripts ?? {}\n } catch {\n // No package.json, or not JSON: there is no script to run.\n }\n\n if (typeof scripts.build === 'string') {\n return onBun ? ['bun', ['run', 'build']] : ['npm', ['run', 'build']]\n }\n\n return onBun ? ['bun', ['--bun', 'vite', 'build']] : ['npx', ['vite', 'build']]\n}\n\n/**\n * Whether the last build can be tested as it is: newer than everything it is\n * made from, and whole. A Nitro build is two places - the server bundle under\n * node_modules/.nitro, and .output, which holds the browser's assets and what\n * staticFile() answers from. Deleting .output (a clean, a deploy step, a\n * `git clean`) left the bundle newer than the source, so the check passed and\n * every asset a test asked for was a 404, far from the cause.\n */\nexport function buildIsFresh(root: string, bundle: string): boolean {\n if (bundle.replaceAll('\\\\', '/').includes('/node_modules/.nitro/') && !existsSync(join(root, '.output/public'))) return false\n\n return statSync(bundle).mtimeMs > newestInput(root)\n}\n\nasync function ensureBuilt(root: string, build: boolean): Promise<string> {\n const existing = findBundle(root)\n\n if (!build) {\n if (!existing) {\n throw new Error(\n `[rsc-kit] No build to test against under ${root}. Run the build first, or let createTestApp() do it by leaving \\`build\\` on.`,\n )\n }\n\n return existing\n }\n\n if (existing && buildIsFresh(root, existing)) return existing\n\n // The user's own build command, so what is tested is what ships. A test\n // that built some other way would pass against a bundle nobody deploys -\n // and used to: this ran `npx vite build` whatever package.json said, which\n // on a Bun project built under Node and failed on the first `import 'bun'`.\n const [command, args] = buildCommand(root)\n const run = spawnSync(command, args, {\n cwd: root,\n stdio: ['ignore', 'pipe', 'pipe'],\n env: { ...process.env, NODE_ENV: 'production' },\n })\n\n if (run.status !== 0) {\n throw new Error(`[rsc-kit] The build failed, so there is nothing to test:\\n${run.stderr}`)\n }\n\n const bundle = findBundle(root)\n\n if (!bundle) {\n throw new Error(\n `[rsc-kit] The build finished but no server bundle was found under ${root}. Is rscKit() in vite.config?`,\n )\n }\n\n return bundle\n}\n\n/**\n * Build the app if it needs it, load it, and hand back something to fetch from.\n *\n * Memoised per root: every test file in a run shares one build and one loaded\n * module, which is both the fast path and the correct one — two copies of the\n * server bundle in one process would be two client-reference registries.\n */\n/**\n * Whether Request is the runtime's, told apart from a browser's by behaviour.\n *\n * A browser forbids a script from setting Cookie, so happy-dom's Request drops\n * it from an init - and every signed-in request a test makes arrives with no\n * session, bounces to sign-in, and fails far from the cause. Behaviour rather\n * than identity, because identity depends on which file loaded first.\n */\nfunction runtimeRequest(): boolean {\n try {\n return new Request('http://probe.test/', { headers: { cookie: 'probe=1' } }).headers.has('cookie')\n } catch {\n return false\n }\n}\n\nexport async function createTestApp(options: TestAppOptions = {}): Promise<TestApp> {\n if (!runtimeRequest()) {\n throw new Error(\n \"[rsc-kit] createTestApp is running against a browser's Request: a DOM is registered globally \" +\n '(happy-dom, in a preload or an earlier test file), and it replaces Request, Response, Headers and fetch. ' +\n 'A browser cannot set Cookie, so every signed-in request would arrive with no session. ' +\n 'Register the DOM only in the component test file and `GlobalRegistrator.unregister()` after it, ' +\n 'and run the suites isolated (`bun test --isolate`). See the testing guide, \"Components\".',\n )\n }\n\n const root = resolve(options.root ?? process.cwd())\n const origin = options.origin ?? 'https://app.test'\n\n built.set(root, built.get(root) ?? ensureBuilt(root, options.build ?? true))\n\n // The app is built for production, which refuses to sign refreshOn names\n // without a key. One process, one key: a fixed one, unless the test set its own.\n if (!process.env.RSC_SIGNING_SECRET) {\n configureChanged({ secret: 'rsc-kit-test' })\n }\n\n const bundle = await built.get(root)!\n const entry = (await import(pathToFileURL(bundle).href)) as {\n default: (request: Request) => Promise<Response>\n installHostFn?: (fn: (name: string, ...args: unknown[]) => Promise<unknown>) => () => void\n installBackendForward?: (fn: ((request: Request) => Response | Promise<Response>) | null) => void\n }\n\n if (typeof entry.default !== 'function') {\n throw new Error(`[rsc-kit] ${bundle} does not export a request handler.`)\n }\n\n const host = options.host\n ? httpHostCalls({ endpoint: 'http://test-host/__rsc/host-call', secret: 'test', fetch: testHostFetch(options.host) })\n : null\n\n if (host && !entry.installHostFn) {\n throw new Error(`[rsc-kit] ${bundle} cannot take a host. Rebuild against the current @rsc-kit/core.`)\n }\n\n const fetch: TestApp['fetch'] = async (path, init) => {\n const request = new Request(new URL(path, origin), init)\n\n // Per request, not once: the loaded bundle is shared by every app a\n // run creates, and the host is this app's.\n if (host) entry.installHostFn!(host)\n if (options.backend && !entry.installBackendForward) {\n throw new Error(`[rsc-kit] ${bundle} cannot take a backend. Rebuild against the current @rsc-kit/core.`)\n }\n entry.installBackendForward?.(options.backend ?? null)\n\n return staticFile(root, request) ?? entry.default(request)\n }\n\n return {\n bundle,\n fetch,\n markup: async (path, init) => {\n const html = await (await fetch(path, init)).text()\n\n // A script's body never holds its own closing tag: the payload writes\n // \"<\" as \\u003c, and React's scripts are its own.\n return html.replace(/<script\\b[^>]*>[\\s\\S]*?<\\/script>/gi, '')\n },\n watched: async (path, init) => {\n // The render records what each region resolves to while this is set:\n // the app's bundle runs in this process, so it sees the same global.\n const recording: Record<string, string[]> = {}\n const into = globalThis as { [WATCHED]?: Record<string, string[]> }\n\n into[WATCHED] = recording\n\n try {\n // Read to the end: the names resolve under their own Suspense, so\n // they are in the stream's tail, not its head.\n await (await fetch(path, init)).text()\n } finally {\n delete into[WATCHED]\n }\n\n return recording\n },\n }\n}\n\n/** Where a render records what its regions refresh on, while watched() asks (js/refreshOn). */\nconst WATCHED = Symbol.for('rsc-kit.test-watched')\n"]}
|
package/dist/vite.d.ts
CHANGED
|
@@ -138,6 +138,14 @@ export interface RscKitOptions {
|
|
|
138
138
|
* for an app that uploads larger files through actions.
|
|
139
139
|
*/
|
|
140
140
|
maxActionBody?: number;
|
|
141
|
+
/**
|
|
142
|
+
* Other sites allowed to post to this app: `['https://admin.example.com']`.
|
|
143
|
+
*
|
|
144
|
+
* An action, a query, a form, or a POST, PUT, PATCH or DELETE to a
|
|
145
|
+
* route.ts, sent by a browser from any other origin, is answered 403. A
|
|
146
|
+
* caller that is not a browser - a webhook, a server - is never refused.
|
|
147
|
+
*/
|
|
148
|
+
trustedOrigins?: string[];
|
|
141
149
|
/** Where the server bundles and generated entries go. Defaults to `.rsc`. */
|
|
142
150
|
outDir?: string;
|
|
143
151
|
/**
|
|
@@ -499,6 +507,25 @@ export declare function compileStep(root: string): {
|
|
|
499
507
|
* the marker names builds twice.
|
|
500
508
|
*/
|
|
501
509
|
export declare function compileRebuilds(root: string): boolean;
|
|
510
|
+
/**
|
|
511
|
+
* Which urls exist for a parameterised route.
|
|
512
|
+
*
|
|
513
|
+
* The one thing about a route the build cannot work out for itself: only the
|
|
514
|
+
* app knows its slugs. Everything else about whether a page can be frozen is
|
|
515
|
+
* observed by rendering it — a page that suspends past the shell budget, or
|
|
516
|
+
* reaches for the host, says so by doing it. This is asked rather than
|
|
517
|
+
* inferred because there is nothing to infer it from.
|
|
518
|
+
*/
|
|
519
|
+
/**
|
|
520
|
+
* How long a shared cache may keep a page: the smallest
|
|
521
|
+
* `export const revalidate` across its layouts and the page. Read from the
|
|
522
|
+
* source, as generateStaticParams is. A number literal only - a value the
|
|
523
|
+
* build cannot read is refused rather than taken as "no cache", which would
|
|
524
|
+
* look like a page that was kept and was not.
|
|
525
|
+
*/
|
|
526
|
+
export declare function revalidateOf(files: string[]): {
|
|
527
|
+
revalidate?: number;
|
|
528
|
+
};
|
|
502
529
|
/**
|
|
503
530
|
* The app's process bootstrap, if it has one: `instrumentation.ts` beside
|
|
504
531
|
* `app/`, the name Next uses so a reader arriving from there knows what it
|
package/dist/vite.js
CHANGED
|
@@ -207,6 +207,7 @@ function clientChunkFileName(chunk) {
|
|
|
207
207
|
return "assets/[name]-[hash].js";
|
|
208
208
|
}
|
|
209
209
|
let maxActionBody;
|
|
210
|
+
let trustedOrigins;
|
|
210
211
|
let outDir;
|
|
211
212
|
let appDir;
|
|
212
213
|
let genDir;
|
|
@@ -562,6 +563,7 @@ function resolvePaths(options) {
|
|
|
562
563
|
: null;
|
|
563
564
|
inlineStylesheets = options.inlineStylesheets ?? "auto";
|
|
564
565
|
maxActionBody = options.maxActionBody;
|
|
566
|
+
trustedOrigins = options.trustedOrigins;
|
|
565
567
|
// One place, and it is the file. A plugin option as well would be the same
|
|
566
568
|
// thing sayable in two places, which is the problem the file was moved to
|
|
567
569
|
// solve rather than a convenience to keep beside it.
|
|
@@ -862,6 +864,7 @@ function routeManifest() {
|
|
|
862
864
|
hostMiddleware: hostMiddleware(dirOf(name)),
|
|
863
865
|
ancestorConfigs: ancestorConfigs(dirOf(name)),
|
|
864
866
|
staticParams: hasStaticParams(components.get(name).absPath),
|
|
867
|
+
...revalidateOf([...layouts, name].map((n) => components.get(n).absPath)),
|
|
865
868
|
});
|
|
866
869
|
}
|
|
867
870
|
// What the build decided, for a host that has to act on it afterwards —
|
|
@@ -1250,10 +1253,20 @@ const mayStore = (response) =>
|
|
|
1250
1253
|
|
|
1251
1254
|
// What a response is filed under. A payload request carries headers the url
|
|
1252
1255
|
// does not, so the url is where they have to go.
|
|
1256
|
+
// The page's url without the key a payload request carries (payloadKey.ts):
|
|
1257
|
+
// the key is for caches that ignore Vary, and this one files by header below.
|
|
1258
|
+
const plainUrl = (href) => {
|
|
1259
|
+
const url = new URL(href)
|
|
1260
|
+
|
|
1261
|
+
url.searchParams.delete('_rsc')
|
|
1262
|
+
|
|
1263
|
+
return url.href
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1253
1266
|
const keyFor = (request) => {
|
|
1254
1267
|
if (!request.headers.get('X-RSC')) return request
|
|
1255
1268
|
|
|
1256
|
-
const url = new URL(request.url)
|
|
1269
|
+
const url = new URL(plainUrl(request.url))
|
|
1257
1270
|
url.searchParams.set('__rsc', request.headers.get('X-RSC-Segments') || '')
|
|
1258
1271
|
|
|
1259
1272
|
return new Request(url, { headers: request.headers })
|
|
@@ -1388,10 +1401,11 @@ self.addEventListener('fetch', (event) => {
|
|
|
1388
1401
|
// Once per url: the document is fetched only when the cache has none,
|
|
1389
1402
|
// so this costs one extra request the first time a page is visited
|
|
1390
1403
|
// rather than one on every navigation to it.
|
|
1391
|
-
if (request.headers.get('X-RSC') && !documentsInFlight.has(request.url)) {
|
|
1392
|
-
const
|
|
1404
|
+
if (request.headers.get('X-RSC') && !documentsInFlight.has(plainUrl(request.url))) {
|
|
1405
|
+
const page = plainUrl(request.url)
|
|
1406
|
+
const document = new Request(page)
|
|
1393
1407
|
|
|
1394
|
-
documentsInFlight.add(
|
|
1408
|
+
documentsInFlight.add(page)
|
|
1395
1409
|
caches
|
|
1396
1410
|
.open(CACHE)
|
|
1397
1411
|
.then(async (cache) => {
|
|
@@ -1401,7 +1415,7 @@ self.addEventListener('fetch', (event) => {
|
|
|
1401
1415
|
|
|
1402
1416
|
if (fresh && mayStore(fresh)) await cache.put(document, fresh)
|
|
1403
1417
|
})
|
|
1404
|
-
.finally(() => documentsInFlight.delete(
|
|
1418
|
+
.finally(() => documentsInFlight.delete(page))
|
|
1405
1419
|
}
|
|
1406
1420
|
}
|
|
1407
1421
|
|
|
@@ -2975,12 +2989,37 @@ function rootMetadataBase(appDir) {
|
|
|
2975
2989
|
const match = /metadataBase\s*:\s*(?:new\s+URL\(\s*)?["'`](https?:\/\/[^"'`]+)["'`]/.exec(readFileSync(layout, "utf-8"));
|
|
2976
2990
|
return match ? match[1] : null;
|
|
2977
2991
|
}
|
|
2992
|
+
/**
|
|
2993
|
+
* Whether a route file exports this name, in any of the ways TypeScript
|
|
2994
|
+
* spells it: a function, a const - which is how the metadata guide writes
|
|
2995
|
+
* `generateMetadata: GenerateMetadata` - or a list, `export { x as name }` or
|
|
2996
|
+
* `export { name } from`. Only the function form was read once, and a page
|
|
2997
|
+
* exporting the guide's own const was titled by its layout, silently.
|
|
2998
|
+
*/
|
|
2999
|
+
function exportsName(src, name) {
|
|
3000
|
+
if (new RegExp(`export\\s+(?:async\\s+)?function\\s*\\*?\\s*${name}\\b`).test(src))
|
|
3001
|
+
return true;
|
|
3002
|
+
if (new RegExp(`export\\s+(?:const|let|var)\\s+${name}\\b`).test(src))
|
|
3003
|
+
return true;
|
|
3004
|
+
// A list exports the name after `as`, or the name itself; `export type {}`
|
|
3005
|
+
// and a `type` entry export no value.
|
|
3006
|
+
for (const [list, names] of Array.from(src.matchAll(/export\s+(type\s+)?\{([^}]*)\}/g), (m) => [m[1], m[2]])) {
|
|
3007
|
+
if (list)
|
|
3008
|
+
continue;
|
|
3009
|
+
for (const raw of names.split(",")) {
|
|
3010
|
+
const entry = raw.trim();
|
|
3011
|
+
if (!entry.startsWith("type ") && entry.split(/\s+as\s+/).pop() === name)
|
|
3012
|
+
return true;
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
return false;
|
|
3016
|
+
}
|
|
2978
3017
|
function metadataExports(absPath) {
|
|
2979
3018
|
const src = readFileSync(absPath, "utf-8");
|
|
2980
3019
|
return {
|
|
2981
|
-
static:
|
|
2982
|
-
generate:
|
|
2983
|
-
viewport:
|
|
3020
|
+
static: exportsName(src, "metadata"),
|
|
3021
|
+
generate: exportsName(src, "generateMetadata"),
|
|
3022
|
+
viewport: exportsName(src, "viewport"),
|
|
2984
3023
|
writes: {
|
|
2985
3024
|
charset: /\bcharSet\s*=/.test(src),
|
|
2986
3025
|
viewport: /name\s*=\s*["']viewport["']/.test(src),
|
|
@@ -2996,6 +3035,28 @@ function metadataExports(absPath) {
|
|
|
2996
3035
|
* reaches for the host, says so by doing it. This is asked rather than
|
|
2997
3036
|
* inferred because there is nothing to infer it from.
|
|
2998
3037
|
*/
|
|
3038
|
+
/**
|
|
3039
|
+
* How long a shared cache may keep a page: the smallest
|
|
3040
|
+
* `export const revalidate` across its layouts and the page. Read from the
|
|
3041
|
+
* source, as generateStaticParams is. A number literal only - a value the
|
|
3042
|
+
* build cannot read is refused rather than taken as "no cache", which would
|
|
3043
|
+
* look like a page that was kept and was not.
|
|
3044
|
+
*/
|
|
3045
|
+
export function revalidateOf(files) {
|
|
3046
|
+
let smallest;
|
|
3047
|
+
for (const file of files) {
|
|
3048
|
+
const src = readFileSync(file, "utf-8");
|
|
3049
|
+
const declared = /export\s+const\s+revalidate\s*(?::[^=]+)?=\s*([^;\n]+)/.exec(src);
|
|
3050
|
+
if (!declared)
|
|
3051
|
+
continue;
|
|
3052
|
+
const value = declared[1].trim().replace(/_/g, "");
|
|
3053
|
+
if (!/^\d+$/.test(value)) {
|
|
3054
|
+
throw new Error(`${file}: export const revalidate must be a number of seconds the build can read, like 60. Found: ${declared[1].trim()}`);
|
|
3055
|
+
}
|
|
3056
|
+
smallest = Math.min(smallest ?? Infinity, Number(value));
|
|
3057
|
+
}
|
|
3058
|
+
return smallest ? { revalidate: smallest } : {};
|
|
3059
|
+
}
|
|
2999
3060
|
function hasStaticParams(absPath) {
|
|
3000
3061
|
const src = readFileSync(absPath, "utf-8");
|
|
3001
3062
|
return /export\s+((async\s+)?function\s+generateStaticParams|const\s+generateStaticParams)/.test(src);
|
|
@@ -3404,12 +3465,14 @@ import { isSharedSection, sectionComponent } from ${JSON.stringify(join(packageD
|
|
|
3404
3465
|
import { shareRender } from ${JSON.stringify(join(packageDir, "sharedRenders"))}
|
|
3405
3466
|
import { PathnameProvider } from ${JSON.stringify(join(packageDir, "js/PathnameProvider"))}
|
|
3406
3467
|
import { DefaultRouteError } from ${JSON.stringify(join(packageDir, "js/DefaultRouteError"))}
|
|
3407
|
-
import { requestFromCrawler, requestSearch, searchParams as requestSearchParams, withRequest, withUrl } from ${JSON.stringify(join(packageDir, "request"))}
|
|
3468
|
+
import { noteNotFoundRow, noteRenderFailure, renderAsNobody, requestFromCrawler, requestSearch, searchParams as requestSearchParams, withRequest, withUrl } from ${JSON.stringify(join(packageDir, "request"))}
|
|
3408
3469
|
import { parseParams, parseSearchParams, parseBody, isSearchParamsError, isBodyError } from ${JSON.stringify(join(packageDir, "routeSchema"))}
|
|
3409
3470
|
import { notFoundDigest, isNotFoundSignal } from ${JSON.stringify(join(packageDir, "notFound"))}
|
|
3410
3471
|
import { noteRequestRead, notePageProps, urlOf } from ${JSON.stringify(join(packageDir, "request"))}
|
|
3411
3472
|
import { redirectDigest } from ${JSON.stringify(join(packageDir, "redirectDigest"))}
|
|
3412
3473
|
import { cancelledByConsumer } from ${JSON.stringify(join(packageDir, "js/fallbackReport"))}
|
|
3474
|
+
import { withoutPayloadKey } from ${JSON.stringify(join(packageDir, "js/payloadKey"))}
|
|
3475
|
+
import { cachingOf, finishCacheable } from ${JSON.stringify(join(packageDir, "pageCache"))}
|
|
3413
3476
|
import { createRscHandler, notFoundRouteOf, pageSaidNotFound, queryAndParams } from ${JSON.stringify(join(packageDir, "host"))}
|
|
3414
3477
|
import { httpHostCalls } from ${JSON.stringify(join(packageDir, "hostCalls"))}
|
|
3415
3478
|
import { backendVersions, installBackendVersionSource } from ${JSON.stringify(join(packageDir, "changed"))}
|
|
@@ -4938,8 +5001,15 @@ export async function handleRscStream(
|
|
|
4938
5001
|
function flightOnError(error: unknown): string | undefined {
|
|
4939
5002
|
const digest = redirectDigest(error) ?? notFoundDigest(error)
|
|
4940
5003
|
|
|
5004
|
+
// A page kept for everyone that turned out to be not-found.tsx is kept as a 404 is.
|
|
5005
|
+
if (digest && notFoundDigest(error)) noteNotFoundRow()
|
|
5006
|
+
|
|
4941
5007
|
if (digest) return digest
|
|
4942
5008
|
|
|
5009
|
+
// In the payload as a failed row, which a page kept for everyone must not
|
|
5010
|
+
// be: the browser throws on it and shows a blank page.
|
|
5011
|
+
noteRenderFailure()
|
|
5012
|
+
|
|
4943
5013
|
// The consumer cancelled: a browser that left mid-stream, a dev reload
|
|
4944
5014
|
// that dropped the page being rendered. The HTML renderer has always kept
|
|
4945
5015
|
// quiet about these; the payload renderer printed each one as an error.
|
|
@@ -6147,6 +6217,7 @@ async function serve(request: Request): Promise<Response> {
|
|
|
6147
6217
|
criticalAssets,
|
|
6148
6218
|
} as never,
|
|
6149
6219
|
${NITRO_HANDLER_OPTIONS}${NITRO_PRERENDERED} maxActionBody: ${maxActionBody === undefined ? "undefined" : String(maxActionBody)},
|
|
6220
|
+
trustedOrigins: ${JSON.stringify(trustedOrigins ?? [])},
|
|
6150
6221
|
})
|
|
6151
6222
|
|
|
6152
6223
|
${fallbackOrigin === false ? NO_FALLBACK_BODY : FALLBACK_BODY}}
|
|
@@ -6177,7 +6248,19 @@ async function notFound(request: Request): Promise<Response> {
|
|
|
6177
6248
|
return new Response('Not found', { status: 404, headers: { 'Content-Type': 'text/plain; charset=utf-8' } })
|
|
6178
6249
|
}
|
|
6179
6250
|
|
|
6180
|
-
|
|
6251
|
+
// As the app saw it: the payload key is for caches, never for a page's search params.
|
|
6252
|
+
const seen = withoutPayloadKey(request)
|
|
6253
|
+
// A page that exports revalidate said notFound(): its 404 is judged the
|
|
6254
|
+
// same way, with what the page read before it said so.
|
|
6255
|
+
const caching = cachingOf(seen)
|
|
6256
|
+
|
|
6257
|
+
return await withRequest(seen, async () => {
|
|
6258
|
+
if (!caching) return await renderNotFound(seen)
|
|
6259
|
+
|
|
6260
|
+
renderAsNobody()
|
|
6261
|
+
|
|
6262
|
+
return await finishCacheable(seen, await renderNotFound(seen), caching, () => true)
|
|
6263
|
+
})
|
|
6181
6264
|
}
|
|
6182
6265
|
|
|
6183
6266
|
/**
|
|
@@ -6299,7 +6382,7 @@ import { parseRedirectDigest } from ${JSON.stringify(redirectDigestModule)}
|
|
|
6299
6382
|
import { isNotFoundDigest } from ${JSON.stringify(notFoundModule)}
|
|
6300
6383
|
import { EARLY_CLICKS } from ${JSON.stringify(earlyClicks)}
|
|
6301
6384
|
import { searchSnapshotContext } from ${JSON.stringify(join(packageDir, "js/useSearchParams"))}
|
|
6302
|
-
import { createElement as h } from 'react'
|
|
6385
|
+
import { createElement as h, use } from 'react'
|
|
6303
6386
|
${webManifestOptions ? `import { INSTALL_CAPTURE } from ${JSON.stringify(installCapture)}` : ""}
|
|
6304
6387
|
|
|
6305
6388
|
// What runs ahead of the runtime: held taps, and - in an app that can be
|
|
@@ -6311,6 +6394,28 @@ const EARLY = EARLY_CLICKS${webManifestOptions ? " + INSTALL_CAPTURE" : ""}
|
|
|
6311
6394
|
// modules only Vite can answer — see devUrls.ts.
|
|
6312
6395
|
const DEV_ORIGIN = ${JSON.stringify(devOrigin)}
|
|
6313
6396
|
|
|
6397
|
+
/**
|
|
6398
|
+
* The payload, decoded by the render it is the tree of.
|
|
6399
|
+
*
|
|
6400
|
+
* Not awaited before rendering, as it once was: a client component's chunk is
|
|
6401
|
+
* loaded the first time a payload names it, and @vitejs/plugin-rsc asks for
|
|
6402
|
+
* its modulepreload as it loads - through React DOM, which files the hint
|
|
6403
|
+
* under the render the call is made inside. Decoded ahead of the render, the
|
|
6404
|
+
* first page after a start was inside none, and named 3 of its 12 chunks; so
|
|
6405
|
+
* did the first page a build stored. Decoded here, the decoding runs within
|
|
6406
|
+
* the render, as the plugin's own starter does it.
|
|
6407
|
+
*
|
|
6408
|
+
* One component for the first render, the prerender and the resume: a shell
|
|
6409
|
+
* and its holes must be the same tree.
|
|
6410
|
+
*/
|
|
6411
|
+
function Decoded({ from }: { from: { stream: ReadableStream; decoded?: Promise<unknown> } }) {
|
|
6412
|
+
return use((from.decoded ??= createFromReadableStream(from.stream)) as Promise<any>)
|
|
6413
|
+
}
|
|
6414
|
+
|
|
6415
|
+
function decodedBy(stream: ReadableStream): unknown {
|
|
6416
|
+
return h(Decoded, { from: { stream } })
|
|
6417
|
+
}
|
|
6418
|
+
|
|
6314
6419
|
/**
|
|
6315
6420
|
* The tree with the query it is rendered for: the request's, so a client
|
|
6316
6421
|
* component reading useSearchParams renders on the server like any other -
|
|
@@ -6337,7 +6442,7 @@ export async function handleSsr(
|
|
|
6337
6442
|
// Declared here so the runtime knows before its first chunk arrives.
|
|
6338
6443
|
inline = false,
|
|
6339
6444
|
): Promise<ReadableStream> {
|
|
6340
|
-
const root = withSearch(
|
|
6445
|
+
const root = withSearch(decodedBy(rscStream), search)
|
|
6341
6446
|
|
|
6342
6447
|
// Without the bootstrap the page ships no client runtime at all: no React,
|
|
6343
6448
|
// no Flight client, no router. HTML only. A page with nothing interactive on
|
|
@@ -6387,7 +6492,7 @@ export async function handleSsrPrerender(
|
|
|
6387
6492
|
options: { nonce?: string; bootstrap?: boolean; signal?: AbortSignal } = {},
|
|
6388
6493
|
): Promise<{ prelude: ReadableStream; postponed: unknown }> {
|
|
6389
6494
|
// Null: a prerender is stored.
|
|
6390
|
-
const root = withSearch(
|
|
6495
|
+
const root = withSearch(decodedBy(rscStream), null)
|
|
6391
6496
|
|
|
6392
6497
|
const bootstrapScriptContent =
|
|
6393
6498
|
options.bootstrap === false
|
|
@@ -6500,7 +6605,7 @@ export async function handleSsrResume(
|
|
|
6500
6605
|
): Promise<ReadableStream> {
|
|
6501
6606
|
// The same tree the shell was prerendered with, now holding this
|
|
6502
6607
|
// request's query: the holes are filled per request.
|
|
6503
|
-
const root = withSearch(
|
|
6608
|
+
const root = withSearch(decodedBy(rscStream), search)
|
|
6504
6609
|
|
|
6505
6610
|
const html = await resume(root as any, postponed as any, {
|
|
6506
6611
|
nonce,
|
|
@@ -7223,6 +7328,7 @@ export const SERVER_ONLY = {
|
|
|
7223
7328
|
request: "request",
|
|
7224
7329
|
redirect: "redirect",
|
|
7225
7330
|
revalidate: "revalidate",
|
|
7331
|
+
flash: "flash",
|
|
7226
7332
|
cache: "cache",
|
|
7227
7333
|
"js/section": "section",
|
|
7228
7334
|
"js/refreshOn": "section",
|