@reckona/mreact-router 0.0.175 → 0.0.176
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 +31 -3
- 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 +31 -3
- 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
package/src/render.ts
CHANGED
|
@@ -144,6 +144,12 @@ import {
|
|
|
144
144
|
prebuiltServerModuleOutputOptionsMatch,
|
|
145
145
|
type BuiltServerModuleOutputLike,
|
|
146
146
|
} from "./route-module-loader.js";
|
|
147
|
+
import {
|
|
148
|
+
loadRouteDataFromModule,
|
|
149
|
+
type RouteDataContext,
|
|
150
|
+
type RouteLoaderModule,
|
|
151
|
+
} from "./route-loader-runtime.js";
|
|
152
|
+
import { pageRouteMethodResponse } from "./route-dispatch.js";
|
|
147
153
|
|
|
148
154
|
const nativeEscapeTransform = {
|
|
149
155
|
batchImportName: "escapeHtmlBatch",
|
|
@@ -166,6 +172,13 @@ interface RenderTiming {
|
|
|
166
172
|
phases: Record<string, number>;
|
|
167
173
|
}
|
|
168
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Loads prebuilt server render artifacts immediately before a route render needs them.
|
|
177
|
+
*/
|
|
178
|
+
export interface AppRouterServerRenderArtifactLoader {
|
|
179
|
+
load(routeFile: string): Promise<void>;
|
|
180
|
+
}
|
|
181
|
+
|
|
169
182
|
/**
|
|
170
183
|
* Configures server rendering for a source app-router request.
|
|
171
184
|
*/
|
|
@@ -205,6 +218,10 @@ export interface RenderAppRequestOptions {
|
|
|
205
218
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
206
219
|
}
|
|
207
220
|
|
|
221
|
+
export interface RenderAppRequestRuntimeOptions extends RenderAppRequestOptions {
|
|
222
|
+
serverRenderArtifactLoader?: AppRouterServerRenderArtifactLoader | undefined;
|
|
223
|
+
}
|
|
224
|
+
|
|
208
225
|
export interface AppRouterRenderPreload {
|
|
209
226
|
promise: Promise<void>;
|
|
210
227
|
wait: "before-render";
|
|
@@ -723,15 +740,11 @@ async function waitForRenderPreload(
|
|
|
723
740
|
}
|
|
724
741
|
|
|
725
742
|
async function loadServerRenderArtifacts(
|
|
726
|
-
options:
|
|
743
|
+
options: RenderAppRequestRuntimeOptions,
|
|
727
744
|
routeFile: string,
|
|
728
745
|
timing: RenderTiming | undefined,
|
|
729
746
|
): Promise<void> {
|
|
730
|
-
const loader =
|
|
731
|
-
options as RenderAppRequestOptions & {
|
|
732
|
-
__mreactLoadServerRenderArtifacts?: ((routeFile: string) => Promise<void>) | undefined;
|
|
733
|
-
}
|
|
734
|
-
).__mreactLoadServerRenderArtifacts;
|
|
747
|
+
const loader = options.serverRenderArtifactLoader;
|
|
735
748
|
|
|
736
749
|
if (loader === undefined) {
|
|
737
750
|
return;
|
|
@@ -739,14 +752,14 @@ async function loadServerRenderArtifacts(
|
|
|
739
752
|
|
|
740
753
|
const phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
741
754
|
try {
|
|
742
|
-
await loader(routeFile);
|
|
755
|
+
await loader.load(routeFile);
|
|
743
756
|
} finally {
|
|
744
757
|
finishRenderTimingPhase(timing, phaseStartedAt, "renderArtifactLoadMs");
|
|
745
758
|
}
|
|
746
759
|
}
|
|
747
760
|
|
|
748
761
|
function emitRenderTiming(
|
|
749
|
-
options:
|
|
762
|
+
options: RenderAppRequestRuntimeOptions,
|
|
750
763
|
timing: RenderTiming | undefined,
|
|
751
764
|
status: number,
|
|
752
765
|
): void {
|
|
@@ -798,7 +811,7 @@ export async function resolveAppRouterMiddleware(options: {
|
|
|
798
811
|
return { response: middlewareResponse, type: "response" };
|
|
799
812
|
}
|
|
800
813
|
|
|
801
|
-
async function renderAppRequestInternal(options:
|
|
814
|
+
async function renderAppRequestInternal(options: RenderAppRequestRuntimeOptions): Promise<Response> {
|
|
802
815
|
warnProductionRenderWithoutPrebuiltModules(options);
|
|
803
816
|
const timing = createRenderTiming(options.logger);
|
|
804
817
|
const clientRouteInferenceCache =
|
|
@@ -967,18 +980,9 @@ async function renderAppRequestInternal(options: RenderAppRequestOptions): Promi
|
|
|
967
980
|
// methods (PUT, PATCH, DELETE, PROPFIND, ...) get 405 with an
|
|
968
981
|
// Allow header so the response shape complies with RFC 9110 §9
|
|
969
982
|
// and so caching intermediaries do not cross-cache method results.
|
|
970
|
-
const
|
|
971
|
-
if (
|
|
972
|
-
return
|
|
973
|
-
status: 204,
|
|
974
|
-
headers: { allow: "GET, HEAD, OPTIONS" },
|
|
975
|
-
});
|
|
976
|
-
}
|
|
977
|
-
if (method !== "GET" && method !== "HEAD") {
|
|
978
|
-
return new Response("Method Not Allowed", {
|
|
979
|
-
status: 405,
|
|
980
|
-
headers: { allow: "GET, HEAD, OPTIONS" },
|
|
981
|
-
});
|
|
983
|
+
const methodResponse = pageRouteMethodResponse(options.request.method);
|
|
984
|
+
if (methodResponse !== undefined) {
|
|
985
|
+
return methodResponse;
|
|
982
986
|
}
|
|
983
987
|
|
|
984
988
|
routeCacheContext = beginRouteCacheContext(options.routeCache);
|
|
@@ -4389,17 +4393,6 @@ async function loadRouteMiddlewareControlFile(options: {
|
|
|
4389
4393
|
return control;
|
|
4390
4394
|
}
|
|
4391
4395
|
|
|
4392
|
-
interface RouteDataContext {
|
|
4393
|
-
env?: unknown;
|
|
4394
|
-
params: RouteParams;
|
|
4395
|
-
queryClient: QueryClient;
|
|
4396
|
-
request: Request;
|
|
4397
|
-
}
|
|
4398
|
-
|
|
4399
|
-
interface RouteLoaderModule {
|
|
4400
|
-
loader?: (context: RouteDataContext) => unknown;
|
|
4401
|
-
}
|
|
4402
|
-
|
|
4403
4396
|
interface RouteMetadataContext {
|
|
4404
4397
|
data: unknown;
|
|
4405
4398
|
params: RouteParams;
|
|
@@ -4427,34 +4420,19 @@ async function loadRouteData(options: {
|
|
|
4427
4420
|
timing?: RenderTiming | undefined;
|
|
4428
4421
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
4429
4422
|
}): Promise<unknown> {
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
return undefined;
|
|
4444
|
-
}
|
|
4445
|
-
|
|
4446
|
-
const executionStartedAt = renderTimingPhaseStartedAt(options.timing);
|
|
4447
|
-
try {
|
|
4448
|
-
return await module.loader(options.context);
|
|
4449
|
-
} catch (error) {
|
|
4450
|
-
if (error instanceof Response) {
|
|
4451
|
-
return error;
|
|
4452
|
-
}
|
|
4453
|
-
|
|
4454
|
-
throw error;
|
|
4455
|
-
} finally {
|
|
4456
|
-
finishRenderTimingPhase(options.timing, executionStartedAt, "loaderExecutionMs");
|
|
4457
|
-
}
|
|
4423
|
+
return await loadRouteDataFromModule({
|
|
4424
|
+
context: options.context,
|
|
4425
|
+
hasLoader: hasLoaderExport(options.code),
|
|
4426
|
+
loadModule: () => loadRouteLoaderModule(options),
|
|
4427
|
+
timing: {
|
|
4428
|
+
finish(startedAt, phase) {
|
|
4429
|
+
finishRenderTimingPhase(options.timing, startedAt, phase);
|
|
4430
|
+
},
|
|
4431
|
+
start() {
|
|
4432
|
+
return renderTimingPhaseStartedAt(options.timing);
|
|
4433
|
+
},
|
|
4434
|
+
},
|
|
4435
|
+
});
|
|
4458
4436
|
}
|
|
4459
4437
|
|
|
4460
4438
|
async function loadRouteDataWithInstrumentation(options: {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function pageRouteMethodResponse(method: string): Response | undefined {
|
|
2
|
+
if (method === "GET" || method === "HEAD") {
|
|
3
|
+
return undefined;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
if (method === "OPTIONS") {
|
|
7
|
+
return new Response(null, {
|
|
8
|
+
status: 204,
|
|
9
|
+
headers: { allow: "GET, HEAD, OPTIONS" },
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return new Response("Method Not Allowed", {
|
|
14
|
+
status: 405,
|
|
15
|
+
headers: { allow: "GET, HEAD, OPTIONS" },
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { QueryClient } from "@reckona/mreact-query";
|
|
2
|
+
import type { RouteParams } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export interface RouteDataContext {
|
|
5
|
+
env?: unknown;
|
|
6
|
+
params: RouteParams;
|
|
7
|
+
queryClient: QueryClient;
|
|
8
|
+
request: Request;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RouteLoaderModule {
|
|
12
|
+
loader?: (context: RouteDataContext) => unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface RouteLoaderRuntimeTiming {
|
|
16
|
+
finish(startedAt: number | undefined, phase: "loaderExecutionMs" | "loaderModuleLoadMs"): void;
|
|
17
|
+
start(): number | undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function loadRouteDataFromModule(options: {
|
|
21
|
+
context: RouteDataContext;
|
|
22
|
+
hasLoader: boolean;
|
|
23
|
+
loadModule(): Promise<RouteLoaderModule>;
|
|
24
|
+
timing?: RouteLoaderRuntimeTiming | undefined;
|
|
25
|
+
}): Promise<unknown> {
|
|
26
|
+
if (!options.hasLoader) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let module: RouteLoaderModule;
|
|
31
|
+
const moduleLoadStartedAt = options.timing?.start();
|
|
32
|
+
try {
|
|
33
|
+
module = await options.loadModule();
|
|
34
|
+
} finally {
|
|
35
|
+
options.timing?.finish(moduleLoadStartedAt, "loaderModuleLoadMs");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (module.loader === undefined) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const executionStartedAt = options.timing?.start();
|
|
43
|
+
try {
|
|
44
|
+
return await module.loader(options.context);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error instanceof Response) {
|
|
47
|
+
return error;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
throw error;
|
|
51
|
+
} finally {
|
|
52
|
+
options.timing?.finish(executionStartedAt, "loaderExecutionMs");
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/routes.ts
CHANGED
|
@@ -80,6 +80,11 @@ export interface RouteMatcher {
|
|
|
80
80
|
match(pathname: string): MatchedRoute | undefined;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
interface MatchRouteCacheEntry {
|
|
84
|
+
fingerprint: string;
|
|
85
|
+
matcher: RouteMatcher;
|
|
86
|
+
}
|
|
87
|
+
|
|
83
88
|
export type CompiledRouteMatcherSegment =
|
|
84
89
|
| { kind: "static"; value: string }
|
|
85
90
|
| { kind: "dynamic"; name: string }
|
|
@@ -99,6 +104,11 @@ export interface CompiledRouteMatcherArtifact {
|
|
|
99
104
|
version: 1;
|
|
100
105
|
}
|
|
101
106
|
|
|
107
|
+
let matchRouteMatcherCache = new WeakMap<readonly AppRoute[], MatchRouteCacheEntry>();
|
|
108
|
+
let matchRouteMatcherBuildCountForTest = 0;
|
|
109
|
+
let matchRouteFingerprintIds = new WeakMap<AppRoute, number>();
|
|
110
|
+
let nextMatchRouteFingerprintId = 1;
|
|
111
|
+
|
|
102
112
|
/**
|
|
103
113
|
* Scans an app directory and returns sorted app-router route definitions.
|
|
104
114
|
*/
|
|
@@ -137,7 +147,28 @@ export function matchRoute(
|
|
|
137
147
|
routes: readonly AppRoute[],
|
|
138
148
|
pathname: string,
|
|
139
149
|
): MatchedRoute | undefined {
|
|
140
|
-
|
|
150
|
+
const fingerprint = routeMatcherFingerprint(routes);
|
|
151
|
+
const cached = matchRouteMatcherCache.get(routes);
|
|
152
|
+
|
|
153
|
+
if (cached !== undefined && cached.fingerprint === fingerprint) {
|
|
154
|
+
return cached.matcher.match(pathname);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const matcher = createRouteMatcher(routes);
|
|
158
|
+
matchRouteMatcherBuildCountForTest += 1;
|
|
159
|
+
matchRouteMatcherCache.set(routes, { fingerprint, matcher });
|
|
160
|
+
return matcher.match(pathname);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function __resetMatchRouteCacheForTest(): void {
|
|
164
|
+
matchRouteMatcherCache = new WeakMap();
|
|
165
|
+
matchRouteMatcherBuildCountForTest = 0;
|
|
166
|
+
matchRouteFingerprintIds = new WeakMap();
|
|
167
|
+
nextMatchRouteFingerprintId = 1;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function __getMatchRouteMatcherBuildCountForTest(): number {
|
|
171
|
+
return matchRouteMatcherBuildCountForTest;
|
|
141
172
|
}
|
|
142
173
|
|
|
143
174
|
export function compileRouteMatcherArtifact(
|
|
@@ -204,6 +235,44 @@ function compileRouteMatcherEntry(
|
|
|
204
235
|
};
|
|
205
236
|
}
|
|
206
237
|
|
|
238
|
+
function routeMatcherFingerprint(routes: readonly AppRoute[]): string {
|
|
239
|
+
const parts: string[] = [String(routes.length)];
|
|
240
|
+
|
|
241
|
+
for (const route of routes) {
|
|
242
|
+
parts.push(
|
|
243
|
+
String(routeMatcherRouteFingerprintId(route)),
|
|
244
|
+
route.kind,
|
|
245
|
+
route.path,
|
|
246
|
+
String(route.segments.length),
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
for (const segment of route.segments) {
|
|
250
|
+
parts.push(segment.kind);
|
|
251
|
+
|
|
252
|
+
if (segment.kind === "static") {
|
|
253
|
+
parts.push(segment.value);
|
|
254
|
+
} else {
|
|
255
|
+
parts.push(segment.name);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return parts.map((part) => `${part.length}:${part}`).join("|");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function routeMatcherRouteFingerprintId(route: AppRoute): number {
|
|
264
|
+
const cached = matchRouteFingerprintIds.get(route);
|
|
265
|
+
|
|
266
|
+
if (cached !== undefined) {
|
|
267
|
+
return cached;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const id = nextMatchRouteFingerprintId;
|
|
271
|
+
nextMatchRouteFingerprintId += 1;
|
|
272
|
+
matchRouteFingerprintIds.set(route, id);
|
|
273
|
+
return id;
|
|
274
|
+
}
|
|
275
|
+
|
|
207
276
|
function matchCompiledRoutes(
|
|
208
277
|
routes: readonly AppRoute[],
|
|
209
278
|
artifact: CompiledRouteMatcherArtifact,
|