@reckona/mreact-router 0.0.174 → 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/build.ts
CHANGED
|
@@ -279,6 +279,7 @@ export interface BuiltServerManifest {
|
|
|
279
279
|
routesDir?: string;
|
|
280
280
|
routeServerActionReferences?: Record<string, BuiltServerActionExpressionReference[]>;
|
|
281
281
|
serverActionManifest?: BuiltServerActionReference[];
|
|
282
|
+
serverModuleClosureFiles?: Record<string, string[]>;
|
|
282
283
|
serverModuleFiles?: Record<string, string>;
|
|
283
284
|
serverModuleRenderFiles?: Record<string, string>;
|
|
284
285
|
serverModuleRequestFiles?: Record<string, string>;
|
|
@@ -625,6 +626,10 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
|
|
|
625
626
|
...(navigationRuntimeScript === undefined ? [] : [navigationRuntimeScript]),
|
|
626
627
|
]),
|
|
627
628
|
).sort();
|
|
629
|
+
const serverModuleClosureFiles = buildServerModuleClosureManifest(
|
|
630
|
+
serverModules,
|
|
631
|
+
sourceAnalysis,
|
|
632
|
+
);
|
|
628
633
|
const prerenderedRoutes =
|
|
629
634
|
shouldTrackBuildPhases === false
|
|
630
635
|
? await prerenderStaticRoutes({
|
|
@@ -708,6 +713,9 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
|
|
|
708
713
|
...(serverActionManifest.allowedActions.length === 0
|
|
709
714
|
? {}
|
|
710
715
|
: { serverActionManifest: serverActionManifest.allowedActions }),
|
|
716
|
+
...(Object.keys(serverModuleClosureFiles).length === 0
|
|
717
|
+
? {}
|
|
718
|
+
: { serverModuleClosureFiles }),
|
|
711
719
|
...(Object.keys(serverModuleArtifacts.files).length === 0
|
|
712
720
|
? {}
|
|
713
721
|
: { serverModuleFiles: serverModuleArtifacts.files }),
|
|
@@ -988,6 +996,141 @@ function buildSourceAnalysisForFile(
|
|
|
988
996
|
return sourceAnalysis.byFile.get(relative(projectRoot, file).split(sep).join("/"));
|
|
989
997
|
}
|
|
990
998
|
|
|
999
|
+
function buildServerModuleClosureManifest(
|
|
1000
|
+
serverModules: Record<string, BuiltServerModuleArtifact>,
|
|
1001
|
+
sourceAnalysis: BuildSourceAnalysisScope,
|
|
1002
|
+
): Record<string, string[]> {
|
|
1003
|
+
const sourceFiles = new Set(sourceAnalysis.byFile.keys());
|
|
1004
|
+
const closureFiles: Record<string, string[]> = {};
|
|
1005
|
+
|
|
1006
|
+
for (const file of Object.keys(serverModules).sort()) {
|
|
1007
|
+
const closure: string[] = [];
|
|
1008
|
+
collectManifestServerModuleClosureFiles(file, sourceAnalysis, sourceFiles, new Set(), closure);
|
|
1009
|
+
|
|
1010
|
+
if (closure.length > 0) {
|
|
1011
|
+
closureFiles[file] = closure;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
return closureFiles;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
function collectManifestServerModuleClosureFiles(
|
|
1019
|
+
file: string,
|
|
1020
|
+
sourceAnalysis: BuildSourceAnalysisScope,
|
|
1021
|
+
sourceFiles: ReadonlySet<string>,
|
|
1022
|
+
seen: Set<string>,
|
|
1023
|
+
closure: string[],
|
|
1024
|
+
): void {
|
|
1025
|
+
if (seen.has(file)) {
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
seen.add(file);
|
|
1029
|
+
closure.push(file);
|
|
1030
|
+
|
|
1031
|
+
const source = sourceAnalysis.byFile.get(file)?.source;
|
|
1032
|
+
if (source === undefined) {
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
for (const specifier of localManifestServerModuleSpecifiers(source)) {
|
|
1037
|
+
const resolved = resolveManifestLocalServerSourceImport(file, specifier, sourceFiles);
|
|
1038
|
+
if (resolved !== undefined) {
|
|
1039
|
+
collectManifestServerModuleClosureFiles(
|
|
1040
|
+
resolved,
|
|
1041
|
+
sourceAnalysis,
|
|
1042
|
+
sourceFiles,
|
|
1043
|
+
seen,
|
|
1044
|
+
closure,
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
const localManifestServerModuleImportPattern =
|
|
1051
|
+
/\b(?:import|export)\s+(?:type\s+)?(?:[^"']*?\s+from\s*)?["'](?<source>\.{1,2}\/[^"']+)["']/g;
|
|
1052
|
+
|
|
1053
|
+
function localManifestServerModuleSpecifiers(code: string): string[] {
|
|
1054
|
+
const specifiers = new Set<string>();
|
|
1055
|
+
localManifestServerModuleImportPattern.lastIndex = 0;
|
|
1056
|
+
|
|
1057
|
+
for (const match of code.matchAll(localManifestServerModuleImportPattern)) {
|
|
1058
|
+
const source = match.groups?.source;
|
|
1059
|
+
|
|
1060
|
+
if (source !== undefined) {
|
|
1061
|
+
specifiers.add(source);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
return Array.from(specifiers);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
function resolveManifestLocalServerSourceImport(
|
|
1069
|
+
fromFile: string,
|
|
1070
|
+
specifier: string,
|
|
1071
|
+
sourceFiles: ReadonlySet<string>,
|
|
1072
|
+
): string | undefined {
|
|
1073
|
+
const base = resolveManifestRelativePath(manifestDirname(fromFile), specifier);
|
|
1074
|
+
|
|
1075
|
+
for (const candidate of manifestLocalServerSourceImportCandidates(base)) {
|
|
1076
|
+
if (sourceFiles.has(candidate)) {
|
|
1077
|
+
return candidate;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
return undefined;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function manifestLocalServerSourceImportCandidates(base: string): string[] {
|
|
1085
|
+
const candidates = [base];
|
|
1086
|
+
|
|
1087
|
+
if (base.endsWith(".js")) {
|
|
1088
|
+
const withoutJs = base.slice(0, -".js".length);
|
|
1089
|
+
candidates.push(`${withoutJs}.ts`, `${withoutJs}.tsx`, `${withoutJs}.mreact.tsx`);
|
|
1090
|
+
} else if (base.endsWith(".jsx")) {
|
|
1091
|
+
const withoutJsx = base.slice(0, -".jsx".length);
|
|
1092
|
+
candidates.push(`${withoutJsx}.tsx`, `${withoutJsx}.mreact.tsx`);
|
|
1093
|
+
} else if (base.endsWith(".mreact")) {
|
|
1094
|
+
candidates.push(`${base}.tsx`);
|
|
1095
|
+
} else {
|
|
1096
|
+
candidates.push(
|
|
1097
|
+
`${base}.ts`,
|
|
1098
|
+
`${base}.tsx`,
|
|
1099
|
+
`${base}.mreact.tsx`,
|
|
1100
|
+
`${base}/index.ts`,
|
|
1101
|
+
`${base}/index.tsx`,
|
|
1102
|
+
`${base}/index.mreact.tsx`,
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
return candidates;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
function manifestDirname(file: string): string {
|
|
1110
|
+
const index = file.lastIndexOf("/");
|
|
1111
|
+
|
|
1112
|
+
return index === -1 ? "" : file.slice(0, index);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
function resolveManifestRelativePath(fromDir: string, specifier: string): string {
|
|
1116
|
+
const segments = fromDir === "" ? [] : fromDir.split("/");
|
|
1117
|
+
|
|
1118
|
+
for (const segment of specifier.split("/")) {
|
|
1119
|
+
if (segment === "" || segment === ".") {
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
if (segment === "..") {
|
|
1124
|
+
segments.pop();
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
segments.push(segment);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
return segments.join("/");
|
|
1132
|
+
}
|
|
1133
|
+
|
|
991
1134
|
function canUseBuildServerActionPlaceholders(
|
|
992
1135
|
references: readonly BuiltServerActionExpressionReference[],
|
|
993
1136
|
): boolean {
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, join, normalize, sep } from "node:path";
|
|
3
|
+
import type { ClientRouteManifestEntry } from "./client-route-inference.js";
|
|
4
|
+
import { clientManifestAssetPaths } from "./client-manifest-assets.js";
|
|
5
|
+
import { bytesResponse, rawNodeRequestUrl } from "./http.js";
|
|
6
|
+
|
|
7
|
+
const builtPublicAssetCache = new Map<
|
|
8
|
+
string,
|
|
9
|
+
{
|
|
10
|
+
bytes: Uint8Array;
|
|
11
|
+
headers: HeadersInit;
|
|
12
|
+
}
|
|
13
|
+
>();
|
|
14
|
+
|
|
15
|
+
export async function readBuiltClientAsset(
|
|
16
|
+
outDir: string,
|
|
17
|
+
pathname: string,
|
|
18
|
+
allowedPaths: ReadonlySet<string>,
|
|
19
|
+
): Promise<Response> {
|
|
20
|
+
const clientPrefix = "/_mreact/client/";
|
|
21
|
+
const normalized = safeBuiltClientAssetPath(pathname.slice(clientPrefix.length));
|
|
22
|
+
|
|
23
|
+
if (normalized === undefined || !allowedPaths.has(normalized)) {
|
|
24
|
+
return new Response("Not Found", { status: 404 });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const code = await readFile(join(outDir, "client", normalized), "utf8");
|
|
29
|
+
|
|
30
|
+
return new Response(code, {
|
|
31
|
+
headers: clientAssetHeaders(normalized),
|
|
32
|
+
});
|
|
33
|
+
} catch {
|
|
34
|
+
return new Response("Not Found", { status: 404 });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function builtClientAssetPathname(request: Request, url: URL): string | undefined {
|
|
39
|
+
const rawUrl = rawNodeRequestUrl(request);
|
|
40
|
+
const rawPathname = rawUrl?.split(/[?#]/, 1)[0];
|
|
41
|
+
const pathname = rawPathname === undefined || rawPathname === "" ? url.pathname : rawPathname;
|
|
42
|
+
const normalizedPathname = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
43
|
+
|
|
44
|
+
return normalizedPathname.startsWith("/_mreact/client/") ? normalizedPathname : undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function safeBuiltClientAssetPath(relativePath: string): string | undefined {
|
|
48
|
+
let decoded: string;
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
decoded = decodeURIComponent(relativePath);
|
|
52
|
+
} catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (decoded.includes("\\") || decoded.includes("\0")) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (decoded.split("/").some((segment) => segment === "..")) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const normalized = normalize(decoded);
|
|
65
|
+
|
|
66
|
+
if (isAbsolute(normalized) || normalized === ".." || normalized.startsWith(`..${sep}`)) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return normalized;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function builtClientAssetPaths(manifest: {
|
|
74
|
+
assets?: readonly string[] | undefined;
|
|
75
|
+
routes: readonly ClientRouteManifestEntry[];
|
|
76
|
+
}): ReadonlySet<string> {
|
|
77
|
+
return clientManifestAssetPaths(manifest);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function readBuiltPublicAsset(
|
|
81
|
+
outDir: string,
|
|
82
|
+
pathname: string,
|
|
83
|
+
): Promise<Response | undefined> {
|
|
84
|
+
const relativePath = pathname.startsWith("/") ? pathname.slice(1) : pathname;
|
|
85
|
+
|
|
86
|
+
if (relativePath === "") {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const normalized = normalize(relativePath);
|
|
91
|
+
|
|
92
|
+
if (isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../")) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const cacheKey = `${outDir}\0${normalized}`;
|
|
98
|
+
const cached = builtPublicAssetCache.get(cacheKey);
|
|
99
|
+
|
|
100
|
+
if (cached !== undefined) {
|
|
101
|
+
return bytesResponse(cached.bytes, {
|
|
102
|
+
headers: cached.headers,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const bytes = await readFile(join(outDir, "client", "public", normalized));
|
|
107
|
+
const headers = publicAssetHeaders(normalized);
|
|
108
|
+
|
|
109
|
+
builtPublicAssetCache.set(cacheKey, {
|
|
110
|
+
bytes,
|
|
111
|
+
headers,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return bytesResponse(bytes, { headers });
|
|
115
|
+
} catch {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function clearBuiltPublicAssetCacheForTest(): void {
|
|
121
|
+
builtPublicAssetCache.clear();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function getBuiltPublicAssetCacheSizeForTest(): number {
|
|
125
|
+
return builtPublicAssetCache.size;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function clientAssetHeaders(pathname: string): HeadersInit {
|
|
129
|
+
if (pathname === "manifest.json") {
|
|
130
|
+
return {
|
|
131
|
+
"cache-control": "no-cache",
|
|
132
|
+
"content-type": "application/json; charset=utf-8",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
"cache-control": "public, max-age=31536000, immutable",
|
|
138
|
+
"content-type": pathname.endsWith(".css")
|
|
139
|
+
? "text/css; charset=utf-8"
|
|
140
|
+
: "text/javascript; charset=utf-8",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function publicAssetHeaders(pathname: string): HeadersInit {
|
|
145
|
+
return {
|
|
146
|
+
"cache-control": "public, max-age=3600",
|
|
147
|
+
"content-type": publicAssetContentType(pathname),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function publicAssetContentType(pathname: string): string {
|
|
152
|
+
if (pathname.endsWith(".css")) return "text/css; charset=utf-8";
|
|
153
|
+
if (pathname.endsWith(".html")) return "text/html; charset=utf-8";
|
|
154
|
+
if (pathname.endsWith(".js")) return "text/javascript; charset=utf-8";
|
|
155
|
+
if (pathname.endsWith(".json")) return "application/json; charset=utf-8";
|
|
156
|
+
if (pathname.endsWith(".svg")) return "image/svg+xml";
|
|
157
|
+
if (pathname.endsWith(".png")) return "image/png";
|
|
158
|
+
if (pathname.endsWith(".jpg") || pathname.endsWith(".jpeg")) return "image/jpeg";
|
|
159
|
+
if (pathname.endsWith(".webp")) return "image/webp";
|
|
160
|
+
if (pathname.endsWith(".ico")) return "image/x-icon";
|
|
161
|
+
if (pathname.endsWith(".txt")) return "text/plain; charset=utf-8";
|
|
162
|
+
|
|
163
|
+
return "application/octet-stream";
|
|
164
|
+
}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, isAbsolute, join, normalize } from "node:path";
|
|
4
|
+
import type {
|
|
5
|
+
BuiltPrerenderedRoute,
|
|
6
|
+
BuiltServerManifest,
|
|
7
|
+
BuiltServerModuleArtifact,
|
|
8
|
+
} from "./build.js";
|
|
9
|
+
import { builtClientAssetPaths } from "./built-assets.js";
|
|
10
|
+
import type { BuiltServerModuleArtifactRuntime } from "./built-server-module-artifacts.js";
|
|
11
|
+
import type { ClientRouteManifestEntry } from "./client-route-inference.js";
|
|
12
|
+
import type { AppRouterImportPolicy } from "./import-policy.js";
|
|
13
|
+
import { createRouteMatcher, type AppRoute, type RouteMatcher } from "./routes.js";
|
|
14
|
+
|
|
15
|
+
export interface BuiltRuntime extends BuiltServerModuleArtifactRuntime {
|
|
16
|
+
allowedSourceDirs: readonly string[];
|
|
17
|
+
assetBaseUrl?: string | undefined;
|
|
18
|
+
clientAssetPaths: ReadonlySet<string>;
|
|
19
|
+
clientScripts: ReadonlyMap<string, string>;
|
|
20
|
+
clientStylesByFile: ReadonlyMap<string, readonly string[]>;
|
|
21
|
+
clientStyles: ReadonlyMap<string, readonly string[]>;
|
|
22
|
+
generatedImportPolicy?: AppRouterImportPolicy | undefined;
|
|
23
|
+
hasMiddleware: boolean;
|
|
24
|
+
navigationScripts: ReadonlyMap<string, string>;
|
|
25
|
+
projectRoot: string;
|
|
26
|
+
publicAssetBaseUrl?: string | undefined;
|
|
27
|
+
prerenderableRoutes: ReadonlySet<string>;
|
|
28
|
+
prerenderLocks: Map<string, Promise<Response>>;
|
|
29
|
+
prerenderedRoutes: Map<string, BuiltPrerenderedRoute>;
|
|
30
|
+
routeMatcher: RouteMatcher;
|
|
31
|
+
routes: readonly AppRoute[];
|
|
32
|
+
serverActionReferencesByFile: ReadonlyMap<
|
|
33
|
+
string,
|
|
34
|
+
readonly {
|
|
35
|
+
end: number;
|
|
36
|
+
expression: string;
|
|
37
|
+
expressionEnd: number;
|
|
38
|
+
expressionStart: number;
|
|
39
|
+
moduleId: string;
|
|
40
|
+
exportName: string;
|
|
41
|
+
inferred: boolean;
|
|
42
|
+
sourceHash: string;
|
|
43
|
+
start: number;
|
|
44
|
+
}[]
|
|
45
|
+
>;
|
|
46
|
+
serverActionManifest?: readonly { moduleId: string; exportName: string; inferred?: boolean }[] | undefined;
|
|
47
|
+
serverModuleCacheVersion: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function materializeBuiltRuntime(options: {
|
|
51
|
+
clientManifestText: string;
|
|
52
|
+
clientManifestPath: string;
|
|
53
|
+
importPolicyPath: string;
|
|
54
|
+
importPolicyText: string | undefined;
|
|
55
|
+
onMaterialize?: (() => void) | undefined;
|
|
56
|
+
outDir: string;
|
|
57
|
+
runtimeDir: string;
|
|
58
|
+
serverManifestText: string;
|
|
59
|
+
serverManifestPath: string;
|
|
60
|
+
}): Promise<BuiltRuntime> {
|
|
61
|
+
options.onMaterialize?.();
|
|
62
|
+
const serverManifest = parseBuiltJsonArtifact<BuiltServerManifest>(
|
|
63
|
+
options.serverManifestText,
|
|
64
|
+
options.serverManifestPath,
|
|
65
|
+
"built app server manifest",
|
|
66
|
+
);
|
|
67
|
+
const clientManifest = parseBuiltJsonArtifact<{
|
|
68
|
+
assets?: readonly string[];
|
|
69
|
+
routes: ClientRouteManifestEntry[];
|
|
70
|
+
styles?: Array<{ css?: readonly string[]; file: string }>;
|
|
71
|
+
}>(options.clientManifestText, options.clientManifestPath, "built app client manifest");
|
|
72
|
+
const appDir = await materializeBuiltServerApp(options.runtimeDir, serverManifest);
|
|
73
|
+
const projectRoot = appDir;
|
|
74
|
+
const routesDir = join(projectRoot, serverManifest.routesDir ?? "");
|
|
75
|
+
const routes = serverManifest.routes.map((route) => ({
|
|
76
|
+
...route,
|
|
77
|
+
file: join(projectRoot, route.file),
|
|
78
|
+
}));
|
|
79
|
+
const prerenderedRoutes = new Map(Object.entries(serverManifest.prerenderedRoutes ?? {}));
|
|
80
|
+
const prerenderableRoutes = new Set(prerenderedRoutes.keys());
|
|
81
|
+
const prerenderLocks = new Map<string, Promise<Response>>();
|
|
82
|
+
const serverModules = new Map<string, BuiltServerModuleArtifact>(
|
|
83
|
+
Object.entries(serverManifest.serverModules ?? {}).map(([file, artifact]) => [
|
|
84
|
+
join(appDir, file),
|
|
85
|
+
artifact,
|
|
86
|
+
]),
|
|
87
|
+
);
|
|
88
|
+
const serverModuleClosureFiles = new Map<string, readonly string[]>(
|
|
89
|
+
Object.entries(serverManifest.serverModuleClosureFiles ?? {}).map(([file, closure]) => [
|
|
90
|
+
join(appDir, safeManifestFilePath(file)),
|
|
91
|
+
closure.map((closureFile) => join(appDir, safeManifestFilePath(closureFile))),
|
|
92
|
+
]),
|
|
93
|
+
);
|
|
94
|
+
const serverModuleFiles = new Map(
|
|
95
|
+
Object.entries(serverManifest.serverModuleFiles ?? {}).map(([file, artifactFile]) => [
|
|
96
|
+
join(appDir, file),
|
|
97
|
+
join(options.outDir, "server", safeManifestFilePath(artifactFile)),
|
|
98
|
+
]),
|
|
99
|
+
);
|
|
100
|
+
const serverModuleRequestFiles = new Map(
|
|
101
|
+
Object.entries(serverManifest.serverModuleRequestFiles ?? {}).map(([file, artifactFile]) => [
|
|
102
|
+
join(appDir, file),
|
|
103
|
+
join(options.outDir, "server", safeManifestFilePath(artifactFile)),
|
|
104
|
+
]),
|
|
105
|
+
);
|
|
106
|
+
const serverModuleRenderFiles = new Map(
|
|
107
|
+
Object.entries(serverManifest.serverModuleRenderFiles ?? {}).map(([file, artifactFile]) => [
|
|
108
|
+
join(appDir, file),
|
|
109
|
+
join(options.outDir, "server", safeManifestFilePath(artifactFile)),
|
|
110
|
+
]),
|
|
111
|
+
);
|
|
112
|
+
const serverSourceFiles = new Map(
|
|
113
|
+
Object.entries(serverManifest.files).map(([file, source]) => [join(appDir, file), source]),
|
|
114
|
+
);
|
|
115
|
+
const serverActionReferencesByFile = new Map(
|
|
116
|
+
Object.entries(serverManifest.routeServerActionReferences ?? {}).map(([file, references]) => [
|
|
117
|
+
join(appDir, file),
|
|
118
|
+
references,
|
|
119
|
+
]),
|
|
120
|
+
);
|
|
121
|
+
const routeMatcher = createRouteMatcher(routes, serverManifest.routeMatcher);
|
|
122
|
+
const clientScripts = new Map(
|
|
123
|
+
clientManifest.routes.flatMap((route) =>
|
|
124
|
+
route.client && route.script !== undefined ? [[route.path, route.script]] : [],
|
|
125
|
+
),
|
|
126
|
+
);
|
|
127
|
+
const clientStyles = new Map(
|
|
128
|
+
clientManifest.routes.flatMap((route) =>
|
|
129
|
+
route.css !== undefined && route.css.length > 0 ? [[route.path, route.css]] : [],
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
const clientStylesByFile = new Map(
|
|
133
|
+
(clientManifest.styles ?? []).flatMap((style) =>
|
|
134
|
+
style.css !== undefined && style.css.length > 0
|
|
135
|
+
? [[join(routesDir, style.file), style.css] as const]
|
|
136
|
+
: [],
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
const navigationScripts = new Map(
|
|
140
|
+
clientManifest.routes.flatMap((route) =>
|
|
141
|
+
route.navigation === true && route.navigationScript !== undefined
|
|
142
|
+
? [[route.path, route.navigationScript]]
|
|
143
|
+
: [],
|
|
144
|
+
),
|
|
145
|
+
);
|
|
146
|
+
const hasMiddleware =
|
|
147
|
+
serverSourceFiles.has(join(routesDir, "middleware.ts")) ||
|
|
148
|
+
serverSourceFiles.has(join(routesDir, "middleware.mreact.ts"));
|
|
149
|
+
const serverModuleCacheVersion = createHash("sha256")
|
|
150
|
+
.update(options.serverManifestText)
|
|
151
|
+
.update("\0")
|
|
152
|
+
.update(options.clientManifestText)
|
|
153
|
+
.digest("hex")
|
|
154
|
+
.slice(0, 16);
|
|
155
|
+
|
|
156
|
+
const allowedSourceDirs = (serverManifest.allowedSourceDirs ?? [""]).map((directory) =>
|
|
157
|
+
join(projectRoot, directory),
|
|
158
|
+
);
|
|
159
|
+
const generatedImportPolicy = builtGeneratedImportPolicy(
|
|
160
|
+
options.importPolicyText,
|
|
161
|
+
options.importPolicyPath,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
appDir: routesDir,
|
|
166
|
+
allowedSourceDirs,
|
|
167
|
+
...(serverManifest.assetBaseUrl === undefined
|
|
168
|
+
? {}
|
|
169
|
+
: { assetBaseUrl: serverManifest.assetBaseUrl }),
|
|
170
|
+
clientAssetPaths: builtClientAssetPaths(clientManifest),
|
|
171
|
+
clientScripts,
|
|
172
|
+
clientStylesByFile,
|
|
173
|
+
clientStyles,
|
|
174
|
+
...(generatedImportPolicy === undefined ? {} : { generatedImportPolicy }),
|
|
175
|
+
hasMiddleware,
|
|
176
|
+
navigationScripts,
|
|
177
|
+
projectRoot,
|
|
178
|
+
...(serverManifest.publicAssetBaseUrl === undefined
|
|
179
|
+
? {}
|
|
180
|
+
: { publicAssetBaseUrl: serverManifest.publicAssetBaseUrl }),
|
|
181
|
+
prerenderableRoutes,
|
|
182
|
+
prerenderLocks,
|
|
183
|
+
prerenderedRoutes,
|
|
184
|
+
routeMatcher,
|
|
185
|
+
routes,
|
|
186
|
+
serverActionReferencesByFile,
|
|
187
|
+
...(serverManifest.serverActionManifest === undefined
|
|
188
|
+
? {}
|
|
189
|
+
: { serverActionManifest: serverManifest.serverActionManifest }),
|
|
190
|
+
serverModuleArtifactLoads: new Map(),
|
|
191
|
+
serverModuleClosureFiles,
|
|
192
|
+
serverModuleFiles,
|
|
193
|
+
serverModuleRenderFiles,
|
|
194
|
+
serverModuleRequestFiles,
|
|
195
|
+
serverModules,
|
|
196
|
+
serverModuleCacheVersion,
|
|
197
|
+
serverSourceFiles,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function readBuiltImportPolicyText(outDir: string): Promise<string | undefined> {
|
|
202
|
+
const policyPath = join(outDir, "server", "import-policy.json");
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
return await readFile(policyPath, "utf8");
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (isMissingFileError(error)) {
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
throw builtArtifactReadError("built app import policy", policyPath, error);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function mergeBuiltRuntimeImportPolicy(
|
|
216
|
+
runtime: BuiltRuntime,
|
|
217
|
+
importPolicy: AppRouterImportPolicy | undefined,
|
|
218
|
+
): AppRouterImportPolicy | undefined {
|
|
219
|
+
const generatedImportPolicy = runtime.generatedImportPolicy;
|
|
220
|
+
|
|
221
|
+
if (generatedImportPolicy === undefined) {
|
|
222
|
+
return importPolicy;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const allowedPackages = [
|
|
226
|
+
...new Set([
|
|
227
|
+
...(generatedImportPolicy.allowedPackages ?? []),
|
|
228
|
+
...(importPolicy?.allowedPackages ?? []),
|
|
229
|
+
]),
|
|
230
|
+
];
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
...(allowedPackages.length === 0 ? {} : { allowedPackages }),
|
|
234
|
+
...(importPolicy?.allowedSourceDirs === undefined
|
|
235
|
+
? {}
|
|
236
|
+
: { allowedSourceDirs: importPolicy.allowedSourceDirs }),
|
|
237
|
+
...(importPolicy?.projectRoot === undefined ? {} : { projectRoot: importPolicy.projectRoot }),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function builtGeneratedImportPolicy(
|
|
242
|
+
importPolicyText: string | undefined,
|
|
243
|
+
importPolicyPath: string,
|
|
244
|
+
): AppRouterImportPolicy | undefined {
|
|
245
|
+
if (importPolicyText === undefined) {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const artifact = parseBuiltJsonArtifact<{
|
|
250
|
+
runtimePackages?: unknown;
|
|
251
|
+
}>(importPolicyText, importPolicyPath, "built app import policy");
|
|
252
|
+
const runtimePackages = Array.isArray(artifact.runtimePackages)
|
|
253
|
+
? artifact.runtimePackages.filter((name): name is string => typeof name === "string")
|
|
254
|
+
: [];
|
|
255
|
+
|
|
256
|
+
return runtimePackages.length === 0 ? undefined : { allowedPackages: runtimePackages };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function materializeBuiltServerApp(
|
|
260
|
+
runtimeDir: string,
|
|
261
|
+
manifest: BuiltServerManifest,
|
|
262
|
+
): Promise<string> {
|
|
263
|
+
const appDir = join(runtimeDir, "app");
|
|
264
|
+
|
|
265
|
+
await rm(appDir, { force: true, recursive: true });
|
|
266
|
+
await Promise.all(
|
|
267
|
+
Object.entries(manifest.files).map(async ([file, code]) => {
|
|
268
|
+
const outputFile = join(appDir, safeManifestFilePath(file));
|
|
269
|
+
|
|
270
|
+
await mkdir(dirname(outputFile), { recursive: true });
|
|
271
|
+
await writeFile(outputFile, code);
|
|
272
|
+
}),
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
return appDir;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function isMissingFileError(error: unknown): boolean {
|
|
279
|
+
return (
|
|
280
|
+
typeof error === "object" &&
|
|
281
|
+
error !== null &&
|
|
282
|
+
"code" in error &&
|
|
283
|
+
(error as { code?: unknown }).code === "ENOENT"
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function builtArtifactReadError(label: string, artifactPath: string, error: unknown): Error {
|
|
288
|
+
const prefix = isMissingFileError(error) ? "Missing" : "Unable to read";
|
|
289
|
+
const detail = error instanceof Error && error.message !== "" ? `: ${error.message}` : "";
|
|
290
|
+
|
|
291
|
+
return new Error(`${prefix} ${label}: ${artifactPath}${detail}`, { cause: error });
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function parseBuiltJsonArtifact<T>(text: string, artifactPath: string, label: string): T {
|
|
295
|
+
try {
|
|
296
|
+
return JSON.parse(text) as T;
|
|
297
|
+
} catch (error) {
|
|
298
|
+
const detail = error instanceof Error && error.message !== "" ? `: ${error.message}` : "";
|
|
299
|
+
|
|
300
|
+
throw new Error(`Invalid ${label}: ${artifactPath}${detail}`, { cause: error });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function safeManifestFilePath(pathname: string): string {
|
|
305
|
+
const normalized = normalize(pathname);
|
|
306
|
+
|
|
307
|
+
if (isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../")) {
|
|
308
|
+
throw new Error(`Invalid built app manifest file path: ${pathname}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return normalized;
|
|
312
|
+
}
|