react-bun-ssr 0.1.1 → 0.3.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 +25 -17
- package/framework/cli/commands.ts +104 -246
- package/framework/cli/dev-client-watch.ts +288 -0
- package/framework/cli/dev-route-table.ts +71 -0
- package/framework/cli/dev-runtime.ts +382 -0
- package/framework/cli/internal.ts +142 -0
- package/framework/cli/main.ts +27 -31
- package/framework/runtime/build-tools.ts +134 -13
- package/framework/runtime/bun-route-adapter.ts +20 -7
- package/framework/runtime/client-runtime.tsx +150 -159
- package/framework/runtime/client-transition-core.ts +159 -0
- package/framework/runtime/config.ts +7 -2
- package/framework/runtime/index.ts +1 -1
- package/framework/runtime/link.tsx +3 -11
- package/framework/runtime/markdown-routes.ts +1 -14
- package/framework/runtime/matcher.ts +11 -11
- package/framework/runtime/module-loader.ts +75 -25
- package/framework/runtime/render.tsx +150 -22
- package/framework/runtime/route-api.ts +1 -1
- package/framework/runtime/router.ts +75 -4
- package/framework/runtime/server.ts +57 -106
- package/framework/runtime/tree.tsx +24 -2
- package/framework/runtime/types.ts +13 -2
- package/framework/runtime/utils.ts +3 -0
- package/package.json +13 -7
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { matchRouteBySegments } from "./matcher";
|
|
2
|
+
import type {
|
|
3
|
+
ClientRouteSnapshot,
|
|
4
|
+
Params,
|
|
5
|
+
TransitionChunk,
|
|
6
|
+
TransitionDeferredChunk,
|
|
7
|
+
TransitionDocumentChunk,
|
|
8
|
+
TransitionInitialChunk,
|
|
9
|
+
TransitionRedirectChunk,
|
|
10
|
+
} from "./types";
|
|
11
|
+
|
|
12
|
+
export const PREFETCH_TTL_MS = 30_000;
|
|
13
|
+
export const MAX_REDIRECT_DEPTH = 8;
|
|
14
|
+
|
|
15
|
+
export interface TransitionChunkParserState {
|
|
16
|
+
buffer: string;
|
|
17
|
+
initialChunk: TransitionInitialChunk | TransitionRedirectChunk | TransitionDocumentChunk | null;
|
|
18
|
+
deferredChunks: TransitionDeferredChunk[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TransitionNavigationOptions {
|
|
22
|
+
historyManagedByNavigationApi?: boolean;
|
|
23
|
+
isPopState?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createTransitionChunkParserState(): TransitionChunkParserState {
|
|
27
|
+
return {
|
|
28
|
+
buffer: "",
|
|
29
|
+
initialChunk: null,
|
|
30
|
+
deferredChunks: [],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function matchClientPageRoute(
|
|
35
|
+
routes: ClientRouteSnapshot[],
|
|
36
|
+
pathname: string,
|
|
37
|
+
): { route: ClientRouteSnapshot; params: Params } | null {
|
|
38
|
+
return matchRouteBySegments(routes, pathname);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function applyParsedTransitionChunk(
|
|
42
|
+
state: TransitionChunkParserState,
|
|
43
|
+
chunk: TransitionChunk,
|
|
44
|
+
): TransitionChunkParserState {
|
|
45
|
+
if (chunk.type === "initial" || chunk.type === "redirect" || chunk.type === "document") {
|
|
46
|
+
if (state.initialChunk) {
|
|
47
|
+
return state;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
...state,
|
|
52
|
+
initialChunk: chunk,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
...state,
|
|
58
|
+
deferredChunks: [...state.deferredChunks, chunk],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function consumeTransitionChunkText(
|
|
63
|
+
state: TransitionChunkParserState,
|
|
64
|
+
text: string,
|
|
65
|
+
): TransitionChunkParserState {
|
|
66
|
+
let buffer = state.buffer + text;
|
|
67
|
+
let nextState = {
|
|
68
|
+
...state,
|
|
69
|
+
buffer: "",
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
let start = 0;
|
|
73
|
+
for (let index = 0; index < buffer.length; index += 1) {
|
|
74
|
+
if (buffer[index] !== "\n") {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const line = buffer.slice(start, index).trim();
|
|
79
|
+
if (line.length > 0) {
|
|
80
|
+
nextState = applyParsedTransitionChunk(
|
|
81
|
+
nextState,
|
|
82
|
+
JSON.parse(line) as TransitionChunk,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
start = index + 1;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
buffer = buffer.slice(start);
|
|
89
|
+
return {
|
|
90
|
+
...nextState,
|
|
91
|
+
buffer,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function flushTransitionChunkText(
|
|
96
|
+
state: TransitionChunkParserState,
|
|
97
|
+
): TransitionChunkParserState {
|
|
98
|
+
const trailing = state.buffer.trim();
|
|
99
|
+
if (trailing.length === 0) {
|
|
100
|
+
return {
|
|
101
|
+
...state,
|
|
102
|
+
buffer: "",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
...applyParsedTransitionChunk(
|
|
108
|
+
{
|
|
109
|
+
...state,
|
|
110
|
+
buffer: "",
|
|
111
|
+
},
|
|
112
|
+
JSON.parse(trailing) as TransitionChunk,
|
|
113
|
+
),
|
|
114
|
+
buffer: "",
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function sanitizePrefetchCache<T extends { createdAt: number }>(
|
|
119
|
+
cache: Map<string, T>,
|
|
120
|
+
options: {
|
|
121
|
+
now?: number;
|
|
122
|
+
ttlMs?: number;
|
|
123
|
+
} = {},
|
|
124
|
+
): void {
|
|
125
|
+
const now = options.now ?? Date.now();
|
|
126
|
+
const ttlMs = options.ttlMs ?? PREFETCH_TTL_MS;
|
|
127
|
+
|
|
128
|
+
for (const [key, entry] of cache.entries()) {
|
|
129
|
+
if (now - entry.createdAt > ttlMs) {
|
|
130
|
+
cache.delete(key);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function shouldSkipSoftNavigation(
|
|
136
|
+
currentPath: string,
|
|
137
|
+
targetPath: string,
|
|
138
|
+
options: TransitionNavigationOptions,
|
|
139
|
+
): boolean {
|
|
140
|
+
return (
|
|
141
|
+
currentPath === targetPath
|
|
142
|
+
&& !options.isPopState
|
|
143
|
+
&& !options.historyManagedByNavigationApi
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function shouldHardNavigateForRedirectDepth(
|
|
148
|
+
depth: number,
|
|
149
|
+
maxDepth = MAX_REDIRECT_DEPTH,
|
|
150
|
+
): boolean {
|
|
151
|
+
return depth > maxDepth;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function isStaleNavigationToken(
|
|
155
|
+
activeToken: number,
|
|
156
|
+
candidateToken: number,
|
|
157
|
+
): boolean {
|
|
158
|
+
return activeToken !== candidateToken;
|
|
159
|
+
}
|
|
@@ -71,15 +71,20 @@ function toHeaderRules(config: FrameworkConfig): ResolvedResponseHeaderRule[] {
|
|
|
71
71
|
throw new Error(`[rbssr config] \`headers[${index}].headers\` must include at least one header.`);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
const headers: Record<string, string> = {};
|
|
74
|
+
const headers: Record<string, string | null> = {};
|
|
75
75
|
for (const [key, value] of entries) {
|
|
76
76
|
if (typeof key !== "string" || key.trim().length === 0) {
|
|
77
77
|
throw new Error(`[rbssr config] \`headers[${index}].headers\` contains an empty header name.`);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
if (value === null) {
|
|
81
|
+
headers[key] = null;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
80
85
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
81
86
|
throw new Error(
|
|
82
|
-
`[rbssr config] \`headers[${index}].headers.${key}\` must be a non-empty string value.`,
|
|
87
|
+
`[rbssr config] \`headers[${index}].headers.${key}\` must be a non-empty string value or null.`,
|
|
83
88
|
);
|
|
84
89
|
}
|
|
85
90
|
|
|
@@ -26,5 +26,5 @@ export { createServer, startHttpServer } from "./server";
|
|
|
26
26
|
export { defer, json, redirect, defineConfig } from "./helpers";
|
|
27
27
|
export { isRouteErrorResponse, notFound, routeError } from "./route-errors";
|
|
28
28
|
export { Link, type LinkProps } from "./link";
|
|
29
|
-
export { useRouter, type Router, type RouterNavigateOptions } from "./router";
|
|
29
|
+
export { useRouter, type Router, type RouterNavigateInfo, type RouterNavigateListener, type RouterNavigateOptions } from "./router";
|
|
30
30
|
export { Outlet, useLoaderData, useParams, useRequestUrl, useRouteError } from "./tree";
|
|
@@ -4,15 +4,7 @@ import type {
|
|
|
4
4
|
MouseEvent,
|
|
5
5
|
TouchEvent,
|
|
6
6
|
} from "react";
|
|
7
|
-
|
|
8
|
-
interface NavigateInfo {
|
|
9
|
-
from: string;
|
|
10
|
-
to: string;
|
|
11
|
-
status: number;
|
|
12
|
-
kind: "page" | "not_found" | "catch" | "error";
|
|
13
|
-
redirected: boolean;
|
|
14
|
-
prefetched: boolean;
|
|
15
|
-
}
|
|
7
|
+
import type { RouterNavigateInfo } from "./router";
|
|
16
8
|
|
|
17
9
|
export interface LinkProps
|
|
18
10
|
extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href"> {
|
|
@@ -20,7 +12,7 @@ export interface LinkProps
|
|
|
20
12
|
replace?: boolean;
|
|
21
13
|
scroll?: boolean;
|
|
22
14
|
prefetch?: "intent" | "none";
|
|
23
|
-
onNavigate?: (info:
|
|
15
|
+
onNavigate?: (info: RouterNavigateInfo) => void;
|
|
24
16
|
}
|
|
25
17
|
|
|
26
18
|
function shouldHandleNavigation(event: MouseEvent<HTMLAnchorElement>): boolean {
|
|
@@ -89,7 +81,7 @@ async function prefetch(href: string): Promise<void> {
|
|
|
89
81
|
async function navigate(href: string, options: {
|
|
90
82
|
replace?: boolean;
|
|
91
83
|
scroll?: boolean;
|
|
92
|
-
onNavigate?: (info:
|
|
84
|
+
onNavigate?: (info: RouterNavigateInfo) => void;
|
|
93
85
|
}): Promise<void> {
|
|
94
86
|
if (typeof window === "undefined") {
|
|
95
87
|
return;
|
|
@@ -112,24 +112,11 @@ function resolveGeneratedRoot(routesDir: string, generatedMarkdownRootDir?: stri
|
|
|
112
112
|
return path.resolve(appRoutesMatch[1]!, ".rbssr", "generated", "markdown-routes");
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
const snapshotRoutesMatch = normalizedRoutesDir.match(
|
|
116
|
-
/^(.*)\/\.rbssr\/dev\/server-snapshots\/v\d+\/routes$/,
|
|
117
|
-
);
|
|
118
|
-
if (snapshotRoutesMatch) {
|
|
119
|
-
return path.resolve(snapshotRoutesMatch[1]!, ".rbssr", "generated", "markdown-routes");
|
|
120
|
-
}
|
|
121
|
-
|
|
122
115
|
return path.resolve(routesDir, "..", ".rbssr", "generated", "markdown-routes");
|
|
123
116
|
}
|
|
124
117
|
|
|
125
118
|
function toRouteGroupKey(routesDir: string): string {
|
|
126
|
-
|
|
127
|
-
const canonical = normalized.replace(
|
|
128
|
-
/\/\.rbssr\/dev\/server-snapshots\/v\d+\/routes$/,
|
|
129
|
-
"/.rbssr/dev/server-snapshots/routes",
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
return stableHash(`${MARKDOWN_WRAPPER_VERSION}\0${canonical}`);
|
|
119
|
+
return stableHash(`${MARKDOWN_WRAPPER_VERSION}\0${normalizeSlashes(path.resolve(routesDir))}`);
|
|
133
120
|
}
|
|
134
121
|
|
|
135
122
|
function parseFrontmatter(raw: string): {
|
|
@@ -60,10 +60,10 @@ function matchSegments(segments: RouteSegment[], pathname: string): Params | nul
|
|
|
60
60
|
return params;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
export function
|
|
64
|
-
routes:
|
|
63
|
+
export function matchRouteBySegments<T extends { segments: RouteSegment[] }>(
|
|
64
|
+
routes: T[],
|
|
65
65
|
pathname: string,
|
|
66
|
-
):
|
|
66
|
+
): { route: T; params: Params } | null {
|
|
67
67
|
for (const route of routes) {
|
|
68
68
|
const params = matchSegments(route.segments, pathname);
|
|
69
69
|
if (params) {
|
|
@@ -74,16 +74,16 @@ export function matchPageRoute(
|
|
|
74
74
|
return null;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
export function matchPageRoute(
|
|
78
|
+
routes: PageRouteDefinition[],
|
|
79
|
+
pathname: string,
|
|
80
|
+
): RouteMatch<PageRouteDefinition> | null {
|
|
81
|
+
return matchRouteBySegments(routes, pathname);
|
|
82
|
+
}
|
|
83
|
+
|
|
77
84
|
export function matchApiRoute(
|
|
78
85
|
routes: ApiRouteDefinition[],
|
|
79
86
|
pathname: string,
|
|
80
87
|
): RouteMatch<ApiRouteDefinition> | null {
|
|
81
|
-
|
|
82
|
-
const params = matchSegments(route.segments, pathname);
|
|
83
|
-
if (params) {
|
|
84
|
-
return { route, params };
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return null;
|
|
88
|
+
return matchRouteBySegments(routes, pathname);
|
|
89
89
|
}
|
|
@@ -28,21 +28,27 @@ const SERVER_BUILD_EXTERNAL = [
|
|
|
28
28
|
export interface RouteModuleLoadOptions {
|
|
29
29
|
cacheBustKey?: string;
|
|
30
30
|
serverBytecode?: boolean;
|
|
31
|
+
devSourceImports?: boolean;
|
|
32
|
+
nodeEnv?: "development" | "production";
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
export function createServerModuleCacheKey(options: {
|
|
34
36
|
absoluteFilePath: string;
|
|
35
37
|
cacheBustKey?: string;
|
|
36
38
|
serverBytecode: boolean;
|
|
39
|
+
nodeEnv?: "development" | "production";
|
|
37
40
|
}): string {
|
|
38
|
-
|
|
41
|
+
const nodeEnv = options.nodeEnv ?? (process.env.NODE_ENV === "production" ? "production" : "development");
|
|
42
|
+
return `${options.absoluteFilePath}|${options.cacheBustKey ?? 'prod'}|bytecode:${options.serverBytecode ? '1' : '0'}|env:${nodeEnv}|bun:${Bun.version}`;
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
export function createServerBuildConfig(options: {
|
|
42
46
|
absoluteFilePath: string;
|
|
43
47
|
outDir: string;
|
|
44
48
|
serverBytecode: boolean;
|
|
49
|
+
nodeEnv?: "development" | "production";
|
|
45
50
|
}): Bun.BuildConfig {
|
|
51
|
+
const nodeEnv = options.nodeEnv ?? (process.env.NODE_ENV === "production" ? "production" : "development");
|
|
46
52
|
return {
|
|
47
53
|
entrypoints: [options.absoluteFilePath],
|
|
48
54
|
outdir: options.outDir,
|
|
@@ -55,6 +61,9 @@ export function createServerBuildConfig(options: {
|
|
|
55
61
|
minify: false,
|
|
56
62
|
naming: 'entry-[hash].[ext]',
|
|
57
63
|
external: SERVER_BUILD_EXTERNAL,
|
|
64
|
+
define: {
|
|
65
|
+
"process.env.NODE_ENV": JSON.stringify(nodeEnv),
|
|
66
|
+
},
|
|
58
67
|
};
|
|
59
68
|
}
|
|
60
69
|
|
|
@@ -67,15 +76,21 @@ export async function importModule<T>(
|
|
|
67
76
|
return (await import(url)) as T;
|
|
68
77
|
}
|
|
69
78
|
|
|
79
|
+
function normalizeLoadOptions(
|
|
80
|
+
options: string | RouteModuleLoadOptions | undefined,
|
|
81
|
+
): RouteModuleLoadOptions {
|
|
82
|
+
if (typeof options === "string") {
|
|
83
|
+
return {
|
|
84
|
+
cacheBustKey: options,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return options ?? {};
|
|
89
|
+
}
|
|
90
|
+
|
|
70
91
|
function toRouteModule(filePath: string, moduleValue: unknown): RouteModule {
|
|
71
92
|
const rawValue = moduleValue as Record<string, unknown>;
|
|
72
|
-
const value =
|
|
73
|
-
rawValue &&
|
|
74
|
-
typeof rawValue.default === 'object' &&
|
|
75
|
-
rawValue.default !== null &&
|
|
76
|
-
'default' in (rawValue.default as Record<string, unknown>)
|
|
77
|
-
? (rawValue.default as Partial<RouteModule>)
|
|
78
|
-
: (rawValue as Partial<RouteModule>);
|
|
93
|
+
const value = unwrapModuleNamespace(rawValue) as Partial<RouteModule>;
|
|
79
94
|
const component = value.default;
|
|
80
95
|
|
|
81
96
|
if (typeof component !== 'function') {
|
|
@@ -93,6 +108,19 @@ function toRouteModule(filePath: string, moduleValue: unknown): RouteModule {
|
|
|
93
108
|
} as RouteModule;
|
|
94
109
|
}
|
|
95
110
|
|
|
111
|
+
function unwrapModuleNamespace(moduleValue: Record<string, unknown>): Record<string, unknown> {
|
|
112
|
+
if (
|
|
113
|
+
moduleValue
|
|
114
|
+
&& typeof moduleValue.default === "object"
|
|
115
|
+
&& moduleValue.default !== null
|
|
116
|
+
&& "default" in (moduleValue.default as Record<string, unknown>)
|
|
117
|
+
) {
|
|
118
|
+
return moduleValue.default as Record<string, unknown>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return moduleValue;
|
|
122
|
+
}
|
|
123
|
+
|
|
96
124
|
function isCompilableRouteModule(filePath: string): boolean {
|
|
97
125
|
return /\.(tsx|jsx|ts|js)$/.test(filePath);
|
|
98
126
|
}
|
|
@@ -108,10 +136,12 @@ async function buildServerModule(
|
|
|
108
136
|
|
|
109
137
|
const cacheBustKey = options.cacheBustKey;
|
|
110
138
|
const serverBytecode = options.serverBytecode ?? true;
|
|
139
|
+
const nodeEnv = options.nodeEnv ?? (process.env.NODE_ENV === "production" ? "production" : "development");
|
|
111
140
|
const cacheKey = createServerModuleCacheKey({
|
|
112
141
|
absoluteFilePath,
|
|
113
142
|
cacheBustKey,
|
|
114
143
|
serverBytecode,
|
|
144
|
+
nodeEnv,
|
|
115
145
|
});
|
|
116
146
|
const existing = serverBundlePathCache.get(cacheKey);
|
|
117
147
|
if (existing) {
|
|
@@ -133,6 +163,7 @@ async function buildServerModule(
|
|
|
133
163
|
absoluteFilePath,
|
|
134
164
|
outDir,
|
|
135
165
|
serverBytecode,
|
|
166
|
+
nodeEnv,
|
|
136
167
|
}),
|
|
137
168
|
);
|
|
138
169
|
|
|
@@ -149,6 +180,7 @@ async function buildServerModule(
|
|
|
149
180
|
absoluteFilePath,
|
|
150
181
|
outDir,
|
|
151
182
|
serverBytecode: false,
|
|
183
|
+
nodeEnv,
|
|
152
184
|
}),
|
|
153
185
|
);
|
|
154
186
|
|
|
@@ -210,10 +242,13 @@ export async function loadRouteModule(
|
|
|
210
242
|
filePath: string,
|
|
211
243
|
options: RouteModuleLoadOptions = {},
|
|
212
244
|
): Promise<RouteModule> {
|
|
213
|
-
const
|
|
245
|
+
const normalizedOptions = normalizeLoadOptions(options);
|
|
246
|
+
const modulePath = normalizedOptions.devSourceImports
|
|
247
|
+
? path.resolve(filePath)
|
|
248
|
+
: await buildServerModule(filePath, normalizedOptions);
|
|
214
249
|
const moduleValue = await importModule<unknown>(
|
|
215
|
-
|
|
216
|
-
|
|
250
|
+
modulePath,
|
|
251
|
+
normalizedOptions.cacheBustKey,
|
|
217
252
|
);
|
|
218
253
|
return toRouteModule(filePath, moduleValue);
|
|
219
254
|
}
|
|
@@ -224,10 +259,12 @@ export async function loadRouteModules(options: {
|
|
|
224
259
|
routeFilePath: string;
|
|
225
260
|
cacheBustKey?: string;
|
|
226
261
|
serverBytecode?: boolean;
|
|
262
|
+
devSourceImports?: boolean;
|
|
227
263
|
}): Promise<RouteModuleBundle> {
|
|
228
264
|
const moduleOptions: RouteModuleLoadOptions = {
|
|
229
265
|
cacheBustKey: options.cacheBustKey,
|
|
230
266
|
serverBytecode: options.serverBytecode,
|
|
267
|
+
devSourceImports: options.devSourceImports,
|
|
231
268
|
};
|
|
232
269
|
const [root, layouts, route] = await Promise.all([
|
|
233
270
|
loadRouteModule(options.rootFilePath, moduleOptions),
|
|
@@ -266,16 +303,20 @@ function normalizeMiddlewareExport(value: unknown): Middleware[] {
|
|
|
266
303
|
|
|
267
304
|
export async function loadGlobalMiddleware(
|
|
268
305
|
middlewareFilePath: string,
|
|
269
|
-
|
|
306
|
+
options: string | RouteModuleLoadOptions = {},
|
|
270
307
|
): Promise<Middleware[]> {
|
|
271
308
|
if (!(await existsPath(middlewareFilePath))) {
|
|
272
309
|
return [];
|
|
273
310
|
}
|
|
274
311
|
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
312
|
+
const normalizedOptions = normalizeLoadOptions(options);
|
|
313
|
+
const modulePath = normalizedOptions.devSourceImports
|
|
314
|
+
? path.resolve(middlewareFilePath)
|
|
315
|
+
: await buildServerModule(middlewareFilePath, normalizedOptions);
|
|
316
|
+
const raw = unwrapModuleNamespace(await importModule<Record<string, unknown>>(
|
|
317
|
+
modulePath,
|
|
318
|
+
normalizedOptions.cacheBustKey,
|
|
319
|
+
));
|
|
279
320
|
|
|
280
321
|
return [
|
|
281
322
|
...normalizeMiddlewareExport(raw.default),
|
|
@@ -285,14 +326,18 @@ export async function loadGlobalMiddleware(
|
|
|
285
326
|
|
|
286
327
|
export async function loadNestedMiddleware(
|
|
287
328
|
middlewareFilePaths: string[],
|
|
288
|
-
|
|
329
|
+
options: string | RouteModuleLoadOptions = {},
|
|
289
330
|
): Promise<Middleware[]> {
|
|
331
|
+
const normalizedOptions = normalizeLoadOptions(options);
|
|
290
332
|
const rawModules = await Promise.all(
|
|
291
|
-
middlewareFilePaths.map((middlewareFilePath) => {
|
|
292
|
-
|
|
293
|
-
middlewareFilePath
|
|
294
|
-
|
|
295
|
-
|
|
333
|
+
middlewareFilePaths.map(async (middlewareFilePath) => {
|
|
334
|
+
const modulePath = normalizedOptions.devSourceImports
|
|
335
|
+
? path.resolve(middlewareFilePath)
|
|
336
|
+
: await buildServerModule(middlewareFilePath, normalizedOptions);
|
|
337
|
+
return unwrapModuleNamespace(await importModule<Record<string, unknown>>(
|
|
338
|
+
modulePath,
|
|
339
|
+
normalizedOptions.cacheBustKey,
|
|
340
|
+
));
|
|
296
341
|
}),
|
|
297
342
|
);
|
|
298
343
|
|
|
@@ -310,8 +355,13 @@ export function extractRouteMiddleware(module: RouteModule): Middleware[] {
|
|
|
310
355
|
|
|
311
356
|
export async function loadApiRouteModule(
|
|
312
357
|
filePath: string,
|
|
313
|
-
|
|
358
|
+
options: string | RouteModuleLoadOptions = {},
|
|
314
359
|
): Promise<ApiRouteModule> {
|
|
315
|
-
const
|
|
316
|
-
|
|
360
|
+
const normalizedOptions = normalizeLoadOptions(options);
|
|
361
|
+
const modulePath = normalizedOptions.devSourceImports
|
|
362
|
+
? path.resolve(filePath)
|
|
363
|
+
: await buildServerModule(filePath, normalizedOptions);
|
|
364
|
+
return unwrapModuleNamespace(
|
|
365
|
+
await importModule<Record<string, unknown>>(modulePath, normalizedOptions.cacheBustKey),
|
|
366
|
+
) as ApiRouteModule;
|
|
317
367
|
}
|