@reckona/mreact-router 0.0.175 → 0.0.177
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +5 -49
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/build.d.ts +1 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +90 -0
- package/dist/build.js.map +1 -1
- package/dist/built-assets.d.ts +11 -0
- package/dist/built-assets.d.ts.map +1 -0
- package/dist/built-assets.js +130 -0
- package/dist/built-assets.js.map +1 -0
- package/dist/built-runtime.d.ts +53 -0
- package/dist/built-runtime.d.ts.map +1 -0
- package/dist/built-runtime.js +178 -0
- package/dist/built-runtime.js.map +1 -0
- package/dist/built-server-module-artifacts.d.ts +19 -0
- package/dist/built-server-module-artifacts.d.ts.map +1 -0
- package/dist/built-server-module-artifacts.js +217 -0
- package/dist/built-server-module-artifacts.js.map +1 -0
- package/dist/client-manifest-assets.d.ts +16 -0
- package/dist/client-manifest-assets.d.ts.map +1 -0
- package/dist/client-manifest-assets.js +60 -0
- package/dist/client-manifest-assets.js.map +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +79 -13
- package/dist/client.js.map +1 -1
- package/dist/node-server.d.ts +29 -0
- package/dist/node-server.d.ts.map +1 -0
- package/dist/node-server.js +81 -0
- package/dist/node-server.js.map +1 -0
- package/dist/render.d.ts +9 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +20 -41
- package/dist/render.js.map +1 -1
- package/dist/route-dispatch.d.ts +2 -0
- package/dist/route-dispatch.d.ts.map +1 -0
- package/dist/route-dispatch.js +16 -0
- package/dist/route-dispatch.js.map +1 -0
- package/dist/route-loader-runtime.d.ts +22 -0
- package/dist/route-loader-runtime.d.ts.map +1 -0
- package/dist/route-loader-runtime.js +30 -0
- package/dist/route-loader-runtime.js.map +1 -0
- package/dist/routes.d.ts +2 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +48 -1
- package/dist/routes.js.map +1 -1
- package/dist/serve.d.ts +4 -3
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +84 -582
- package/dist/serve.js.map +1 -1
- package/package.json +11 -11
- package/src/adapters/cloudflare.ts +5 -60
- package/src/build.ts +143 -0
- package/src/built-assets.ts +164 -0
- package/src/built-runtime.ts +312 -0
- package/src/built-server-module-artifacts.ts +340 -0
- package/src/client-manifest-assets.ts +92 -0
- package/src/client.ts +79 -13
- package/src/node-server.ts +129 -0
- package/src/render.ts +38 -60
- package/src/route-dispatch.ts +17 -0
- package/src/route-loader-runtime.ts +54 -0
- package/src/routes.ts +70 -1
- package/src/serve.ts +105 -863
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, isAbsolute, join, normalize, sep } from "node:path";
|
|
3
|
+
import type { BuiltServerModuleArtifact } from "./build.js";
|
|
4
|
+
import { routeShellCandidates } from "./route-shells.js";
|
|
5
|
+
|
|
6
|
+
export interface BuiltServerModuleArtifactRuntime {
|
|
7
|
+
appDir: string;
|
|
8
|
+
serverModuleArtifactLoads: Map<string, Promise<void>>;
|
|
9
|
+
serverModuleClosureFiles: Map<string, readonly string[]>;
|
|
10
|
+
serverModuleFiles: ReadonlyMap<string, string>;
|
|
11
|
+
serverModuleRenderFiles: ReadonlyMap<string, string>;
|
|
12
|
+
serverModuleRequestFiles: ReadonlyMap<string, string>;
|
|
13
|
+
serverModules: Map<string, BuiltServerModuleArtifact>;
|
|
14
|
+
serverSourceFiles: ReadonlyMap<string, string>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type BuiltServerModuleArtifactKind = "all" | "render" | "request";
|
|
18
|
+
|
|
19
|
+
export async function loadBuiltServerModuleArtifacts(
|
|
20
|
+
runtime: BuiltServerModuleArtifactRuntime,
|
|
21
|
+
files: Iterable<string>,
|
|
22
|
+
kind: BuiltServerModuleArtifactKind = "all",
|
|
23
|
+
): Promise<void> {
|
|
24
|
+
for (const file of files) {
|
|
25
|
+
await loadBuiltServerModuleArtifact(runtime, file, kind);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function loadBuiltServerModuleArtifact(
|
|
30
|
+
runtime: BuiltServerModuleArtifactRuntime,
|
|
31
|
+
file: string,
|
|
32
|
+
kind: BuiltServerModuleArtifactKind = "all",
|
|
33
|
+
): Promise<void> {
|
|
34
|
+
if (kind === "all") {
|
|
35
|
+
await loadBuiltServerModuleArtifact(runtime, file, "request");
|
|
36
|
+
await loadBuiltServerModuleArtifact(runtime, file, "render");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const artifactPath =
|
|
41
|
+
kind === "request"
|
|
42
|
+
? runtime.serverModuleRequestFiles.get(file) ?? runtime.serverModuleFiles.get(file)
|
|
43
|
+
: runtime.serverModuleRenderFiles.get(file) ?? runtime.serverModuleFiles.get(file);
|
|
44
|
+
|
|
45
|
+
if (artifactPath === undefined) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const cached = runtime.serverModuleArtifactLoads.get(`${kind}\0${file}`);
|
|
50
|
+
|
|
51
|
+
if (cached !== undefined) {
|
|
52
|
+
await cached;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const loaded = readFile(artifactPath, "utf8")
|
|
57
|
+
.then((text) => {
|
|
58
|
+
const existing = runtime.serverModules.get(file) ?? {};
|
|
59
|
+
runtime.serverModules.set(
|
|
60
|
+
file,
|
|
61
|
+
mergeBuiltServerModuleArtifacts(
|
|
62
|
+
existing,
|
|
63
|
+
hydrateBuiltServerModuleArtifact(
|
|
64
|
+
parseBuiltJsonArtifact<BuiltServerModuleArtifact>(
|
|
65
|
+
text,
|
|
66
|
+
artifactPath,
|
|
67
|
+
`built server module artifact for ${file}`,
|
|
68
|
+
),
|
|
69
|
+
builtServerDirForArtifactPath(artifactPath),
|
|
70
|
+
),
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
})
|
|
74
|
+
.catch((error) => {
|
|
75
|
+
runtime.serverModuleArtifactLoads.delete(`${kind}\0${file}`);
|
|
76
|
+
if (isMissingFileError(error)) {
|
|
77
|
+
throw builtArtifactReadError(`built server module artifact for ${file}`, artifactPath, error);
|
|
78
|
+
}
|
|
79
|
+
throw error;
|
|
80
|
+
});
|
|
81
|
+
runtime.serverModuleArtifactLoads.set(`${kind}\0${file}`, loaded);
|
|
82
|
+
|
|
83
|
+
await loaded;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function allBuiltServerModuleFiles(
|
|
87
|
+
runtime: BuiltServerModuleArtifactRuntime,
|
|
88
|
+
): Iterable<string> {
|
|
89
|
+
return new Set([
|
|
90
|
+
...runtime.serverModuleFiles.keys(),
|
|
91
|
+
...runtime.serverModuleRequestFiles.keys(),
|
|
92
|
+
...runtime.serverModuleRenderFiles.keys(),
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function builtServerDirForArtifactPath(artifactPath: string): string {
|
|
97
|
+
const marker = `${sep}server-modules${sep}`;
|
|
98
|
+
const index = artifactPath.lastIndexOf(marker);
|
|
99
|
+
|
|
100
|
+
return index === -1 ? dirname(dirname(artifactPath)) : artifactPath.slice(0, index);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function hydrateBuiltServerModuleArtifact(
|
|
104
|
+
artifact: BuiltServerModuleArtifact,
|
|
105
|
+
serverDir: string,
|
|
106
|
+
): BuiltServerModuleArtifact {
|
|
107
|
+
return {
|
|
108
|
+
...(artifact.analysis === undefined ? {} : { analysis: artifact.analysis }),
|
|
109
|
+
...(artifact.loader === undefined
|
|
110
|
+
? {}
|
|
111
|
+
: { loader: hydrateBuiltServerModuleOutput(artifact.loader, serverDir) }),
|
|
112
|
+
...(artifact.routeMetadata === undefined
|
|
113
|
+
? {}
|
|
114
|
+
: { routeMetadata: hydrateBuiltServerModuleOutput(artifact.routeMetadata, serverDir) }),
|
|
115
|
+
...(artifact.request === undefined
|
|
116
|
+
? {}
|
|
117
|
+
: { request: hydrateBuiltServerModuleOutput(artifact.request, serverDir) }),
|
|
118
|
+
...(artifact.stream === undefined
|
|
119
|
+
? {}
|
|
120
|
+
: { stream: hydrateBuiltServerModuleOutput(artifact.stream, serverDir) }),
|
|
121
|
+
...(artifact.string === undefined
|
|
122
|
+
? {}
|
|
123
|
+
: { string: hydrateBuiltServerModuleOutput(artifact.string, serverDir) }),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function hydrateBuiltServerModuleOutput<T extends { moduleFile?: string | undefined }>(
|
|
128
|
+
output: T,
|
|
129
|
+
serverDir: string,
|
|
130
|
+
): T {
|
|
131
|
+
if (output.moduleFile === undefined || isAbsolute(output.moduleFile)) {
|
|
132
|
+
return output;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
...output,
|
|
137
|
+
moduleFile: join(serverDir, safeBuiltServerManifestFilePath(output.moduleFile)),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function mergeBuiltServerModuleArtifacts(
|
|
142
|
+
existing: BuiltServerModuleArtifact,
|
|
143
|
+
loaded: BuiltServerModuleArtifact,
|
|
144
|
+
): BuiltServerModuleArtifact {
|
|
145
|
+
return {
|
|
146
|
+
...existing,
|
|
147
|
+
...loaded,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function loadBuiltServerModuleArtifactsForRequest(
|
|
152
|
+
runtime: BuiltServerModuleArtifactRuntime,
|
|
153
|
+
routeFile: string | undefined,
|
|
154
|
+
options: {
|
|
155
|
+
includeRender?: boolean | undefined;
|
|
156
|
+
includeShells?: boolean | undefined;
|
|
157
|
+
} = {},
|
|
158
|
+
): Promise<void> {
|
|
159
|
+
const roots = [
|
|
160
|
+
join(runtime.appDir, "middleware.ts"),
|
|
161
|
+
join(runtime.appDir, "middleware.mreact.ts"),
|
|
162
|
+
...(routeFile === undefined
|
|
163
|
+
? []
|
|
164
|
+
: [
|
|
165
|
+
routeFile,
|
|
166
|
+
...(options.includeShells === false ? [] : shellFilesForRoute(runtime, routeFile)),
|
|
167
|
+
]),
|
|
168
|
+
];
|
|
169
|
+
const seen = new Set<string>();
|
|
170
|
+
|
|
171
|
+
for (const file of roots) {
|
|
172
|
+
await loadBuiltServerModuleArtifactClosure(runtime, file, seen, "request");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (options.includeRender === true) {
|
|
176
|
+
seen.clear();
|
|
177
|
+
for (const file of roots) {
|
|
178
|
+
await loadBuiltServerModuleArtifactClosure(runtime, file, seen, "render");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function loadBuiltServerModuleArtifactClosure(
|
|
184
|
+
runtime: BuiltServerModuleArtifactRuntime,
|
|
185
|
+
file: string,
|
|
186
|
+
seen: Set<string>,
|
|
187
|
+
kind: BuiltServerModuleArtifactKind,
|
|
188
|
+
): Promise<void> {
|
|
189
|
+
for (const closureFile of builtServerModuleClosureFiles(runtime, file)) {
|
|
190
|
+
if (seen.has(closureFile)) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
seen.add(closureFile);
|
|
194
|
+
await loadBuiltServerModuleArtifact(runtime, closureFile, kind);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function builtServerModuleClosureFiles(
|
|
199
|
+
runtime: BuiltServerModuleArtifactRuntime,
|
|
200
|
+
file: string,
|
|
201
|
+
): readonly string[] {
|
|
202
|
+
const cached = runtime.serverModuleClosureFiles.get(file);
|
|
203
|
+
if (cached !== undefined) {
|
|
204
|
+
return cached;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const closure: string[] = [];
|
|
208
|
+
collectBuiltServerModuleClosureFiles(runtime, file, new Set(), closure);
|
|
209
|
+
runtime.serverModuleClosureFiles.set(file, closure);
|
|
210
|
+
return closure;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function collectBuiltServerModuleClosureFiles(
|
|
214
|
+
runtime: BuiltServerModuleArtifactRuntime,
|
|
215
|
+
file: string,
|
|
216
|
+
seen: Set<string>,
|
|
217
|
+
closure: string[],
|
|
218
|
+
): void {
|
|
219
|
+
if (seen.has(file)) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
seen.add(file);
|
|
223
|
+
closure.push(file);
|
|
224
|
+
|
|
225
|
+
const source = runtime.serverSourceFiles.get(file);
|
|
226
|
+
if (source === undefined) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
for (const specifier of localServerModuleSpecifiers(source)) {
|
|
231
|
+
const resolved = resolveBuiltLocalServerSourceImport(runtime, file, specifier);
|
|
232
|
+
if (resolved !== undefined) {
|
|
233
|
+
collectBuiltServerModuleClosureFiles(runtime, resolved, seen, closure);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const localServerModuleImportPattern =
|
|
239
|
+
/\b(?:import|export)\s+(?:type\s+)?(?:[^"']*?\s+from\s*)?["'](?<source>\.{1,2}\/[^"']+)["']/g;
|
|
240
|
+
|
|
241
|
+
function localServerModuleSpecifiers(code: string): string[] {
|
|
242
|
+
const specifiers = new Set<string>();
|
|
243
|
+
localServerModuleImportPattern.lastIndex = 0;
|
|
244
|
+
|
|
245
|
+
for (const match of code.matchAll(localServerModuleImportPattern)) {
|
|
246
|
+
const source = match.groups?.source;
|
|
247
|
+
|
|
248
|
+
if (source !== undefined) {
|
|
249
|
+
specifiers.add(source);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return Array.from(specifiers);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function resolveBuiltLocalServerSourceImport(
|
|
257
|
+
runtime: BuiltServerModuleArtifactRuntime,
|
|
258
|
+
fromFile: string,
|
|
259
|
+
specifier: string,
|
|
260
|
+
): string | undefined {
|
|
261
|
+
const base = join(dirname(fromFile), specifier);
|
|
262
|
+
|
|
263
|
+
for (const candidate of localServerSourceImportCandidates(base)) {
|
|
264
|
+
if (runtime.serverSourceFiles.has(candidate)) {
|
|
265
|
+
return candidate;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function localServerSourceImportCandidates(base: string): string[] {
|
|
273
|
+
const candidates = [base];
|
|
274
|
+
|
|
275
|
+
if (base.endsWith(".js")) {
|
|
276
|
+
const withoutJs = base.slice(0, -".js".length);
|
|
277
|
+
candidates.push(`${withoutJs}.ts`, `${withoutJs}.tsx`, `${withoutJs}.mreact.tsx`);
|
|
278
|
+
} else if (base.endsWith(".jsx")) {
|
|
279
|
+
const withoutJsx = base.slice(0, -".jsx".length);
|
|
280
|
+
candidates.push(`${withoutJsx}.tsx`, `${withoutJsx}.mreact.tsx`);
|
|
281
|
+
} else if (base.endsWith(".mreact")) {
|
|
282
|
+
candidates.push(`${base}.tsx`);
|
|
283
|
+
} else {
|
|
284
|
+
candidates.push(
|
|
285
|
+
`${base}.ts`,
|
|
286
|
+
`${base}.tsx`,
|
|
287
|
+
`${base}.mreact.tsx`,
|
|
288
|
+
join(base, "index.ts"),
|
|
289
|
+
join(base, "index.tsx"),
|
|
290
|
+
join(base, "index.mreact.tsx"),
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return candidates;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function shellFilesForRoute(
|
|
298
|
+
runtime: BuiltServerModuleArtifactRuntime,
|
|
299
|
+
routeFile: string,
|
|
300
|
+
): string[] {
|
|
301
|
+
return routeShellCandidates(runtime.appDir, routeFile)
|
|
302
|
+
.map((candidate) => candidate.file)
|
|
303
|
+
.filter((file) => runtime.serverSourceFiles.has(file));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function isMissingFileError(error: unknown): boolean {
|
|
307
|
+
return (
|
|
308
|
+
typeof error === "object" &&
|
|
309
|
+
error !== null &&
|
|
310
|
+
"code" in error &&
|
|
311
|
+
(error as { code?: unknown }).code === "ENOENT"
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function builtArtifactReadError(label: string, artifactPath: string, error: unknown): Error {
|
|
316
|
+
const prefix = isMissingFileError(error) ? "Missing" : "Unable to read";
|
|
317
|
+
const detail = error instanceof Error && error.message !== "" ? `: ${error.message}` : "";
|
|
318
|
+
|
|
319
|
+
return new Error(`${prefix} ${label}: ${artifactPath}${detail}`, { cause: error });
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function parseBuiltJsonArtifact<T>(text: string, artifactPath: string, label: string): T {
|
|
323
|
+
try {
|
|
324
|
+
return JSON.parse(text) as T;
|
|
325
|
+
} catch (error) {
|
|
326
|
+
const detail = error instanceof Error && error.message !== "" ? `: ${error.message}` : "";
|
|
327
|
+
|
|
328
|
+
throw new Error(`Invalid ${label}: ${artifactPath}${detail}`, { cause: error });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function safeBuiltServerManifestFilePath(pathname: string): string {
|
|
333
|
+
const normalized = normalize(pathname);
|
|
334
|
+
|
|
335
|
+
if (isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../")) {
|
|
336
|
+
throw new Error(`Invalid built app manifest file path: ${pathname}`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return normalized;
|
|
340
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export interface ClientManifestAssetRoute {
|
|
2
|
+
css?: readonly string[] | undefined;
|
|
3
|
+
imports?: readonly string[] | undefined;
|
|
4
|
+
navigationScript?: string | undefined;
|
|
5
|
+
script?: string | undefined;
|
|
6
|
+
sourceMap?: string | undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ClientManifestAssetSource {
|
|
10
|
+
assets?: readonly string[] | undefined;
|
|
11
|
+
routes: readonly ClientManifestAssetRoute[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function clientManifestAssetPaths(
|
|
15
|
+
manifest: ClientManifestAssetSource,
|
|
16
|
+
options: {
|
|
17
|
+
extraPaths?: readonly string[] | undefined;
|
|
18
|
+
prefix?: string | undefined;
|
|
19
|
+
} = {},
|
|
20
|
+
): Set<string> {
|
|
21
|
+
const prefix = normalizeClientManifestAssetPrefix(options.prefix ?? "");
|
|
22
|
+
const paths = new Set<string>([`${prefix}manifest.json`]);
|
|
23
|
+
|
|
24
|
+
for (const route of manifest.routes) {
|
|
25
|
+
for (const asset of [
|
|
26
|
+
route.script,
|
|
27
|
+
route.sourceMap,
|
|
28
|
+
route.navigationScript,
|
|
29
|
+
...(route.css ?? []),
|
|
30
|
+
...(route.imports ?? []),
|
|
31
|
+
]) {
|
|
32
|
+
const path = safeClientManifestAssetPath(asset);
|
|
33
|
+
|
|
34
|
+
if (path !== undefined) {
|
|
35
|
+
paths.add(`${prefix}${path}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const asset of manifest.assets ?? []) {
|
|
41
|
+
const path = safeClientManifestAssetPath(asset);
|
|
42
|
+
|
|
43
|
+
if (path !== undefined) {
|
|
44
|
+
paths.add(`${prefix}${path}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const asset of options.extraPaths ?? []) {
|
|
49
|
+
const path = safeClientManifestAssetPath(asset);
|
|
50
|
+
|
|
51
|
+
if (path !== undefined) {
|
|
52
|
+
paths.add(`${prefix}${path}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return paths;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizeClientManifestAssetPrefix(prefix: string): string {
|
|
60
|
+
if (prefix === "") {
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const withLeadingSlash = prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
65
|
+
|
|
66
|
+
return withLeadingSlash.endsWith("/") ? withLeadingSlash : `${withLeadingSlash}/`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function safeClientManifestAssetPath(asset: string | undefined): string | undefined {
|
|
70
|
+
if (asset === undefined || asset === "" || asset.startsWith("/") || asset.includes("\\")) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const segments = asset.split("/");
|
|
75
|
+
|
|
76
|
+
return segments.some((segment) => unsafeClientManifestAssetSegment(segment))
|
|
77
|
+
? undefined
|
|
78
|
+
: segments.join("/");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function unsafeClientManifestAssetSegment(segment: string): boolean {
|
|
82
|
+
if (segment === "" || segment === "." || segment === "..") {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const decoded = decodeURIComponent(segment);
|
|
88
|
+
return decoded === "." || decoded === ".." || decoded.includes("/") || decoded.includes("\\");
|
|
89
|
+
} catch {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -2270,6 +2270,7 @@ export async function buildClientRouteEntrySource(
|
|
|
2270
2270
|
},
|
|
2271
2271
|
fetchRevalidationInstalled: false,
|
|
2272
2272
|
installed: false,
|
|
2273
|
+
pendingHtmlFetches: new Map(),
|
|
2273
2274
|
prefetchedUrls: new Set(),
|
|
2274
2275
|
prefetchedScripts: new Set(),
|
|
2275
2276
|
reloadNextNavigationFetch: false,
|
|
@@ -2657,7 +2658,7 @@ export async function __mreactPrefetch(url) {
|
|
|
2657
2658
|
return true;
|
|
2658
2659
|
}
|
|
2659
2660
|
|
|
2660
|
-
|
|
2661
|
+
__mreactRememberPrefetchedUrl(href);
|
|
2661
2662
|
|
|
2662
2663
|
const script = __mreactRouteScriptForNavigationUrl(href);
|
|
2663
2664
|
|
|
@@ -2673,17 +2674,39 @@ function __mreactPrefetchNavigationHtml(href) {
|
|
|
2673
2674
|
return true;
|
|
2674
2675
|
}
|
|
2675
2676
|
|
|
2676
|
-
return
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
}
|
|
2680
|
-
|
|
2681
|
-
__mreactRememberNavigationHtml(href, html);
|
|
2682
|
-
return true;
|
|
2683
|
-
}).catch(() => false);
|
|
2677
|
+
return __mreactFetchNavigationHtmlOnce(href)
|
|
2678
|
+
.then((html) => typeof html === "string")
|
|
2679
|
+
.catch(() => false);
|
|
2684
2680
|
}
|
|
2685
2681
|
|
|
2686
2682
|
const __mreactNavigationHtmlCacheMaxEntries = 64;
|
|
2683
|
+
const __mreactNavigationPrefetchHistoryMaxEntries = 64;
|
|
2684
|
+
|
|
2685
|
+
function __mreactRememberPrefetchedUrl(href) {
|
|
2686
|
+
__mreactRememberPrefetchHistory(__mreactNavigationState.prefetchedUrls, href);
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
function __mreactRememberPrefetchedScript(href) {
|
|
2690
|
+
__mreactRememberPrefetchHistory(__mreactNavigationState.prefetchedScripts, href);
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
function __mreactRememberPrefetchHistory(history, href) {
|
|
2694
|
+
if (history.has(href)) {
|
|
2695
|
+
history.delete(href);
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
history.add(href);
|
|
2699
|
+
|
|
2700
|
+
while (history.size > __mreactNavigationPrefetchHistoryMaxEntries) {
|
|
2701
|
+
const oldestHref = history.keys().next().value;
|
|
2702
|
+
|
|
2703
|
+
if (oldestHref === undefined) {
|
|
2704
|
+
return;
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
history.delete(oldestHref);
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2687
2710
|
|
|
2688
2711
|
function __mreactCachedNavigationHtml(href) {
|
|
2689
2712
|
const html = __mreactNavigationState.cache.get(href);
|
|
@@ -2712,6 +2735,7 @@ function __mreactRememberNavigationHtml(href, html) {
|
|
|
2712
2735
|
}
|
|
2713
2736
|
|
|
2714
2737
|
__mreactNavigationState.cache.delete(oldestHref);
|
|
2738
|
+
__mreactNavigationState.prefetchedUrls.delete(oldestHref);
|
|
2715
2739
|
}
|
|
2716
2740
|
}
|
|
2717
2741
|
|
|
@@ -2732,7 +2756,7 @@ function __mreactPrefetchRouteScript(script) {
|
|
|
2732
2756
|
|
|
2733
2757
|
for (const link of Array.from(document.querySelectorAll('link[rel="modulepreload"][href]'))) {
|
|
2734
2758
|
if (link.href === href) {
|
|
2735
|
-
|
|
2759
|
+
__mreactRememberPrefetchedScript(href);
|
|
2736
2760
|
return true;
|
|
2737
2761
|
}
|
|
2738
2762
|
}
|
|
@@ -2741,7 +2765,7 @@ function __mreactPrefetchRouteScript(script) {
|
|
|
2741
2765
|
link.rel = "modulepreload";
|
|
2742
2766
|
link.href = href;
|
|
2743
2767
|
document.head.appendChild(link);
|
|
2744
|
-
|
|
2768
|
+
__mreactRememberPrefetchedScript(href);
|
|
2745
2769
|
return true;
|
|
2746
2770
|
}
|
|
2747
2771
|
|
|
@@ -2760,7 +2784,7 @@ export async function __mreactNavigate(url, options = {}) {
|
|
|
2760
2784
|
if (script !== undefined) {
|
|
2761
2785
|
void __mreactPrefetchRouteScript(script);
|
|
2762
2786
|
}
|
|
2763
|
-
const html = cachedHtml ?? await
|
|
2787
|
+
const html = cachedHtml ?? await __mreactFetchNavigationHtmlOnce(href);
|
|
2764
2788
|
|
|
2765
2789
|
if (typeof html !== "string") {
|
|
2766
2790
|
return false;
|
|
@@ -2909,10 +2933,16 @@ export function __mreactInvalidateNavigationCache(path) {
|
|
|
2909
2933
|
return;
|
|
2910
2934
|
}
|
|
2911
2935
|
|
|
2912
|
-
|
|
2936
|
+
const hrefs = new Set([
|
|
2937
|
+
...Array.from(__mreactNavigationState.cache.keys()),
|
|
2938
|
+
...Array.from(__mreactNavigationState.pendingHtmlFetches.keys()),
|
|
2939
|
+
]);
|
|
2940
|
+
|
|
2941
|
+
for (const href of hrefs) {
|
|
2913
2942
|
if (__mreactNormalizeNavigationPath(href) === normalizedPath) {
|
|
2914
2943
|
__mreactNavigationState.cache.delete(href);
|
|
2915
2944
|
__mreactNavigationState.prefetchedUrls.delete(href);
|
|
2945
|
+
__mreactNavigationState.pendingHtmlFetches.delete(href);
|
|
2916
2946
|
}
|
|
2917
2947
|
}
|
|
2918
2948
|
}
|
|
@@ -2920,6 +2950,35 @@ export function __mreactInvalidateNavigationCache(path) {
|
|
|
2920
2950
|
function __mreactInvalidateAllNavigationCache() {
|
|
2921
2951
|
__mreactNavigationState.cache.clear();
|
|
2922
2952
|
__mreactNavigationState.prefetchedUrls.clear();
|
|
2953
|
+
__mreactNavigationState.pendingHtmlFetches.clear();
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
function __mreactFetchNavigationHtmlOnce(href) {
|
|
2957
|
+
const pending = __mreactNavigationState.pendingHtmlFetches.get(href);
|
|
2958
|
+
|
|
2959
|
+
if (pending !== undefined) {
|
|
2960
|
+
return pending;
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
const request = __mreactFetchNavigationHtml(href)
|
|
2964
|
+
.then((html) => {
|
|
2965
|
+
if (
|
|
2966
|
+
typeof html === "string" &&
|
|
2967
|
+
__mreactNavigationState.pendingHtmlFetches.get(href) === request
|
|
2968
|
+
) {
|
|
2969
|
+
__mreactRememberNavigationHtml(href, html);
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
return html;
|
|
2973
|
+
})
|
|
2974
|
+
.finally(() => {
|
|
2975
|
+
if (__mreactNavigationState.pendingHtmlFetches.get(href) === request) {
|
|
2976
|
+
__mreactNavigationState.pendingHtmlFetches.delete(href);
|
|
2977
|
+
}
|
|
2978
|
+
});
|
|
2979
|
+
|
|
2980
|
+
__mreactNavigationState.pendingHtmlFetches.set(href, request);
|
|
2981
|
+
return request;
|
|
2923
2982
|
}
|
|
2924
2983
|
|
|
2925
2984
|
function __mreactFetchNavigationHtml(href) {
|
|
@@ -3495,6 +3554,13 @@ function __mreactInstallNavigation() {
|
|
|
3495
3554
|
location.reload();
|
|
3496
3555
|
}
|
|
3497
3556
|
});
|
|
3557
|
+
document.addEventListener("pointerover", (event) => {
|
|
3558
|
+
const anchor = __mreactAnchorFromEvent(event);
|
|
3559
|
+
|
|
3560
|
+
if (anchor !== null && __mreactAnchorPrefetchMode(anchor) === "intent") {
|
|
3561
|
+
void __mreactPrefetch(anchor.href);
|
|
3562
|
+
}
|
|
3563
|
+
}, true);
|
|
3498
3564
|
document.addEventListener("pointerenter", (event) => {
|
|
3499
3565
|
const anchor = __mreactAnchorFromEvent(event);
|
|
3500
3566
|
|