@nifrajs/web 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/build-vite.d.ts +68 -0
- package/dist/build-vite.d.ts.map +1 -0
- package/dist/build-vite.js +433 -0
- package/dist/build-vite.js.map +1 -0
- package/dist/build.d.ts +174 -65
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +376 -135
- package/dist/build.js.map +1 -1
- package/dist/bun-dev-entry.d.ts +83 -0
- package/dist/bun-dev-entry.d.ts.map +1 -0
- package/dist/bun-dev-entry.js +136 -0
- package/dist/bun-dev-entry.js.map +1 -0
- package/dist/chunk-names.js +1 -1
- package/dist/client.d.ts +3 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +209 -27
- package/dist/client.js.map +1 -1
- package/dist/conformance.d.ts +2 -2
- package/dist/conformance.js +3 -3
- package/dist/deferred.d.ts +11 -11
- package/dist/deferred.d.ts.map +1 -1
- package/dist/deferred.js +16 -16
- package/dist/dev-error.d.ts +2 -2
- package/dist/dev-error.js +3 -3
- package/dist/dev-port.d.ts +36 -0
- package/dist/dev-port.d.ts.map +1 -0
- package/dist/dev-port.js +48 -0
- package/dist/dev-port.js.map +1 -0
- package/dist/dev.d.ts +62 -5
- package/dist/dev.d.ts.map +1 -1
- package/dist/dev.js +419 -60
- package/dist/dev.js.map +1 -1
- package/dist/draft.d.ts +44 -7
- package/dist/draft.d.ts.map +1 -1
- package/dist/draft.js +92 -9
- package/dist/draft.js.map +1 -1
- package/dist/fn-state.d.ts +56 -0
- package/dist/fn-state.d.ts.map +1 -0
- package/dist/fn-state.js +77 -0
- package/dist/fn-state.js.map +1 -0
- package/dist/fn.d.ts +111 -0
- package/dist/fn.d.ts.map +1 -0
- package/dist/fn.js +125 -0
- package/dist/fn.js.map +1 -0
- package/dist/fonts-google.d.ts +9 -9
- package/dist/fonts-google.js +10 -10
- package/dist/fonts.d.ts +7 -7
- package/dist/fonts.js +1 -1
- package/dist/forms.d.ts +8 -8
- package/dist/fs.d.ts +15 -2
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +8 -2
- package/dist/fs.js.map +1 -1
- package/dist/index.d.ts +266 -74
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +869 -179
- package/dist/index.js.map +1 -1
- package/dist/internal/head-attributes.d.ts +15 -0
- package/dist/internal/head-attributes.d.ts.map +1 -0
- package/dist/internal/head-attributes.js +99 -0
- package/dist/internal/head-attributes.js.map +1 -0
- package/dist/internal/script-types.d.ts +14 -0
- package/dist/internal/script-types.d.ts.map +1 -0
- package/dist/internal/script-types.js +17 -0
- package/dist/internal/script-types.js.map +1 -0
- package/dist/internal/server-boundary.d.ts +60 -0
- package/dist/internal/server-boundary.d.ts.map +1 -0
- package/dist/internal/server-boundary.js +174 -0
- package/dist/internal/server-boundary.js.map +1 -0
- package/dist/internal/timing-safe-equal.d.ts +16 -0
- package/dist/internal/timing-safe-equal.d.ts.map +1 -0
- package/dist/internal/timing-safe-equal.js +26 -0
- package/dist/internal/timing-safe-equal.js.map +1 -0
- package/dist/internal/vite-import.d.ts +63 -0
- package/dist/internal/vite-import.d.ts.map +1 -0
- package/dist/internal/vite-import.js +94 -0
- package/dist/internal/vite-import.js.map +1 -0
- package/dist/islands.d.ts +5 -5
- package/dist/islands.js +5 -5
- package/dist/isr.d.ts +15 -15
- package/dist/isr.d.ts.map +1 -1
- package/dist/isr.js +8 -21
- package/dist/isr.js.map +1 -1
- package/dist/llms-txt.d.ts.map +1 -1
- package/dist/llms-txt.js +12 -6
- package/dist/llms-txt.js.map +1 -1
- package/dist/manifest.d.ts +121 -40
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +136 -26
- package/dist/manifest.js.map +1 -1
- package/dist/module-graph.d.ts +91 -0
- package/dist/module-graph.d.ts.map +1 -0
- package/dist/module-graph.js +92 -0
- package/dist/module-graph.js.map +1 -0
- package/dist/navigation.d.ts +120 -5
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +37 -5
- package/dist/navigation.js.map +1 -1
- package/dist/plugins/css-modules.d.ts +3 -3
- package/dist/plugins/css-modules.d.ts.map +1 -1
- package/dist/plugins/css-modules.js +0 -0
- package/dist/plugins/css-modules.js.map +1 -1
- package/dist/plugins/kit.d.ts +8 -8
- package/dist/plugins/kit.js +9 -9
- package/dist/plugins/scss.js +3 -3
- package/dist/plugins/vite-leak-guard.d.ts +45 -0
- package/dist/plugins/vite-leak-guard.d.ts.map +1 -0
- package/dist/plugins/vite-leak-guard.js +67 -0
- package/dist/plugins/vite-leak-guard.js.map +1 -0
- package/dist/plugins/vite-server-fn.d.ts +23 -0
- package/dist/plugins/vite-server-fn.d.ts.map +1 -0
- package/dist/plugins/vite-server-fn.js +51 -0
- package/dist/plugins/vite-server-fn.js.map +1 -0
- package/dist/plugins/vite-server-only.d.ts +49 -0
- package/dist/plugins/vite-server-only.d.ts.map +1 -0
- package/dist/plugins/vite-server-only.js +57 -0
- package/dist/plugins/vite-server-only.js.map +1 -0
- package/dist/prerender.d.ts +5 -5
- package/dist/prerender.js +7 -7
- package/dist/public-dir.d.ts +36 -0
- package/dist/public-dir.d.ts.map +1 -0
- package/dist/public-dir.js +89 -0
- package/dist/public-dir.js.map +1 -0
- package/dist/query.d.ts +10 -10
- package/dist/query.js +15 -15
- package/dist/route-manifest.d.ts +91 -0
- package/dist/route-manifest.d.ts.map +1 -0
- package/dist/route-manifest.js +128 -0
- package/dist/route-manifest.js.map +1 -0
- package/dist/router.d.ts +94 -22
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +170 -58
- package/dist/router.js.map +1 -1
- package/dist/search.d.ts +94 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +232 -0
- package/dist/search.js.map +1 -0
- package/dist/server-only.d.ts +6 -6
- package/dist/server-only.js +7 -7
- package/dist/service-worker.d.ts +56 -0
- package/dist/service-worker.d.ts.map +1 -0
- package/dist/service-worker.js +127 -0
- package/dist/service-worker.js.map +1 -0
- package/dist/vite.d.ts +25 -17
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +75 -33
- package/dist/vite.js.map +1 -1
- package/package.json +48 -4
- package/server-manifest.d.ts +31 -0
- package/src/build-vite.ts +553 -0
- package/src/build.ts +563 -172
- package/src/bun-dev-entry.ts +184 -0
- package/src/chunk-names.ts +1 -1
- package/src/client.ts +221 -30
- package/src/conformance.ts +3 -3
- package/src/deferred.ts +17 -17
- package/src/dev-error.ts +3 -3
- package/src/dev-port.ts +63 -0
- package/src/dev.ts +468 -66
- package/src/draft.ts +124 -12
- package/src/fn-state.ts +107 -0
- package/src/fn.ts +199 -0
- package/src/fonts-google.ts +13 -13
- package/src/fonts.ts +7 -7
- package/src/forms.ts +8 -8
- package/src/fs.ts +24 -6
- package/src/index.ts +1174 -225
- package/src/internal/head-attributes.ts +109 -0
- package/src/internal/script-types.ts +18 -0
- package/src/internal/server-boundary.ts +187 -0
- package/src/internal/timing-safe-equal.ts +24 -0
- package/src/internal/vite-import.ts +92 -0
- package/src/islands.ts +7 -7
- package/src/isr.ts +18 -30
- package/src/llms-txt.ts +13 -5
- package/src/manifest.ts +264 -48
- package/src/module-graph.ts +146 -0
- package/src/navigation.ts +151 -5
- package/src/plugins/css-modules.ts +0 -0
- package/src/plugins/kit.ts +11 -11
- package/src/plugins/scss.ts +3 -3
- package/src/plugins/vite-leak-guard.ts +110 -0
- package/src/plugins/vite-server-fn.ts +67 -0
- package/src/plugins/vite-server-only.ts +75 -0
- package/src/prerender.ts +10 -10
- package/src/public-dir.ts +108 -0
- package/src/query.ts +22 -22
- package/src/route-manifest.ts +240 -0
- package/src/router.ts +288 -74
- package/src/search.ts +297 -0
- package/src/server-only.ts +7 -7
- package/src/service-worker.ts +165 -0
- package/src/vite.ts +103 -49
package/dist/manifest.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Route manifest
|
|
2
|
+
* Route manifest - the fs-free heart of file-based routing. Maps route file paths to nifra
|
|
3
3
|
* router patterns + their nested layout chain. `discoverRoutes` (in `@nifrajs/web/fs`) scans
|
|
4
4
|
* the filesystem and feeds `buildManifest`; everything here is pure logic, so it stays
|
|
5
5
|
* portable (no fs, no DOM) and fully unit-testable. Edge deploys pre-build the manifest.
|
|
6
6
|
*/
|
|
7
|
+
import type { StandardSchemaV1 } from "@nifrajs/core/server";
|
|
7
8
|
/** Context passed to a route `loader`. The `api` + `env` are injected by `createWebApp` and typed
|
|
8
9
|
* per-route via `@nifrajs/client`'s `LoaderArgs<Api, Env>` (here they are opaque to the agnostic core). */
|
|
9
10
|
export interface LoaderContext {
|
|
10
11
|
readonly params: Record<string, string>;
|
|
11
12
|
readonly request: Request;
|
|
12
|
-
/** Alias of {@link request}
|
|
13
|
+
/** Alias of {@link request} - mirrors a route handler's `c.req` so the same name works in both. */
|
|
13
14
|
readonly req: Request;
|
|
14
15
|
readonly api: unknown;
|
|
15
16
|
/** Platform bindings forwarded from the request `c.env` (Workers env/KV/D1). Opaque here. */
|
|
@@ -17,24 +18,28 @@ export interface LoaderContext {
|
|
|
17
18
|
/** `true` when the request carries a valid draft/preview cookie (only when `createWebApp` is given a
|
|
18
19
|
* `draftSecret`; otherwise always `false`). Branch on it to load unpublished content for editors. */
|
|
19
20
|
readonly draft: boolean;
|
|
21
|
+
/** The URL search params, validated against the route's `searchSchema` (a Standard Schema) when it
|
|
22
|
+
* declares one - failing closed to the schema's defaults - else the raw parsed query. Typed per-route
|
|
23
|
+
* via `@nifrajs/client`'s `LoaderArgs<Api, Env, Search>`. */
|
|
24
|
+
readonly search: Record<string, unknown>;
|
|
20
25
|
}
|
|
21
26
|
/** A route's optional data loader: params/request in, data out. */
|
|
22
27
|
export type Loader = (ctx: LoaderContext) => unknown | Promise<unknown>;
|
|
23
28
|
/**
|
|
24
29
|
* A route's optional mutation, run on POST. Shares the loader context (params/request/api);
|
|
25
|
-
* read the form/JSON body off `request`. Returns either a `Response` (e.g. a redirect
|
|
30
|
+
* read the form/JSON body off `request`. Returns either a `Response` (e.g. a redirect -
|
|
26
31
|
* passed straight through) or data, surfaced to the page component as `actionData`.
|
|
27
32
|
*/
|
|
28
33
|
export type Action = (ctx: LoaderContext) => unknown | Promise<unknown>;
|
|
29
34
|
/**
|
|
30
35
|
* One `<link>` tag's attributes for a route/layout's `meta.link`. The common HTML `<link>` attributes
|
|
31
|
-
* are spelled out and **optional** so a typed partial like `{ rel, href, hreflang }` is assignable
|
|
36
|
+
* are spelled out and **optional** so a typed partial like `{ rel, href, hreflang }` is assignable -
|
|
32
37
|
* the previous `Record<string, string>` required *every* value to be a present string, which rejected
|
|
33
|
-
* exactly that idiomatic shape (the bug this fixes).
|
|
34
|
-
*
|
|
35
|
-
* `disabled` (rendered
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
+
* exactly that idiomatic shape (the bug this fixes). Standard attributes are explicit and the template
|
|
39
|
+
* index signature admits inert `data-*` metadata without opening executable `on*` attributes. `boolean`
|
|
40
|
+
* covers `disabled` (rendered bare when `true`, omitted when `false`), and `undefined` lets a caller
|
|
41
|
+
* spread in a conditionally absent attribute. SSR and soft navigation apply one runtime allowlist too,
|
|
42
|
+
* so a cast or untyped route cannot widen the injection surface.
|
|
38
43
|
*/
|
|
39
44
|
export interface LinkDescriptor {
|
|
40
45
|
readonly rel?: string;
|
|
@@ -42,6 +47,7 @@ export interface LinkDescriptor {
|
|
|
42
47
|
readonly hreflang?: string;
|
|
43
48
|
readonly crossorigin?: string;
|
|
44
49
|
readonly media?: string;
|
|
50
|
+
readonly nonce?: string;
|
|
45
51
|
readonly sizes?: string;
|
|
46
52
|
readonly type?: string;
|
|
47
53
|
readonly as?: string;
|
|
@@ -53,40 +59,79 @@ export interface LinkDescriptor {
|
|
|
53
59
|
readonly imagesizes?: string;
|
|
54
60
|
readonly color?: string;
|
|
55
61
|
readonly disabled?: boolean;
|
|
56
|
-
readonly [attr: string]: string |
|
|
62
|
+
readonly [attr: `data-${string}`]: string | undefined;
|
|
57
63
|
}
|
|
58
|
-
/** One `<
|
|
64
|
+
/** One managed `<meta>` tag. Standard attributes and inert `data-*` metadata only. */
|
|
65
|
+
export interface MetaDescriptor {
|
|
66
|
+
readonly charset?: string;
|
|
67
|
+
readonly content?: string;
|
|
68
|
+
readonly "http-equiv"?: string;
|
|
69
|
+
readonly itemprop?: string;
|
|
70
|
+
readonly media?: string;
|
|
71
|
+
readonly name?: string;
|
|
72
|
+
readonly property?: string;
|
|
73
|
+
readonly scheme?: string;
|
|
74
|
+
readonly [attr: `data-${string}`]: string | undefined;
|
|
75
|
+
}
|
|
76
|
+
/** One `<script>` element a route contributes to `<head>` - for structured data (JSON-LD) and other
|
|
59
77
|
* inert, non-executable head scripts. The `content` is the script body; `type` defaults to
|
|
60
78
|
* `"application/ld+json"` (the common case). The renderer escapes `content` against an HTML breakout
|
|
61
|
-
* (`</`, `<!--`, `]]>`)
|
|
79
|
+
* (`</`, `<!--`, `]]>`) - see `escapeScriptContent` - so a JSON-LD payload can never close the
|
|
62
80
|
* `<script>` element early. `content` is **JSON/text, never raw HTML**: this slot is not an XSS escape
|
|
63
81
|
* hatch for arbitrary markup. */
|
|
82
|
+
export type InertScriptType = "application/ld+json" | "application/json";
|
|
64
83
|
export interface ScriptDescriptor {
|
|
65
84
|
/** The script's `type` attribute. Default `"application/ld+json"`. */
|
|
66
|
-
readonly type?:
|
|
85
|
+
readonly type?: InertScriptType;
|
|
67
86
|
/** The script body (e.g. a `JSON.stringify`'d JSON-LD object). Escaped for safe `<script>` embedding. */
|
|
68
87
|
readonly content: string;
|
|
69
88
|
}
|
|
89
|
+
/** Explicit escape hatch for executable inline code. A CSP nonce is mandatory. */
|
|
90
|
+
export interface UnsafeScriptDescriptor {
|
|
91
|
+
readonly unsafe: true;
|
|
92
|
+
readonly type: "module" | "text/javascript";
|
|
93
|
+
readonly nonce: string;
|
|
94
|
+
readonly content: string;
|
|
95
|
+
}
|
|
70
96
|
/**
|
|
71
|
-
* The document head a route contributes
|
|
97
|
+
* The document head a route contributes - title + `<meta>`/`<link>`/`<script>` tag sets. Returned by a
|
|
72
98
|
* route/layout `meta` (statically, or from a {@link MetaArgs} function). Every value is serialized into
|
|
73
|
-
* managed (`data-nifra`) head tags: attribute
|
|
74
|
-
*
|
|
75
|
-
* loader-derived strings (LLM-authored `og:*`, user content) are XSS-safe
|
|
99
|
+
* managed (`data-nifra`) head tags: tag-specific attribute allowlists reject event handlers and active
|
|
100
|
+
* URL/refresh contexts, values are HTML-escaped at render, and `script[].content` is breakout-escaped -
|
|
101
|
+
* so loader-derived strings (LLM-authored `og:*`, user content) are XSS-safe by construction.
|
|
76
102
|
* Layout-chain heads merge with the page's via `mergeHeads` (arrays concat outermost→page; `title` is
|
|
77
103
|
* nearest-wins). Build `og:*`/`twitter:*` with `openGraph(...)`, canonical with `canonical(...)`, and
|
|
78
104
|
* JSON-LD with `jsonLd(...)` (all from `@nifrajs/web`) rather than hand-writing the records.
|
|
79
105
|
*/
|
|
80
106
|
export interface Meta {
|
|
81
107
|
readonly title?: string;
|
|
82
|
-
readonly meta?:
|
|
108
|
+
readonly meta?: readonly MetaDescriptor[];
|
|
83
109
|
readonly link?: readonly LinkDescriptor[];
|
|
84
110
|
/** Inert head `<script>`s (JSON-LD structured data, etc.). See {@link ScriptDescriptor}. */
|
|
85
111
|
readonly script?: readonly ScriptDescriptor[];
|
|
112
|
+
/** Executable inline scripts. Prefer external modules; construct only with `unsafeInlineScript()`. */
|
|
113
|
+
readonly unsafeScript?: readonly UnsafeScriptDescriptor[];
|
|
114
|
+
/**
|
|
115
|
+
* The document language - `<html lang="...">`. Nearest-wins like `title`, so a localized route can
|
|
116
|
+
* override a layout's default. Defaults to `"en"` when no head in the chain sets it.
|
|
117
|
+
*
|
|
118
|
+
* This is the ONLY way to set it: the shell's `<html>` is framework-owned, so a multilingual app
|
|
119
|
+
* otherwise serves every URL as `lang="en"` - which tells a screen reader to pronounce every locale
|
|
120
|
+
* with an English voice, and suppresses the browser's translation offer.
|
|
121
|
+
*/
|
|
122
|
+
readonly lang?: string;
|
|
123
|
+
/**
|
|
124
|
+
* The document writing direction - `<html dir="...">`. Nearest-wins like `title`. Omitted from the
|
|
125
|
+
* shell when unset, which is HTML's `ltr` default, so an LTR app's output is byte-identical.
|
|
126
|
+
*
|
|
127
|
+
* Required for Arabic/Hebrew/Urdu/Persian: without it the browser lays the page out left-to-right
|
|
128
|
+
* and mis-orders trailing punctuation, so the text renders wrong rather than merely unstyled.
|
|
129
|
+
*/
|
|
130
|
+
readonly dir?: "ltr" | "rtl" | "auto";
|
|
86
131
|
}
|
|
87
132
|
/**
|
|
88
133
|
* Args for a route's `meta` function: the loader's `data` + the route `params` + the request `origin`.
|
|
89
|
-
* `meta()` runs in BOTH SSR and client navigation, so it has **no `request`/`process.env`/server access**
|
|
134
|
+
* `meta()` runs in BOTH SSR and client navigation, so it has **no `request`/`process.env`/server access** -
|
|
90
135
|
* `origin` is the only server-resolved fact it gets (so you needn't thread `siteUrl` through loader data
|
|
91
136
|
* for absolute `og:url`/`canonical`/`og:image` URLs). See {@link origin}.
|
|
92
137
|
*/
|
|
@@ -94,10 +139,10 @@ export interface MetaArgs<Data = unknown> {
|
|
|
94
139
|
readonly data: Data;
|
|
95
140
|
readonly params: Record<string, string>;
|
|
96
141
|
/**
|
|
97
|
-
* The site origin
|
|
142
|
+
* The site origin - scheme + host (+ port), e.g. `"https://news.example.com"`, **with no trailing
|
|
98
143
|
* slash**. The single piece of server/env knowledge `meta()` otherwise can't see: it runs in BOTH
|
|
99
144
|
* SSR and client navigation, so it has no `request`/`process.env`. The framework resolves it from the
|
|
100
|
-
* request URL during SSR and from `location.origin` on client nav
|
|
145
|
+
* request URL during SSR and from `location.origin` on client nav - and they match, so an absolute
|
|
101
146
|
* `og:url`/`canonical`/`og:image` built from it never drifts between the server-rendered `<head>` and
|
|
102
147
|
* a soft-nav. Use it for absolute URLs (`origin + "/posts/" + slug`) instead of threading `siteUrl`
|
|
103
148
|
* through loader data. Empty string (`""`) when the origin is unknown (e.g. a hand-built test render
|
|
@@ -106,7 +151,7 @@ export interface MetaArgs<Data = unknown> {
|
|
|
106
151
|
readonly origin: string;
|
|
107
152
|
}
|
|
108
153
|
/** A route's `meta`: a static {@link Meta}, or a function of the loader data + params + the request
|
|
109
|
-
* origin ({@link MetaArgs}). Use the `origin` arg for absolute `canonical`/`og:url`/`og:image` URLs
|
|
154
|
+
* origin ({@link MetaArgs}). Use the `origin` arg for absolute `canonical`/`og:url`/`og:image` URLs -
|
|
110
155
|
* it's resolved server-side from the request and matches the client's `location.origin`. */
|
|
111
156
|
export type MetaInput = Meta | ((args: MetaArgs) => Meta);
|
|
112
157
|
/** One concrete parameterization of a dynamic route, returned by {@link GetStaticPaths}. */
|
|
@@ -119,18 +164,28 @@ export interface StaticPaths {
|
|
|
119
164
|
readonly paths: readonly StaticPath[];
|
|
120
165
|
/**
|
|
121
166
|
* How a path NOT in `paths` is handled. `"ssr"` (default) → rendered on-demand by the worker
|
|
122
|
-
* (the natural hybrid behavior
|
|
167
|
+
* (the natural hybrid behavior - an unlisted path simply isn't a static file); `"404"` → only the
|
|
123
168
|
* listed paths exist. Recorded by `prerenderRoutes` for the deploy layer.
|
|
124
169
|
*/
|
|
125
170
|
readonly fallback?: "ssr" | "404";
|
|
126
171
|
}
|
|
127
172
|
/** A dynamic route's build-time param enumeration (the SSG equivalent of "which pages exist"). */
|
|
128
173
|
export type GetStaticPaths = () => StaticPaths | Promise<StaticPaths>;
|
|
129
|
-
/** A route module
|
|
174
|
+
/** A route module - the default component + optional loader / action / meta. */
|
|
130
175
|
export interface RouteModule {
|
|
131
176
|
readonly default: unknown;
|
|
132
177
|
readonly loader?: Loader;
|
|
133
178
|
readonly action?: Action;
|
|
179
|
+
/** A Standard Schema validating this route's URL search params. When present, `ctx.search` is parsed +
|
|
180
|
+
* validated against it (failing closed to the schema's defaults on invalid input); type it into the
|
|
181
|
+
* loader with `LoaderArgs<Api, Env, typeof searchSchema>`. */
|
|
182
|
+
readonly searchSchema?: StandardSchemaV1<unknown, Record<string, unknown>>;
|
|
183
|
+
/** Search keys that are purely client-side UI (`"tab"`, a client-side `"sort"`, `"modal"`) and do NOT
|
|
184
|
+
* affect this route's loader. When a client navigation stays on the same route + pathname and changes
|
|
185
|
+
* ONLY these keys, the router updates the URL (so `useSearch` re-renders) WITHOUT re-running the loader.
|
|
186
|
+
* Any other key change revalidates as usual - so omitting a data-affecting key here can never serve
|
|
187
|
+
* stale data, it only forgoes the optimization. Client-only: the server always renders fresh. */
|
|
188
|
+
readonly searchClientKeys?: readonly string[];
|
|
134
189
|
readonly meta?: MetaInput;
|
|
135
190
|
/**
|
|
136
191
|
* Opt this route out of nifra's full-document client hydration. The server still renders the full
|
|
@@ -142,12 +197,12 @@ export interface RouteModule {
|
|
|
142
197
|
/**
|
|
143
198
|
* Opt a **static** route (no `:param`/`*`) into build-time prerendering (SSG): `prerenderRoutes`
|
|
144
199
|
* (from `@nifrajs/web/build`) renders it to a static `index.html` at build. The loader runs at build
|
|
145
|
-
* with the in-process `api` (build-safe data only
|
|
200
|
+
* with the in-process `api` (build-safe data only - no per-request cookies/secrets); `defer()` on a
|
|
146
201
|
* prerendered route resolves at build. For **dynamic** routes use {@link getStaticPaths} instead.
|
|
147
202
|
*/
|
|
148
203
|
readonly prerender?: boolean;
|
|
149
204
|
/**
|
|
150
|
-
* Enumerate the concrete params to prerender for a **dynamic** (`:param`) route
|
|
205
|
+
* Enumerate the concrete params to prerender for a **dynamic** (`:param`) route - the SSG path list
|
|
151
206
|
* (blogs/docs/etc.). Runs at build; `prerenderRoutes` renders one `index.html` per returned path.
|
|
152
207
|
*/
|
|
153
208
|
readonly getStaticPaths?: GetStaticPaths;
|
|
@@ -159,7 +214,7 @@ export interface RouteModule {
|
|
|
159
214
|
readonly revalidate?: number;
|
|
160
215
|
/**
|
|
161
216
|
* No-framework island bundles (`@nifrajs/web/islands`) to load on this route, as `<script
|
|
162
|
-
* type="module">` in the document tail. Loaded **regardless of `hydrate`**
|
|
217
|
+
* type="module">` in the document tail. Loaded **regardless of `hydrate`** - pair with
|
|
163
218
|
* `export const hydrate = false` for a static page that ships zero framework JS and mounts
|
|
164
219
|
* interactivity through `<Island>` markers + `mountIslands` enhancers instead.
|
|
165
220
|
*/
|
|
@@ -168,7 +223,7 @@ export interface RouteModule {
|
|
|
168
223
|
/** A layout (or `_404`/`_error`) entry: its source file (for client codegen) + a lazy loader.
|
|
169
224
|
*
|
|
170
225
|
* A `_layout.tsx` may export `meta` (static {@link Meta} or a function of the loader data + params,
|
|
171
|
-
* same shape as a route's) to contribute sitewide `<head>` tags
|
|
226
|
+
* same shape as a route's) to contribute sitewide `<head>` tags - `hreflang`/`preconnect`/etc. that
|
|
172
227
|
* belong on every page under the layout. The layout chain's heads merge with the page's: arrays
|
|
173
228
|
* (`meta`/`link`) concatenate outermost→innermost→page; scalars (`title`) are nearest-wins (the page
|
|
174
229
|
* overrides an inner layout, which overrides an outer one). See `mergeHeads` in `@nifrajs/web`. */
|
|
@@ -184,6 +239,16 @@ export interface RouteEntry {
|
|
|
184
239
|
readonly id: string;
|
|
185
240
|
readonly pattern: string;
|
|
186
241
|
readonly layoutIds: readonly string[];
|
|
242
|
+
/**
|
|
243
|
+
* Param names each layout in {@link layoutIds} owns, aligned by index.
|
|
244
|
+
*
|
|
245
|
+
* A layout wraps a URL prefix, so these are the params it may read and the ones whose change should
|
|
246
|
+
* re-run its loader - everything deeper belongs to routes beneath it. Derived per (route, layout)
|
|
247
|
+
* pair rather than per layout, because one layout can own different params on different expanded
|
|
248
|
+
* patterns: `[[lang]]/docs/_layout` owns nothing on `/docs/:slug` and `{lang}` on `/:lang/docs/:slug`.
|
|
249
|
+
* Optional only so hand-built test manifests may omit it.
|
|
250
|
+
*/
|
|
251
|
+
readonly layoutParams?: ReadonlyArray<readonly string[]>;
|
|
187
252
|
/** `_error` boundary ids in this route's ancestor chain (outermost → innermost). The last is the
|
|
188
253
|
* **nearest** boundary, rendered when the route's loader throws. Always set by `buildManifest`
|
|
189
254
|
* (optional only so hand-built test manifests may omit it); absent/empty ⇒ no boundary (error 500s). */
|
|
@@ -199,25 +264,41 @@ export interface Manifest {
|
|
|
199
264
|
* `buildManifest` (optional only so hand-built test manifests may omit it). */
|
|
200
265
|
readonly errors?: Readonly<Record<string, LayoutEntry>>;
|
|
201
266
|
readonly notFound?: LayoutEntry;
|
|
267
|
+
/** Per-status terminal pages from `_<status>.tsx` at the routes root (`_410`, `_451`, …), keyed by
|
|
268
|
+
* the status as a string. Rendered by a loader's `gone()` / `statusPage(n)`; a status with no page
|
|
269
|
+
* falls back to `_404`, and then to plain text. `_404` itself stays on {@link notFound} - it is
|
|
270
|
+
* reached by unmatched paths too, which these are not. */
|
|
271
|
+
readonly statusPages?: Readonly<Record<string, LayoutEntry>>;
|
|
202
272
|
}
|
|
203
|
-
/**
|
|
204
|
-
* Derive **every** nifra router pattern a route file maps to (relative to the routes dir):
|
|
205
|
-
* `index` → the parent path, `[id]` → `:id`, `[...slug]` → `*slug` (catch-all, captures the rest of
|
|
206
|
-
* the path into one param), `(group)` folders are dropped from the URL (organization only), and an
|
|
207
|
-
* optional `[[lang]]` expands the set — once with the segment present (`:lang`) and once absent. A file
|
|
208
|
-
* with no optionals yields exactly one pattern. Throws on an invalid param or a catch-all that isn't
|
|
209
|
-
* the last segment.
|
|
210
|
-
*/
|
|
211
273
|
export declare function filePathToPatterns(file: string): string[];
|
|
274
|
+
/** One URL pattern a route file expands to, plus the scope information layouts need. */
|
|
275
|
+
export interface FileRoutePattern {
|
|
276
|
+
readonly pattern: string;
|
|
277
|
+
/**
|
|
278
|
+
* URL segments contributed by the first *k* path parts of the file, indexed by *k*.
|
|
279
|
+
*
|
|
280
|
+
* This is what makes a layout's scope derivable without turning layouts into router nodes. A layout
|
|
281
|
+
* lives at a directory; the segments that directory contributes are exactly the prefix of the URL it
|
|
282
|
+
* wraps, so `depths[partsIn(layoutDir)]` is how many leading pattern segments it owns - and the
|
|
283
|
+
* params inside that prefix are the ones it may read.
|
|
284
|
+
*
|
|
285
|
+
* Per EXPANDED pattern, not per file, because the two diverge: `[[lang]]/docs/_layout` owns nothing
|
|
286
|
+
* on `/docs/:slug` and `{lang}` on `/:lang/docs/:slug`. A route group contributes no segment, so
|
|
287
|
+
* `(marketing)/_layout` owns nothing at all.
|
|
288
|
+
*/
|
|
289
|
+
readonly depths: readonly number[];
|
|
290
|
+
}
|
|
291
|
+
/** Expand a route file into its URL patterns, each carrying per-directory segment counts. */
|
|
292
|
+
export declare function filePathToRoutes(file: string): FileRoutePattern[];
|
|
212
293
|
/**
|
|
213
|
-
* The **canonical** single pattern for a route file
|
|
294
|
+
* The **canonical** single pattern for a route file - all optional segments present. A file with no
|
|
214
295
|
* optionals yields its one pattern. Use {@link filePathToPatterns} to get every pattern (optionals
|
|
215
296
|
* expand the set).
|
|
216
297
|
*/
|
|
217
298
|
export declare function filePathToPattern(file: string): string;
|
|
218
299
|
/**
|
|
219
300
|
* Build a manifest from route file paths (relative to the routes dir) + an `importer` that
|
|
220
|
-
* turns a path into a lazy module loader. Pure
|
|
301
|
+
* turns a path into a lazy module loader. Pure - no fs. Throws at boot (the loud-and-early
|
|
221
302
|
* RouteConfigError ethos) on duplicate patterns. `_layout`/`_404`/`_error` files are special; other
|
|
222
303
|
* `_`-prefixed files are ignored (private/colocated, never routed).
|
|
223
304
|
*/
|
|
@@ -235,14 +316,14 @@ export declare function fillRoutePattern(pattern: string, params: Record<string,
|
|
|
235
316
|
/** The static-routing facts a server needs from the route modules: which concrete paths are
|
|
236
317
|
* prerendered, plus each dynamic route's `getStaticPaths` fallback policy. */
|
|
237
318
|
export interface StaticRoutes {
|
|
238
|
-
/** Concrete prerendered paths
|
|
319
|
+
/** Concrete prerendered paths - static `prerender` routes + each `getStaticPaths` entry. */
|
|
239
320
|
readonly paths: string[];
|
|
240
321
|
/** Per dynamic route pattern, its `getStaticPaths` `fallback` (`"ssr"` default). `createWebApp` uses
|
|
241
322
|
* `"404"` to reject an unlisted path under that route at runtime (the path simply doesn't exist). */
|
|
242
323
|
readonly fallbacks: Record<string, "ssr" | "404">;
|
|
243
324
|
}
|
|
244
325
|
/**
|
|
245
|
-
* Enumerate the static-routing facts `prerenderRoutes` would produce
|
|
326
|
+
* Enumerate the static-routing facts `prerenderRoutes` would produce - static routes opted in via
|
|
246
327
|
* `export const prerender = true`, each `getStaticPaths` entry of a dynamic route, and each dynamic
|
|
247
328
|
* route's `fallback` policy. Pure (no rendering), so a server can compute what to hand `createWebApp`
|
|
248
329
|
* (the prerendered set for the client's static-`_data.json` soft-nav + the fallback map for
|
package/dist/manifest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
|
package/dist/manifest.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Route manifest — the fs-free heart of file-based routing. Maps route file paths to nifra
|
|
3
|
-
* router patterns + their nested layout chain. `discoverRoutes` (in `@nifrajs/web/fs`) scans
|
|
4
|
-
* the filesystem and feeds `buildManifest`; everything here is pure logic, so it stays
|
|
5
|
-
* portable (no fs, no DOM) and fully unit-testable. Edge deploys pre-build the manifest.
|
|
6
|
-
*/
|
|
7
1
|
// `.svelte` and `.vue` routes are supported too: their `default` export is the component and
|
|
8
2
|
// `loader`/`action`/`meta` come from a module-level script block (Svelte `<script module>`, Vue's plain
|
|
9
|
-
// `<script>`) as named ESM exports
|
|
3
|
+
// `<script>`) as named ESM exports - the same RouteModule shape as `.tsx`. Both compile via their
|
|
10
4
|
// package's Bun plugin (`@nifrajs/web-svelte/plugin`, `@nifrajs/web-vue/plugin`).
|
|
11
5
|
const ROUTE_EXT = /\.(tsx|jsx|svelte|vue|mdx)$/;
|
|
12
6
|
const PARAM = /^\[([A-Za-z_][A-Za-z0-9_]*)\]$/;
|
|
@@ -15,8 +9,12 @@ const CATCH_ALL = /^\[\.\.\.([A-Za-z_][A-Za-z0-9_]*)\]$/;
|
|
|
15
9
|
// two patterns (`:lang` present / absent), so `[[lang]]/about` serves both `/about` and `/en/about`.
|
|
16
10
|
const OPTIONAL = /^\[\[([A-Za-z_][A-Za-z0-9_]*)\]\]$/;
|
|
17
11
|
// A route group: a `(name)` folder organizes routes (and can hold its own `_layout`) without
|
|
18
|
-
// contributing a URL segment
|
|
12
|
+
// contributing a URL segment - mirrors Next/Remix. Requires content between the parens.
|
|
19
13
|
const GROUP = /^\(.+\)$/;
|
|
14
|
+
// A terminal status page: `_410.tsx`, `_451.tsx`. `_404` is matched before this and stays its own
|
|
15
|
+
// thing. Restricted to 3 digits so `_401k` or `_4` is treated as an ordinary underscore-prefixed
|
|
16
|
+
// file (ignored) rather than silently becoming a status page.
|
|
17
|
+
const STATUS_PAGE = /^_[1-5][0-9][0-9]$/;
|
|
20
18
|
const stripExt = (file) => file.replace(ROUTE_EXT, "");
|
|
21
19
|
const baseName = (file) => file.slice(file.lastIndexOf("/") + 1);
|
|
22
20
|
const dirOf = (file) => file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
|
|
@@ -26,20 +24,92 @@ const errorIdFor = (dir) => (dir === "" ? "_error" : `${dir}/_error`);
|
|
|
26
24
|
* Derive **every** nifra router pattern a route file maps to (relative to the routes dir):
|
|
27
25
|
* `index` → the parent path, `[id]` → `:id`, `[...slug]` → `*slug` (catch-all, captures the rest of
|
|
28
26
|
* the path into one param), `(group)` folders are dropped from the URL (organization only), and an
|
|
29
|
-
* optional `[[lang]]` expands the set
|
|
27
|
+
* optional `[[lang]]` expands the set - once with the segment present (`:lang`) and once absent. A file
|
|
30
28
|
* with no optionals yields exactly one pattern. Throws on an invalid param or a catch-all that isn't
|
|
31
29
|
* the last segment.
|
|
32
30
|
*/
|
|
31
|
+
/** Every `[…]` marker in a file segment, so a part-literal segment can be spliced into a pattern. */
|
|
32
|
+
const FILE_MARKER = /\[([^\]]*)\]/g;
|
|
33
|
+
const FILE_PARAM_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
34
|
+
/**
|
|
35
|
+
* Translate a part-literal-part-`[param]` file segment into its route pattern, or `undefined` when
|
|
36
|
+
* the segment is wholly literal or wholly a marker (both already handled by the caller).
|
|
37
|
+
*
|
|
38
|
+
* `[inKey].txt` becomes `:inKey.txt`. Needed because IndexNow-style verification files, per-locale
|
|
39
|
+
* sitemaps, and static-site URL parity (`post-[id].html`) all put a variable INSIDE a filename, and
|
|
40
|
+
* `stripExt` already reduces `[inKey].txt.tsx` to `[inKey].txt` - `robots.txt.tsx` proves literal
|
|
41
|
+
* dots survive into a segment, so this was only ever a parser gap.
|
|
42
|
+
*
|
|
43
|
+
* `[[optional]]` and `[...catchAll]` are **rejected** inside a mixed segment rather than given a
|
|
44
|
+
* meaning. There is no sensible absent form for `/[[locale]]-feed.xml` - `/-feed.xml` and dropping
|
|
45
|
+
* the segment are both surprising - and a catch-all captures the remaining path verbatim, which
|
|
46
|
+
* conflicts with a trailing literal. A clear rejection beats semantics nobody can predict.
|
|
47
|
+
*/
|
|
48
|
+
function mixedFileSegment(seg, file) {
|
|
49
|
+
// A segment that is ENTIRELY one of the existing whole-segment forms belongs to the caller, not
|
|
50
|
+
// here. Checked against the real patterns rather than by comparing the first marker to the
|
|
51
|
+
// segment: `[[lang]]` does not equal its own first `[…]` match, so a naive comparison sends every
|
|
52
|
+
// optional segment down the mixed path and rejects it.
|
|
53
|
+
if (PARAM.test(seg) || OPTIONAL.test(seg) || CATCH_ALL.test(seg))
|
|
54
|
+
return undefined;
|
|
55
|
+
const markers = [...seg.matchAll(FILE_MARKER)];
|
|
56
|
+
if (markers.length === 0)
|
|
57
|
+
return undefined;
|
|
58
|
+
let pattern = "";
|
|
59
|
+
let cursor = 0;
|
|
60
|
+
for (const marker of markers) {
|
|
61
|
+
const inner = marker[1] ?? "";
|
|
62
|
+
if (inner.startsWith("...")) {
|
|
63
|
+
throw new Error(`[nifra/web] catch-all "[${inner}]" cannot be combined with literal text in "${file}" (segment "${seg}") - a catch-all captures the rest of the path, so a trailing literal can never match. Give it its own segment.`);
|
|
64
|
+
}
|
|
65
|
+
if (inner.startsWith("[")) {
|
|
66
|
+
throw new Error(`[nifra/web] optional "[${inner}]]" cannot be combined with literal text in "${file}" (segment "${seg}") - there is no sensible form for the segment when it is absent. Use a required [name], or give the optional its own segment.`);
|
|
67
|
+
}
|
|
68
|
+
if (!FILE_PARAM_NAME.test(inner)) {
|
|
69
|
+
throw new Error(`[nifra/web] invalid route param in "${file}": "[${inner}]" must be [name], [[name]], [...name], or a (group) folder`);
|
|
70
|
+
}
|
|
71
|
+
pattern += `${seg.slice(cursor, marker.index)}:${inner}`;
|
|
72
|
+
cursor = (marker.index ?? 0) + marker[0].length;
|
|
73
|
+
}
|
|
74
|
+
return pattern + seg.slice(cursor);
|
|
75
|
+
}
|
|
33
76
|
export function filePathToPatterns(file) {
|
|
34
|
-
|
|
35
|
-
|
|
77
|
+
return filePathToRoutes(file).map((route) => route.pattern);
|
|
78
|
+
}
|
|
79
|
+
/** Expand a route file into its URL patterns, each carrying per-directory segment counts. */
|
|
80
|
+
export function filePathToRoutes(file) {
|
|
81
|
+
// Each combo is the URL segments built so far, plus the running per-path-part segment count.
|
|
82
|
+
let combos = [{ segs: [], depths: [0] }];
|
|
36
83
|
const segments = stripExt(file).split("/");
|
|
84
|
+
// Record the running segment count after each path part, on every combo. Called once per part so
|
|
85
|
+
// `depths` stays index-aligned with the file's path parts even for parts that emit nothing.
|
|
86
|
+
const mark = () => {
|
|
87
|
+
for (const combo of combos)
|
|
88
|
+
combo.depths.push(combo.segs.length);
|
|
89
|
+
};
|
|
90
|
+
const push = (segment) => {
|
|
91
|
+
for (const combo of combos)
|
|
92
|
+
combo.segs.push(segment);
|
|
93
|
+
};
|
|
37
94
|
for (let i = 0; i < segments.length; i++) {
|
|
38
95
|
const seg = segments[i];
|
|
39
|
-
if (seg === "index")
|
|
96
|
+
if (seg === "index") {
|
|
97
|
+
mark();
|
|
40
98
|
continue;
|
|
41
|
-
|
|
42
|
-
|
|
99
|
+
}
|
|
100
|
+
if (GROUP.test(seg)) {
|
|
101
|
+
// Route group - no URL segment (its `_layout` still applies, keyed by dir).
|
|
102
|
+
mark();
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
// A segment that is part literal, part `[param]` - `[inKey].txt`, `post-[id].html`. Checked
|
|
106
|
+
// before the whole-segment forms below, which only handle a marker spanning the entire segment.
|
|
107
|
+
const mixed = mixedFileSegment(seg, file);
|
|
108
|
+
if (mixed !== undefined) {
|
|
109
|
+
push(mixed);
|
|
110
|
+
mark();
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
43
113
|
if (seg.startsWith("[")) {
|
|
44
114
|
const catchAll = CATCH_ALL.exec(seg);
|
|
45
115
|
if (catchAll !== null) {
|
|
@@ -49,29 +119,35 @@ export function filePathToPatterns(file) {
|
|
|
49
119
|
if (after.length > 0) {
|
|
50
120
|
throw new Error(`[nifra/web] catch-all "${seg}" must be the last segment in "${file}" (found "${after.join("/")}" after it)`);
|
|
51
121
|
}
|
|
52
|
-
|
|
122
|
+
push(`*${catchAll[1]}`);
|
|
123
|
+
mark();
|
|
53
124
|
continue;
|
|
54
125
|
}
|
|
55
126
|
const optional = OPTIONAL.exec(seg);
|
|
56
127
|
if (optional !== null) {
|
|
57
128
|
// Optional segment: keep each existing combo (absent) AND a copy with `:name` appended (present).
|
|
58
|
-
combos = combos.flatMap((c) => [
|
|
129
|
+
combos = combos.flatMap((c) => [
|
|
130
|
+
{ segs: c.segs, depths: c.depths },
|
|
131
|
+
{ segs: [...c.segs, `:${optional[1]}`], depths: [...c.depths] },
|
|
132
|
+
]);
|
|
133
|
+
mark();
|
|
59
134
|
continue;
|
|
60
135
|
}
|
|
61
136
|
const match = PARAM.exec(seg);
|
|
62
137
|
if (match === null) {
|
|
63
138
|
throw new Error(`[nifra/web] invalid route param in "${file}": "${seg}" must be [name], [[name]], [...name], or a (group) folder`);
|
|
64
139
|
}
|
|
65
|
-
|
|
140
|
+
push(`:${match[1]}`);
|
|
66
141
|
}
|
|
67
142
|
else {
|
|
68
|
-
|
|
143
|
+
push(seg);
|
|
69
144
|
}
|
|
145
|
+
mark();
|
|
70
146
|
}
|
|
71
|
-
return combos.map((c) => `/${c.join("/")}
|
|
147
|
+
return combos.map((c) => ({ pattern: `/${c.segs.join("/")}`, depths: c.depths }));
|
|
72
148
|
}
|
|
73
149
|
/**
|
|
74
|
-
* The **canonical** single pattern for a route file
|
|
150
|
+
* The **canonical** single pattern for a route file - all optional segments present. A file with no
|
|
75
151
|
* optionals yields its one pattern. Use {@link filePathToPatterns} to get every pattern (optionals
|
|
76
152
|
* expand the set).
|
|
77
153
|
*/
|
|
@@ -80,6 +156,26 @@ export function filePathToPattern(file) {
|
|
|
80
156
|
return patterns[patterns.length - 1];
|
|
81
157
|
}
|
|
82
158
|
/** The dir chain from root → the file's own dir, e.g. "a/b/p.tsx" → ["", "a", "a/b"]. */
|
|
159
|
+
/**
|
|
160
|
+
* Param names in the first `count` segments of a pattern - the params a layout at that depth owns.
|
|
161
|
+
*
|
|
162
|
+
* This is the whole scoping mechanism: a layout wraps a URL prefix, so the params inside that prefix
|
|
163
|
+
* are the ones it may read, and the ones whose change should re-run its loader. Everything deeper
|
|
164
|
+
* belongs to routes below it and must not invalidate it.
|
|
165
|
+
*/
|
|
166
|
+
function paramsInPrefix(pattern, count) {
|
|
167
|
+
if (count === 0)
|
|
168
|
+
return [];
|
|
169
|
+
const names = [];
|
|
170
|
+
const segments = pattern === "/" ? [] : pattern.slice(1).split("/");
|
|
171
|
+
for (const segment of segments.slice(0, count)) {
|
|
172
|
+
// Mirrors the router's own grammar: `:name`, `*name`, and a mixed segment's embedded `:name`s.
|
|
173
|
+
for (const match of segment.matchAll(/[:*]([A-Za-z_][A-Za-z0-9_]*)/g)) {
|
|
174
|
+
names.push(match[1]);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return names;
|
|
178
|
+
}
|
|
83
179
|
const ancestorDirs = (file) => {
|
|
84
180
|
const dirs = [""];
|
|
85
181
|
const dir = dirOf(file);
|
|
@@ -94,7 +190,7 @@ const ancestorDirs = (file) => {
|
|
|
94
190
|
};
|
|
95
191
|
/**
|
|
96
192
|
* Build a manifest from route file paths (relative to the routes dir) + an `importer` that
|
|
97
|
-
* turns a path into a lazy module loader. Pure
|
|
193
|
+
* turns a path into a lazy module loader. Pure - no fs. Throws at boot (the loud-and-early
|
|
98
194
|
* RouteConfigError ethos) on duplicate patterns. `_layout`/`_404`/`_error` files are special; other
|
|
99
195
|
* `_`-prefixed files are ignored (private/colocated, never routed).
|
|
100
196
|
*/
|
|
@@ -104,6 +200,7 @@ export function buildManifest(files, importer) {
|
|
|
104
200
|
const errorDirs = new Set();
|
|
105
201
|
const errors = {};
|
|
106
202
|
let notFound;
|
|
203
|
+
const statusPages = {};
|
|
107
204
|
const routeFiles = [];
|
|
108
205
|
for (const file of files) {
|
|
109
206
|
const stem = stripExt(baseName(file));
|
|
@@ -120,6 +217,12 @@ export function buildManifest(files, importer) {
|
|
|
120
217
|
else if (stem === "_404") {
|
|
121
218
|
notFound = { file, load: importer(file) };
|
|
122
219
|
}
|
|
220
|
+
else if (STATUS_PAGE.test(stem) && dirOf(file) === "") {
|
|
221
|
+
// `_410.tsx`, `_451.tsx`, … at the routes root. Root-only: unlike `_error`, these are not
|
|
222
|
+
// resolved per segment - a terminal status is a property of the outcome, not of where in the
|
|
223
|
+
// tree the route lives, and a per-segment variant would need a precedence rule nobody asked for.
|
|
224
|
+
statusPages[stem.slice(1)] = { file, load: importer(file) };
|
|
225
|
+
}
|
|
123
226
|
else if (!stem.startsWith("_")) {
|
|
124
227
|
routeFiles.push(file);
|
|
125
228
|
}
|
|
@@ -128,22 +231,29 @@ export function buildManifest(files, importer) {
|
|
|
128
231
|
const routes = [];
|
|
129
232
|
for (const file of routeFiles) {
|
|
130
233
|
const dirs = ancestorDirs(file);
|
|
131
|
-
const
|
|
234
|
+
const layoutDirsForFile = dirs.filter((dir) => layoutDirs.has(dir));
|
|
235
|
+
const layoutIds = layoutDirsForFile.map(layoutIdFor);
|
|
132
236
|
const errorIds = dirs.filter((dir) => errorDirs.has(dir)).map(errorIdFor);
|
|
133
237
|
const id = stripExt(file);
|
|
134
238
|
const load = importer(file); // one lazy loader per file, shared by its (possibly expanded) patterns
|
|
135
239
|
// An optional `[[x]]` segment expands a file into multiple patterns, all pointing at the same
|
|
136
240
|
// module (same id/load/layout chain). Distinct patterns ⇒ no match ambiguity (different lengths).
|
|
137
|
-
for (const pattern of
|
|
241
|
+
for (const { pattern, depths } of filePathToRoutes(file)) {
|
|
138
242
|
const existing = byPattern.get(pattern);
|
|
139
243
|
if (existing !== undefined) {
|
|
140
244
|
throw new Error(`[nifra/web] duplicate route: "${file}" and "${existing}" both map to "${pattern}"`);
|
|
141
245
|
}
|
|
142
246
|
byPattern.set(pattern, file);
|
|
143
|
-
|
|
247
|
+
const layoutParams = layoutDirsForFile.map((dir) => paramsInPrefix(pattern, depths[dir === "" ? 0 : dir.split("/").length] ?? 0));
|
|
248
|
+
routes.push({ id, pattern, layoutIds, layoutParams, errorIds, file, load });
|
|
144
249
|
}
|
|
145
250
|
}
|
|
146
|
-
const base = {
|
|
251
|
+
const base = {
|
|
252
|
+
routes,
|
|
253
|
+
layouts,
|
|
254
|
+
errors,
|
|
255
|
+
...(Object.keys(statusPages).length > 0 ? { statusPages } : {}),
|
|
256
|
+
};
|
|
147
257
|
return notFound === undefined ? base : { ...base, notFound };
|
|
148
258
|
}
|
|
149
259
|
const FILL_PARAM = /:([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
@@ -173,7 +283,7 @@ export function fillRoutePattern(pattern, params) {
|
|
|
173
283
|
return { path, missing };
|
|
174
284
|
}
|
|
175
285
|
/**
|
|
176
|
-
* Enumerate the static-routing facts `prerenderRoutes` would produce
|
|
286
|
+
* Enumerate the static-routing facts `prerenderRoutes` would produce - static routes opted in via
|
|
177
287
|
* `export const prerender = true`, each `getStaticPaths` entry of a dynamic route, and each dynamic
|
|
178
288
|
* route's `fallback` policy. Pure (no rendering), so a server can compute what to hand `createWebApp`
|
|
179
289
|
* (the prerendered set for the client's static-`_data.json` soft-nav + the fallback map for
|