@reckona/mreact-router 0.0.192 → 0.0.194

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/src/render.ts CHANGED
@@ -677,12 +677,13 @@ export async function renderAppRequest(options: RenderAppRequestOptions): Promis
677
677
  };
678
678
  invokeRouterInstrumentation(options.instrumentation?.onRequestStart, requestEvent);
679
679
  const response = await renderAppRequestInternal({ ...options, requestUrl: url });
680
+ const finalResponse = await applyAppRouterResponseHook(response, options);
680
681
  invokeRouterInstrumentation(options.instrumentation?.onRequestEnd, {
681
682
  ...requestEvent,
682
- status: response.status,
683
+ status: finalResponse.status,
683
684
  });
684
685
 
685
- return applyAppRouterResponseHook(response, options);
686
+ return finalResponse;
686
687
  }
687
688
 
688
689
  function createRenderTiming(logger: AppRouterLogger | undefined): RenderTiming | undefined {
@@ -1693,13 +1694,77 @@ async function applyAppRouterResponseHook(
1693
1694
  response: Response,
1694
1695
  options: RenderAppRequestOptions,
1695
1696
  ): Promise<Response> {
1696
- const hooked = await options.onResponse?.(response, {
1697
+ const onResponse = await resolveAppRouterResponseHook(options);
1698
+ const hooked = await onResponse?.(response, {
1697
1699
  request: options.request,
1698
1700
  });
1699
1701
 
1700
1702
  return hooked instanceof Response ? hooked : response;
1701
1703
  }
1702
1704
 
1705
+ export async function resolveAppRouterResponseHook(
1706
+ options: Pick<
1707
+ RenderAppRequestOptions,
1708
+ | "appDir"
1709
+ | "define"
1710
+ | "devServerModuleCacheVersion"
1711
+ | "importPolicy"
1712
+ | "onResponse"
1713
+ | "serverModules"
1714
+ | "serverModuleCacheVersion"
1715
+ | "serverSourceFiles"
1716
+ | "vitePlugins"
1717
+ >,
1718
+ ): Promise<AppRouterResponseHook | undefined> {
1719
+ if (options.onResponse !== undefined) {
1720
+ return options.onResponse;
1721
+ }
1722
+
1723
+ const candidates = [
1724
+ join(options.appDir, "on-response.ts"),
1725
+ join(options.appDir, "on-response.mreact.ts"),
1726
+ ];
1727
+
1728
+ for (const file of candidates) {
1729
+ const embedded =
1730
+ options.serverSourceFiles?.has(file) === true || options.serverModules?.has(file) === true;
1731
+ if (!embedded) {
1732
+ try {
1733
+ await access(file);
1734
+ } catch {
1735
+ continue;
1736
+ }
1737
+ }
1738
+
1739
+ const code = await readServerSourceFile(
1740
+ file,
1741
+ options.serverModuleCacheVersion,
1742
+ options.serverSourceFiles,
1743
+ );
1744
+ const loaded = (await loadBundledMiddlewareModule({
1745
+ appDir: options.appDir,
1746
+ code,
1747
+ define: options.define,
1748
+ devServerModuleCacheVersion: options.devServerModuleCacheVersion,
1749
+ file,
1750
+ importPolicy: options.importPolicy,
1751
+ prebuiltArtifact: prebuiltRequestModuleArtifact(options.serverModules, file, code),
1752
+ serverModuleCacheVersion: options.serverModuleCacheVersion,
1753
+ vitePlugins: options.vitePlugins,
1754
+ })) as unknown as { onResponse?: unknown };
1755
+
1756
+ if (typeof loaded.onResponse !== "function") {
1757
+ throw new Error(
1758
+ `Invalid on-response convention ${file}: expected a callable onResponse export.`,
1759
+ );
1760
+ }
1761
+
1762
+ return loaded.onResponse as AppRouterResponseHook;
1763
+ }
1764
+
1765
+ return undefined;
1766
+ }
1767
+
1703
1768
  function withOptionalActionCookie(
1704
1769
  response: Response,
1705
1770
  csrfToken: string | undefined,
package/src/serve.ts CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  } from "./built-runtime.js";
19
19
  import {
20
20
  allBuiltServerModuleFiles,
21
+ loadBuiltResponseHookArtifacts,
21
22
  loadBuiltServerModuleArtifacts,
22
23
  loadBuiltServerModuleArtifactsForRequest,
23
24
  } from "./built-server-module-artifacts.js";
@@ -30,13 +31,18 @@ import type { AppRouterImportPolicy } from "./import-policy.js";
30
31
  import {
31
32
  preloadBuiltRequestModules,
32
33
  renderAppRequest,
34
+ resolveAppRouterResponseHook,
33
35
  resolveAppRouterMiddleware,
34
36
  type AppRouterServerRenderArtifactLoader,
35
37
  type AppRouterRenderPreload,
36
38
  type AppRouterResponseHook,
37
39
  type RenderAppRequestRuntimeOptions,
38
40
  } from "./render.js";
39
- import type { RouterInstrumentation } from "./trace.js";
41
+ import {
42
+ invokeRouterInstrumentation,
43
+ traceContextFromRequest,
44
+ type RouterInstrumentation,
45
+ } from "./trace.js";
40
46
  import {
41
47
  bytesResponse,
42
48
  htmlResponse,
@@ -234,8 +240,10 @@ export async function createBuiltRequestRuntime(
234
240
  });
235
241
  },
236
242
  async render(request, renderOptions = {}) {
243
+ emitBuiltRequestStart(renderOptions.instrumentation, request);
237
244
  const response = await renderBuiltAppRequestWithRuntime({
238
245
  ...renderOptions,
246
+ instrumentation: withoutBuiltRequestInstrumentation(renderOptions.instrumentation),
239
247
  importPolicy:
240
248
  renderOptions.importPolicy === undefined
241
249
  ? defaultImportPolicy
@@ -245,10 +253,16 @@ export async function createBuiltRequestRuntime(
245
253
  runtime,
246
254
  });
247
255
 
248
- return applyBuiltAppResponseHook(response, {
249
- onResponse: renderOptions.onResponse,
250
- request,
251
- });
256
+ const finalResponse = await applyBuiltAppResponseHook(
257
+ response,
258
+ {
259
+ onResponse: renderOptions.onResponse,
260
+ request,
261
+ },
262
+ runtime,
263
+ );
264
+ emitBuiltRequestEnd(renderOptions.instrumentation, request, finalResponse.status);
265
+ return finalResponse;
252
266
  },
253
267
  };
254
268
  }
@@ -349,6 +363,7 @@ function builtRuntimePreloadRoutes(
349
363
  export async function renderBuiltAppRequest(
350
364
  options: RenderBuiltAppRequestOptions,
351
365
  ): Promise<Response> {
366
+ emitBuiltRequestStart(options.instrumentation, options.request);
352
367
  const runtime = await readBuiltRuntime({
353
368
  outDir: options.outDir,
354
369
  immutable: options.immutableRuntime,
@@ -356,11 +371,54 @@ export async function renderBuiltAppRequest(
356
371
  });
357
372
  const response = await renderBuiltAppRequestWithRuntime({
358
373
  ...options,
374
+ instrumentation: withoutBuiltRequestInstrumentation(options.instrumentation),
359
375
  importPolicy: mergeBuiltRuntimeImportPolicy(runtime, options.importPolicy),
360
376
  runtime,
361
377
  });
362
378
 
363
- return applyBuiltAppResponseHook(response, options);
379
+ const finalResponse = await applyBuiltAppResponseHook(response, options, runtime);
380
+ emitBuiltRequestEnd(options.instrumentation, options.request, finalResponse.status);
381
+ return finalResponse;
382
+ }
383
+
384
+ function withoutBuiltRequestInstrumentation(
385
+ instrumentation: RouterInstrumentation | undefined,
386
+ ): RouterInstrumentation | undefined {
387
+ if (instrumentation === undefined) {
388
+ return undefined;
389
+ }
390
+
391
+ const { onRequestEnd: _onRequestEnd, onRequestStart: _onRequestStart, ...remaining } =
392
+ instrumentation;
393
+ return remaining;
394
+ }
395
+
396
+ function emitBuiltRequestStart(
397
+ instrumentation: RouterInstrumentation | undefined,
398
+ request: Request,
399
+ ): void {
400
+ const trace = traceContextFromRequest(request);
401
+ invokeRouterInstrumentation(instrumentation?.onRequestStart, {
402
+ method: request.method,
403
+ path: new URL(request.url).pathname,
404
+ request,
405
+ ...(trace === undefined ? {} : { trace }),
406
+ });
407
+ }
408
+
409
+ function emitBuiltRequestEnd(
410
+ instrumentation: RouterInstrumentation | undefined,
411
+ request: Request,
412
+ status: number,
413
+ ): void {
414
+ const trace = traceContextFromRequest(request);
415
+ invokeRouterInstrumentation(instrumentation?.onRequestEnd, {
416
+ method: request.method,
417
+ path: new URL(request.url).pathname,
418
+ request,
419
+ status,
420
+ ...(trace === undefined ? {} : { trace }),
421
+ });
364
422
  }
365
423
 
366
424
  async function renderBuiltAppRequestWithRuntime(
@@ -534,8 +592,18 @@ function emitBuiltRenderTiming(
534
592
  async function applyBuiltAppResponseHook(
535
593
  response: Response,
536
594
  options: Pick<RenderBuiltAppRequestOptions, "onResponse" | "request">,
595
+ runtime: BuiltRuntime,
537
596
  ): Promise<Response> {
538
- const hooked = await options.onResponse?.(response, {
597
+ await loadBuiltResponseHookArtifacts(runtime);
598
+ const onResponse = await resolveAppRouterResponseHook({
599
+ appDir: runtime.appDir,
600
+ importPolicy: runtime.generatedImportPolicy,
601
+ onResponse: options.onResponse,
602
+ serverModules: runtime.serverModules,
603
+ serverModuleCacheVersion: runtime.serverModuleCacheVersion,
604
+ serverSourceFiles: runtime.serverSourceFiles,
605
+ });
606
+ const hooked = await onResponse?.(response, {
539
607
  request: options.request,
540
608
  });
541
609
 
@@ -814,6 +882,7 @@ function builtRenderAppRequestOptions(
814
882
  options.runtime.serverActionManifest,
815
883
  ),
816
884
  skipMiddleware: true,
885
+ onResponse: () => undefined,
817
886
  ...(options.preload === undefined ? {} : { preload: options.preload }),
818
887
  };
819
888
 
package/src/vite.ts CHANGED
@@ -277,6 +277,7 @@ export function createAppRouterVitePlugin(options: AppRouterVitePluginOptions):
277
277
  },
278
278
  configureServer(server) {
279
279
  server.middlewares.use(createDevCssProxyMiddleware());
280
+ server.middlewares.use(createClientRouteTransformMiddleware(project.routesDir, server));
280
281
 
281
282
  return () => {
282
283
  const middlewareOptions: AppRouterViteRuntimeMiddlewareOptions = {
@@ -451,6 +452,48 @@ export function prepareReactiveEffectRunDevtoolsEvent() { return undefined; }`;
451
452
  return plugin;
452
453
  }
453
454
 
455
+ function createClientRouteTransformMiddleware(
456
+ appDir: string,
457
+ server: ViteDevServer,
458
+ ): Connect.NextHandleFunction {
459
+ return (request, response, next) => {
460
+ void (async () => {
461
+ const url = new URL(request.url ?? "/", "http://localhost");
462
+
463
+ if (!url.pathname.startsWith(`${clientPrefix}routes/`)) {
464
+ next();
465
+ return;
466
+ }
467
+
468
+ const route = await clientRouteForRequestPath(appDir, url.pathname);
469
+
470
+ if (route === undefined) {
471
+ next();
472
+ return;
473
+ }
474
+
475
+ try {
476
+ const transformed = await server.transformRequest(`${url.pathname}${url.search}`);
477
+
478
+ if (transformed === null) {
479
+ next();
480
+ return;
481
+ }
482
+
483
+ await sendResponse(
484
+ response,
485
+ new Response(transformed.code, {
486
+ headers: { "content-type": "text/javascript; charset=utf-8" },
487
+ }),
488
+ );
489
+ } catch (error) {
490
+ sendViteErrorOverlay(server, error);
491
+ await sendResponse(response, clientAssetBuildErrorResponse(route.file, error));
492
+ }
493
+ })().catch(next);
494
+ };
495
+ }
496
+
454
497
  function isMreactClientSourceDependency(
455
498
  filename: string,
456
499
  normalizedSourceDirs: readonly string[],