flamefront 0.1.0-alpha.0 → 0.1.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 +35 -620
- package/package.json +45 -22
- package/src/cli.ts +16 -1
- package/src/entry.ts +49 -0
- package/src/fetch.ts +280 -0
- package/src/fragment-client.ts +127 -87
- package/src/fragment-hydration-client.tsx +81 -0
- package/src/fragment-protocol.ts +3 -3
- package/src/fragment.tsx +656 -0
- package/src/glob.ts +294 -0
- package/src/identifier-prefix.ts +5 -0
- package/src/index.ts +569 -49
- package/src/lifecycle.ts +7 -6
- package/src/octane-client-core.ts +10 -3
- package/src/octane-client.ts +12 -6
- package/src/octane-compiler.d.ts +19 -0
- package/src/octane-default-renderer.tsx +124 -0
- package/src/octane-router-document.ts +19 -2
- package/src/{octane.ts → octane.tsx} +152 -145
- package/src/output.ts +48 -0
- package/src/remix-route-data.ts +28 -20
- package/src/remix-router.ts +10 -4
- package/src/route-data-client.ts +22 -12
- package/src/route-prefetch.ts +19 -12
- package/src/server.ts +122 -24
- package/src/srvx.ts +43 -183
- package/src/typegen.ts +207 -0
- package/src/virtual-remix-routes.d.ts +16 -1
- package/src/vite.ts +364 -27
- package/src/fragment.ts +0 -260
package/src/route-prefetch.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
AppDefinition,
|
|
3
2
|
LoadRouteOptions,
|
|
3
|
+
MatchRouteOptions,
|
|
4
4
|
RouteDefinition,
|
|
5
|
+
RouteMatchForUrl,
|
|
5
6
|
} from "./index.ts"
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Resources that a route-aware prefetcher can warm without taking over
|
|
9
|
-
* navigation. The framework adapter supplies the
|
|
10
|
+
* navigation. The framework adapter supplies the route fragment transport;
|
|
10
11
|
* callers can replace it when they own the transport.
|
|
11
12
|
*/
|
|
12
13
|
export interface RoutePrefetchResources<
|
|
13
14
|
Route extends RouteDefinition = RouteDefinition,
|
|
14
15
|
> {
|
|
15
|
-
readonly
|
|
16
|
+
readonly routeFragment?: (
|
|
16
17
|
url: string | URL,
|
|
17
18
|
route: Route,
|
|
18
19
|
options?: LoadRouteOptions,
|
|
@@ -23,15 +24,21 @@ export type RouteModulePreloader = (entry: string) => void | Promise<void>
|
|
|
23
24
|
|
|
24
25
|
export type RoutePrefetchCallback = (to: string) => void | Promise<void>
|
|
25
26
|
|
|
26
|
-
type RoutePrefetchApp<Route extends RouteDefinition> =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
type RoutePrefetchApp<Route extends RouteDefinition> = {
|
|
28
|
+
readonly match: (
|
|
29
|
+
url: string | URL,
|
|
30
|
+
options?: MatchRouteOptions,
|
|
31
|
+
) => RouteMatchForUrl<Route, string> | null
|
|
32
|
+
readonly prefetch: (
|
|
33
|
+
url: string | URL,
|
|
34
|
+
options?: LoadRouteOptions,
|
|
35
|
+
) => Promise<void>
|
|
36
|
+
}
|
|
30
37
|
|
|
31
38
|
/**
|
|
32
|
-
* Warm the resources used by a matched route.
|
|
33
|
-
* and
|
|
34
|
-
*
|
|
39
|
+
* Warm the resources used by a matched route. Client routes share route data
|
|
40
|
+
* and module caches. Server and static routes use the fragment resource and
|
|
41
|
+
* never import their route module as a browser rendering path.
|
|
35
42
|
*/
|
|
36
43
|
export async function prefetchRouteResources<
|
|
37
44
|
Route extends RouteDefinition = RouteDefinition,
|
|
@@ -48,8 +55,8 @@ export async function prefetchRouteResources<
|
|
|
48
55
|
return
|
|
49
56
|
}
|
|
50
57
|
|
|
51
|
-
if (match.data.render
|
|
52
|
-
await resources.
|
|
58
|
+
if (match.data.render !== "client") {
|
|
59
|
+
await resources.routeFragment?.(url, match.data, options)
|
|
53
60
|
return
|
|
54
61
|
}
|
|
55
62
|
|
package/src/server.ts
CHANGED
|
@@ -1,25 +1,50 @@
|
|
|
1
|
-
import type { Match } from "@remix-run/route-pattern/match"
|
|
2
1
|
import {
|
|
3
2
|
type AppDefinition,
|
|
4
3
|
type MatchRouteOptions,
|
|
4
|
+
type RouteLoaderFor,
|
|
5
5
|
type RenderMode,
|
|
6
|
+
type RouteLoaderData,
|
|
7
|
+
type RouteMatchForUrl,
|
|
8
|
+
type RouteParams,
|
|
6
9
|
type RouteDefinition,
|
|
7
10
|
} from "./index.ts"
|
|
8
11
|
import { stripFlamefrontProtocolRequest } from "./fragment-protocol.ts"
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
type LoaderPath<ContextOrPath, PathOrContext> =
|
|
14
|
+
ContextOrPath extends `/${string}`
|
|
15
|
+
? ContextOrPath
|
|
16
|
+
: PathOrContext extends `/${string}`
|
|
17
|
+
? PathOrContext
|
|
18
|
+
: string
|
|
19
|
+
|
|
20
|
+
type LoaderContext<ContextOrPath, PathOrContext> =
|
|
21
|
+
ContextOrPath extends `/${string}`
|
|
22
|
+
? PathOrContext extends `/${string}`
|
|
23
|
+
? unknown
|
|
24
|
+
: PathOrContext
|
|
25
|
+
: ContextOrPath
|
|
26
|
+
|
|
27
|
+
export interface LoaderArgs<ContextOrPath = unknown, PathOrContext = unknown> {
|
|
11
28
|
readonly request: Request
|
|
12
|
-
readonly params: Readonly<
|
|
13
|
-
|
|
29
|
+
readonly params: Readonly<
|
|
30
|
+
RouteParams<LoaderPath<ContextOrPath, PathOrContext>>
|
|
31
|
+
>
|
|
32
|
+
readonly context: LoaderContext<ContextOrPath, PathOrContext>
|
|
14
33
|
}
|
|
15
34
|
|
|
16
|
-
export type Loader<
|
|
17
|
-
|
|
18
|
-
|
|
35
|
+
export type Loader<
|
|
36
|
+
Data = unknown,
|
|
37
|
+
Context = unknown,
|
|
38
|
+
Path extends string = string,
|
|
39
|
+
> = (args: LoaderArgs<Context, Path>) => Data | Promise<Data>
|
|
19
40
|
|
|
20
|
-
export interface RouteModule<
|
|
41
|
+
export interface RouteModule<
|
|
42
|
+
Data = unknown,
|
|
43
|
+
Context = unknown,
|
|
44
|
+
Path extends string = string,
|
|
45
|
+
> {
|
|
21
46
|
readonly default: unknown
|
|
22
|
-
readonly loader?: Loader<Data, Context>
|
|
47
|
+
readonly loader?: Loader<Data, Context, Path>
|
|
23
48
|
}
|
|
24
49
|
|
|
25
50
|
export type DocumentMode = "shell" | RenderMode
|
|
@@ -31,7 +56,7 @@ export interface RequestContextArgs<
|
|
|
31
56
|
> {
|
|
32
57
|
readonly request: Request
|
|
33
58
|
readonly route: Route | null
|
|
34
|
-
readonly params: Readonly<
|
|
59
|
+
readonly params: Readonly<RouteParams<Route["path"]>>
|
|
35
60
|
readonly purpose: RequestPurpose
|
|
36
61
|
readonly mode?: DocumentMode
|
|
37
62
|
}
|
|
@@ -42,9 +67,11 @@ export type RequestContextFactory<
|
|
|
42
67
|
> = (args: RequestContextArgs<Route>) => Context | Promise<Context>
|
|
43
68
|
|
|
44
69
|
/** Import a generated or application-provided route module by its entry ID. */
|
|
45
|
-
export type RouteImporter<
|
|
46
|
-
|
|
47
|
-
|
|
70
|
+
export type RouteImporter<
|
|
71
|
+
Data = unknown,
|
|
72
|
+
Context = unknown,
|
|
73
|
+
Path extends string = string,
|
|
74
|
+
> = (entry: string) => Promise<RouteModule<Data, Context, Path>>
|
|
48
75
|
|
|
49
76
|
export interface RenderedDocument {
|
|
50
77
|
readonly html: string
|
|
@@ -65,6 +92,41 @@ export interface LoadedRoute<
|
|
|
65
92
|
readonly loaderData: Data | undefined
|
|
66
93
|
}
|
|
67
94
|
|
|
95
|
+
/** A loaded route whose data follows the generated route-module map. */
|
|
96
|
+
export type LoadedRouteFor<
|
|
97
|
+
Route extends RouteDefinition,
|
|
98
|
+
Context = unknown,
|
|
99
|
+
> = Route extends RouteDefinition
|
|
100
|
+
? RouteLoaderFor<Route["path"]> extends (
|
|
101
|
+
...args: infer _Args
|
|
102
|
+
) => infer _Result
|
|
103
|
+
? Omit<
|
|
104
|
+
LoadedRoute<RouteLoaderData<Route["path"]>, Context, Route>,
|
|
105
|
+
"loaderData"
|
|
106
|
+
> & {
|
|
107
|
+
readonly loaderData: RouteLoaderData<Route["path"]>
|
|
108
|
+
}
|
|
109
|
+
: LoadedRoute<unknown, Context, Route>
|
|
110
|
+
: never
|
|
111
|
+
|
|
112
|
+
/** Route-module shape selected from one authored route definition. */
|
|
113
|
+
export type RouteModuleForRoute<
|
|
114
|
+
Route extends RouteDefinition,
|
|
115
|
+
Context = unknown,
|
|
116
|
+
> = Route extends RouteDefinition
|
|
117
|
+
? RouteLoaderFor<Route["path"]> extends (
|
|
118
|
+
...args: infer _Args
|
|
119
|
+
) => infer _Result
|
|
120
|
+
? RouteModule<RouteLoaderData<Route["path"]>, Context, Route["path"]>
|
|
121
|
+
: RouteModule<unknown, Context, Route["path"]>
|
|
122
|
+
: never
|
|
123
|
+
|
|
124
|
+
/** Importer shape for applications that own a typed route-module boundary. */
|
|
125
|
+
export type RouteImporterFor<
|
|
126
|
+
Route extends RouteDefinition,
|
|
127
|
+
Context = unknown,
|
|
128
|
+
> = (entry: Route["entry"]) => Promise<RouteModuleForRoute<Route, Context>>
|
|
129
|
+
|
|
68
130
|
export interface RouteRuntimeContextOptions {
|
|
69
131
|
readonly purpose: RequestPurpose
|
|
70
132
|
readonly mode?: DocumentMode
|
|
@@ -84,7 +146,7 @@ export interface RouteRuntime<
|
|
|
84
146
|
readonly match: (
|
|
85
147
|
url: string | URL,
|
|
86
148
|
options?: MatchRouteOptions,
|
|
87
|
-
) =>
|
|
149
|
+
) => RouteMatchForUrl<Route, string> | null
|
|
88
150
|
readonly createRequestContext: (
|
|
89
151
|
request: Request,
|
|
90
152
|
options: RouteRuntimeContextOptions,
|
|
@@ -92,7 +154,7 @@ export interface RouteRuntime<
|
|
|
92
154
|
readonly loadRoute: (
|
|
93
155
|
request: Request,
|
|
94
156
|
options?: RouteLoadOptions<Context>,
|
|
95
|
-
) => Promise<
|
|
157
|
+
) => Promise<LoadedRouteFor<Route, Context> | null>
|
|
96
158
|
readonly loadRouteData: (request: Request) => Promise<Response>
|
|
97
159
|
}
|
|
98
160
|
|
|
@@ -111,7 +173,7 @@ async function loadMatchedRoute<
|
|
|
111
173
|
Context = unknown,
|
|
112
174
|
Route extends RouteDefinition = RouteDefinition,
|
|
113
175
|
>(
|
|
114
|
-
match:
|
|
176
|
+
match: RouteMatchForUrl<Route, string>,
|
|
115
177
|
request: Request,
|
|
116
178
|
importRoute: RouteImporter<Data, Context>,
|
|
117
179
|
context?: Context,
|
|
@@ -120,8 +182,8 @@ async function loadMatchedRoute<
|
|
|
120
182
|
const loaderData = routeModule.loader
|
|
121
183
|
? await routeModule.loader({
|
|
122
184
|
request,
|
|
123
|
-
params: match.params,
|
|
124
|
-
context: context as Context,
|
|
185
|
+
params: match.params as LoaderArgs<Context, string>["params"],
|
|
186
|
+
context: context as LoaderContext<Context, string>,
|
|
125
187
|
})
|
|
126
188
|
: undefined
|
|
127
189
|
|
|
@@ -132,6 +194,15 @@ async function loadMatchedRoute<
|
|
|
132
194
|
}
|
|
133
195
|
}
|
|
134
196
|
|
|
197
|
+
export function createRouteRuntime<
|
|
198
|
+
Context = unknown,
|
|
199
|
+
Route extends RouteDefinition = RouteDefinition,
|
|
200
|
+
>(
|
|
201
|
+
options: Omit<RouteRuntimeOptions<Context, Route>, "importRoute"> & {
|
|
202
|
+
readonly importRoute: RouteImporterFor<Route, Context>
|
|
203
|
+
},
|
|
204
|
+
): RouteRuntime<Context, Route>
|
|
205
|
+
|
|
135
206
|
export function createRouteRuntime<
|
|
136
207
|
Context = unknown,
|
|
137
208
|
Route extends RouteDefinition = RouteDefinition,
|
|
@@ -148,7 +219,7 @@ export function createRouteRuntime<
|
|
|
148
219
|
return options.requestContext({
|
|
149
220
|
request,
|
|
150
221
|
route: match?.data ?? null,
|
|
151
|
-
params: match?.params ?? {}
|
|
222
|
+
params: (match?.params ?? {}) as Readonly<RouteParams<Route["path"]>>,
|
|
152
223
|
purpose: contextOptions.purpose,
|
|
153
224
|
...(contextOptions.mode === undefined
|
|
154
225
|
? {}
|
|
@@ -159,7 +230,7 @@ export function createRouteRuntime<
|
|
|
159
230
|
const loadRouteForRequest = async (
|
|
160
231
|
request: Request,
|
|
161
232
|
loadOptions: RouteLoadOptions<Context> = {},
|
|
162
|
-
): Promise<
|
|
233
|
+
): Promise<LoadedRouteFor<Route, Context> | null> => {
|
|
163
234
|
const sanitizedRequest = stripFlamefrontProtocolRequest(request)
|
|
164
235
|
const match = options.app.match(sanitizedRequest.url)
|
|
165
236
|
|
|
@@ -181,7 +252,7 @@ export function createRouteRuntime<
|
|
|
181
252
|
sanitizedRequest,
|
|
182
253
|
options.importRoute,
|
|
183
254
|
context,
|
|
184
|
-
)
|
|
255
|
+
) as Promise<LoadedRouteFor<Route, Context>>
|
|
185
256
|
}
|
|
186
257
|
|
|
187
258
|
const loadRouteData = async (request: Request): Promise<Response> => {
|
|
@@ -219,6 +290,28 @@ export function createRouteRuntime<
|
|
|
219
290
|
}
|
|
220
291
|
}
|
|
221
292
|
|
|
293
|
+
export function loadRoute<
|
|
294
|
+
_Data = unknown,
|
|
295
|
+
Context = unknown,
|
|
296
|
+
Route extends RouteDefinition = RouteDefinition,
|
|
297
|
+
>(
|
|
298
|
+
app: AppDefinition<Route>,
|
|
299
|
+
request: Request,
|
|
300
|
+
importRoute: RouteImporterFor<Route, Context>,
|
|
301
|
+
context?: Context,
|
|
302
|
+
): Promise<LoadedRouteFor<Route, Context> | null>
|
|
303
|
+
|
|
304
|
+
export function loadRoute<
|
|
305
|
+
Data = unknown,
|
|
306
|
+
Context = unknown,
|
|
307
|
+
Route extends RouteDefinition = RouteDefinition,
|
|
308
|
+
>(
|
|
309
|
+
app: AppDefinition<Route>,
|
|
310
|
+
request: Request,
|
|
311
|
+
importRoute: RouteImporter<Data, Context>,
|
|
312
|
+
context?: Context,
|
|
313
|
+
): Promise<LoadedRoute<Data, Context, Route> | null>
|
|
314
|
+
|
|
222
315
|
export async function loadRoute<
|
|
223
316
|
Data = unknown,
|
|
224
317
|
Context = unknown,
|
|
@@ -226,9 +319,9 @@ export async function loadRoute<
|
|
|
226
319
|
>(
|
|
227
320
|
app: AppDefinition<Route>,
|
|
228
321
|
request: Request,
|
|
229
|
-
importRoute:
|
|
322
|
+
importRoute: RouteImporter<Data, Context>,
|
|
230
323
|
context?: Context,
|
|
231
|
-
): Promise<
|
|
324
|
+
): Promise<LoadedRouteFor<Route, Context> | null> {
|
|
232
325
|
const sanitizedRequest = stripFlamefrontProtocolRequest(request)
|
|
233
326
|
const match = app.match(sanitizedRequest.url)
|
|
234
327
|
|
|
@@ -236,5 +329,10 @@ export async function loadRoute<
|
|
|
236
329
|
return null
|
|
237
330
|
}
|
|
238
331
|
|
|
239
|
-
return loadMatchedRoute(
|
|
332
|
+
return loadMatchedRoute(
|
|
333
|
+
match,
|
|
334
|
+
sanitizedRequest,
|
|
335
|
+
importRoute,
|
|
336
|
+
context,
|
|
337
|
+
) as Promise<LoadedRouteFor<Route, Context>>
|
|
240
338
|
}
|
package/src/srvx.ts
CHANGED
|
@@ -4,33 +4,36 @@ import { resolve } from "node:path"
|
|
|
4
4
|
import { staticMiddleware } from "srvx/static"
|
|
5
5
|
import type { ServerMiddleware, ServerOptions } from "srvx"
|
|
6
6
|
import {
|
|
7
|
-
joinBasename,
|
|
8
7
|
stripBasename,
|
|
9
8
|
type AppDefinition,
|
|
10
9
|
type RouteDefinition,
|
|
11
10
|
} from "./index.ts"
|
|
12
|
-
import
|
|
13
|
-
import type { DocumentMode, RenderedDocument } from "./server.ts"
|
|
11
|
+
import { isRouteFragmentRequest } from "./fragment-protocol.ts"
|
|
14
12
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
createFetchServerEntry,
|
|
14
|
+
type ResponseHeadersHook,
|
|
15
|
+
type ServerDocuments,
|
|
16
|
+
type ServerEntryLifecycle,
|
|
17
|
+
type TemplateLoader,
|
|
18
|
+
} from "./fetch.ts"
|
|
18
19
|
import { staticRouteFragmentDataFile } from "./static-fragment-artifacts.ts"
|
|
19
|
-
import type { StaticFragmentArtifact } from "./fragment-client.ts"
|
|
20
20
|
|
|
21
|
-
export type
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
export type {
|
|
22
|
+
FetchMiddleware,
|
|
23
|
+
FetchServerAssets,
|
|
24
|
+
FetchServerEntryOptions,
|
|
25
|
+
FlamefrontFetchServerEntry,
|
|
26
|
+
ResponseHeaders,
|
|
27
|
+
ResponseHeadersContext,
|
|
28
|
+
ResponseHeadersHook,
|
|
29
|
+
ServerDocuments,
|
|
30
|
+
StaticFragmentContext,
|
|
31
|
+
StaticFragmentLoader,
|
|
32
|
+
TemplateContext,
|
|
33
|
+
TemplateLoader,
|
|
34
|
+
} from "./fetch.ts"
|
|
30
35
|
|
|
31
|
-
export type
|
|
32
|
-
context: TemplateContext<Route>,
|
|
33
|
-
) => string | Promise<string>
|
|
36
|
+
export type SrvxMiddleware = ServerMiddleware
|
|
34
37
|
|
|
35
38
|
/** Client asset location and the optional replacement for template lookup. */
|
|
36
39
|
export interface ServerAssets<Route extends RouteDefinition = RouteDefinition> {
|
|
@@ -38,30 +41,7 @@ export interface ServerAssets<Route extends RouteDefinition = RouteDefinition> {
|
|
|
38
41
|
readonly loadTemplate?: TemplateLoader<Route>
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
export
|
|
42
|
-
Route extends RouteDefinition = RouteDefinition,
|
|
43
|
-
> {
|
|
44
|
-
readonly request: Request
|
|
45
|
-
readonly route: Route | null
|
|
46
|
-
readonly mode: DocumentMode
|
|
47
|
-
readonly document: RenderedDocument
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export type ResponseHeaders = HeadersInit
|
|
51
|
-
|
|
52
|
-
/** Add or override response headers after document rendering. */
|
|
53
|
-
export type ResponseHeadersHook<
|
|
54
|
-
Route extends RouteDefinition = RouteDefinition,
|
|
55
|
-
> = (
|
|
56
|
-
context: ResponseHeadersContext<Route>,
|
|
57
|
-
) => ResponseHeaders | Promise<ResponseHeaders>
|
|
58
|
-
|
|
59
|
-
/** Lifecycle operations exposed alongside the srvx server options. */
|
|
60
|
-
export interface ServerEntryLifecycle {
|
|
61
|
-
readonly renderDocument: OctaneDocuments["renderDocument"]
|
|
62
|
-
readonly loadRouteData: OctaneDocuments["loadRouteData"]
|
|
63
|
-
readonly renderFragment?: OctaneDocuments["renderFragment"]
|
|
64
|
-
}
|
|
44
|
+
export type { ServerEntryLifecycle }
|
|
65
45
|
|
|
66
46
|
/** The single default-export value consumed by Flamefront's lifecycle. */
|
|
67
47
|
export type FlamefrontServerEntry = ServerOptions & ServerEntryLifecycle
|
|
@@ -71,11 +51,7 @@ export interface SrvxServerEntryOptions<
|
|
|
71
51
|
Route extends RouteDefinition = RouteDefinition,
|
|
72
52
|
> {
|
|
73
53
|
readonly app: AppDefinition<Route>
|
|
74
|
-
readonly documents:
|
|
75
|
-
OctaneDocuments,
|
|
76
|
-
"renderDocument" | "loadRouteData"
|
|
77
|
-
> &
|
|
78
|
-
Partial<Pick<OctaneDocuments, "renderFragment">>
|
|
54
|
+
readonly documents: ServerDocuments
|
|
79
55
|
readonly assets: ServerAssets<Route>
|
|
80
56
|
/** Applied outermost first, in declaration order, around framework transport. */
|
|
81
57
|
readonly middleware?: readonly SrvxMiddleware[]
|
|
@@ -105,16 +81,6 @@ async function loadDefaultTemplate(clientDirectory: string): Promise<string> {
|
|
|
105
81
|
throw lastError
|
|
106
82
|
}
|
|
107
83
|
|
|
108
|
-
function mergeHeaders(target: Headers, source: HeadersInit | undefined): void {
|
|
109
|
-
if (source === undefined) {
|
|
110
|
-
return
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
for (const [name, value] of new Headers(source)) {
|
|
114
|
-
target.set(name, value)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
84
|
function staticRequest(request: Request, basename: string): Request {
|
|
119
85
|
if (
|
|
120
86
|
basename === "/" ||
|
|
@@ -145,14 +111,11 @@ export function createSrvxServerEntry<
|
|
|
145
111
|
const loadTemplate =
|
|
146
112
|
options.assets.loadTemplate ?? (() => loadDefaultTemplate(clientDirectory))
|
|
147
113
|
const serveClientFile = staticMiddleware({ dir: clientDirectory })
|
|
148
|
-
const defaultClientRoute = options.app.routes.find(
|
|
149
|
-
(route) => route.render === "client",
|
|
150
|
-
)
|
|
151
114
|
|
|
152
115
|
const frameworkMiddleware: SrvxMiddleware = (request, next) => {
|
|
153
116
|
const url = new URL(request.url)
|
|
154
117
|
|
|
155
|
-
if (
|
|
118
|
+
if (isRouteFragmentRequest(url)) {
|
|
156
119
|
return next()
|
|
157
120
|
}
|
|
158
121
|
|
|
@@ -176,139 +139,36 @@ export function createSrvxServerEntry<
|
|
|
176
139
|
)
|
|
177
140
|
}
|
|
178
141
|
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
url.pathname === options.app.routing.basename &&
|
|
186
|
-
!match &&
|
|
187
|
-
defaultClientRoute
|
|
188
|
-
) {
|
|
189
|
-
return new Response(null, {
|
|
190
|
-
status: 302,
|
|
191
|
-
headers: {
|
|
192
|
-
Location: joinBasename(
|
|
193
|
-
options.app.routing.basename,
|
|
194
|
-
defaultClientRoute.path,
|
|
195
|
-
),
|
|
196
|
-
},
|
|
197
|
-
})
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (url.pathname === options.app.routing.dataPath) {
|
|
201
|
-
return options.documents.loadRouteData(request)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (isStaticFragmentRequest(url)) {
|
|
205
|
-
const sanitizedRequest = stripFlamefrontProtocolRequest(request)
|
|
206
|
-
const fragmentMatch = options.app.match(sanitizedRequest.url)
|
|
207
|
-
|
|
208
|
-
if (!fragmentMatch || fragmentMatch.data.render !== "static") {
|
|
209
|
-
return new Response("Not found", { status: 404 })
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
let artifact: StaticFragmentArtifact | undefined
|
|
213
|
-
|
|
142
|
+
const fetchEntry = createFetchServerEntry({
|
|
143
|
+
app: options.app,
|
|
144
|
+
documents: options.documents,
|
|
145
|
+
assets: {
|
|
146
|
+
loadTemplate,
|
|
147
|
+
loadStaticFragment: async ({ route }) => {
|
|
214
148
|
try {
|
|
215
|
-
|
|
149
|
+
return JSON.parse(
|
|
216
150
|
await readFile(
|
|
217
|
-
staticRouteFragmentDataFile(clientDirectory,
|
|
151
|
+
staticRouteFragmentDataFile(clientDirectory, route),
|
|
218
152
|
"utf8",
|
|
219
153
|
),
|
|
220
|
-
)
|
|
154
|
+
)
|
|
221
155
|
} catch (error) {
|
|
222
156
|
if ((error as { code?: string }).code !== "ENOENT") {
|
|
223
157
|
throw error
|
|
224
158
|
}
|
|
225
159
|
|
|
226
|
-
|
|
227
|
-
return new Response("Not found", { status: 404 })
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
artifact = await options.documents.renderFragment(sanitizedRequest)
|
|
160
|
+
return undefined
|
|
231
161
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
if (options.headers) {
|
|
239
|
-
mergeHeaders(
|
|
240
|
-
responseHeaders,
|
|
241
|
-
await options.headers({
|
|
242
|
-
request: sanitizedRequest,
|
|
243
|
-
route: fragmentMatch.data,
|
|
244
|
-
mode: "static",
|
|
245
|
-
document: {
|
|
246
|
-
html: artifact.html,
|
|
247
|
-
routeData: artifact.routeData,
|
|
248
|
-
status: artifact.status,
|
|
249
|
-
},
|
|
250
|
-
}),
|
|
251
|
-
)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return new Response(JSON.stringify(artifact), {
|
|
255
|
-
status: artifact.status ?? 200,
|
|
256
|
-
headers: responseHeaders,
|
|
257
|
-
})
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (!match) {
|
|
261
|
-
return new Response("Not found", { status: 404 })
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const mode: DocumentMode = url.searchParams.has("__flamefront_shell")
|
|
265
|
-
? "shell"
|
|
266
|
-
: match.data.render
|
|
267
|
-
const template = await loadTemplate({
|
|
268
|
-
request,
|
|
269
|
-
route: match.data,
|
|
270
|
-
mode,
|
|
271
|
-
})
|
|
272
|
-
const document = await options.documents.renderDocument(
|
|
273
|
-
template,
|
|
274
|
-
request,
|
|
275
|
-
{ mode },
|
|
276
|
-
)
|
|
277
|
-
const responseHeaders = new Headers({
|
|
278
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
mergeHeaders(responseHeaders, document.headers)
|
|
282
|
-
if (options.headers) {
|
|
283
|
-
mergeHeaders(
|
|
284
|
-
responseHeaders,
|
|
285
|
-
await options.headers({
|
|
286
|
-
request,
|
|
287
|
-
route: match.data,
|
|
288
|
-
mode,
|
|
289
|
-
document,
|
|
290
|
-
}),
|
|
291
|
-
)
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return new Response(document.html, {
|
|
295
|
-
status: document.status ?? 200,
|
|
296
|
-
headers: responseHeaders,
|
|
297
|
-
})
|
|
298
|
-
} catch (error) {
|
|
299
|
-
if (error instanceof Response) {
|
|
300
|
-
return error
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
throw error
|
|
304
|
-
}
|
|
305
|
-
}
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
headers: options.headers,
|
|
165
|
+
})
|
|
306
166
|
|
|
307
167
|
return {
|
|
308
|
-
fetch,
|
|
168
|
+
fetch: fetchEntry.fetch,
|
|
309
169
|
middleware: [...(options.middleware ?? []), frameworkMiddleware],
|
|
310
|
-
renderDocument:
|
|
311
|
-
loadRouteData:
|
|
312
|
-
renderFragment:
|
|
170
|
+
renderDocument: fetchEntry.renderDocument,
|
|
171
|
+
loadRouteData: fetchEntry.loadRouteData,
|
|
172
|
+
renderFragment: fetchEntry.renderFragment,
|
|
313
173
|
}
|
|
314
174
|
}
|