@reckona/mreact-router 0.0.65 → 0.0.67

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.
Files changed (59) hide show
  1. package/package.json +13 -12
  2. package/src/actions.ts +1130 -0
  3. package/src/adapters/aws-lambda.ts +993 -0
  4. package/src/adapters/cloudflare.ts +1286 -0
  5. package/src/adapters/devtools.ts +5 -0
  6. package/src/adapters/edge.ts +70 -0
  7. package/src/adapters/node.ts +126 -0
  8. package/src/adapters/static.ts +61 -0
  9. package/src/app-router-globals.ts +19 -0
  10. package/src/assets.ts +113 -0
  11. package/src/build.ts +2948 -0
  12. package/src/bundle-pipeline.ts +496 -0
  13. package/src/cache-config.ts +35 -0
  14. package/src/cache-stats.ts +54 -0
  15. package/src/cache.ts +418 -0
  16. package/src/cli-options.ts +296 -0
  17. package/src/cli.ts +94 -0
  18. package/src/client.ts +3398 -0
  19. package/src/config.ts +146 -0
  20. package/src/cookies.ts +113 -0
  21. package/src/csp.ts +103 -0
  22. package/src/csrf.ts +132 -0
  23. package/src/deferred.ts +52 -0
  24. package/src/dev-server.ts +262 -0
  25. package/src/file-conventions.ts +88 -0
  26. package/src/http.ts +128 -0
  27. package/src/i18n.ts +98 -0
  28. package/src/import-policy.ts +261 -0
  29. package/src/index.ts +221 -0
  30. package/src/link.ts +47 -0
  31. package/src/logger.ts +149 -0
  32. package/src/module-runner.ts +554 -0
  33. package/src/multipart.ts +577 -0
  34. package/src/native-escape.ts +53 -0
  35. package/src/native-route-matcher.ts +173 -0
  36. package/src/navigation-state.ts +98 -0
  37. package/src/navigation.ts +215 -0
  38. package/src/prerender-store.ts +233 -0
  39. package/src/render.ts +5187 -0
  40. package/src/route-path.ts +5 -0
  41. package/src/route-shells.ts +47 -0
  42. package/src/route-source.ts +224 -0
  43. package/src/route-styles.ts +91 -0
  44. package/src/routes.ts +362 -0
  45. package/src/runtime-cache.ts +8 -0
  46. package/src/runtime-state.ts +37 -0
  47. package/src/security-headers.ts +134 -0
  48. package/src/serve.ts +1265 -0
  49. package/src/session.ts +238 -0
  50. package/src/source-jsx.ts +30 -0
  51. package/src/source-modules.ts +69 -0
  52. package/src/stream-list.ts +45 -0
  53. package/src/trace.ts +114 -0
  54. package/src/types.ts +155 -0
  55. package/src/upgrade.ts +8 -0
  56. package/src/vite-config.ts +63 -0
  57. package/src/vite-plugin-cache-key.ts +53 -0
  58. package/src/vite.ts +690 -0
  59. package/src/workspace-packages.ts +67 -0
