@rshono/core 1.0.0-rc.2 → 1.0.0-rc.21
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/LICENSE +21 -0
- package/README.md +348 -158
- package/bin/rshono.mjs +3 -4
- package/dist/builder/env-shadow-loader.cjs +123 -8
- package/dist/builder/page-entry-loader.cjs +28 -1
- package/dist/builder/page-files.d.ts +0 -1
- package/dist/builder/page-files.js +7 -3
- package/dist/builder/page-files.js.map +1 -1
- package/dist/builder/public-env.d.ts +7 -1
- package/dist/builder/public-env.js +7 -0
- package/dist/builder/public-env.js.map +1 -1
- package/dist/builder/react-versions.d.ts +12 -0
- package/dist/builder/react-versions.js +74 -0
- package/dist/builder/react-versions.js.map +1 -0
- package/dist/builder/rspack-config.d.ts +13 -4
- package/dist/builder/rspack-config.js +136 -53
- package/dist/builder/rspack-config.js.map +1 -1
- package/dist/builder/server-only-imports.d.ts +32 -0
- package/dist/builder/server-only-imports.js +51 -0
- package/dist/builder/server-only-imports.js.map +1 -0
- package/dist/cli/build.d.ts +2 -3
- package/dist/cli/build.js +51 -13
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/dev.d.ts +2 -3
- package/dist/cli/dev.js +149 -45
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/exit.d.ts +12 -0
- package/dist/cli/exit.js +16 -0
- package/dist/cli/exit.js.map +1 -0
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +91 -30
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/start.d.ts +0 -1
- package/dist/cli/start.js +15 -18
- package/dist/cli/start.js.map +1 -1
- package/dist/config.d.ts +72 -78
- package/dist/config.js +17 -1
- package/dist/config.js.map +1 -1
- package/dist/deploy/aws-lambda/runtime.d.ts +4 -7
- package/dist/deploy/aws-lambda/runtime.js +5 -8
- package/dist/deploy/aws-lambda/runtime.js.map +1 -1
- package/dist/deploy/build-marker.d.ts +9 -6
- package/dist/deploy/build-marker.js +8 -4
- package/dist/deploy/build-marker.js.map +1 -1
- package/dist/deploy/cloudflare/build.d.ts +0 -1
- package/dist/deploy/cloudflare/build.js +17 -13
- package/dist/deploy/cloudflare/build.js.map +1 -1
- package/dist/deploy/cloudflare/runtime.d.ts +2 -6
- package/dist/deploy/cloudflare/runtime.js +73 -68
- package/dist/deploy/cloudflare/runtime.js.map +1 -1
- package/dist/deploy/contract.d.ts +57 -41
- package/dist/deploy/contract.js.map +1 -1
- package/dist/deploy/filesystem.d.ts +3 -6
- package/dist/deploy/filesystem.js +23 -20
- package/dist/deploy/filesystem.js.map +1 -1
- package/dist/deploy/node/runtime.d.ts +5 -3
- package/dist/deploy/node/runtime.js +23 -9
- package/dist/deploy/node/runtime.js.map +1 -1
- package/dist/deploy/presets.d.ts +34 -30
- package/dist/deploy/presets.js +56 -44
- package/dist/deploy/presets.js.map +1 -1
- package/dist/deploy/public-paths.d.ts +42 -0
- package/dist/deploy/public-paths.js +66 -0
- package/dist/deploy/public-paths.js.map +1 -0
- package/dist/deploy/vercel/build.d.ts +0 -1
- package/dist/deploy/vercel/build.js +25 -16
- package/dist/deploy/vercel/build.js.map +1 -1
- package/dist/deploy/vercel/runtime.d.ts +10 -7
- package/dist/deploy/vercel/runtime.js +48 -11
- package/dist/deploy/vercel/runtime.js.map +1 -1
- package/dist/index.d.ts +19 -17
- package/dist/index.js +20 -10
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts +212 -120
- package/dist/router.js +3 -8
- package/dist/router.js.map +1 -1
- package/dist/runtime/boundaries.d.ts +62 -48
- package/dist/runtime/boundaries.js +32 -34
- package/dist/runtime/boundaries.js.map +1 -1
- package/dist/runtime/client.d.ts +20 -11
- package/dist/runtime/client.js +20 -10
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/context.d.ts +304 -123
- package/dist/runtime/context.js +377 -149
- package/dist/runtime/context.js.map +1 -1
- package/dist/runtime/control.d.ts +15 -1
- package/dist/runtime/control.js +24 -0
- package/dist/runtime/control.js.map +1 -1
- package/dist/runtime/dev-protocol.d.ts +4 -9
- package/dist/runtime/dev-protocol.js.map +1 -1
- package/dist/runtime/empty-server-app.d.ts +0 -1
- package/dist/runtime/entry.client.d.ts +4 -1
- package/dist/runtime/entry.client.js +455 -313
- package/dist/runtime/entry.client.js.map +1 -1
- package/dist/runtime/entry.rsc.d.ts +15 -7
- package/dist/runtime/entry.rsc.js +811 -252
- package/dist/runtime/entry.rsc.js.map +1 -1
- package/dist/runtime/entry.ssr.d.ts +27 -10
- package/dist/runtime/entry.ssr.js +30 -48
- package/dist/runtime/entry.ssr.js.map +1 -1
- package/dist/runtime/failure-document.d.ts +11 -0
- package/dist/runtime/failure-document.js +35 -0
- package/dist/runtime/failure-document.js.map +1 -0
- package/dist/runtime/flight-inject.d.ts +23 -0
- package/dist/runtime/flight-inject.js +403 -0
- package/dist/runtime/flight-inject.js.map +1 -0
- package/dist/runtime/hot-update.d.ts +44 -0
- package/dist/runtime/hot-update.js +44 -0
- package/dist/runtime/hot-update.js.map +1 -0
- package/dist/runtime/navigation.d.ts +46 -54
- package/dist/runtime/navigation.js +22 -63
- package/dist/runtime/navigation.js.map +1 -1
- package/dist/runtime/request.d.ts +42 -8
- package/dist/runtime/request.js +84 -7
- package/dist/runtime/request.js.map +1 -1
- package/dist/runtime/server.d.ts +20 -18
- package/dist/runtime/server.js +22 -17
- package/dist/runtime/server.js.map +1 -1
- package/dist/runtime/validate-entries.d.ts +77 -0
- package/dist/runtime/validate-entries.js +405 -0
- package/dist/runtime/validate-entries.js.map +1 -0
- package/dist/server/headers.d.ts +41 -15
- package/dist/server/headers.js +53 -24
- package/dist/server/headers.js.map +1 -1
- package/dist/server/load-config.d.ts +4 -5
- package/dist/server/load-config.js +22 -14
- package/dist/server/load-config.js.map +1 -1
- package/dist/server/load-env.d.ts +0 -1
- package/dist/server/prerendered.d.ts +88 -34
- package/dist/server/prerendered.js +184 -24
- package/dist/server/prerendered.js.map +1 -1
- package/dist/server/server-config.d.ts +35 -50
- package/dist/server/server-config.js +27 -74
- package/dist/server/server-config.js.map +1 -1
- package/dist/server/shutdown.d.ts +5 -4
- package/dist/server/shutdown.js +5 -3
- package/dist/server/shutdown.js.map +1 -1
- package/dist/server/ssg.d.ts +9 -7
- package/dist/server/ssg.js +209 -65
- package/dist/server/ssg.js.map +1 -1
- package/dist/server/static.d.ts +0 -1
- package/dist/server/static.js +7 -1
- package/dist/server/static.js.map +1 -1
- package/package.json +21 -23
- package/dist/builder/page-files.d.ts.map +0 -1
- package/dist/builder/public-env.d.ts.map +0 -1
- package/dist/builder/rspack-config.d.ts.map +0 -1
- package/dist/cli/build.d.ts.map +0 -1
- package/dist/cli/dev.d.ts.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/start.d.ts.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/deploy/aws-lambda/runtime.d.ts.map +0 -1
- package/dist/deploy/build-marker.d.ts.map +0 -1
- package/dist/deploy/bun/runtime.d.ts +0 -11
- package/dist/deploy/bun/runtime.d.ts.map +0 -1
- package/dist/deploy/bun/runtime.js +0 -22
- package/dist/deploy/bun/runtime.js.map +0 -1
- package/dist/deploy/cloudflare/build.d.ts.map +0 -1
- package/dist/deploy/cloudflare/runtime.d.ts.map +0 -1
- package/dist/deploy/contract.d.ts.map +0 -1
- package/dist/deploy/deno/runtime.d.ts +0 -11
- package/dist/deploy/deno/runtime.d.ts.map +0 -1
- package/dist/deploy/deno/runtime.js +0 -16
- package/dist/deploy/deno/runtime.js.map +0 -1
- package/dist/deploy/filesystem.d.ts.map +0 -1
- package/dist/deploy/listen.d.ts +0 -20
- package/dist/deploy/listen.d.ts.map +0 -1
- package/dist/deploy/listen.js +0 -24
- package/dist/deploy/listen.js.map +0 -1
- package/dist/deploy/netlify/build.d.ts +0 -8
- package/dist/deploy/netlify/build.d.ts.map +0 -1
- package/dist/deploy/netlify/build.js +0 -52
- package/dist/deploy/netlify/build.js.map +0 -1
- package/dist/deploy/netlify/runtime.d.ts +0 -13
- package/dist/deploy/netlify/runtime.d.ts.map +0 -1
- package/dist/deploy/netlify/runtime.js +0 -24
- package/dist/deploy/netlify/runtime.js.map +0 -1
- package/dist/deploy/node/runtime.d.ts.map +0 -1
- package/dist/deploy/presets.d.ts.map +0 -1
- package/dist/deploy/vercel/build.d.ts.map +0 -1
- package/dist/deploy/vercel/runtime.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/router.d.ts.map +0 -1
- package/dist/runtime/boundaries.d.ts.map +0 -1
- package/dist/runtime/client.d.ts.map +0 -1
- package/dist/runtime/context.d.ts.map +0 -1
- package/dist/runtime/control.d.ts.map +0 -1
- package/dist/runtime/dev-protocol.d.ts.map +0 -1
- package/dist/runtime/empty-server-app.d.ts.map +0 -1
- package/dist/runtime/entry.client.d.ts.map +0 -1
- package/dist/runtime/entry.rsc.d.ts.map +0 -1
- package/dist/runtime/entry.ssr.d.ts.map +0 -1
- package/dist/runtime/navigation.d.ts.map +0 -1
- package/dist/runtime/request.d.ts.map +0 -1
- package/dist/runtime/server.d.ts.map +0 -1
- package/dist/server/compress.d.ts +0 -15
- package/dist/server/compress.d.ts.map +0 -1
- package/dist/server/compress.js +0 -76
- package/dist/server/compress.js.map +0 -1
- package/dist/server/headers.d.ts.map +0 -1
- package/dist/server/load-config.d.ts.map +0 -1
- package/dist/server/load-env.d.ts.map +0 -1
- package/dist/server/prerendered.d.ts.map +0 -1
- package/dist/server/server-config.d.ts.map +0 -1
- package/dist/server/shutdown.d.ts.map +0 -1
- package/dist/server/ssg.d.ts.map +0 -1
- package/dist/server/static.d.ts.map +0 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type Route } from '../router.js';
|
|
2
|
+
/**
|
|
3
|
+
* The request paths a `public/` tree answers, and the collisions with the route table that only show up
|
|
4
|
+
* after a deploy.
|
|
5
|
+
*
|
|
6
|
+
* `DeployRuntime.mountPublicFallback` registers `public/` **after** every route, so it answers only what no
|
|
7
|
+
* route claimed. That is the whole story on `node` and `aws-lambda`, where the app owns the URL surface. On
|
|
8
|
+
* the two targets with a CDN in front, `public/` is part of the static output and the platform answers from
|
|
9
|
+
* it **before** the app is invoked:
|
|
10
|
+
*
|
|
11
|
+
* - **vercel** — `{ handle: 'filesystem' }` sits ahead of the catch-all in `config.json`, by construction.
|
|
12
|
+
* - **cloudflare** — Workers Assets is checked first, which the generated `wrangler.jsonc` says out loud.
|
|
13
|
+
*
|
|
14
|
+
* So an app with `public/about.html` and a page route at `/about.html` renders the page under `rshono dev`
|
|
15
|
+
* and `rshono start`, and serves the file on those two — a behaviour difference discovered by deploying. A
|
|
16
|
+
* `public/index.html` beside a page route at `/` is the same collision, on the one path every app has.
|
|
17
|
+
*
|
|
18
|
+
* The framework cannot reorder either platform, so this is a build-time warning: the build knows both sides
|
|
19
|
+
* and comparing them is a set lookup.
|
|
20
|
+
*/
|
|
21
|
+
/** `.html` files are also reachable without the extension where the platform's HTML handling does that. */
|
|
22
|
+
export interface PublicPathOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Whether `public/about.html` also answers `/about`. True on Cloudflare, whose Workers Assets defaults to
|
|
25
|
+
* `auto-trailing-slash` HTML handling; false on Vercel, which serves the file at its own path unless the
|
|
26
|
+
* project turns `cleanUrls` on. Getting this wrong in the permissive direction is a warning about a
|
|
27
|
+
* collision the platform does not have, which is the noise that gets a check ignored.
|
|
28
|
+
*/
|
|
29
|
+
htmlExtensionless: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The literal route paths a `public/` file would answer instead, on a target whose platform serves the
|
|
33
|
+
* static output first — `[]` when there is no `public/` directory or nothing collides.
|
|
34
|
+
*
|
|
35
|
+
* Literal paths only. A `public/` file landing inside a parameterised route's pattern is a real collision
|
|
36
|
+
* too, but deciding it needs the router rather than a set, and the paths every app actually writes files
|
|
37
|
+
* beside — `/`, `/about`, `/favicon.ico` — are literal. The limit is here rather than in the message
|
|
38
|
+
* because a reader of the message can do nothing with it.
|
|
39
|
+
*/
|
|
40
|
+
export declare function publicRouteCollisions(publicDir: string | null, routes: readonly Route[], options: PublicPathOptions): string[];
|
|
41
|
+
/** The warning both CDN presets print, so the two say the same thing about the same problem. */
|
|
42
|
+
export declare function warnAboutPublicCollisions(collisions: string[], target: string): void;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { readdirSync } from 'node:fs';
|
|
2
|
+
import { join, posix } from 'node:path';
|
|
3
|
+
import { isPageRoute } from '../router.js';
|
|
4
|
+
/** Every file in a directory tree, as a `/`-separated path relative to its root. */
|
|
5
|
+
function filesUnder(root, prefix = '') {
|
|
6
|
+
const found = [];
|
|
7
|
+
for (const entry of readdirSync(join(root, prefix), { withFileTypes: true })) {
|
|
8
|
+
const rel = prefix ? posix.join(prefix, entry.name) : entry.name;
|
|
9
|
+
if (entry.isDirectory())
|
|
10
|
+
found.push(...filesUnder(root, rel));
|
|
11
|
+
else if (entry.isFile())
|
|
12
|
+
found.push(rel);
|
|
13
|
+
}
|
|
14
|
+
return found;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The request paths one `public/` file is served at — its own, plus the extra ones a platform's HTML
|
|
18
|
+
* handling adds. The same normalisation `ssgFilePath` does for a prerendered page, in the other direction:
|
|
19
|
+
* a check keyed on raw filenames would miss `/` for `index.html` and would be wrong about the rest.
|
|
20
|
+
*
|
|
21
|
+
* Deliberately not exhaustive. It lists what a file is *certainly* reachable at, because a false warning
|
|
22
|
+
* about a route that works is worse than the collision it would have caught — the same asymmetry
|
|
23
|
+
* `assertNothingIsShadowed` keeps.
|
|
24
|
+
*/
|
|
25
|
+
function pathsFor(file, options) {
|
|
26
|
+
const paths = [`/${file}`];
|
|
27
|
+
const index = file === 'index.html' ? '' : file.endsWith('/index.html') ? file.slice(0, -'/index.html'.length) : null;
|
|
28
|
+
if (index !== null)
|
|
29
|
+
paths.push(`/${index}`, `/${index}/`);
|
|
30
|
+
else if (options.htmlExtensionless && file.endsWith('.html'))
|
|
31
|
+
paths.push(`/${file.slice(0, -'.html'.length)}`);
|
|
32
|
+
return [...new Set(paths)];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The literal route paths a `public/` file would answer instead, on a target whose platform serves the
|
|
36
|
+
* static output first — `[]` when there is no `public/` directory or nothing collides.
|
|
37
|
+
*
|
|
38
|
+
* Literal paths only. A `public/` file landing inside a parameterised route's pattern is a real collision
|
|
39
|
+
* too, but deciding it needs the router rather than a set, and the paths every app actually writes files
|
|
40
|
+
* beside — `/`, `/about`, `/favicon.ico` — are literal. The limit is here rather than in the message
|
|
41
|
+
* because a reader of the message can do nothing with it.
|
|
42
|
+
*/
|
|
43
|
+
export function publicRouteCollisions(publicDir, routes, options) {
|
|
44
|
+
if (publicDir === null)
|
|
45
|
+
return [];
|
|
46
|
+
const claimed = new Map(routes.filter((route) => !route.path.includes(':') && !route.path.includes('*')).map((route) => [route.path, route]));
|
|
47
|
+
const collisions = [];
|
|
48
|
+
for (const file of filesUnder(publicDir)) {
|
|
49
|
+
for (const path of pathsFor(file, options)) {
|
|
50
|
+
const route = claimed.get(path);
|
|
51
|
+
if (route)
|
|
52
|
+
collisions.push(`public/${file} answers ${path}, which ${isPageRoute(route) ? 'a page' : 'an endpoint'} route claims`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return collisions;
|
|
56
|
+
}
|
|
57
|
+
/** The warning both CDN presets print, so the two say the same thing about the same problem. */
|
|
58
|
+
export function warnAboutPublicCollisions(collisions, target) {
|
|
59
|
+
if (collisions.length === 0)
|
|
60
|
+
return;
|
|
61
|
+
console.warn(` ⚠ ${collisions.length} public/ file(s) shadow a route on ${target}, where the CDN answers before the app is invoked:`);
|
|
62
|
+
for (const line of collisions)
|
|
63
|
+
console.warn(` ${line}`);
|
|
64
|
+
console.warn(` Under \`rshono dev\` and \`rshono start\` the route wins instead, so this only appears once deployed.`);
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=public-paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public-paths.js","sourceRoot":"","sources":["../../src/deploy/public-paths.ts"],"names":[],"mappings":"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","sourcesContent":["import { readdirSync } from 'node:fs';\nimport { join, posix } from 'node:path';\nimport { isPageRoute, type Route } from '../router.js';\n\n/**\n * The request paths a `public/` tree answers, and the collisions with the route table that only show up\n * after a deploy.\n *\n * `DeployRuntime.mountPublicFallback` registers `public/` **after** every route, so it answers only what no\n * route claimed. That is the whole story on `node` and `aws-lambda`, where the app owns the URL surface. On\n * the two targets with a CDN in front, `public/` is part of the static output and the platform answers from\n * it **before** the app is invoked:\n *\n * - **vercel** — `{ handle: 'filesystem' }` sits ahead of the catch-all in `config.json`, by construction.\n * - **cloudflare** — Workers Assets is checked first, which the generated `wrangler.jsonc` says out loud.\n *\n * So an app with `public/about.html` and a page route at `/about.html` renders the page under `rshono dev`\n * and `rshono start`, and serves the file on those two — a behaviour difference discovered by deploying. A\n * `public/index.html` beside a page route at `/` is the same collision, on the one path every app has.\n *\n * The framework cannot reorder either platform, so this is a build-time warning: the build knows both sides\n * and comparing them is a set lookup.\n */\n\n/** `.html` files are also reachable without the extension where the platform's HTML handling does that. */\nexport interface PublicPathOptions {\n /**\n * Whether `public/about.html` also answers `/about`. True on Cloudflare, whose Workers Assets defaults to\n * `auto-trailing-slash` HTML handling; false on Vercel, which serves the file at its own path unless the\n * project turns `cleanUrls` on. Getting this wrong in the permissive direction is a warning about a\n * collision the platform does not have, which is the noise that gets a check ignored.\n */\n htmlExtensionless: boolean;\n}\n\n/** Every file in a directory tree, as a `/`-separated path relative to its root. */\nfunction filesUnder(root: string, prefix = ''): string[] {\n const found: string[] = [];\n for (const entry of readdirSync(join(root, prefix), { withFileTypes: true })) {\n const rel = prefix ? posix.join(prefix, entry.name) : entry.name;\n if (entry.isDirectory()) found.push(...filesUnder(root, rel));\n else if (entry.isFile()) found.push(rel);\n }\n return found;\n}\n\n/**\n * The request paths one `public/` file is served at — its own, plus the extra ones a platform's HTML\n * handling adds. The same normalisation `ssgFilePath` does for a prerendered page, in the other direction:\n * a check keyed on raw filenames would miss `/` for `index.html` and would be wrong about the rest.\n *\n * Deliberately not exhaustive. It lists what a file is *certainly* reachable at, because a false warning\n * about a route that works is worse than the collision it would have caught — the same asymmetry\n * `assertNothingIsShadowed` keeps.\n */\nfunction pathsFor(file: string, options: PublicPathOptions): string[] {\n const paths = [`/${file}`];\n const index = file === 'index.html' ? '' : file.endsWith('/index.html') ? file.slice(0, -'/index.html'.length) : null;\n if (index !== null) paths.push(`/${index}`, `/${index}/`);\n else if (options.htmlExtensionless && file.endsWith('.html')) paths.push(`/${file.slice(0, -'.html'.length)}`);\n return [...new Set(paths)];\n}\n\n/**\n * The literal route paths a `public/` file would answer instead, on a target whose platform serves the\n * static output first — `[]` when there is no `public/` directory or nothing collides.\n *\n * Literal paths only. A `public/` file landing inside a parameterised route's pattern is a real collision\n * too, but deciding it needs the router rather than a set, and the paths every app actually writes files\n * beside — `/`, `/about`, `/favicon.ico` — are literal. The limit is here rather than in the message\n * because a reader of the message can do nothing with it.\n */\nexport function publicRouteCollisions(publicDir: string | null, routes: readonly Route[], options: PublicPathOptions): string[] {\n if (publicDir === null) return [];\n const claimed = new Map(routes.filter((route) => !route.path.includes(':') && !route.path.includes('*')).map((route) => [route.path, route]));\n const collisions: string[] = [];\n for (const file of filesUnder(publicDir)) {\n for (const path of pathsFor(file, options)) {\n const route = claimed.get(path);\n if (route) collisions.push(`public/${file} answers ${path}, which ${isPageRoute(route) ? 'a page' : 'an endpoint'} route claims`);\n }\n }\n return collisions;\n}\n\n/** The warning both CDN presets print, so the two say the same thing about the same problem. */\nexport function warnAboutPublicCollisions(collisions: string[], target: string): void {\n if (collisions.length === 0) return;\n console.warn(` ⚠ ${collisions.length} public/ file(s) shadow a route on ${target}, where the CDN answers before the app is invoked:`);\n for (const line of collisions) console.warn(` ${line}`);\n console.warn(` Under \\`rshono dev\\` and \\`rshono start\\` the route wins instead, so this only appears once deployed.`);\n}\n"]}
|
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
import { cpSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { rm } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { publicRouteCollisions, warnAboutPublicCollisions } from '../public-paths.js';
|
|
4
5
|
/**
|
|
5
|
-
* The Build Output API v3 layout, which
|
|
6
|
-
*
|
|
6
|
+
* The Build Output API v3 layout, which `vercel deploy --prebuilt` uploads verbatim — producing it directly
|
|
7
|
+
* means Vercel runs no build of its own.
|
|
7
8
|
*/
|
|
8
9
|
const OUTPUT_DIR = join('.vercel', 'output');
|
|
9
10
|
const FUNCTION_DIR = join('functions', 'index.func');
|
|
10
11
|
/**
|
|
11
|
-
* The function
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* project root from where the bundle sits (see `deploy/filesystem.ts`) — so `dist/ssg` and `dist/public`
|
|
15
|
-
* land where it already looks for them, and no runtime code needs to know it is on Vercel.
|
|
12
|
+
* The bundle keeps this exact path inside the function because the runtime derives the project root from where
|
|
13
|
+
* it sits (see `deploy/filesystem.ts`), so `dist/ssg` and `dist/public` land where it already looks — and no
|
|
14
|
+
* runtime code needs to know it is on Vercel.
|
|
16
15
|
*/
|
|
17
16
|
const HANDLER = 'dist/server/main.mjs';
|
|
18
17
|
/**
|
|
19
|
-
* Routes, in the order Vercel evaluates them: hashed assets get the immutable header the CDN cannot
|
|
20
|
-
*
|
|
21
|
-
* app's to render.
|
|
18
|
+
* Routes, in the order Vercel evaluates them: hashed assets get the immutable header the CDN cannot infer,
|
|
19
|
+
* then anything in the static output is served from it, and whatever is left is the app's to render.
|
|
22
20
|
*/
|
|
23
21
|
const ROUTES = [
|
|
24
22
|
{ src: '^/_static/(.*)$', headers: { 'cache-control': 'public, max-age=31536000, immutable' }, continue: true },
|
|
@@ -29,7 +27,7 @@ export async function finalizeVercelBuild(ctx) {
|
|
|
29
27
|
const outputDir = join(ctx.rootDir, OUTPUT_DIR);
|
|
30
28
|
const staticOut = join(outputDir, 'static');
|
|
31
29
|
const functionDir = join(outputDir, FUNCTION_DIR);
|
|
32
|
-
//
|
|
30
|
+
// From scratch, so a deleted page or asset cannot survive in the uploaded output.
|
|
33
31
|
await rm(outputDir, { recursive: true, force: true });
|
|
34
32
|
mkdirSync(staticOut, { recursive: true });
|
|
35
33
|
mkdirSync(functionDir, { recursive: true });
|
|
@@ -37,17 +35,28 @@ export async function finalizeVercelBuild(ctx) {
|
|
|
37
35
|
cpSync(ctx.staticDir, join(staticOut, '_static'), { recursive: true });
|
|
38
36
|
if (ctx.publicDir)
|
|
39
37
|
cpSync(ctx.publicDir, staticOut, { recursive: true });
|
|
40
|
-
//
|
|
38
|
+
// `{ handle: 'filesystem' }` is ahead of the function by construction, so a `public/` file on a route's
|
|
39
|
+
// path answers instead of the route here — and only here, which is what makes it worth saying at build
|
|
40
|
+
// time. `htmlExtensionless: false`: Vercel serves `about.html` at `/about.html` and adds `/about` only
|
|
41
|
+
// with `cleanUrls`, which is the project's setting and not something this build can read.
|
|
42
|
+
warnAboutPublicCollisions(publicRouteCollisions(ctx.publicDir, ctx.routes, { htmlExtensionless: false }), 'vercel');
|
|
43
|
+
// Shipped inside the function: the bundle, and the one thing it reads from disk at request time.
|
|
44
|
+
//
|
|
45
|
+
// `public/` is deliberately *not* among them, though it is on the filesystem targets. It went into the
|
|
46
|
+
// static output above, and `{ handle: 'filesystem' }` sits ahead of the catch-all route, so the platform
|
|
47
|
+
// answers those paths before the function is invoked — a copy here is bytes uploaded and unpacked on every
|
|
48
|
+
// cold start that nothing can ever read. `vercel/runtime.ts` turns the matching mount off to say so.
|
|
41
49
|
cpSync(join(ctx.distDir, 'server'), join(functionDir, 'dist', 'server'), { recursive: true });
|
|
42
50
|
if (existsSync(ctx.ssgDir))
|
|
43
51
|
cpSync(ctx.ssgDir, join(functionDir, 'dist', 'ssg'), { recursive: true });
|
|
44
|
-
if (ctx.publicDir)
|
|
45
|
-
cpSync(ctx.publicDir, join(functionDir, 'dist', 'public'), { recursive: true });
|
|
46
52
|
writeFileSync(join(functionDir, '.vc-config.json'), `${JSON.stringify({
|
|
47
|
-
runtime
|
|
53
|
+
// The Node the build ran on, so the function runs the runtime the app was tested against — and so
|
|
54
|
+
// this does not become a hard-coded version the platform retires on a date the framework does not
|
|
55
|
+
// control. A major Vercel does not offer is an explicit error from the deploy rather than a mismatch.
|
|
56
|
+
runtime: `nodejs${process.versions.node.split('.')[0]}.x`,
|
|
48
57
|
handler: HANDLER,
|
|
49
58
|
launcherType: 'Nodejs',
|
|
50
|
-
// Without this the platform buffers the whole response,
|
|
59
|
+
// Without this the platform buffers the whole response, undoing streamed SSR.
|
|
51
60
|
supportsResponseStreaming: true,
|
|
52
61
|
}, null, 2)}\n`);
|
|
53
62
|
writeFileSync(join(outputDir, 'config.json'), `${JSON.stringify({ version: 3, routes: ROUTES }, null, 2)}\n`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/deploy/vercel/build.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/deploy/vercel/build.ts"],"names":[],"mappings":"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","sourcesContent":["import { cpSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { rm } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { DeployBuildContext } from '../presets.js';\nimport { publicRouteCollisions, warnAboutPublicCollisions } from '../public-paths.js';\n\n/**\n * The Build Output API v3 layout, which `vercel deploy --prebuilt` uploads verbatim — producing it directly\n * means Vercel runs no build of its own.\n */\nconst OUTPUT_DIR = join('.vercel', 'output');\nconst FUNCTION_DIR = join('functions', 'index.func');\n\n/**\n * The bundle keeps this exact path inside the function because the runtime derives the project root from where\n * it sits (see `deploy/filesystem.ts`), so `dist/ssg` and `dist/public` land where it already looks — and no\n * runtime code needs to know it is on Vercel.\n */\nconst HANDLER = 'dist/server/main.mjs';\n\n/**\n * Routes, in the order Vercel evaluates them: hashed assets get the immutable header the CDN cannot infer,\n * then anything in the static output is served from it, and whatever is left is the app's to render.\n */\nconst ROUTES = [\n { src: '^/_static/(.*)$', headers: { 'cache-control': 'public, max-age=31536000, immutable' }, continue: true },\n { handle: 'filesystem' },\n { src: '/(.*)', dest: '/index' },\n];\n\nexport async function finalizeVercelBuild(ctx: DeployBuildContext): Promise<void> {\n const outputDir = join(ctx.rootDir, OUTPUT_DIR);\n const staticOut = join(outputDir, 'static');\n const functionDir = join(outputDir, FUNCTION_DIR);\n\n // From scratch, so a deleted page or asset cannot survive in the uploaded output.\n await rm(outputDir, { recursive: true, force: true });\n mkdirSync(staticOut, { recursive: true });\n mkdirSync(functionDir, { recursive: true });\n\n // Served by the CDN: the hashed bundle under its public path, and `public/` at the web root.\n cpSync(ctx.staticDir, join(staticOut, '_static'), { recursive: true });\n if (ctx.publicDir) cpSync(ctx.publicDir, staticOut, { recursive: true });\n\n // `{ handle: 'filesystem' }` is ahead of the function by construction, so a `public/` file on a route's\n // path answers instead of the route here — and only here, which is what makes it worth saying at build\n // time. `htmlExtensionless: false`: Vercel serves `about.html` at `/about.html` and adds `/about` only\n // with `cleanUrls`, which is the project's setting and not something this build can read.\n warnAboutPublicCollisions(publicRouteCollisions(ctx.publicDir, ctx.routes, { htmlExtensionless: false }), 'vercel');\n\n // Shipped inside the function: the bundle, and the one thing it reads from disk at request time.\n //\n // `public/` is deliberately *not* among them, though it is on the filesystem targets. It went into the\n // static output above, and `{ handle: 'filesystem' }` sits ahead of the catch-all route, so the platform\n // answers those paths before the function is invoked — a copy here is bytes uploaded and unpacked on every\n // cold start that nothing can ever read. `vercel/runtime.ts` turns the matching mount off to say so.\n cpSync(join(ctx.distDir, 'server'), join(functionDir, 'dist', 'server'), { recursive: true });\n if (existsSync(ctx.ssgDir)) cpSync(ctx.ssgDir, join(functionDir, 'dist', 'ssg'), { recursive: true });\n\n writeFileSync(\n join(functionDir, '.vc-config.json'),\n `${JSON.stringify(\n {\n // The Node the build ran on, so the function runs the runtime the app was tested against — and so\n // this does not become a hard-coded version the platform retires on a date the framework does not\n // control. A major Vercel does not offer is an explicit error from the deploy rather than a mismatch.\n runtime: `nodejs${process.versions.node.split('.')[0]}.x`,\n handler: HANDLER,\n launcherType: 'Nodejs',\n // Without this the platform buffers the whole response, undoing streamed SSR.\n supportsResponseStreaming: true,\n },\n null,\n 2,\n )}\\n`,\n );\n\n writeFileSync(join(outputDir, 'config.json'), `${JSON.stringify({ version: 3, routes: ROUTES }, null, 2)}\\n`);\n\n console.log(` • assembled ${OUTPUT_DIR} (Build Output API v3)`);\n}\n"]}
|
|
@@ -2,13 +2,16 @@ import type { DeployRuntime } from '../contract.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Vercel, as a single Node function fed by the platform's own router.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The function is invoked the way `launcherType: 'Nodejs'` invokes one — with `(IncomingMessage,
|
|
6
|
+
* ServerResponse)`, not a web `Request`. That is the whole of the Build Output API's Node contract; the
|
|
7
|
+
* `Request`/`Response` handler shape the platform documents elsewhere belongs to the `@vercel/node` builder,
|
|
8
|
+
* which compiles and wraps a source file, and a `--prebuilt` upload never runs it. So the handoff converts,
|
|
9
|
+
* and `hono/vercel`'s `handle` — a pass-through that forwards its first argument straight to `app.fetch` —
|
|
10
|
+
* cannot be what does it.
|
|
8
11
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
+
* `/_static` and `public/` are in the static output, and `config.json` puts the filesystem handler ahead of the
|
|
13
|
+
* function, so mounting them here would be dead weight. Prerendered pages are *not* static output — one URL
|
|
14
|
+
* answers with a document or a flight payload on the `RSC` request header, and a path-keyed CDN cannot choose — so
|
|
15
|
+
* they ship inside the function and are read from its read-only disk, exactly as on a server.
|
|
12
16
|
*/
|
|
13
17
|
export declare const runtime: DeployRuntime;
|
|
14
|
-
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -1,26 +1,63 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getRequestListener } from '@hono/node-server';
|
|
2
2
|
import { fileSystemRuntime } from '../filesystem.js';
|
|
3
|
+
/**
|
|
4
|
+
* The scheme the browser used, which the socket cannot answer for: Vercel terminates TLS at the edge and
|
|
5
|
+
* reaches the function over plain HTTP, so an `https://` request arrives on an unencrypted socket and every
|
|
6
|
+
* absolute URL the app derived from it would carry the wrong scheme — a redirect back to `http://`, an
|
|
7
|
+
* origin check comparing against one.
|
|
8
|
+
*
|
|
9
|
+
* Read without `trustProxy`, unlike `publicUrl`, because on this target the header is not
|
|
10
|
+
* client-supplied: the function is reachable only through Vercel's edge, which sets it on every request. The
|
|
11
|
+
* config field stays what it is — the answer for a proxy *you* put in front, which the framework cannot vouch
|
|
12
|
+
* for. Falls back to `https`, the only scheme a deployment is served on.
|
|
13
|
+
*/
|
|
14
|
+
function browserScheme(incoming) {
|
|
15
|
+
const header = incoming.headers['x-forwarded-proto'];
|
|
16
|
+
const value = (Array.isArray(header) ? header[0] : header)?.split(',')[0]?.trim();
|
|
17
|
+
return value === 'http' ? 'http' : 'https';
|
|
18
|
+
}
|
|
3
19
|
/**
|
|
4
20
|
* Vercel, as a single Node function fed by the platform's own router.
|
|
5
21
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
22
|
+
* The function is invoked the way `launcherType: 'Nodejs'` invokes one — with `(IncomingMessage,
|
|
23
|
+
* ServerResponse)`, not a web `Request`. That is the whole of the Build Output API's Node contract; the
|
|
24
|
+
* `Request`/`Response` handler shape the platform documents elsewhere belongs to the `@vercel/node` builder,
|
|
25
|
+
* which compiles and wraps a source file, and a `--prebuilt` upload never runs it. So the handoff converts,
|
|
26
|
+
* and `hono/vercel`'s `handle` — a pass-through that forwards its first argument straight to `app.fetch` —
|
|
27
|
+
* cannot be what does it.
|
|
9
28
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
29
|
+
* `/_static` and `public/` are in the static output, and `config.json` puts the filesystem handler ahead of the
|
|
30
|
+
* function, so mounting them here would be dead weight. Prerendered pages are *not* static output — one URL
|
|
31
|
+
* answers with a document or a flight payload on the `RSC` request header, and a path-keyed CDN cannot choose — so
|
|
32
|
+
* they ship inside the function and are read from its read-only disk, exactly as on a server.
|
|
13
33
|
*/
|
|
14
34
|
export const runtime = {
|
|
15
35
|
...fileSystemRuntime,
|
|
16
36
|
mountStaticAssets() {
|
|
17
37
|
// Served from `.vercel/output/static` before the function runs.
|
|
18
38
|
},
|
|
39
|
+
mountPublicFallback() {
|
|
40
|
+
// As above: `public/` is in the static output and `{ handle: 'filesystem' }` is matched ahead of this
|
|
41
|
+
// function, so nothing here could ever answer for one of those paths. Stated as an override rather than
|
|
42
|
+
// left to fall out of `dist/public` not being uploaded, because that would make the behaviour incidental
|
|
43
|
+
// to a `cpSync` in `build.ts` instead of a decision.
|
|
44
|
+
//
|
|
45
|
+
// The one thing this hands to the platform: resolving a directory to its `index.html`. Vercel's static
|
|
46
|
+
// handler does that; `serveStatic`, which the filesystem targets use, is no longer in the path.
|
|
47
|
+
},
|
|
19
48
|
serveApp(app) {
|
|
20
|
-
|
|
49
|
+
// The same conversion `rshono start` runs on, so a request is built here exactly as it is on the `node`
|
|
50
|
+
// target — including a streamed response body, which `supportsResponseStreaming` in `.vc-config.json`
|
|
51
|
+
// then keeps unbuffered on the way out.
|
|
52
|
+
const listener = getRequestListener(app.fetch);
|
|
53
|
+
return (incoming, outgoing) => {
|
|
54
|
+
const host = incoming.headers.host;
|
|
55
|
+
// Left alone when there is no `Host`, so the listener answers with its own 400 rather than this
|
|
56
|
+
// building `https://undefined/…` and the app rejecting a URL it should never have been handed.
|
|
57
|
+
if (host)
|
|
58
|
+
incoming.url = `${browserScheme(incoming)}://${host}${incoming.url ?? '/'}`;
|
|
59
|
+
return listener(incoming, outgoing);
|
|
60
|
+
};
|
|
21
61
|
},
|
|
22
|
-
// Vercel compresses at the edge. Its Node runtime does support streaming, so the framework's own
|
|
23
|
-
// streamed SSR still reaches the browser progressively.
|
|
24
|
-
compress: null,
|
|
25
62
|
};
|
|
26
63
|
//# sourceMappingURL=runtime.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../../src/deploy/vercel/runtime.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../../src/deploy/vercel/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAIvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD;;;;;;;;;;GAUG;AACH,SAAS,aAAa,CAAC,QAAyB;IAC9C,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACrD,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;IAClF,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;AAC7C,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,OAAO,GAAkB;IACpC,GAAG,iBAAiB;IAEpB,iBAAiB;QACf,gEAAgE;IAClE,CAAC;IAED,mBAAmB;QACjB,sGAAsG;QACtG,wGAAwG;QACxG,yGAAyG;QACzG,qDAAqD;QACrD,EAAE;QACF,uGAAuG;QACvG,gGAAgG;IAClG,CAAC;IAED,QAAQ,CAAC,GAAS;QAChB,wGAAwG;QACxG,sGAAsG;QACtG,wCAAwC;QACxC,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAE/C,OAAO,CAAC,QAAyB,EAAE,QAAwB,EAAiB,EAAE;YAC5E,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC;YACnC,gGAAgG;YAChG,+FAA+F;YAC/F,IAAI,IAAI;gBAAE,QAAQ,CAAC,GAAG,GAAG,GAAG,aAAa,CAAC,QAAQ,CAAC,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,IAAI,GAAG,EAAE,CAAC;YACtF,OAAO,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACtC,CAAC,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["import { getRequestListener } from '@hono/node-server';\nimport type { Hono } from 'hono';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { DeployRuntime } from '../contract.js';\nimport { fileSystemRuntime } from '../filesystem.js';\n\n/**\n * The scheme the browser used, which the socket cannot answer for: Vercel terminates TLS at the edge and\n * reaches the function over plain HTTP, so an `https://` request arrives on an unencrypted socket and every\n * absolute URL the app derived from it would carry the wrong scheme — a redirect back to `http://`, an\n * origin check comparing against one.\n *\n * Read without `trustProxy`, unlike `publicUrl`, because on this target the header is not\n * client-supplied: the function is reachable only through Vercel's edge, which sets it on every request. The\n * config field stays what it is — the answer for a proxy *you* put in front, which the framework cannot vouch\n * for. Falls back to `https`, the only scheme a deployment is served on.\n */\nfunction browserScheme(incoming: IncomingMessage): string {\n const header = incoming.headers['x-forwarded-proto'];\n const value = (Array.isArray(header) ? header[0] : header)?.split(',')[0]?.trim();\n return value === 'http' ? 'http' : 'https';\n}\n\n/**\n * Vercel, as a single Node function fed by the platform's own router.\n *\n * The function is invoked the way `launcherType: 'Nodejs'` invokes one — with `(IncomingMessage,\n * ServerResponse)`, not a web `Request`. That is the whole of the Build Output API's Node contract; the\n * `Request`/`Response` handler shape the platform documents elsewhere belongs to the `@vercel/node` builder,\n * which compiles and wraps a source file, and a `--prebuilt` upload never runs it. So the handoff converts,\n * and `hono/vercel`'s `handle` — a pass-through that forwards its first argument straight to `app.fetch` —\n * cannot be what does it.\n *\n * `/_static` and `public/` are in the static output, and `config.json` puts the filesystem handler ahead of the\n * function, so mounting them here would be dead weight. Prerendered pages are *not* static output — one URL\n * answers with a document or a flight payload on the `RSC` request header, and a path-keyed CDN cannot choose — so\n * they ship inside the function and are read from its read-only disk, exactly as on a server.\n */\nexport const runtime: DeployRuntime = {\n ...fileSystemRuntime,\n\n mountStaticAssets(): void {\n // Served from `.vercel/output/static` before the function runs.\n },\n\n mountPublicFallback(): void {\n // As above: `public/` is in the static output and `{ handle: 'filesystem' }` is matched ahead of this\n // function, so nothing here could ever answer for one of those paths. Stated as an override rather than\n // left to fall out of `dist/public` not being uploaded, because that would make the behaviour incidental\n // to a `cpSync` in `build.ts` instead of a decision.\n //\n // The one thing this hands to the platform: resolving a directory to its `index.html`. Vercel's static\n // handler does that; `serveStatic`, which the filesystem targets use, is no longer in the path.\n },\n\n serveApp(app: Hono): unknown {\n // The same conversion `rshono start` runs on, so a request is built here exactly as it is on the `node`\n // target — including a streamed response body, which `supportsResponseStreaming` in `.vc-config.json`\n // then keeps unbuffered on the way out.\n const listener = getRequestListener(app.fetch);\n\n return (incoming: IncomingMessage, outgoing: ServerResponse): Promise<void> => {\n const host = incoming.headers.host;\n // Left alone when there is no `Host`, so the listener answers with its own 400 rather than this\n // building `https://undefined/…` and the app rejecting a URL it should never have been handed.\n if (host) incoming.url = `${browserScheme(incoming)}://${host}${incoming.url ?? '/'}`;\n return listener(incoming, outgoing);\n };\n },\n};\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@rshono/core` — the build-time surface: route and config declaration plus the types
|
|
3
|
-
* your pages and endpoints are written against.
|
|
4
|
-
* import from server code; none of it pulls in runtime machinery.
|
|
2
|
+
* `@rshono/core` — the build-time surface: route and config declaration, plus the types
|
|
3
|
+
* your pages and endpoints are written against. Nothing here pulls in runtime machinery.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
* - `@rshono/core/server` —
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
5
|
+
* Two companion entry points are runtime-only:
|
|
6
|
+
* - `@rshono/core/server` — request context, `redirect`, `notFound`, `onServerError`, `publicUrl`.
|
|
7
|
+
* - `@rshono/core/client` — `useNavigation`, `AsyncBoundary`, `CatchBoundary`.
|
|
8
|
+
*
|
|
9
|
+
* ESM only: the `exports` map declares `types` and `import` conditions and no `require` one.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* // src/routes.ts
|
|
14
|
+
* import { defineRoutes } from '@rshono/core';
|
|
15
|
+
*
|
|
16
|
+
* export const routes = defineRoutes([{ path: '/', component: () => import('./components/home') }]);
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @see {@link https://www.rshono.com/docs/api | Docs — API reference}
|
|
12
20
|
*
|
|
13
21
|
* @packageDocumentation
|
|
14
22
|
*/
|
|
15
|
-
export { defineRoutes,
|
|
16
|
-
export { defineConfig, type
|
|
23
|
+
export { defineRoutes, type EndpointRoute, type EndpointServerModule, type ErrorPageInfo, type ErrorPageProps, type FallbackPage, type HTTPMethod, type PageComponent, type PageProps, type PageRoute, type PathParams, type Route, type RouteConfig, } from './router.js';
|
|
24
|
+
export { defineConfig, type RshonoConfig, type RspackHookContext } from './config.js';
|
|
17
25
|
export type { DeployTarget } from './deploy/contract.js';
|
|
18
|
-
/**
|
|
19
|
-
* Re-exported from Hono for convenience, so an endpoint module can type its
|
|
20
|
-
* `handler` without depending on `hono` directly.
|
|
21
|
-
*/
|
|
22
|
-
export type { Context, Handler } from 'hono';
|
|
23
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@rshono/core` — the build-time surface: route and config declaration plus the types
|
|
3
|
-
* your pages and endpoints are written against.
|
|
4
|
-
* import from server code; none of it pulls in runtime machinery.
|
|
2
|
+
* `@rshono/core` — the build-time surface: route and config declaration, plus the types
|
|
3
|
+
* your pages and endpoints are written against. Nothing here pulls in runtime machinery.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
* - `@rshono/core/server` —
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
5
|
+
* Two companion entry points are runtime-only:
|
|
6
|
+
* - `@rshono/core/server` — request context, `redirect`, `notFound`, `onServerError`, `publicUrl`.
|
|
7
|
+
* - `@rshono/core/client` — `useNavigation`, `AsyncBoundary`, `CatchBoundary`.
|
|
8
|
+
*
|
|
9
|
+
* ESM only: the `exports` map declares `types` and `import` conditions and no `require` one.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* // src/routes.ts
|
|
14
|
+
* import { defineRoutes } from '@rshono/core';
|
|
15
|
+
*
|
|
16
|
+
* export const routes = defineRoutes([{ path: '/', component: () => import('./components/home') }]);
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @see {@link https://www.rshono.com/docs/api | Docs — API reference}
|
|
12
20
|
*
|
|
13
21
|
* @packageDocumentation
|
|
14
22
|
*/
|
|
15
|
-
export { defineRoutes,
|
|
23
|
+
export { defineRoutes, } from './router.js';
|
|
16
24
|
export { defineConfig } from './config.js';
|
|
25
|
+
// Hono's `Context` and `Handler` are deliberately not re-exported — `hono` is a peer dependency, so
|
|
26
|
+
// endpoints import them from there rather than from two spellings of the same type.
|
|
17
27
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EACL,YAAY,GAab,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,YAAY,EAA6C,MAAM,aAAa,CAAC;AAItF,oGAAoG;AACpG,oFAAoF","sourcesContent":["/**\n * `@rshono/core` — the build-time surface: route and config declaration, plus the types\n * your pages and endpoints are written against. Nothing here pulls in runtime machinery.\n *\n * Two companion entry points are runtime-only:\n * - `@rshono/core/server` — request context, `redirect`, `notFound`, `onServerError`, `publicUrl`.\n * - `@rshono/core/client` — `useNavigation`, `AsyncBoundary`, `CatchBoundary`.\n *\n * ESM only: the `exports` map declares `types` and `import` conditions and no `require` one.\n *\n * @example\n * ```ts\n * // src/routes.ts\n * import { defineRoutes } from '@rshono/core';\n *\n * export const routes = defineRoutes([{ path: '/', component: () => import('./components/home') }]);\n * ```\n *\n * @see {@link https://www.rshono.com/docs/api | Docs — API reference}\n *\n * @packageDocumentation\n */\n\nexport {\n defineRoutes,\n type EndpointRoute,\n type EndpointServerModule,\n type ErrorPageInfo,\n type ErrorPageProps,\n type FallbackPage,\n type HTTPMethod,\n type PageComponent,\n type PageProps,\n type PageRoute,\n type PathParams,\n type Route,\n type RouteConfig,\n} from './router.js';\n\nexport { defineConfig, type RshonoConfig, type RspackHookContext } from './config.js';\n\nexport type { DeployTarget } from './deploy/contract.js';\n\n// Hono's `Context` and `Handler` are deliberately not re-exported — `hono` is a peer dependency, so\n// endpoints import them from there rather than from two spellings of the same type.\n"]}
|