package/src/vite.ts ADDED
@@ -0,0 +1,690 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import type { ServerResponse } from "node:http";
3
+ import { dirname } from "node:path";
4
+ import { formatDiagnostic } from "@reckona/mreact-compiler";
5
+ import {
6
+ createCompilerModuleContext,
7
+ transformCompilerModuleContext,
8
+ } from "@reckona/mreact-compiler/internal";
9
+ import { normalizePath, type Connect, type Plugin, type PluginOption, type ViteDevServer } from "vite";
10
+ import type { AppRouterServerActionOptions } from "./actions.js";
11
+ import type { AppRouterCache } from "./cache.js";
12
+ import {
13
+ resolveAppRouterProjectOptions,
14
+ type AppRouterProjectOptions,
15
+ type ResolvedAppRouterProject,
16
+ } from "./config.js";
17
+ import type { AppRouterImportPolicy } from "./import-policy.js";
18
+ import {
19
+ buildNavigationRuntimeBundle,
20
+ buildClientRouteBundle,
21
+ buildClientRouteEntrySource,
22
+ clientScriptForPath,
23
+ collectClientRouteReferences,
24
+ detectNavigationRuntimeHint,
25
+ isClientRouteSource,
26
+ navigationRuntimeScriptForDev,
27
+ } from "./client.js";
28
+ import { nodeRequestToWebRequest, sendResponse } from "./http.js";
29
+ import { renderAppRequest } from "./render.js";
30
+ import { stripRouteClientOnlyExports } from "./route-source.js";
31
+ import { collectRouteCssHrefs } from "./route-styles.js";
32
+ import { scanAppRoutes } from "./routes.js";
33
+ import { resolveRequestHost, type RequestHostPolicy } from "./serve.js";
34
+ import { hasJsxSyntax } from "./source-jsx.js";
35
+ import { workspacePackageFile } from "./workspace-packages.js";
36
+
37
+ export interface AppRouterViteMiddlewareOptions extends AppRouterProjectOptions {
38
+ allowedHosts?: readonly string[] | undefined;
39
+ hostPolicy?: RequestHostPolicy | undefined;
40
+ importPolicy?: AppRouterImportPolicy | undefined;
41
+ routeCache?: AppRouterCache | undefined;
42
+ serverActions?: AppRouterServerActionOptions | undefined;
43
+ vitePlugins?: readonly PluginOption[] | undefined;
44
+ }
45
+
46
+ type AppRouterViteRuntimeMiddlewareOptions = AppRouterViteMiddlewareOptions & {
47
+ viteDevServer?: ViteDevServer | undefined;
48
+ };
49
+
50
+ export interface AppRouterVitePluginOptions extends AppRouterProjectOptions {
51
+ allowedHosts?: readonly string[] | undefined;
52
+ hostPolicy?: RequestHostPolicy | undefined;
53
+ importPolicy?: AppRouterImportPolicy | undefined;
54
+ routeCache?: AppRouterCache | undefined;
55
+ serverActions?: AppRouterServerActionOptions | undefined;
56
+ }
57
+
58
+ const clientPrefix = "/_mreact/client/";
59
+ const devCssPrefix = "/_mreact/dev-css/";
60
+ const clientRouteModuleQuery = "mreact-router-client-route";
61
+ const virtualClientPrefix = "\0mreact-router-client:";
62
+ const virtualReactiveCoreId = "\0mreact-router-reactive-core";
63
+ const virtualReactiveDevtoolsId = "\0mreact-router-reactive-devtools";
64
+ const mreactRouterConfigKey = "__mreactRouterConfig";
65
+
66
+ type MreactRouterPlugin = Plugin & {
67
+ [mreactRouterConfigKey]: ResolvedAppRouterProject;
68
+ };
69
+
70
+ export function createAppRouterVitePlugin(
71
+ options: AppRouterVitePluginOptions,
72
+ ): Plugin {
73
+ const project = resolveAppRouterProjectOptions(options);
74
+ const normalizedSourceDirs = project.allowedSourceDirs.map((directory) => normalizePath(directory));
75
+ const packageFile = (monorepoDir: string, packageName: string, entry: string): string =>
76
+ workspacePackageFile({
77
+ currentFileUrl: import.meta.url,
78
+ entry,
79
+ monorepoDir,
80
+ packageName,
81
+ });
82
+ const reactiveCorePath = packageFile("reactive-core", "@reckona/mreact-reactive-core", "index");
83
+ const reactiveCoreDir = normalizePath(dirname(reactiveCorePath));
84
+ const runtimePaths = new Map([
85
+ [
86
+ "@reckona/mreact-reactive-core/internal",
87
+ packageFile("reactive-core", "@reckona/mreact-reactive-core", "internal"),
88
+ ],
89
+ [
90
+ "@reckona/mreact-reactive-dom",
91
+ packageFile("reactive-dom", "@reckona/mreact-reactive-dom", "index"),
92
+ ],
93
+ [
94
+ "@reckona/mreact-compat",
95
+ packageFile("react-compat", "@reckona/mreact-compat", "index"),
96
+ ],
97
+ [
98
+ "@reckona/mreact-compat/event-priority",
99
+ packageFile("react-compat", "@reckona/mreact-compat", "event-priority"),
100
+ ],
101
+ [
102
+ "@reckona/mreact-compat/flight",
103
+ packageFile("react-compat", "@reckona/mreact-compat", "flight"),
104
+ ],
105
+ [
106
+ "@reckona/mreact-compat/internal",
107
+ packageFile("react-compat", "@reckona/mreact-compat", "internal"),
108
+ ],
109
+ [
110
+ "@reckona/mreact-compat/jsx-dev-runtime",
111
+ packageFile("react-compat", "@reckona/mreact-compat", "jsx-dev-runtime"),
112
+ ],
113
+ [
114
+ "@reckona/mreact-compat/jsx-runtime",
115
+ packageFile("react-compat", "@reckona/mreact-compat", "jsx-runtime"),
116
+ ],
117
+ [
118
+ "@reckona/mreact-compat/scheduler",
119
+ packageFile("react-compat", "@reckona/mreact-compat", "scheduler"),
120
+ ],
121
+ [
122
+ "@reckona/mreact-router/link",
123
+ packageFile("router", "@reckona/mreact-router", "link"),
124
+ ],
125
+ [
126
+ "@reckona/mreact-router/navigation-state",
127
+ packageFile("router", "@reckona/mreact-router", "navigation-state"),
128
+ ],
129
+ [
130
+ "@reckona/mreact-shared/url-safety",
131
+ packageFile("shared", "@reckona/mreact-shared", "url-safety"),
132
+ ],
133
+ ]);
134
+
135
+ const plugin: MreactRouterPlugin = {
136
+ [mreactRouterConfigKey]: project,
137
+ enforce: "pre",
138
+ name: "mreact-router",
139
+ configureServer(server) {
140
+ server.middlewares.use(createDevCssProxyMiddleware());
141
+
142
+ return () => {
143
+ const middlewareOptions: AppRouterViteRuntimeMiddlewareOptions = {
144
+ ...options,
145
+ viteDevServer: server,
146
+ vitePlugins: server.config.plugins,
147
+ };
148
+
149
+ server.middlewares.use(
150
+ createAppRouterViteMiddleware(middlewareOptions),
151
+ );
152
+ };
153
+ },
154
+ handleHotUpdate(context) {
155
+ const normalizedFile = normalizePath(context.file);
156
+
157
+ if (!normalizedSourceDirs.some((directory) => normalizedFile.startsWith(directory))) {
158
+ return;
159
+ }
160
+
161
+ const timestamp = Date.now();
162
+ const updates = Array.from(context.server.moduleGraph.idToModuleMap.values())
163
+ .filter((moduleNode) => isMreactClientDevModuleId(moduleNode.id))
164
+ .map((moduleNode) => {
165
+ context.server.moduleGraph.invalidateModule(moduleNode);
166
+
167
+ return {
168
+ acceptedPath: moduleNode.url,
169
+ path: moduleNode.url,
170
+ timestamp,
171
+ type: "js-update" as const,
172
+ };
173
+ });
174
+
175
+ if (updates.length > 0) {
176
+ context.server.ws.send({ type: "update", updates });
177
+ }
178
+
179
+ return [];
180
+ },
181
+ load(id) {
182
+ if (id === virtualReactiveCoreId) {
183
+ return `import { cell as nativeCell } from ${JSON.stringify(reactiveCorePath)};
184
+ export * from ${JSON.stringify(reactiveCorePath)};
185
+ export function cell(initial) {
186
+ const routeCell = globalThis.__mreactRouteCell;
187
+ return typeof routeCell === "function" ? routeCell(nativeCell, initial) : nativeCell(initial);
188
+ }`;
189
+ }
190
+
191
+ if (id === virtualReactiveDevtoolsId) {
192
+ return `export function emitReactiveDevtoolsEvent() {}
193
+ export function hasReactiveDevtoolsEmitter() { return false; }
194
+ export function currentDevtoolsEmitter() { return undefined; }`;
195
+ }
196
+
197
+ if (id.startsWith(virtualClientPrefix)) {
198
+ return renderAppRouterClientAsset(
199
+ project.routesDir,
200
+ id.slice(virtualClientPrefix.length),
201
+ { dev: true },
202
+ ).then(async (response) => {
203
+ if (!response.ok) {
204
+ const message = await response.text();
205
+ throw new Error(message || `MReact client route asset was not found: ${id}`);
206
+ }
207
+
208
+ return response.text();
209
+ });
210
+ }
211
+
212
+ const requestPath = clientRouteModuleRequestPath(id);
213
+
214
+ return requestPath === undefined
215
+ ? undefined
216
+ : renderAppRouterClientRouteDevModule(project.routesDir, requestPath);
217
+ },
218
+ transform(code, id, options) {
219
+ if (options?.ssr === true) {
220
+ return undefined;
221
+ }
222
+
223
+ if (isMreactClientDevModuleId(id)) {
224
+ return undefined;
225
+ }
226
+
227
+ const filename = clientRequestPath(id);
228
+
229
+ if (!isMreactClientSourceDependency(filename, normalizedSourceDirs)) {
230
+ return undefined;
231
+ }
232
+
233
+ const moduleContext = createCompilerModuleContext({ code, filename });
234
+
235
+ if (!hasJsxSyntax(moduleContext.program)) {
236
+ return undefined;
237
+ }
238
+
239
+ const output = transformCompilerModuleContext({
240
+ code,
241
+ dev: true,
242
+ filename,
243
+ mode: isCompatSourcePath(filename) ? "compat" : "reactive",
244
+ moduleContext,
245
+ target: "client",
246
+ });
247
+
248
+ if (output.diagnostics.length > 0) {
249
+ throw new Error(
250
+ output.diagnostics
251
+ .map((diagnostic) => formatDiagnostic(filename, diagnostic))
252
+ .join("\n"),
253
+ );
254
+ }
255
+
256
+ return {
257
+ code: output.code,
258
+ map: null,
259
+ };
260
+ },
261
+ async resolveId(id, importer) {
262
+ const runtimePath = runtimePaths.get(id);
263
+
264
+ if (id === "@reckona/mreact-reactive-core") {
265
+ return virtualReactiveCoreId;
266
+ }
267
+
268
+ if (id === "./devtools.js" && importerInDirectory(importer, reactiveCoreDir)) {
269
+ return virtualReactiveDevtoolsId;
270
+ }
271
+
272
+ if (runtimePath !== undefined) {
273
+ return runtimePath;
274
+ }
275
+
276
+ const requestPath = clientRequestPath(id);
277
+
278
+ if (requestPath === `${clientPrefix}${navigationRuntimeScriptForDev()}`) {
279
+ return `${virtualClientPrefix}${requestPath}`;
280
+ }
281
+
282
+ if (!requestPath.startsWith(clientPrefix)) {
283
+ return undefined;
284
+ }
285
+
286
+ const route = await clientRouteForRequestPath(project.routesDir, requestPath);
287
+
288
+ return route === undefined ? undefined : clientRouteModuleId(route.file, requestPath);
289
+ },
290
+ };
291
+
292
+ return plugin;
293
+ }
294
+
295
+ function isMreactClientSourceDependency(
296
+ filename: string,
297
+ normalizedSourceDirs: readonly string[],
298
+ ): boolean {
299
+ const normalized = normalizePath(filename);
300
+
301
+ return (
302
+ /\.(?:mreact\.)?[cm]?[jt]sx?$/.test(normalized) &&
303
+ !normalized.includes("/node_modules/") &&
304
+ normalizedSourceDirs.some(
305
+ (directory) => normalized === directory || normalized.startsWith(`${directory}/`),
306
+ )
307
+ );
308
+ }
309
+
310
+ function isCompatSourcePath(filename: string): boolean {
311
+ return /\.compat(?:\.mreact)?(?:\.[cm]?[jt]sx?)?$/.test(filename);
312
+ }
313
+
314
+ export const mreactRouter = createAppRouterVitePlugin;
315
+
316
+ export function mreactRouterConfigFromPlugins(
317
+ plugins: readonly unknown[],
318
+ ): ResolvedAppRouterProject | undefined {
319
+ for (const plugin of plugins.flat(Infinity)) {
320
+ if (
321
+ plugin !== null &&
322
+ typeof plugin === "object" &&
323
+ mreactRouterConfigKey in plugin
324
+ ) {
325
+ return (plugin as MreactRouterPlugin)[mreactRouterConfigKey];
326
+ }
327
+ }
328
+
329
+ return undefined;
330
+ }
331
+
332
+ export function createAppRouterViteMiddleware(
333
+ options: AppRouterViteMiddlewareOptions,
334
+ ): Connect.NextHandleFunction {
335
+ return (request, response, next) => {
336
+ void handleAppRouterViteRequest(
337
+ options as AppRouterViteRuntimeMiddlewareOptions,
338
+ request,
339
+ response,
340
+ next,
341
+ );
342
+ };
343
+ }
344
+
345
+ async function handleAppRouterViteRequest(
346
+ options: AppRouterViteRuntimeMiddlewareOptions,
347
+ incoming: Connect.IncomingMessage,
348
+ outgoing: ServerResponse,
349
+ next: Connect.NextFunction,
350
+ ): Promise<void> {
351
+ try {
352
+ const project = resolveAppRouterProjectOptions(options);
353
+ const host = resolveRequestHost({
354
+ allowedHosts: options.allowedHosts,
355
+ fallbackHost: "localhost",
356
+ hostPolicy: options.hostPolicy,
357
+ rawHost: incoming.headers.host,
358
+ });
359
+ const origin = `http://${host}`;
360
+ const url = new URL(incoming.url ?? "/", origin);
361
+
362
+ if (url.pathname.startsWith(clientPrefix)) {
363
+ if (options.viteDevServer !== undefined) {
364
+ const transformed = await options.viteDevServer.transformRequest(
365
+ `${url.pathname}${url.search}`,
366
+ );
367
+
368
+ if (transformed !== null) {
369
+ await sendResponse(
370
+ outgoing,
371
+ new Response(transformed.code, {
372
+ headers: { "content-type": "text/javascript; charset=utf-8" },
373
+ }),
374
+ );
375
+ return;
376
+ }
377
+ }
378
+
379
+ await sendResponse(
380
+ outgoing,
381
+ await renderAppRouterClientAsset(project.routesDir, url.pathname, {
382
+ vitePlugins: options.vitePlugins,
383
+ }),
384
+ );
385
+ return;
386
+ }
387
+
388
+ const request = nodeRequestToWebRequest(incoming, origin);
389
+
390
+ await sendResponse(
391
+ outgoing,
392
+ await renderAppRequest({
393
+ appDir: project.routesDir,
394
+ importPolicy: {
395
+ ...options.importPolicy,
396
+ allowedSourceDirs: project.allowedSourceDirs,
397
+ projectRoot: project.projectRoot,
398
+ },
399
+ clientStyles: await devRouteStyles(project),
400
+ navigationScripts: await devNavigationScripts(project.routesDir),
401
+ request,
402
+ routeCache: options.routeCache,
403
+ serverActions: options.serverActions,
404
+ }),
405
+ );
406
+ } catch (error) {
407
+ next(error);
408
+ }
409
+ }
410
+
411
+ export async function renderAppRouterClientAsset(
412
+ appDir: string,
413
+ pathname: string,
414
+ options: { dev?: boolean; vitePlugins?: readonly PluginOption[] | undefined } = {},
415
+ ): Promise<Response> {
416
+ if (pathname === `/_mreact/client/${navigationRuntimeScriptForDev()}`) {
417
+ const output = await buildNavigationRuntimeBundle();
418
+
419
+ return new Response(options.dev === true ? withViteHmrRuntime(output.code) : output.code, {
420
+ headers: { "content-type": "text/javascript; charset=utf-8" },
421
+ });
422
+ }
423
+
424
+ const routes = await scanAppRoutes({ appDir });
425
+ const route = routes.find(
426
+ (candidate) =>
427
+ candidate.kind === "page" &&
428
+ `/_mreact/client/${clientScriptForPath(candidate.path)}` === pathname,
429
+ );
430
+
431
+ if (route === undefined || route.kind !== "page") {
432
+ return new Response("Not Found", { status: 404 });
433
+ }
434
+
435
+ const code = await readFile(route.file, "utf8");
436
+ const clientSource = stripRouteClientOnlyExports(code);
437
+ let references: Awaited<ReturnType<typeof collectClientRouteReferences>>;
438
+
439
+ try {
440
+ references = await collectClientRouteReferences({
441
+ appDir,
442
+ code: clientSource,
443
+ filename: route.file,
444
+ vitePlugins: options.vitePlugins,
445
+ });
446
+ } catch (error) {
447
+ return clientAssetBuildErrorResponse(route.file, error);
448
+ }
449
+
450
+ if (!references.client) {
451
+ if (isClientRouteSource(clientSource)) {
452
+ return clientAssetBuildErrorResponse(
453
+ route.file,
454
+ new Error(
455
+ [
456
+ "Client route analysis did not produce a client asset.",
457
+ "Browser build cannot import Node builtins or other server-only modules.",
458
+ ...references.diagnostics.map((diagnostic) => diagnostic.message),
459
+ ].join("\n"),
460
+ ),
461
+ );
462
+ }
463
+
464
+ return new Response("Not Found", { status: 404 });
465
+ }
466
+
467
+ let bundle: string;
468
+
469
+ try {
470
+ bundle = await buildClientRouteBundle({
471
+ code: clientSource,
472
+ clientBoundaryImports: references.clientBoundaryImports,
473
+ clientReferenceImports: references.clientReferenceImports,
474
+ clientReferenceManifest: references.clientReferenceManifest,
475
+ filename: route.file,
476
+ routePath: route.path,
477
+ vitePlugins: options.vitePlugins,
478
+ });
479
+ } catch (error) {
480
+ return clientAssetBuildErrorResponse(route.file, error);
481
+ }
482
+
483
+ return new Response(options.dev === true ? withViteHmrRuntime(bundle) : bundle, {
484
+ headers: { "content-type": "text/javascript; charset=utf-8" },
485
+ });
486
+ }
487
+
488
+ async function renderAppRouterClientRouteDevModule(
489
+ appDir: string,
490
+ pathname: string,
491
+ options: { vitePlugins?: readonly PluginOption[] | undefined } = {},
492
+ ): Promise<string> {
493
+ const route = await clientRouteForRequestPath(appDir, pathname);
494
+
495
+ if (route === undefined) {
496
+ throw new Error(`MReact client route asset was not found: ${pathname}`);
497
+ }
498
+
499
+ const code = await readFile(route.file, "utf8");
500
+ const clientSource = stripRouteClientOnlyExports(code);
501
+ const references = await collectClientRouteReferences({
502
+ appDir,
503
+ code: clientSource,
504
+ filename: route.file,
505
+ vitePlugins: options.vitePlugins,
506
+ });
507
+
508
+ if (!references.client) {
509
+ throw new Error(
510
+ isClientRouteSource(clientSource)
511
+ ? [
512
+ "Client route analysis did not produce a client asset.",
513
+ "Browser build cannot import Node builtins or other server-only modules.",
514
+ ...references.diagnostics.map((diagnostic) => diagnostic.message),
515
+ ].join("\n")
516
+ : `MReact client route asset was not found: ${pathname}`,
517
+ );
518
+ }
519
+
520
+ const entry = await buildClientRouteEntrySource({
521
+ code: clientSource,
522
+ clientBoundaryImports: references.clientBoundaryImports,
523
+ clientReferenceImports: references.clientReferenceImports,
524
+ clientReferenceManifest: references.clientReferenceManifest,
525
+ filename: route.file,
526
+ routePath: route.path,
527
+ vitePlugins: options.vitePlugins,
528
+ });
529
+
530
+ return withViteHmrRuntime(entry.code);
531
+ }
532
+
533
+ async function clientRouteForRequestPath(appDir: string, pathname: string) {
534
+ const routes = await scanAppRoutes({ appDir });
535
+
536
+ return routes.find(
537
+ (candidate) =>
538
+ candidate.kind === "page" &&
539
+ `/_mreact/client/${clientScriptForPath(candidate.path)}` === pathname,
540
+ );
541
+ }
542
+
543
+ function clientRequestPath(id: string): string {
544
+ const [path] = id.split(/[?#]/, 1);
545
+
546
+ return path ?? id;
547
+ }
548
+
549
+ function clientRouteModuleId(filename: string, requestPath: string): string {
550
+ return `${normalizePath(filename)}?${clientRouteModuleQuery}=${encodeURIComponent(requestPath)}`;
551
+ }
552
+
553
+ function clientRouteModuleRequestPath(id: string): string | undefined {
554
+ const queryStart = id.indexOf("?");
555
+
556
+ if (queryStart === -1) {
557
+ return undefined;
558
+ }
559
+
560
+ const params = new URLSearchParams(id.slice(queryStart + 1));
561
+ const value = params.get(clientRouteModuleQuery);
562
+
563
+ return value === null ? undefined : value;
564
+ }
565
+
566
+ function isMreactClientDevModuleId(id: string | null | undefined): boolean {
567
+ return (
568
+ id?.startsWith(virtualClientPrefix) === true ||
569
+ id?.includes(`?${clientRouteModuleQuery}=`) === true ||
570
+ id?.includes(`&${clientRouteModuleQuery}=`) === true
571
+ );
572
+ }
573
+
574
+ function importerInDirectory(importer: string | undefined, directory: string): boolean {
575
+ return importer !== undefined && normalizePath(importer).startsWith(`${directory}/`);
576
+ }
577
+
578
+ function clientAssetBuildErrorResponse(filename: string, error: unknown): Response {
579
+ const message = [
580
+ `Failed to build mreact client route asset for ${filename}.`,
581
+ error instanceof Error ? error.message : String(error),
582
+ ].join("\n");
583
+
584
+ return new Response(`throw new Error(${JSON.stringify(message)});\n`, {
585
+ status: 500,
586
+ headers: { "content-type": "text/javascript; charset=utf-8" },
587
+ });
588
+ }
589
+
590
+ async function devRouteStyles(
591
+ project: ResolvedAppRouterProject,
592
+ ): Promise<ReadonlyMap<string, readonly string[]>> {
593
+ const entries = await Promise.all(
594
+ (await scanAppRoutes({ appDir: project.routesDir })).map(async (route) => {
595
+ if (route.kind !== "page") {
596
+ return undefined;
597
+ }
598
+
599
+ const hrefs = await collectRouteCssHrefs({
600
+ appDir: project.routesDir,
601
+ hrefPrefix: devCssPrefix,
602
+ pageFile: route.file,
603
+ projectRoot: project.projectRoot,
604
+ });
605
+
606
+ return hrefs.length === 0 ? undefined : ([route.path, hrefs as readonly string[]] as const);
607
+ }),
608
+ );
609
+ const routeStyles = entries.filter(
610
+ (entry): entry is readonly [string, readonly string[]] => entry !== undefined,
611
+ );
612
+
613
+ return new Map<string, readonly string[]>(routeStyles);
614
+ }
615
+
616
+ async function devNavigationScripts(appDir: string): Promise<ReadonlyMap<string, string>> {
617
+ const entries = await Promise.all(
618
+ (await scanAppRoutes({ appDir })).map(async (route) => {
619
+ if (route.kind !== "page") {
620
+ return undefined;
621
+ }
622
+
623
+ const source = await readFile(route.file, "utf8");
624
+
625
+ return detectNavigationRuntimeHint(source)
626
+ ? ([route.path, navigationRuntimeScriptForDev()] as const)
627
+ : undefined;
628
+ }),
629
+ );
630
+
631
+ return new Map(entries.filter((entry): entry is readonly [string, string] => entry !== undefined));
632
+ }
633
+
634
+ function createDevCssProxyMiddleware(): Connect.NextHandleFunction {
635
+ return (incoming, outgoing, next) => {
636
+ const originalUrl = incoming.url ?? "/";
637
+ const url = new URL(originalUrl, "http://mreact.local");
638
+
639
+ if (!url.pathname.startsWith(devCssPrefix)) {
640
+ next();
641
+ return;
642
+ }
643
+
644
+ const sourcePath = `/${url.pathname.slice(devCssPrefix.length)}`;
645
+
646
+ if (sourcePath === "/" || sourcePath.includes("\0")) {
647
+ next();
648
+ return;
649
+ }
650
+
651
+ const originalAccept = incoming.headers.accept;
652
+ let restored = false;
653
+ const restore = () => {
654
+ if (restored) {
655
+ return;
656
+ }
657
+ restored = true;
658
+ incoming.url = originalUrl;
659
+ if (originalAccept === undefined) {
660
+ delete incoming.headers.accept;
661
+ } else {
662
+ incoming.headers.accept = originalAccept;
663
+ }
664
+ };
665
+
666
+ incoming.url = `${sourcePath}${url.search}`;
667
+ incoming.headers.accept = "text/css,*/*;q=0.1";
668
+ outgoing.once("finish", restore);
669
+ outgoing.once("close", restore);
670
+ next();
671
+ };
672
+ }
673
+
674
+ function withViteHmrRuntime(code: string): string {
675
+ return `import "/@vite/client";
676
+ if (import.meta.hot?.data.__mreactRouteStates) {
677
+ globalThis.__mreactRouteStates = import.meta.hot.data.__mreactRouteStates;
678
+ }
679
+ ${code}
680
+ if (import.meta.hot) {
681
+ const __mreactPreserveRouteState = () => {
682
+ import.meta.hot.data.__mreactRouteStates = globalThis.__mreactRouteStates;
683
+ };
684
+ import.meta.hot.dispose(__mreactPreserveRouteState);
685
+ import.meta.hot.accept((module) => {
686
+ module?.__mreactHydrateRoute?.();
687
+ });
688
+ }
689
+ `;
690
+ }