@reckona/mreact-router 0.0.96 → 0.0.98
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 +4 -0
- package/dist/actions.d.ts +7 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +26 -2
- package/dist/actions.js.map +1 -1
- package/dist/adapters/aws-lambda.d.ts.map +1 -1
- package/dist/adapters/aws-lambda.js +10 -7
- package/dist/adapters/aws-lambda.js.map +1 -1
- package/dist/adapters/static.d.ts.map +1 -1
- package/dist/adapters/static.js +13 -0
- package/dist/adapters/static.js.map +1 -1
- package/dist/build.d.ts +38 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +1093 -307
- package/dist/build.js.map +1 -1
- package/dist/bundle-pipeline.d.ts +7 -0
- package/dist/bundle-pipeline.d.ts.map +1 -1
- package/dist/bundle-pipeline.js +121 -69
- package/dist/bundle-pipeline.js.map +1 -1
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +10 -8
- package/dist/cache.js.map +1 -1
- package/dist/cli-options.d.ts +1 -0
- package/dist/cli-options.d.ts.map +1 -1
- package/dist/cli-options.js +16 -0
- package/dist/cli-options.js.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +6 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +120 -9
- package/dist/client.js.map +1 -1
- package/dist/csrf.d.ts.map +1 -1
- package/dist/csrf.js +7 -3
- package/dist/csrf.js.map +1 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +18 -1
- package/dist/http.js.map +1 -1
- package/dist/import-policy.d.ts.map +1 -1
- package/dist/import-policy.js +7 -3
- package/dist/import-policy.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/link.d.ts +5 -3
- package/dist/link.d.ts.map +1 -1
- package/dist/link.js +92 -3
- package/dist/link.js.map +1 -1
- package/dist/logger.d.ts +7 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/module-runner.d.ts +2 -0
- package/dist/module-runner.d.ts.map +1 -1
- package/dist/module-runner.js +1 -0
- package/dist/module-runner.js.map +1 -1
- package/dist/native-route-matcher.d.ts +2 -1
- package/dist/native-route-matcher.d.ts.map +1 -1
- package/dist/native-route-matcher.js +13 -2
- package/dist/native-route-matcher.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +121 -6
- package/dist/render.js.map +1 -1
- package/dist/route-source.d.ts +11 -11
- package/dist/route-source.d.ts.map +1 -1
- package/dist/route-source.js +32 -25
- package/dist/route-source.js.map +1 -1
- package/dist/route-styles.d.ts +6 -0
- package/dist/route-styles.d.ts.map +1 -1
- package/dist/route-styles.js +10 -1
- package/dist/route-styles.js.map +1 -1
- package/dist/serve.d.ts +5 -0
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +7 -4
- package/dist/serve.js.map +1 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +32 -5
- package/dist/vite.js.map +1 -1
- package/package.json +11 -11
- package/src/actions.ts +42 -2
- package/src/adapters/aws-lambda.ts +33 -16
- package/src/adapters/static.ts +25 -0
- package/src/build.ts +1605 -421
- package/src/bundle-pipeline.ts +136 -70
- package/src/cache.ts +13 -8
- package/src/cli-options.ts +20 -0
- package/src/cli.ts +1 -0
- package/src/client.ts +152 -15
- package/src/csrf.ts +8 -3
- package/src/http.ts +21 -1
- package/src/import-policy.ts +8 -3
- package/src/index.ts +5 -0
- package/src/link.ts +135 -8
- package/src/logger.ts +9 -1
- package/src/module-runner.ts +4 -0
- package/src/native-route-matcher.ts +13 -2
- package/src/render.ts +181 -6
- package/src/route-source.ts +33 -20
- package/src/route-styles.ts +17 -1
- package/src/serve.ts +11 -6
- package/src/vite.ts +41 -5
package/dist/build.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { copyFile, cp, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { builtinModules } from "node:module";
|
|
4
|
+
import { availableParallelism } from "node:os";
|
|
4
5
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
5
7
|
import { collectStaticImportReferences, collectTopLevelValueExportNames, formatDiagnostic, hasModuleDirective, transform, } from "@reckona/mreact-compiler";
|
|
6
8
|
import { transformCompilerModuleContext } from "@reckona/mreact-compiler/internal";
|
|
7
9
|
import { compilerModuleContextForSource, collectClientRouteReferences, createClientRouteInferenceCache, detectClientNavigationHint, formatClientRouteInferenceDiagnostic, inferClientRouteModule, resolveNavigationRuntime, } from "./client-route-inference.js";
|
|
8
10
|
import { buildClientRouteBatchOutput, buildNavigationRuntimeBundle, clientScriptForPath, routeIdForPath, } from "./navigation-runtime.js";
|
|
9
|
-
import { bundleAppRouterSourceModule, importAppRouterSourceModule } from "./module-runner.js";
|
|
11
|
+
import { bundleAppRouterSourceModule, fileImportMetaUrlPlugin, importAppRouterSourceModule, } from "./module-runner.js";
|
|
10
12
|
import { scanAppRoutes } from "./routes.js";
|
|
11
13
|
import { appFileConventionForRootFilename, } from "./file-conventions.js";
|
|
12
14
|
import { resolveAppRouterProjectOptions, resolveBuildTargets, } from "./config.js";
|
|
@@ -14,110 +16,250 @@ import { routeCachePolicyFromSource } from "./cache.js";
|
|
|
14
16
|
import { bundleMiddlewareModuleCode, renderAppRequest } from "./render.js";
|
|
15
17
|
import { createAppRouterImportPolicyPlugin } from "./import-policy.js";
|
|
16
18
|
import { hasGenerateStaticParamsExport, hasLoaderExport, hasPrerenderExport, isStreamRouteSource, routeClosureMayUseAwaitBoundary, stripRouteBuildExports, stripRouteClientOnlyExports, stripRouteClientSource, stripRouteLoaderOnlyExports, stripRouteMetadataOnlyExports, stripRouteRequestOnlyExports, } from "./route-source.js";
|
|
17
|
-
import { bundleRouterModule, } from "./bundle-pipeline.js";
|
|
18
|
-
import {
|
|
19
|
+
import { bundleRouterModule, bundleRouterModules, } from "./bundle-pipeline.js";
|
|
20
|
+
import { collectRouteCssFilesFromSources } from "./route-styles.js";
|
|
19
21
|
import { existingRouteShellCandidates } from "./route-shells.js";
|
|
20
22
|
import { sourceModuleCandidates } from "./source-modules.js";
|
|
21
23
|
import { collectBuildInferredServerActions } from "./server-action-inference.js";
|
|
24
|
+
import { vitePluginsCacheKey } from "./vite-plugin-cache-key.js";
|
|
22
25
|
import { workspacePackageFile } from "./workspace-packages.js";
|
|
23
26
|
const nativeEscapeTransform = {
|
|
24
27
|
batchImportName: "escapeHtmlBatch",
|
|
25
28
|
batchImportSource: "@reckona/mreact-router/native-escape",
|
|
26
29
|
};
|
|
30
|
+
const defaultBuildConcurrency = Math.max(1, Math.min(2, availableParallelism()));
|
|
31
|
+
const serverArtifactFilesystemConcurrency = 2;
|
|
27
32
|
export async function buildApp(options) {
|
|
33
|
+
const timingSink = options.onBuildPhaseTiming;
|
|
28
34
|
const project = resolveAppRouterProjectOptions(options);
|
|
29
35
|
const buildTargets = resolveBuildTargets(options.targets ?? project.buildTargets);
|
|
30
36
|
const shouldBuildCloudflare = buildTargets.includes("cloudflare");
|
|
31
37
|
const shouldBuildAwsLambda = buildTargets.includes("aws-lambda");
|
|
32
|
-
const routes =
|
|
38
|
+
const routes = timingSink === undefined
|
|
39
|
+
? await scanAppRoutes({ appDir: project.routesDir })
|
|
40
|
+
: await timeBuildPhase(timingSink, "scan", () => scanAppRoutes({ appDir: project.routesDir }));
|
|
33
41
|
const vitePlugins = options.viteConfig?.plugins;
|
|
34
|
-
const files =
|
|
42
|
+
const files = timingSink === undefined
|
|
43
|
+
? await collectBuildFiles(project.projectRoot, project.allowedSourceDirs, project.routesDir)
|
|
44
|
+
: await timeBuildPhase(timingSink, "collectFiles", () => collectBuildFiles(project.projectRoot, project.allowedSourceDirs, project.routesDir));
|
|
45
|
+
const serverClientRouteInferenceCache = createClientRouteInferenceCache();
|
|
46
|
+
const serverTransformCache = new Map();
|
|
35
47
|
const serverDir = join(options.outDir, "server");
|
|
36
48
|
const clientDir = join(options.outDir, "client");
|
|
37
49
|
const cloudflareDir = join(options.outDir, "cloudflare");
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
const sourceAnalysis = timingSink === undefined
|
|
51
|
+
? await analyzeBuildRouteSources({
|
|
52
|
+
clientRouteInferenceCache: serverClientRouteInferenceCache,
|
|
53
|
+
files,
|
|
54
|
+
project,
|
|
55
|
+
projectRoot: project.projectRoot,
|
|
56
|
+
routes,
|
|
57
|
+
vitePlugins,
|
|
58
|
+
})
|
|
59
|
+
: await timeBuildPhase(timingSink, "analyzeSources", () => analyzeBuildRouteSources({
|
|
60
|
+
clientRouteInferenceCache: serverClientRouteInferenceCache,
|
|
61
|
+
files,
|
|
62
|
+
project,
|
|
63
|
+
projectRoot: project.projectRoot,
|
|
64
|
+
routes,
|
|
65
|
+
vitePlugins,
|
|
66
|
+
}));
|
|
67
|
+
if (timingSink === undefined) {
|
|
68
|
+
await validateProductionRoutes({
|
|
69
|
+
clientRouteInferenceCache: serverClientRouteInferenceCache,
|
|
70
|
+
files,
|
|
71
|
+
project,
|
|
72
|
+
projectRoot: project.projectRoot,
|
|
73
|
+
routes,
|
|
74
|
+
sourceAnalysis,
|
|
75
|
+
serverTransformCache,
|
|
76
|
+
vitePlugins,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
await timeBuildPhase(timingSink, "validate", () => validateProductionRoutes({
|
|
81
|
+
clientRouteInferenceCache: serverClientRouteInferenceCache,
|
|
82
|
+
files,
|
|
83
|
+
project,
|
|
84
|
+
projectRoot: project.projectRoot,
|
|
85
|
+
routes,
|
|
86
|
+
sourceAnalysis,
|
|
87
|
+
serverTransformCache,
|
|
88
|
+
vitePlugins,
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
if (timingSink === undefined) {
|
|
92
|
+
await rm(options.outDir, { force: true, recursive: true });
|
|
93
|
+
await Promise.all([
|
|
94
|
+
mkdir(serverDir, { recursive: true }),
|
|
95
|
+
mkdir(clientDir, { recursive: true }),
|
|
96
|
+
...(shouldBuildCloudflare ? [mkdir(cloudflareDir, { recursive: true })] : []),
|
|
97
|
+
mkdir(join(clientDir, ".vite"), { recursive: true }),
|
|
98
|
+
mkdir(join(clientDir, "assets", "routes"), { recursive: true }),
|
|
99
|
+
]);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
await timeBuildPhase(timingSink, "prepareOutput", async () => {
|
|
103
|
+
await rm(options.outDir, { force: true, recursive: true });
|
|
104
|
+
await Promise.all([
|
|
105
|
+
mkdir(serverDir, { recursive: true }),
|
|
106
|
+
mkdir(clientDir, { recursive: true }),
|
|
107
|
+
...(shouldBuildCloudflare ? [mkdir(cloudflareDir, { recursive: true })] : []),
|
|
108
|
+
mkdir(join(clientDir, ".vite"), { recursive: true }),
|
|
109
|
+
mkdir(join(clientDir, "assets", "routes"), { recursive: true }),
|
|
110
|
+
]);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const publicAssets = timingSink === undefined
|
|
114
|
+
? await buildPublicAssetManifest(project, clientDir)
|
|
115
|
+
: await timeBuildPhase(timingSink, "publicAssets", () => buildPublicAssetManifest(project, clientDir));
|
|
59
116
|
const clientRouteInferenceCache = createClientRouteInferenceCache();
|
|
60
|
-
const serverModules = await
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
117
|
+
const [serverActionManifest, serverModules, generatedImportPolicy] = await Promise.all([
|
|
118
|
+
timingSink === undefined
|
|
119
|
+
? collectBuildServerActionManifest({
|
|
120
|
+
files,
|
|
121
|
+
projectRoot: project.projectRoot,
|
|
122
|
+
routes,
|
|
123
|
+
routesDir: project.routesDir,
|
|
124
|
+
})
|
|
125
|
+
: timeBuildPhase(timingSink, "serverActionManifest", () => collectBuildServerActionManifest({
|
|
126
|
+
files,
|
|
127
|
+
projectRoot: project.projectRoot,
|
|
128
|
+
routes,
|
|
129
|
+
routesDir: project.routesDir,
|
|
130
|
+
})),
|
|
131
|
+
timingSink === undefined
|
|
132
|
+
? buildServerModuleArtifacts({
|
|
133
|
+
bundleCache: new Map(),
|
|
134
|
+
clientRouteInferenceCache: serverClientRouteInferenceCache,
|
|
135
|
+
files,
|
|
136
|
+
prebundleServerComponents: buildTargets.includes("node") || shouldBuildAwsLambda,
|
|
137
|
+
project,
|
|
138
|
+
projectRoot: project.projectRoot,
|
|
139
|
+
routes,
|
|
140
|
+
sourceAnalysis,
|
|
141
|
+
serverTransformCache,
|
|
142
|
+
vitePlugins,
|
|
143
|
+
})
|
|
144
|
+
: timeBuildPhase(timingSink, "serverModules", () => buildServerModuleArtifacts({
|
|
145
|
+
bundleCache: new Map(),
|
|
146
|
+
clientRouteInferenceCache: serverClientRouteInferenceCache,
|
|
147
|
+
files,
|
|
148
|
+
prebundleServerComponents: buildTargets.includes("node") || shouldBuildAwsLambda,
|
|
149
|
+
project,
|
|
150
|
+
projectRoot: project.projectRoot,
|
|
151
|
+
routes,
|
|
152
|
+
sourceAnalysis,
|
|
153
|
+
serverTransformCache,
|
|
154
|
+
vitePlugins,
|
|
155
|
+
})),
|
|
156
|
+
timingSink === undefined
|
|
157
|
+
? buildGeneratedImportPolicy({
|
|
158
|
+
files,
|
|
159
|
+
projectRoot: project.projectRoot,
|
|
160
|
+
routes,
|
|
161
|
+
routesDir: project.routesDir,
|
|
162
|
+
})
|
|
163
|
+
: timeBuildPhase(timingSink, "importPolicy", () => buildGeneratedImportPolicy({
|
|
164
|
+
files,
|
|
165
|
+
projectRoot: project.projectRoot,
|
|
166
|
+
routes,
|
|
167
|
+
routesDir: project.routesDir,
|
|
168
|
+
})),
|
|
169
|
+
]);
|
|
76
170
|
const serverRoutes = routes.map((route) => ({
|
|
77
171
|
...route,
|
|
78
172
|
file: relative(project.projectRoot, route.file),
|
|
79
173
|
}));
|
|
80
|
-
const clientRoutes = await
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
174
|
+
const [serverModuleArtifacts, clientRoutes] = await Promise.all([
|
|
175
|
+
timingSink === undefined
|
|
176
|
+
? writeServerModuleArtifactFiles(serverDir, serverModules, generatedImportPolicy.runtimePackages)
|
|
177
|
+
: timeBuildPhase(timingSink, "serverModuleArtifacts", () => writeServerModuleArtifactFiles(serverDir, serverModules, generatedImportPolicy.runtimePackages)),
|
|
178
|
+
timingSink === undefined
|
|
179
|
+
? writeClientRouteBundles({
|
|
180
|
+
appDir: project.routesDir,
|
|
181
|
+
assetBaseUrl: project.assetBaseUrl,
|
|
182
|
+
clientDir,
|
|
183
|
+
clientConsolePureFunctions: project.clientConsolePureFunctions,
|
|
184
|
+
clientRouteInferenceCache,
|
|
185
|
+
projectRoot: project.projectRoot,
|
|
186
|
+
routes,
|
|
187
|
+
sourceAnalysis,
|
|
188
|
+
sourceMapDir: join(options.outDir, "source-maps", "client"),
|
|
189
|
+
sourceMaps: project.clientSourceMaps,
|
|
190
|
+
vitePlugins,
|
|
191
|
+
})
|
|
192
|
+
: timeBuildPhase(timingSink, "clientBundles", () => writeClientRouteBundles({
|
|
193
|
+
appDir: project.routesDir,
|
|
194
|
+
assetBaseUrl: project.assetBaseUrl,
|
|
195
|
+
clientDir,
|
|
196
|
+
clientConsolePureFunctions: project.clientConsolePureFunctions,
|
|
197
|
+
clientRouteInferenceCache,
|
|
198
|
+
projectRoot: project.projectRoot,
|
|
199
|
+
routes,
|
|
200
|
+
sourceAnalysis,
|
|
201
|
+
sourceMapDir: join(options.outDir, "source-maps", "client"),
|
|
202
|
+
sourceMaps: project.clientSourceMaps,
|
|
203
|
+
vitePlugins,
|
|
204
|
+
})),
|
|
205
|
+
]);
|
|
92
206
|
const navigationRuntimeScript = clientRoutes.some((route) => route.navigation === true && !route.client)
|
|
93
|
-
?
|
|
207
|
+
? timingSink === undefined
|
|
208
|
+
? await writeNavigationRuntimeBundle(clientDir, project.clientConsolePureFunctions)
|
|
209
|
+
: await timeBuildPhase(timingSink, "navigationRuntime", () => writeNavigationRuntimeBundle(clientDir, project.clientConsolePureFunctions))
|
|
94
210
|
: undefined;
|
|
95
211
|
const clientManifestRoutes = navigationRuntimeScript === undefined
|
|
96
212
|
? clientRoutes
|
|
97
213
|
: clientRoutes.map((route) => route.navigation === true && !route.client
|
|
98
214
|
? { ...route, navigationScript: navigationRuntimeScript }
|
|
99
215
|
: route);
|
|
100
|
-
const prerenderedRoutes =
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
serverModules,
|
|
107
|
-
vitePlugins,
|
|
108
|
-
});
|
|
109
|
-
let cloudflareRouteModules;
|
|
110
|
-
if (shouldBuildCloudflare) {
|
|
111
|
-
cloudflareRouteModules = await writeCloudflareRouteModules({
|
|
112
|
-
cloudflareDir,
|
|
113
|
-
files,
|
|
114
|
-
prerenderedRoutes,
|
|
115
|
-
projectRoot: project.projectRoot,
|
|
116
|
-
routesDir: project.routesDir,
|
|
216
|
+
const prerenderedRoutes = timingSink === undefined
|
|
217
|
+
? await prerenderStaticRoutes({
|
|
218
|
+
appDir: project.routesDir,
|
|
219
|
+
assetBaseUrl: project.assetBaseUrl,
|
|
220
|
+
clientRoutes: clientManifestRoutes,
|
|
221
|
+
project,
|
|
117
222
|
routes,
|
|
118
223
|
serverModules,
|
|
224
|
+
sourceAnalysis,
|
|
119
225
|
vitePlugins,
|
|
120
|
-
})
|
|
226
|
+
})
|
|
227
|
+
: await timeBuildPhase(timingSink, "prerender", () => prerenderStaticRoutes({
|
|
228
|
+
appDir: project.routesDir,
|
|
229
|
+
assetBaseUrl: project.assetBaseUrl,
|
|
230
|
+
clientRoutes: clientManifestRoutes,
|
|
231
|
+
project,
|
|
232
|
+
routes,
|
|
233
|
+
serverModules,
|
|
234
|
+
sourceAnalysis,
|
|
235
|
+
vitePlugins,
|
|
236
|
+
}));
|
|
237
|
+
let cloudflareRouteModules;
|
|
238
|
+
if (shouldBuildCloudflare) {
|
|
239
|
+
cloudflareRouteModules =
|
|
240
|
+
timingSink === undefined
|
|
241
|
+
? await writeCloudflareRouteModules({
|
|
242
|
+
cloudflareDir,
|
|
243
|
+
files,
|
|
244
|
+
prerenderedRoutes,
|
|
245
|
+
projectRoot: project.projectRoot,
|
|
246
|
+
routesDir: project.routesDir,
|
|
247
|
+
routes,
|
|
248
|
+
serverModules,
|
|
249
|
+
sourceAnalysis,
|
|
250
|
+
vitePlugins,
|
|
251
|
+
})
|
|
252
|
+
: await timeBuildPhase(timingSink, "cloudflare", () => writeCloudflareRouteModules({
|
|
253
|
+
cloudflareDir,
|
|
254
|
+
files,
|
|
255
|
+
prerenderedRoutes,
|
|
256
|
+
projectRoot: project.projectRoot,
|
|
257
|
+
routesDir: project.routesDir,
|
|
258
|
+
routes,
|
|
259
|
+
serverModules,
|
|
260
|
+
sourceAnalysis,
|
|
261
|
+
vitePlugins,
|
|
262
|
+
}));
|
|
121
263
|
}
|
|
122
264
|
const routeServerActionReferences = Object.fromEntries([...serverActionManifest.routeReferences.entries()].sort(([left], [right]) => left.localeCompare(right)));
|
|
123
265
|
const serverManifest = {
|
|
@@ -151,82 +293,263 @@ export async function buildApp(options) {
|
|
|
151
293
|
...(publicAssets.length === 0 ? {} : { publicAssets }),
|
|
152
294
|
routes: clientManifestRoutes,
|
|
153
295
|
};
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
296
|
+
const writeManifestFiles = async () => {
|
|
297
|
+
await Promise.all([
|
|
298
|
+
writeFile(join(serverDir, "manifest.json"), JSON.stringify(serverManifest, null, 2)),
|
|
299
|
+
writeFile(join(serverDir, "import-policy.json"), JSON.stringify(generatedImportPolicy, null, 2)),
|
|
300
|
+
writeFile(join(clientDir, "manifest.json"), JSON.stringify(clientManifest, null, 2)),
|
|
301
|
+
writeFile(join(clientDir, ".vite", "manifest.json"), JSON.stringify(viteManifestFromClientRoutes(clientManifestRoutes), null, 2)),
|
|
302
|
+
]);
|
|
303
|
+
};
|
|
304
|
+
if (timingSink === undefined) {
|
|
305
|
+
await writeManifestFiles();
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
await timeBuildPhase(timingSink, "writeManifests", writeManifestFiles);
|
|
309
|
+
}
|
|
310
|
+
if (timingSink === undefined) {
|
|
311
|
+
await Promise.all([
|
|
312
|
+
...(shouldBuildAwsLambda ? [writeAwsLambdaHandlerArtifact(options.outDir)] : []),
|
|
313
|
+
...(shouldBuildCloudflare
|
|
314
|
+
? [
|
|
315
|
+
writeCloudflareWorkerArtifact({
|
|
316
|
+
cloudflareDir,
|
|
317
|
+
clientManifest,
|
|
318
|
+
modulesFile: cloudflareRouteModules?.registryFile ?? "route-modules.mjs",
|
|
319
|
+
serverManifest,
|
|
320
|
+
}),
|
|
321
|
+
]
|
|
322
|
+
: []),
|
|
323
|
+
]);
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
await timeBuildPhase(timingSink, "adapterArtifacts", async () => {
|
|
327
|
+
await Promise.all([
|
|
328
|
+
...(shouldBuildAwsLambda ? [writeAwsLambdaHandlerArtifact(options.outDir)] : []),
|
|
329
|
+
...(shouldBuildCloudflare
|
|
330
|
+
? [
|
|
331
|
+
writeCloudflareWorkerArtifact({
|
|
332
|
+
cloudflareDir,
|
|
333
|
+
clientManifest,
|
|
334
|
+
modulesFile: cloudflareRouteModules?.registryFile ?? "route-modules.mjs",
|
|
335
|
+
serverManifest,
|
|
336
|
+
}),
|
|
337
|
+
]
|
|
338
|
+
: []),
|
|
339
|
+
]);
|
|
167
340
|
});
|
|
168
341
|
}
|
|
169
342
|
return { routes };
|
|
170
343
|
}
|
|
171
|
-
async function
|
|
344
|
+
async function timeBuildPhase(sink, phase, run) {
|
|
345
|
+
const startedAt = performance.now();
|
|
346
|
+
try {
|
|
347
|
+
return await run();
|
|
348
|
+
}
|
|
349
|
+
finally {
|
|
350
|
+
sink({
|
|
351
|
+
ms: roundBuildPhaseMs(performance.now() - startedAt),
|
|
352
|
+
phase,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function roundBuildPhaseMs(value) {
|
|
357
|
+
return Math.round(value * 100) / 100;
|
|
358
|
+
}
|
|
359
|
+
async function mapWithBuildConcurrency(items, map, concurrency = defaultBuildConcurrency) {
|
|
360
|
+
if (items.length === 0) {
|
|
361
|
+
return [];
|
|
362
|
+
}
|
|
363
|
+
const workerCount = Math.max(1, Math.min(concurrency, items.length));
|
|
364
|
+
const results = [];
|
|
365
|
+
results.length = items.length;
|
|
366
|
+
let nextIndex = 0;
|
|
367
|
+
async function runWorker() {
|
|
368
|
+
while (nextIndex < items.length) {
|
|
369
|
+
const index = nextIndex;
|
|
370
|
+
nextIndex += 1;
|
|
371
|
+
results[index] = await map(items[index], index);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
await Promise.all(Array.from({ length: workerCount }, async () => {
|
|
375
|
+
await runWorker();
|
|
376
|
+
}));
|
|
377
|
+
return results;
|
|
378
|
+
}
|
|
379
|
+
export async function __mapWithBuildConcurrencyForTests(items, map, concurrency) {
|
|
380
|
+
return await mapWithBuildConcurrency(items, map, concurrency);
|
|
381
|
+
}
|
|
382
|
+
async function analyzeBuildRouteSources(options) {
|
|
383
|
+
const byFile = new Map();
|
|
384
|
+
for (const [file, source] of Object.entries(options.files)) {
|
|
385
|
+
byFile.set(file, analyzeBuildSource(source, join(options.projectRoot, file)));
|
|
386
|
+
}
|
|
387
|
+
const routeAnalyses = await mapWithBuildConcurrency(options.routes.filter((route) => route.kind === "page"), async (route) => {
|
|
388
|
+
const file = relative(options.projectRoot, route.file).split(sep).join("/");
|
|
389
|
+
const source = options.files[file];
|
|
390
|
+
if (source === undefined) {
|
|
391
|
+
return undefined;
|
|
392
|
+
}
|
|
393
|
+
const routeCode = stripRouteBuildExports(source, route.file);
|
|
394
|
+
const clientInference = await inferClientRouteModule({
|
|
395
|
+
appDir: options.project.routesDir,
|
|
396
|
+
cache: options.clientRouteInferenceCache,
|
|
397
|
+
code: stripRouteClientSource({ code: source, filename: route.file }),
|
|
398
|
+
filename: route.file,
|
|
399
|
+
routePath: route.path,
|
|
400
|
+
vitePlugins: options.vitePlugins,
|
|
401
|
+
});
|
|
402
|
+
for (const diagnostic of clientInference.diagnostics) {
|
|
403
|
+
console.warn(formatClientRouteInferenceDiagnostic(diagnostic));
|
|
404
|
+
}
|
|
405
|
+
return [
|
|
406
|
+
file,
|
|
407
|
+
{
|
|
408
|
+
...analyzeBuildSource(source, route.file),
|
|
409
|
+
clientBoundaryImports: clientInference.clientBoundaryImports,
|
|
410
|
+
clientRoute: clientInference.client,
|
|
411
|
+
file,
|
|
412
|
+
route,
|
|
413
|
+
routeCode,
|
|
414
|
+
streamRoute: shouldBuildRouteAsStream({
|
|
415
|
+
filename: file,
|
|
416
|
+
files: options.files,
|
|
417
|
+
projectRoot: options.projectRoot,
|
|
418
|
+
source,
|
|
419
|
+
}),
|
|
420
|
+
},
|
|
421
|
+
];
|
|
422
|
+
});
|
|
423
|
+
const byRouteFile = new Map();
|
|
424
|
+
for (const entry of routeAnalyses) {
|
|
425
|
+
if (entry !== undefined) {
|
|
426
|
+
byRouteFile.set(entry[0], entry[1]);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return { byFile, byRouteFile };
|
|
430
|
+
}
|
|
431
|
+
function analyzeBuildSource(source, filename) {
|
|
432
|
+
const cachePolicy = routeCachePolicyFromSource(source);
|
|
433
|
+
const sourceHash = hashText(source);
|
|
434
|
+
if (!isSourceModuleFile(filename)) {
|
|
435
|
+
return {
|
|
436
|
+
authIncludesClaims: false,
|
|
437
|
+
...(cachePolicy === undefined ? {} : { cachePolicy }),
|
|
438
|
+
hasGenerateStaticParams: false,
|
|
439
|
+
hasLoader: false,
|
|
440
|
+
hasMetadata: false,
|
|
441
|
+
hasPrerender: false,
|
|
442
|
+
source,
|
|
443
|
+
sourceHash,
|
|
444
|
+
usesRuntimeCacheControl: usesRuntimeCacheControl(source),
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
authIncludesClaims: authIncludesClaims(source),
|
|
449
|
+
...(cachePolicy === undefined ? {} : { cachePolicy }),
|
|
450
|
+
hasGenerateStaticParams: hasGenerateStaticParamsExport(source, filename),
|
|
451
|
+
hasLoader: hasLoaderExport(source, filename),
|
|
452
|
+
hasMetadata: hasMetadataExport(source),
|
|
453
|
+
hasPrerender: hasPrerenderExport(source, filename),
|
|
454
|
+
source,
|
|
455
|
+
sourceHash,
|
|
456
|
+
usesRuntimeCacheControl: usesRuntimeCacheControl(source),
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
function buildSourceAnalysisForFile(sourceAnalysis, projectRoot, file) {
|
|
460
|
+
return sourceAnalysis.byFile.get(relative(projectRoot, file).split(sep).join("/"));
|
|
461
|
+
}
|
|
462
|
+
async function buildPublicAssetManifest(project, clientDir) {
|
|
463
|
+
const [publicAssetPaths, appConventionPublicAssets] = await Promise.all([
|
|
464
|
+
collectPublicAssetPaths(project.publicDir),
|
|
465
|
+
copyAppFileConventionAssets(project.routesDir, clientDir),
|
|
466
|
+
copyPublicAssets(project.publicDir, join(clientDir, "public")),
|
|
467
|
+
copyPublicAssets(project.publicDir, clientDir),
|
|
468
|
+
]);
|
|
469
|
+
return [...new Set([...publicAssetPaths, ...appConventionPublicAssets])].sort();
|
|
470
|
+
}
|
|
471
|
+
async function writeServerModuleArtifactFiles(serverDir, serverModules, portableRuntimePackages = []) {
|
|
172
472
|
const files = {};
|
|
173
473
|
const renderFiles = {};
|
|
174
474
|
const requestFiles = {};
|
|
175
475
|
const modulesDir = join(serverDir, "server-modules");
|
|
176
|
-
|
|
177
|
-
|
|
476
|
+
const artifactEntries = Object.entries(serverModules);
|
|
477
|
+
await Promise.all([
|
|
478
|
+
mkdir(modulesDir, { recursive: true }),
|
|
479
|
+
mkdir(join(modulesDir, "code"), { recursive: true }),
|
|
480
|
+
mkdir(join(modulesDir, "request"), { recursive: true }),
|
|
481
|
+
mkdir(join(modulesDir, "render"), { recursive: true }),
|
|
482
|
+
]);
|
|
483
|
+
const writtenArtifacts = await mapWithBuildConcurrency(artifactEntries, async ([file, artifact]) => {
|
|
484
|
+
const externalized = await externalizeServerModuleArtifactCode(serverDir, artifact, portableRuntimePackages);
|
|
178
485
|
const requestArtifact = requestServerModuleArtifact(externalized);
|
|
179
486
|
const renderArtifact = renderServerModuleArtifact(externalized);
|
|
180
|
-
await mkdir(modulesDir, { recursive: true });
|
|
181
487
|
if (Object.keys(requestArtifact).length > 0 && Object.keys(renderArtifact).length > 0) {
|
|
182
488
|
const requestJson = JSON.stringify(requestArtifact);
|
|
183
489
|
const requestArtifactFile = `server-modules/request/${hashText(`${file}\0request\0${requestJson}`).slice(0, 16)}.json`;
|
|
184
|
-
await mkdir(join(modulesDir, "request"), { recursive: true });
|
|
185
490
|
await writeFile(join(serverDir, requestArtifactFile), requestJson);
|
|
186
|
-
requestFiles[file] = requestArtifactFile;
|
|
187
491
|
const renderJson = JSON.stringify(renderArtifact);
|
|
188
492
|
const renderArtifactFile = `server-modules/render/${hashText(`${file}\0render\0${renderJson}`).slice(0, 16)}.json`;
|
|
189
|
-
await mkdir(join(modulesDir, "render"), { recursive: true });
|
|
190
493
|
await writeFile(join(serverDir, renderArtifactFile), renderJson);
|
|
191
|
-
|
|
192
|
-
continue;
|
|
494
|
+
return { file, renderArtifactFile, requestArtifactFile };
|
|
193
495
|
}
|
|
194
496
|
const json = JSON.stringify(externalized);
|
|
195
497
|
const artifactFile = `server-modules/${hashText(`${file}\0${json}`).slice(0, 16)}.json`;
|
|
196
498
|
await writeFile(join(serverDir, artifactFile), json);
|
|
197
|
-
|
|
499
|
+
return { artifactFile, file };
|
|
500
|
+
}, serverArtifactFilesystemConcurrency);
|
|
501
|
+
for (const artifact of writtenArtifacts) {
|
|
502
|
+
if ("requestArtifactFile" in artifact) {
|
|
503
|
+
requestFiles[artifact.file] = artifact.requestArtifactFile;
|
|
504
|
+
renderFiles[artifact.file] = artifact.renderArtifactFile;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
files[artifact.file] = artifact.artifactFile;
|
|
198
508
|
}
|
|
199
509
|
return { files, renderFiles, requestFiles };
|
|
200
510
|
}
|
|
201
|
-
async function
|
|
511
|
+
export async function __writeServerModuleArtifactFilesForTests(serverDir, serverModules) {
|
|
512
|
+
return await writeServerModuleArtifactFiles(serverDir, serverModules);
|
|
513
|
+
}
|
|
514
|
+
async function externalizeServerModuleArtifactCode(serverDir, artifact, portableRuntimePackages) {
|
|
202
515
|
return {
|
|
203
516
|
...(artifact.analysis === undefined ? {} : { analysis: artifact.analysis }),
|
|
204
517
|
...(artifact.loader === undefined
|
|
205
518
|
? {}
|
|
206
|
-
: {
|
|
519
|
+
: {
|
|
520
|
+
loader: await externalizeServerModuleOutputCode(serverDir, artifact.loader, "code", portableRuntimePackages),
|
|
521
|
+
}),
|
|
207
522
|
...(artifact.routeMetadata === undefined
|
|
208
523
|
? {}
|
|
209
524
|
: {
|
|
210
|
-
routeMetadata: await externalizeServerModuleOutputCode(serverDir, artifact.routeMetadata, "code"),
|
|
525
|
+
routeMetadata: await externalizeServerModuleOutputCode(serverDir, artifact.routeMetadata, "code", portableRuntimePackages),
|
|
211
526
|
}),
|
|
212
527
|
...(artifact.request === undefined
|
|
213
528
|
? {}
|
|
214
|
-
: {
|
|
529
|
+
: {
|
|
530
|
+
request: await externalizeServerModuleOutputCode(serverDir, artifact.request, "code", portableRuntimePackages),
|
|
531
|
+
}),
|
|
215
532
|
...(artifact.stream === undefined
|
|
216
533
|
? {}
|
|
217
|
-
: {
|
|
534
|
+
: {
|
|
535
|
+
stream: await externalizeServerModuleOutputCode(serverDir, artifact.stream, "bundle", portableRuntimePackages),
|
|
536
|
+
}),
|
|
218
537
|
...(artifact.string === undefined
|
|
219
538
|
? {}
|
|
220
|
-
: {
|
|
539
|
+
: {
|
|
540
|
+
string: await externalizeServerModuleOutputCode(serverDir, artifact.string, "bundle", portableRuntimePackages),
|
|
541
|
+
}),
|
|
221
542
|
};
|
|
222
543
|
}
|
|
223
|
-
async function externalizeServerModuleOutputCode(serverDir, output, kind) {
|
|
224
|
-
const
|
|
544
|
+
async function externalizeServerModuleOutputCode(serverDir, output, kind, portableRuntimePackages) {
|
|
545
|
+
const sourceCode = kind === "bundle" ? output.bundleCode : output.code;
|
|
546
|
+
const moduleCode = sourceCode === undefined
|
|
547
|
+
? undefined
|
|
548
|
+
: rewritePortableRuntimePackageImports(sourceCode, portableRuntimePackages);
|
|
225
549
|
if (moduleCode === undefined || moduleCode.length === 0) {
|
|
226
550
|
return output;
|
|
227
551
|
}
|
|
228
552
|
const moduleFile = `server-modules/code/${hashText(moduleCode).slice(0, 16)}.mjs`;
|
|
229
|
-
await mkdir(join(serverDir, "server-modules", "code"), { recursive: true });
|
|
230
553
|
await writeFile(join(serverDir, moduleFile), moduleCode);
|
|
231
554
|
return {
|
|
232
555
|
code: kind === "code" ? "" : output.code,
|
|
@@ -235,6 +558,27 @@ async function externalizeServerModuleOutputCode(serverDir, output, kind) {
|
|
|
235
558
|
sourceHash: output.sourceHash,
|
|
236
559
|
};
|
|
237
560
|
}
|
|
561
|
+
function rewritePortableRuntimePackageImports(code, runtimePackages) {
|
|
562
|
+
if (runtimePackages.length === 0 || !code.includes("file://")) {
|
|
563
|
+
return code;
|
|
564
|
+
}
|
|
565
|
+
return code.replace(/\b(from\s+|import\s*)["'](?<specifier>file:\/\/[^"']+)["']/gu, (match, prefix, specifier) => {
|
|
566
|
+
const packageName = specifier === undefined ? undefined : runtimePackageForFileUrl(specifier, runtimePackages);
|
|
567
|
+
return packageName === undefined ? match : `${prefix}"${packageName}"`;
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
function runtimePackageForFileUrl(specifier, runtimePackages) {
|
|
571
|
+
let filePath;
|
|
572
|
+
try {
|
|
573
|
+
filePath = fileURLToPath(specifier).split(sep).join("/");
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
return undefined;
|
|
577
|
+
}
|
|
578
|
+
return [...runtimePackages]
|
|
579
|
+
.sort((left, right) => right.length - left.length)
|
|
580
|
+
.find((packageName) => filePath.includes(`/node_modules/${packageName}/`));
|
|
581
|
+
}
|
|
238
582
|
function requestServerModuleArtifact(artifact) {
|
|
239
583
|
return {
|
|
240
584
|
...(artifact.analysis === undefined ? {} : { analysis: artifact.analysis }),
|
|
@@ -604,15 +948,14 @@ async function prerenderStaticRoutes(options) {
|
|
|
604
948
|
join(options.project.projectRoot, file),
|
|
605
949
|
artifact,
|
|
606
950
|
]));
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
if (!hasPrerenderExport(source)) {
|
|
613
|
-
continue;
|
|
951
|
+
const prerenderedEntries = await mapWithBuildConcurrency(options.routes.filter((route) => route.kind === "page"), async (route) => {
|
|
952
|
+
const routeFile = relative(options.project.projectRoot, route.file).split(sep).join("/");
|
|
953
|
+
const analysis = options.sourceAnalysis.byRouteFile.get(routeFile);
|
|
954
|
+
if (analysis === undefined || !analysis.hasPrerender) {
|
|
955
|
+
return [];
|
|
614
956
|
}
|
|
615
|
-
|
|
957
|
+
const entries = [];
|
|
958
|
+
for (const pathname of await prerenderPathsForRoute(route, analysis, options.vitePlugins)) {
|
|
616
959
|
const response = await renderAppRequest({
|
|
617
960
|
appDir: options.appDir,
|
|
618
961
|
assetBaseUrl: options.assetBaseUrl,
|
|
@@ -626,24 +969,33 @@ async function prerenderStaticRoutes(options) {
|
|
|
626
969
|
response.headers.forEach((value, key) => {
|
|
627
970
|
headers[key] = value;
|
|
628
971
|
});
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
972
|
+
entries.push([
|
|
973
|
+
pathname,
|
|
974
|
+
{
|
|
975
|
+
headers,
|
|
976
|
+
html: await response.text(),
|
|
977
|
+
status: response.status,
|
|
978
|
+
},
|
|
979
|
+
]);
|
|
980
|
+
}
|
|
981
|
+
return entries;
|
|
982
|
+
});
|
|
983
|
+
for (const entries of prerenderedEntries) {
|
|
984
|
+
for (const [pathname, route] of entries) {
|
|
985
|
+
prerendered[pathname] = route;
|
|
634
986
|
}
|
|
635
987
|
}
|
|
636
988
|
return prerendered;
|
|
637
989
|
}
|
|
638
|
-
async function prerenderPathsForRoute(route,
|
|
990
|
+
async function prerenderPathsForRoute(route, analysis, vitePlugins) {
|
|
639
991
|
if (route.segments.every((segment) => segment.kind === "static")) {
|
|
640
992
|
return [route.path];
|
|
641
993
|
}
|
|
642
|
-
if (!
|
|
994
|
+
if (!analysis.hasGenerateStaticParams) {
|
|
643
995
|
return [];
|
|
644
996
|
}
|
|
645
997
|
const module = await importAppRouterSourceModule({
|
|
646
|
-
code: source,
|
|
998
|
+
code: analysis.source,
|
|
647
999
|
label: `generate-static-params:${route.file}`,
|
|
648
1000
|
resolveDir: dirname(route.file),
|
|
649
1001
|
sourcefile: route.file,
|
|
@@ -683,119 +1035,171 @@ async function buildServerModuleArtifacts(options) {
|
|
|
683
1035
|
projectRoot: options.project.projectRoot,
|
|
684
1036
|
};
|
|
685
1037
|
const artifacts = {};
|
|
686
|
-
for (const
|
|
1038
|
+
for (const file of Object.keys(options.files)) {
|
|
687
1039
|
const absoluteFile = join(options.projectRoot, file);
|
|
688
1040
|
const route = routeByFile.get(file);
|
|
1041
|
+
const analysis = options.sourceAnalysis.byFile.get(file);
|
|
689
1042
|
if (isMiddlewareFile(options.project.routesDir, absoluteFile)) {
|
|
690
1043
|
requestArtifactFiles.add(file);
|
|
691
1044
|
}
|
|
692
1045
|
if (route?.kind === "server" || route?.kind === "metadata") {
|
|
693
1046
|
requestArtifactFiles.add(file);
|
|
694
1047
|
}
|
|
695
|
-
if (route?.kind === "page" &&
|
|
1048
|
+
if (route?.kind === "page" && analysis?.hasLoader === true) {
|
|
696
1049
|
loaderArtifactFiles.add(file);
|
|
697
1050
|
}
|
|
698
|
-
if (
|
|
1051
|
+
if (isServerComponentFile(file) && analysis?.hasMetadata === true) {
|
|
699
1052
|
metadataArtifactFiles.add(file);
|
|
700
1053
|
}
|
|
701
1054
|
}
|
|
1055
|
+
const requestBatchEntries = [];
|
|
702
1056
|
for (const [file, source] of Object.entries(options.files)) {
|
|
703
1057
|
const absoluteFile = join(options.projectRoot, file);
|
|
704
1058
|
const route = routeByFile.get(file);
|
|
1059
|
+
if (loaderArtifactFiles.has(file)) {
|
|
1060
|
+
requestBatchEntries.push({
|
|
1061
|
+
code: stripRouteLoaderOnlyExports(source, absoluteFile),
|
|
1062
|
+
filename: absoluteFile,
|
|
1063
|
+
key: routeRequestArtifactBatchKey(file, "loader"),
|
|
1064
|
+
label: "Loader",
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
if (metadataArtifactFiles.has(file)) {
|
|
1068
|
+
requestBatchEntries.push({
|
|
1069
|
+
code: stripRouteMetadataOnlyExports(source, absoluteFile),
|
|
1070
|
+
filename: absoluteFile,
|
|
1071
|
+
key: routeRequestArtifactBatchKey(file, "metadata"),
|
|
1072
|
+
label: "Metadata",
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
if (requestArtifactFiles.has(file) &&
|
|
1076
|
+
!isMiddlewareFile(options.project.routesDir, absoluteFile) &&
|
|
1077
|
+
route?.kind !== "server" &&
|
|
1078
|
+
route?.kind !== "metadata") {
|
|
1079
|
+
requestBatchEntries.push({
|
|
1080
|
+
code: stripRouteRequestOnlyExports(source, absoluteFile),
|
|
1081
|
+
filename: absoluteFile,
|
|
1082
|
+
key: routeRequestArtifactBatchKey(file, "request"),
|
|
1083
|
+
label: "Loader",
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
const requestBatchOutputs = requestBatchEntries.length >= 3
|
|
1088
|
+
? await bundleRouteRequestModuleBatchCode({
|
|
1089
|
+
appDir: options.project.routesDir,
|
|
1090
|
+
bundleCache: options.bundleCache,
|
|
1091
|
+
cacheDir: options.cacheDir,
|
|
1092
|
+
entries: requestBatchEntries,
|
|
1093
|
+
importPolicy: requestModuleImportPolicy,
|
|
1094
|
+
vitePlugins: options.vitePlugins,
|
|
1095
|
+
})
|
|
1096
|
+
: new Map();
|
|
1097
|
+
const artifactEntries = await mapWithBuildConcurrency(Object.entries(options.files), async ([file, source]) => {
|
|
1098
|
+
const absoluteFile = join(options.projectRoot, file);
|
|
1099
|
+
const route = routeByFile.get(file);
|
|
1100
|
+
const routeAnalysis = options.sourceAnalysis.byRouteFile.get(file);
|
|
705
1101
|
const artifact = {};
|
|
706
1102
|
if (requestArtifactFiles.has(file) ||
|
|
707
1103
|
loaderArtifactFiles.has(file) ||
|
|
708
1104
|
metadataArtifactFiles.has(file)) {
|
|
709
1105
|
if (loaderArtifactFiles.has(file)) {
|
|
710
|
-
const code =
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
1106
|
+
const code = requestBatchOutputs.get(routeRequestArtifactBatchKey(file, "loader")) ??
|
|
1107
|
+
await bundleRouteLoaderModuleCode({
|
|
1108
|
+
appDir: options.project.routesDir,
|
|
1109
|
+
bundleCache: options.bundleCache,
|
|
1110
|
+
cacheDir: options.cacheDir,
|
|
1111
|
+
code: stripRouteLoaderOnlyExports(source, absoluteFile),
|
|
1112
|
+
filename: absoluteFile,
|
|
1113
|
+
importPolicy: requestModuleImportPolicy,
|
|
1114
|
+
vitePlugins: options.vitePlugins,
|
|
1115
|
+
});
|
|
717
1116
|
artifact.loader = {
|
|
718
1117
|
code,
|
|
719
1118
|
sourceHash: hashText(source),
|
|
720
1119
|
};
|
|
721
1120
|
}
|
|
722
1121
|
if (metadataArtifactFiles.has(file)) {
|
|
723
|
-
const code =
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
1122
|
+
const code = requestBatchOutputs.get(routeRequestArtifactBatchKey(file, "metadata")) ??
|
|
1123
|
+
await bundleRouteRequestModuleCode({
|
|
1124
|
+
appDir: options.project.routesDir,
|
|
1125
|
+
bundleCache: options.bundleCache,
|
|
1126
|
+
cacheDir: options.cacheDir,
|
|
1127
|
+
code: stripRouteMetadataOnlyExports(source, absoluteFile),
|
|
1128
|
+
filename: absoluteFile,
|
|
1129
|
+
importPolicy: requestModuleImportPolicy,
|
|
1130
|
+
label: "Metadata",
|
|
1131
|
+
vitePlugins: options.vitePlugins,
|
|
1132
|
+
});
|
|
730
1133
|
artifact.routeMetadata = {
|
|
731
1134
|
code,
|
|
732
1135
|
sourceHash: hashText(source),
|
|
733
1136
|
};
|
|
734
1137
|
}
|
|
735
1138
|
if (requestArtifactFiles.has(file)) {
|
|
1139
|
+
const batchedRequestCode = requestBatchOutputs.get(routeRequestArtifactBatchKey(file, "request"));
|
|
736
1140
|
artifact.request = {
|
|
737
|
-
code:
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
1141
|
+
code: batchedRequestCode ??
|
|
1142
|
+
await buildRequestModuleArtifactCode({
|
|
1143
|
+
appDir: options.project.routesDir,
|
|
1144
|
+
bundleCache: options.bundleCache,
|
|
1145
|
+
cacheDir: options.cacheDir,
|
|
1146
|
+
filename: absoluteFile,
|
|
1147
|
+
importPolicy: requestModuleImportPolicy,
|
|
1148
|
+
routeKind: route?.kind,
|
|
1149
|
+
source,
|
|
1150
|
+
vitePlugins: options.vitePlugins,
|
|
1151
|
+
}),
|
|
745
1152
|
sourceHash: hashText(source),
|
|
746
1153
|
};
|
|
747
1154
|
}
|
|
748
1155
|
}
|
|
749
1156
|
if (!isServerComponentFile(file)) {
|
|
750
|
-
|
|
751
|
-
artifacts[file] = artifact;
|
|
752
|
-
}
|
|
753
|
-
continue;
|
|
1157
|
+
return Object.keys(artifact).length > 0 ? [file, artifact] : undefined;
|
|
754
1158
|
}
|
|
755
|
-
const closureUsesAwait =
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
1159
|
+
const closureUsesAwait = routeAnalysis?.streamRoute ??
|
|
1160
|
+
shouldBuildRouteAsStream({
|
|
1161
|
+
filename: file,
|
|
1162
|
+
files: options.files,
|
|
1163
|
+
projectRoot: options.projectRoot,
|
|
1164
|
+
source,
|
|
1165
|
+
});
|
|
761
1166
|
const streamRoute = route !== undefined && closureUsesAwait;
|
|
762
1167
|
const serverOutputs = streamRoute || (route === undefined && closureUsesAwait)
|
|
763
1168
|
? ["stream", "string"]
|
|
764
1169
|
: ["string"];
|
|
765
|
-
const code =
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
1170
|
+
const code = routeAnalysis?.routeCode ??
|
|
1171
|
+
(route === undefined ? source : stripRouteBuildExports(source, absoluteFile));
|
|
1172
|
+
const clientInference = routeAnalysis === undefined
|
|
1173
|
+
? await inferClientRouteModule({
|
|
1174
|
+
...(route === undefined ? {} : { appDir: options.project.routesDir }),
|
|
1175
|
+
cache: options.clientRouteInferenceCache,
|
|
1176
|
+
code: route === undefined
|
|
1177
|
+
? stripRouteClientOnlyExports(source, absoluteFile)
|
|
1178
|
+
: stripRouteClientSource({ code: source, filename: route.file }),
|
|
1179
|
+
filename: join(options.projectRoot, file),
|
|
1180
|
+
...(route === undefined ? {} : { routePath: route.path }),
|
|
1181
|
+
vitePlugins: options.vitePlugins,
|
|
1182
|
+
})
|
|
1183
|
+
: undefined;
|
|
1184
|
+
const clientBoundaryImports = routeAnalysis?.clientBoundaryImports ??
|
|
1185
|
+
clientInference?.clientBoundaryImports ??
|
|
1186
|
+
[];
|
|
1187
|
+
for (const diagnostic of clientInference?.diagnostics ?? []) {
|
|
778
1188
|
console.warn(formatClientRouteInferenceDiagnostic(diagnostic));
|
|
779
1189
|
}
|
|
780
|
-
if (
|
|
1190
|
+
if (routeAnalysis !== undefined) {
|
|
781
1191
|
artifact.analysis = builtRouteSourceAnalysisSummary({
|
|
782
|
-
|
|
783
|
-
clientRoute: clientInference.client,
|
|
784
|
-
route,
|
|
785
|
-
routeCode: code,
|
|
786
|
-
source,
|
|
787
|
-
streamRoute,
|
|
1192
|
+
analysis: routeAnalysis,
|
|
788
1193
|
});
|
|
789
1194
|
}
|
|
790
|
-
|
|
791
|
-
const output =
|
|
1195
|
+
const serverOutputArtifacts = await mapWithBuildConcurrency(serverOutputs, async (serverOutput) => {
|
|
1196
|
+
const output = await transformServerRouteSource({
|
|
1197
|
+
cache: options.serverTransformCache,
|
|
792
1198
|
code,
|
|
793
1199
|
clientBoundaryImports,
|
|
794
|
-
dev: false,
|
|
795
1200
|
filename: join(options.projectRoot, file),
|
|
796
|
-
|
|
1201
|
+
moduleContextCache: options.clientRouteInferenceCache,
|
|
797
1202
|
serverOutput,
|
|
798
|
-
target: "server",
|
|
799
1203
|
});
|
|
800
1204
|
const fatalDiagnostics = output.diagnostics.filter((diagnostic) => diagnostic.code !== "MR_UNSUPPORTED_SERVER_EVENT_HANDLER");
|
|
801
1205
|
if (fatalDiagnostics.length > 0) {
|
|
@@ -803,28 +1207,52 @@ async function buildServerModuleArtifacts(options) {
|
|
|
803
1207
|
streamRoute &&
|
|
804
1208
|
route?.kind === "page" &&
|
|
805
1209
|
fatalDiagnostics.every((diagnostic) => diagnostic.code === "MR_UNSUPPORTED_AWAIT_INNER_COMPONENT")) {
|
|
806
|
-
|
|
1210
|
+
return undefined;
|
|
807
1211
|
}
|
|
808
1212
|
throw new Error(fatalDiagnostics.map((diagnostic) => formatDiagnostic(file, diagnostic)).join("\n"));
|
|
809
1213
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1214
|
+
if (serverOutput === "string" &&
|
|
1215
|
+
streamRoute &&
|
|
1216
|
+
route?.kind === "page" &&
|
|
1217
|
+
source.includes("<Await") &&
|
|
1218
|
+
/\bLink\b/.test(source)) {
|
|
1219
|
+
// Native Link renders pre-rendered children as HTML. Keep direct
|
|
1220
|
+
// Await renderers on the stream artifact so the string artifact
|
|
1221
|
+
// cannot drop deferred Link content before the boundary resolves.
|
|
1222
|
+
return undefined;
|
|
1223
|
+
}
|
|
1224
|
+
return [
|
|
1225
|
+
serverOutput,
|
|
1226
|
+
{
|
|
1227
|
+
...(options.prebundleServerComponents
|
|
1228
|
+
? {
|
|
1229
|
+
bundleCode: await buildServerComponentBundleArtifactCode({
|
|
1230
|
+
clientRouteInferenceCache: options.clientRouteInferenceCache,
|
|
1231
|
+
code: output.code,
|
|
1232
|
+
filename: absoluteFile,
|
|
1233
|
+
root: options.projectRoot,
|
|
1234
|
+
serverOutput,
|
|
1235
|
+
vitePlugins: options.vitePlugins,
|
|
1236
|
+
}),
|
|
1237
|
+
}
|
|
1238
|
+
: {}),
|
|
1239
|
+
code: output.code,
|
|
1240
|
+
metadata: output.metadata,
|
|
1241
|
+
sourceHash: hashText(code),
|
|
1242
|
+
},
|
|
1243
|
+
];
|
|
1244
|
+
});
|
|
1245
|
+
for (const entry of serverOutputArtifacts) {
|
|
1246
|
+
if (entry !== undefined) {
|
|
1247
|
+
artifact[entry[0]] = entry[1];
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
return [file, artifact];
|
|
1251
|
+
});
|
|
1252
|
+
for (const entry of artifactEntries) {
|
|
1253
|
+
if (entry !== undefined) {
|
|
1254
|
+
artifacts[entry[0]] = entry[1];
|
|
826
1255
|
}
|
|
827
|
-
artifacts[file] = artifact;
|
|
828
1256
|
}
|
|
829
1257
|
return artifacts;
|
|
830
1258
|
}
|
|
@@ -833,6 +1261,7 @@ async function buildServerComponentBundleArtifactCode(options) {
|
|
|
833
1261
|
code: options.code,
|
|
834
1262
|
label: `server-component:${options.filename}`,
|
|
835
1263
|
resolveDir: dirname(options.filename),
|
|
1264
|
+
root: options.root,
|
|
836
1265
|
serverSourceTransform: {
|
|
837
1266
|
clientRouteInferenceCache: options.clientRouteInferenceCache,
|
|
838
1267
|
dev: false,
|
|
@@ -843,23 +1272,54 @@ async function buildServerComponentBundleArtifactCode(options) {
|
|
|
843
1272
|
vitePlugins: options.vitePlugins,
|
|
844
1273
|
});
|
|
845
1274
|
}
|
|
1275
|
+
async function transformServerRouteSource(options) {
|
|
1276
|
+
const cacheKey = stableCacheKey({
|
|
1277
|
+
clientBoundaryImports: options.clientBoundaryImports,
|
|
1278
|
+
codeHash: hashText(options.code),
|
|
1279
|
+
filename: resolve(options.filename),
|
|
1280
|
+
serverOutput: options.serverOutput,
|
|
1281
|
+
target: "server",
|
|
1282
|
+
});
|
|
1283
|
+
const cached = options.cache.get(cacheKey);
|
|
1284
|
+
if (cached !== undefined) {
|
|
1285
|
+
return await cached;
|
|
1286
|
+
}
|
|
1287
|
+
const transformed = Promise.resolve().then(async () => {
|
|
1288
|
+
const moduleContext = await compilerModuleContextForSource({
|
|
1289
|
+
cache: options.moduleContextCache,
|
|
1290
|
+
code: options.code,
|
|
1291
|
+
filename: options.filename,
|
|
1292
|
+
});
|
|
1293
|
+
return transformCompilerModuleContext({
|
|
1294
|
+
code: options.code,
|
|
1295
|
+
clientBoundaryImports: options.clientBoundaryImports,
|
|
1296
|
+
dev: false,
|
|
1297
|
+
filename: options.filename,
|
|
1298
|
+
moduleContext,
|
|
1299
|
+
serverEscape: nativeEscapeTransform,
|
|
1300
|
+
serverOutput: options.serverOutput,
|
|
1301
|
+
target: "server",
|
|
1302
|
+
});
|
|
1303
|
+
});
|
|
1304
|
+
options.cache.set(cacheKey, transformed);
|
|
1305
|
+
return await transformed;
|
|
1306
|
+
}
|
|
846
1307
|
function builtRouteSourceAnalysisSummary(options) {
|
|
847
|
-
const cachePolicy = routeCachePolicyFromSource(options.source);
|
|
848
1308
|
return {
|
|
849
|
-
authIncludesClaims:
|
|
850
|
-
...(cachePolicy === undefined ? {} : { cachePolicy }),
|
|
851
|
-
clientBoundaryImports: options.clientBoundaryImports,
|
|
852
|
-
clientRoute: options.clientRoute,
|
|
853
|
-
hasLoader:
|
|
854
|
-
routeCode: options.routeCode,
|
|
855
|
-
routePath: options.route.path,
|
|
856
|
-
sourceHash:
|
|
857
|
-
streamRoute: options.streamRoute,
|
|
858
|
-
usesRuntimeCacheControl:
|
|
1309
|
+
authIncludesClaims: options.analysis.authIncludesClaims,
|
|
1310
|
+
...(options.analysis.cachePolicy === undefined ? {} : { cachePolicy: options.analysis.cachePolicy }),
|
|
1311
|
+
clientBoundaryImports: options.analysis.clientBoundaryImports,
|
|
1312
|
+
clientRoute: options.analysis.clientRoute,
|
|
1313
|
+
hasLoader: options.analysis.hasLoader,
|
|
1314
|
+
routeCode: options.analysis.routeCode,
|
|
1315
|
+
routePath: options.analysis.route.path,
|
|
1316
|
+
sourceHash: options.analysis.sourceHash,
|
|
1317
|
+
streamRoute: options.analysis.streamRoute,
|
|
1318
|
+
usesRuntimeCacheControl: options.analysis.usesRuntimeCacheControl,
|
|
859
1319
|
};
|
|
860
1320
|
}
|
|
861
1321
|
function shouldBuildRouteAsStream(options) {
|
|
862
|
-
return (isStreamRouteSource(options.source) ||
|
|
1322
|
+
return (isStreamRouteSource(options.source, sourceFilenameForBuildAnalysis(options.projectRoot, options.filename)) ||
|
|
863
1323
|
routeClosureMayUseAwaitBoundary({
|
|
864
1324
|
filename: options.filename,
|
|
865
1325
|
files: options.files,
|
|
@@ -867,6 +1327,9 @@ function shouldBuildRouteAsStream(options) {
|
|
|
867
1327
|
source: options.source,
|
|
868
1328
|
}));
|
|
869
1329
|
}
|
|
1330
|
+
function sourceFilenameForBuildAnalysis(projectRoot, filename) {
|
|
1331
|
+
return filename.startsWith("/") ? filename : join(projectRoot, filename);
|
|
1332
|
+
}
|
|
870
1333
|
function resolveBuildLocalSourceImport(files, importer, specifier) {
|
|
871
1334
|
if (!specifier.startsWith(".")) {
|
|
872
1335
|
return undefined;
|
|
@@ -882,6 +1345,9 @@ function resolveBuildLocalSourceImport(files, importer, specifier) {
|
|
|
882
1345
|
function usesRuntimeCacheControl(code) {
|
|
883
1346
|
return /\bcacheControl\s*\(/.test(code);
|
|
884
1347
|
}
|
|
1348
|
+
function routeRequestArtifactBatchKey(file, kind) {
|
|
1349
|
+
return `${kind}:${file}`;
|
|
1350
|
+
}
|
|
885
1351
|
function authIncludesClaims(code) {
|
|
886
1352
|
return /\bexport\s+const\s+auth\s*=\s*["']include-claims["']\s*;?/.test(code);
|
|
887
1353
|
}
|
|
@@ -899,14 +1365,18 @@ async function buildRequestModuleArtifactCode(options) {
|
|
|
899
1365
|
return await bundleAppRouterSourceModule({
|
|
900
1366
|
code: options.source,
|
|
901
1367
|
label: `server-route:${options.filename}`,
|
|
1368
|
+
plugins: [fileImportMetaUrlPlugin()],
|
|
902
1369
|
resolveDir: dirname(options.filename),
|
|
1370
|
+
root: options.importPolicy.projectRoot,
|
|
903
1371
|
sourcefile: options.filename,
|
|
904
1372
|
vitePlugins: options.vitePlugins,
|
|
905
1373
|
});
|
|
906
1374
|
}
|
|
907
1375
|
return await bundleRouteLoaderModuleCode({
|
|
908
1376
|
appDir: options.appDir,
|
|
909
|
-
|
|
1377
|
+
bundleCache: options.bundleCache,
|
|
1378
|
+
cacheDir: options.cacheDir,
|
|
1379
|
+
code: stripRouteRequestOnlyExports(options.source, options.filename),
|
|
910
1380
|
filename: options.filename,
|
|
911
1381
|
importPolicy: options.importPolicy,
|
|
912
1382
|
vitePlugins: options.vitePlugins,
|
|
@@ -918,19 +1388,103 @@ async function bundleRouteLoaderModuleCode(options) {
|
|
|
918
1388
|
label: "Loader",
|
|
919
1389
|
});
|
|
920
1390
|
}
|
|
921
|
-
async function
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1391
|
+
async function bundleRouteRequestModuleBatchCode(options) {
|
|
1392
|
+
if (options.entries.length === 0) {
|
|
1393
|
+
return new Map();
|
|
1394
|
+
}
|
|
1395
|
+
if (options.entries.length === 1) {
|
|
1396
|
+
const entry = options.entries[0];
|
|
1397
|
+
if (entry === undefined) {
|
|
1398
|
+
return new Map();
|
|
1399
|
+
}
|
|
1400
|
+
return new Map([
|
|
1401
|
+
[
|
|
1402
|
+
entry.key,
|
|
1403
|
+
await bundleRouteRequestModuleCode({
|
|
1404
|
+
appDir: options.appDir,
|
|
1405
|
+
bundleCache: options.bundleCache,
|
|
1406
|
+
cacheDir: options.cacheDir,
|
|
1407
|
+
code: entry.code,
|
|
1408
|
+
filename: entry.filename,
|
|
1409
|
+
importPolicy: options.importPolicy,
|
|
1410
|
+
label: entry.label,
|
|
1411
|
+
vitePlugins: options.vitePlugins,
|
|
1412
|
+
}),
|
|
1413
|
+
],
|
|
1414
|
+
]);
|
|
1415
|
+
}
|
|
1416
|
+
const namesByKey = new Map(options.entries.map((entry, index) => [
|
|
1417
|
+
entry.key,
|
|
1418
|
+
`request-${index}-${hashText(entry.key).slice(0, 8)}`,
|
|
1419
|
+
]));
|
|
1420
|
+
const output = await bundleRouterModules({
|
|
1421
|
+
cacheDir: options.cacheDir,
|
|
1422
|
+
chunkFileNames: "request/chunks/[name].[hash].js",
|
|
1423
|
+
entries: options.entries.map((entry) => ({
|
|
1424
|
+
code: entry.code,
|
|
1425
|
+
filename: entry.filename,
|
|
1426
|
+
name: namesByKey.get(entry.key) ?? hashText(entry.key).slice(0, 8),
|
|
1427
|
+
})),
|
|
1428
|
+
entryFileNames: "request/[name].js",
|
|
1429
|
+
platform: "node",
|
|
1430
|
+
plugins: [
|
|
1431
|
+
fileImportMetaUrlPlugin(),
|
|
1432
|
+
createAppRouterImportPolicyPlugin({
|
|
1433
|
+
appDir: options.appDir,
|
|
1434
|
+
importPolicy: options.importPolicy,
|
|
1435
|
+
label: "Request artifact",
|
|
1436
|
+
}),
|
|
1437
|
+
],
|
|
1438
|
+
root: options.importPolicy?.projectRoot ?? dirname(options.entries[0]?.filename ?? options.appDir),
|
|
1439
|
+
vitePlugins: options.vitePlugins,
|
|
925
1440
|
});
|
|
1441
|
+
if (output.chunks.some((chunk) => !chunk.isEntry)) {
|
|
1442
|
+
const fallbackEntries = await mapWithBuildConcurrency(options.entries, async (entry) => [
|
|
1443
|
+
entry.key,
|
|
1444
|
+
await bundleRouteRequestModuleCode({
|
|
1445
|
+
appDir: options.appDir,
|
|
1446
|
+
bundleCache: options.bundleCache,
|
|
1447
|
+
cacheDir: options.cacheDir,
|
|
1448
|
+
code: entry.code,
|
|
1449
|
+
filename: entry.filename,
|
|
1450
|
+
importPolicy: options.importPolicy,
|
|
1451
|
+
label: entry.label,
|
|
1452
|
+
vitePlugins: options.vitePlugins,
|
|
1453
|
+
}),
|
|
1454
|
+
]);
|
|
1455
|
+
return new Map(fallbackEntries);
|
|
1456
|
+
}
|
|
1457
|
+
const chunksByName = new Map(output.chunks.map((chunk) => [chunk.name, chunk]));
|
|
1458
|
+
return new Map(options.entries.map((entry) => {
|
|
1459
|
+
const name = namesByKey.get(entry.key);
|
|
1460
|
+
const chunk = name === undefined ? undefined : chunksByName.get(name);
|
|
1461
|
+
if (chunk === undefined) {
|
|
1462
|
+
throw new Error(`Failed to compile request artifact for ${entry.filename}.`);
|
|
1463
|
+
}
|
|
1464
|
+
return [entry.key, chunk.code];
|
|
1465
|
+
}));
|
|
1466
|
+
}
|
|
1467
|
+
export async function __bundleRouteRequestModuleBatchForTests(options) {
|
|
1468
|
+
return Object.fromEntries(await bundleRouteRequestModuleBatchCode({
|
|
1469
|
+
appDir: options.appDir,
|
|
1470
|
+
cacheDir: options.cacheDir,
|
|
1471
|
+
entries: options.entries,
|
|
1472
|
+
importPolicy: options.importPolicy,
|
|
1473
|
+
vitePlugins: options.vitePlugins,
|
|
1474
|
+
}));
|
|
926
1475
|
}
|
|
927
1476
|
async function bundleRouteRequestModuleCode(options) {
|
|
928
1477
|
const output = await bundleRouterModule({
|
|
1478
|
+
cache: options.bundleCache,
|
|
1479
|
+
cacheDir: options.cacheDir,
|
|
1480
|
+
cacheKey: routeRequestBundleCacheKey(options),
|
|
929
1481
|
code: options.code,
|
|
930
1482
|
filename: options.filename,
|
|
931
1483
|
platform: "node",
|
|
1484
|
+
root: options.importPolicy?.projectRoot,
|
|
932
1485
|
vitePlugins: options.vitePlugins,
|
|
933
1486
|
plugins: [
|
|
1487
|
+
fileImportMetaUrlPlugin(),
|
|
934
1488
|
createAppRouterImportPolicyPlugin({
|
|
935
1489
|
appDir: options.appDir,
|
|
936
1490
|
importPolicy: options.importPolicy,
|
|
@@ -944,6 +1498,31 @@ async function bundleRouteRequestModuleCode(options) {
|
|
|
944
1498
|
}
|
|
945
1499
|
return code;
|
|
946
1500
|
}
|
|
1501
|
+
function routeRequestBundleCacheKey(options) {
|
|
1502
|
+
return stableCacheKey({
|
|
1503
|
+
appDir: resolve(options.appDir),
|
|
1504
|
+
codeHash: hashText(options.code),
|
|
1505
|
+
filename: resolve(options.filename),
|
|
1506
|
+
importPolicy: options.importPolicy === undefined
|
|
1507
|
+
? undefined
|
|
1508
|
+
: {
|
|
1509
|
+
allowedPackages: [...(options.importPolicy.allowedPackages ?? [])].sort(),
|
|
1510
|
+
allowedSourceDirs: (options.importPolicy.allowedSourceDirs ?? [])
|
|
1511
|
+
.map((dir) => resolve(dir))
|
|
1512
|
+
.sort(),
|
|
1513
|
+
projectRoot: options.importPolicy.projectRoot === undefined
|
|
1514
|
+
? undefined
|
|
1515
|
+
: resolve(options.importPolicy.projectRoot),
|
|
1516
|
+
},
|
|
1517
|
+
label: options.label,
|
|
1518
|
+
platform: "node",
|
|
1519
|
+
target: "es2022",
|
|
1520
|
+
vitePlugins: vitePluginsCacheKey(options.vitePlugins),
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
function stableCacheKey(value) {
|
|
1524
|
+
return JSON.stringify(value);
|
|
1525
|
+
}
|
|
947
1526
|
function isMiddlewareFile(appDir, file) {
|
|
948
1527
|
return file === join(appDir, "middleware.ts") || file === join(appDir, "middleware.mreact.ts");
|
|
949
1528
|
}
|
|
@@ -968,23 +1547,50 @@ async function readDeclaredProjectPackages(projectRoot) {
|
|
|
968
1547
|
}
|
|
969
1548
|
async function writeCloudflareRouteModules(options) {
|
|
970
1549
|
const routesDir = join(options.cloudflareDir, "routes");
|
|
971
|
-
const requiredRoutes =
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
for (const route of requiredRoutes) {
|
|
1550
|
+
const requiredRoutes = await Promise.all(options.routes
|
|
1551
|
+
.filter((route) => cloudflareRouteRequiresGeneratedModule(route, options.prerenderedRoutes))
|
|
1552
|
+
.flatMap((route) => {
|
|
975
1553
|
const routeFile = relative(options.projectRoot, route.file).replaceAll(sep, "/");
|
|
976
|
-
const
|
|
977
|
-
|
|
1554
|
+
const analysis = options.sourceAnalysis.byFile.get(routeFile);
|
|
1555
|
+
return analysis === undefined
|
|
1556
|
+
? []
|
|
1557
|
+
: [{
|
|
1558
|
+
route,
|
|
1559
|
+
routeFile,
|
|
1560
|
+
routeId: routeIdForPath(route.path),
|
|
1561
|
+
}];
|
|
1562
|
+
}));
|
|
1563
|
+
await mkdir(routesDir, { recursive: true });
|
|
1564
|
+
const serverRouteModules = await buildCloudflareServerRouteModuleBatch({
|
|
1565
|
+
cacheDir: options.cacheDir,
|
|
1566
|
+
routes: requiredRoutes
|
|
1567
|
+
.filter(({ route }) => route.kind === "server" || route.kind === "metadata")
|
|
1568
|
+
.map(({ route, routeId }) => ({ filename: route.file, routeId })),
|
|
1569
|
+
root: options.projectRoot,
|
|
1570
|
+
vitePlugins: options.vitePlugins,
|
|
1571
|
+
});
|
|
1572
|
+
const loaderRouteModules = await buildCloudflareRouteLoaderModuleBatch({
|
|
1573
|
+
cacheDir: options.cacheDir,
|
|
1574
|
+
routes: requiredRoutes
|
|
1575
|
+
.filter(({ route, routeFile }) => route.kind === "page" && options.sourceAnalysis.byRouteFile.get(routeFile)?.hasLoader === true)
|
|
1576
|
+
.map(({ route, routeId }) => ({ filename: route.file, routeId })),
|
|
1577
|
+
root: options.projectRoot,
|
|
1578
|
+
vitePlugins: options.vitePlugins,
|
|
1579
|
+
});
|
|
1580
|
+
await Promise.all([
|
|
1581
|
+
writeCloudflareBatchedRouteModuleChunks(options.cloudflareDir, serverRouteModules),
|
|
1582
|
+
writeCloudflareBatchedRouteModuleChunks(options.cloudflareDir, loaderRouteModules),
|
|
1583
|
+
]);
|
|
1584
|
+
const registryEntries = await mapWithBuildConcurrency(requiredRoutes, async ({ route, routeFile, routeId }) => {
|
|
978
1585
|
const routeModuleFile = `routes/${routeId}.mjs`;
|
|
979
1586
|
let routeModuleExports;
|
|
980
1587
|
if (route.kind === "server" || route.kind === "metadata") {
|
|
981
1588
|
try {
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
}
|
|
986
|
-
const serverRouteFile =
|
|
987
|
-
await writeFile(join(options.cloudflareDir, serverRouteFile), routeOutput);
|
|
1589
|
+
const serverRouteModule = serverRouteModules.entries.get(routeId);
|
|
1590
|
+
if (serverRouteModule === undefined) {
|
|
1591
|
+
throw new Error(`Missing bundled Cloudflare ${route.kind} route module.`);
|
|
1592
|
+
}
|
|
1593
|
+
const serverRouteFile = serverRouteModule.fileName;
|
|
988
1594
|
const serverRouteImport = `./${serverRouteFile.split("/").pop() ?? serverRouteFile}`;
|
|
989
1595
|
routeModuleExports = [
|
|
990
1596
|
`export * from ${JSON.stringify(serverRouteImport)};`,
|
|
@@ -997,32 +1603,30 @@ async function writeCloudflareRouteModules(options) {
|
|
|
997
1603
|
throw new Error(`Failed to build Cloudflare ${route.kind} route module for ${routeFile}: ${errorMessage(error)}`);
|
|
998
1604
|
}
|
|
999
1605
|
await writeFile(join(options.cloudflareDir, routeModuleFile), `${routeModuleExports.join("\n")}\n`);
|
|
1000
|
-
|
|
1001
|
-
continue;
|
|
1606
|
+
return `${JSON.stringify(routeFile)}: () => import(${JSON.stringify(`./${routeModuleFile}`)})`;
|
|
1002
1607
|
}
|
|
1003
1608
|
const serverOutput = options.serverModules[routeFile]?.analysis?.streamRoute === true ||
|
|
1004
|
-
|
|
1005
|
-
filename: routeFile,
|
|
1006
|
-
files: options.files,
|
|
1007
|
-
projectRoot: options.projectRoot,
|
|
1008
|
-
source,
|
|
1009
|
-
})
|
|
1609
|
+
options.sourceAnalysis.byRouteFile.get(routeFile)?.streamRoute === true
|
|
1010
1610
|
? "stream"
|
|
1011
1611
|
: "string";
|
|
1012
1612
|
try {
|
|
1013
1613
|
const componentOutput = serverOutput === "stream"
|
|
1014
1614
|
? await buildCloudflareStreamRouteComponentModule({
|
|
1615
|
+
cacheDir: options.cacheDir,
|
|
1015
1616
|
filename: route.file,
|
|
1016
1617
|
projectRoot: options.projectRoot,
|
|
1017
1618
|
routesDir: options.routesDir,
|
|
1018
1619
|
serverModules: options.serverModules,
|
|
1620
|
+
sourceAnalysis: options.sourceAnalysis,
|
|
1019
1621
|
vitePlugins: options.vitePlugins,
|
|
1020
1622
|
})
|
|
1021
1623
|
: await buildCloudflareStringRouteComponentModule({
|
|
1624
|
+
cacheDir: options.cacheDir,
|
|
1022
1625
|
filename: route.file,
|
|
1023
1626
|
projectRoot: options.projectRoot,
|
|
1024
1627
|
routesDir: options.routesDir,
|
|
1025
1628
|
serverModules: options.serverModules,
|
|
1629
|
+
sourceAnalysis: options.sourceAnalysis,
|
|
1026
1630
|
vitePlugins: options.vitePlugins,
|
|
1027
1631
|
});
|
|
1028
1632
|
const componentFile = `routes/${routeId}.${hashText(componentOutput).slice(0, 8)}.component.mjs`;
|
|
@@ -1035,15 +1639,13 @@ async function writeCloudflareRouteModules(options) {
|
|
|
1035
1639
|
catch (error) {
|
|
1036
1640
|
throw new Error(`Failed to build Cloudflare route module for ${routeFile}: ${errorMessage(error)}`);
|
|
1037
1641
|
}
|
|
1038
|
-
if (
|
|
1642
|
+
if (options.sourceAnalysis.byRouteFile.get(routeFile)?.hasLoader === true) {
|
|
1039
1643
|
try {
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
const loaderFile = `routes/${routeId}.${hashText(loaderOutput).slice(0, 8)}.loader.mjs`;
|
|
1046
|
-
await writeFile(join(options.cloudflareDir, loaderFile), loaderOutput);
|
|
1644
|
+
const loaderModule = loaderRouteModules.entries.get(routeId);
|
|
1645
|
+
if (loaderModule === undefined) {
|
|
1646
|
+
throw new Error("Missing bundled Cloudflare loader module.");
|
|
1647
|
+
}
|
|
1648
|
+
const loaderFile = loaderModule.fileName;
|
|
1047
1649
|
const loaderImport = `./${loaderFile.split("/").pop() ?? loaderFile}`;
|
|
1048
1650
|
routeModuleExports.push(`export { loader } from ${JSON.stringify(loaderImport)};`);
|
|
1049
1651
|
}
|
|
@@ -1052,8 +1654,8 @@ async function writeCloudflareRouteModules(options) {
|
|
|
1052
1654
|
}
|
|
1053
1655
|
}
|
|
1054
1656
|
await writeFile(join(options.cloudflareDir, routeModuleFile), `${routeModuleExports.join("\n")}\n`);
|
|
1055
|
-
|
|
1056
|
-
}
|
|
1657
|
+
return `${JSON.stringify(routeFile)}: () => import(${JSON.stringify(`./${routeModuleFile}`)})`;
|
|
1658
|
+
});
|
|
1057
1659
|
const registrySource = [
|
|
1058
1660
|
`export const routeModules = {`,
|
|
1059
1661
|
...registryEntries.map((entry) => ` ${entry},`),
|
|
@@ -1066,7 +1668,10 @@ async function writeCloudflareRouteModules(options) {
|
|
|
1066
1668
|
}
|
|
1067
1669
|
async function buildCloudflareServerComponentModule(options) {
|
|
1068
1670
|
const metadataModule = await buildCloudflareRouteMetadataExportModule({
|
|
1671
|
+
cacheDir: options.cacheDir,
|
|
1069
1672
|
filename: options.filename,
|
|
1673
|
+
hasMetadata: buildSourceAnalysisForFile(options.sourceAnalysis, options.projectRoot, options.filename)?.hasMetadata,
|
|
1674
|
+
root: options.projectRoot,
|
|
1070
1675
|
vitePlugins: options.vitePlugins,
|
|
1071
1676
|
});
|
|
1072
1677
|
const entry = `import * as routeModule from ${JSON.stringify(options.filename)};
|
|
@@ -1081,6 +1686,7 @@ export const slots = routeModule.slots;`;
|
|
|
1081
1686
|
return bundleCloudflareVirtualModule({
|
|
1082
1687
|
entry,
|
|
1083
1688
|
filename: `${options.filename}.mreact-cloudflare-component.js`,
|
|
1689
|
+
cacheDir: options.cacheDir,
|
|
1084
1690
|
modules: metadataModule === undefined
|
|
1085
1691
|
? new Map()
|
|
1086
1692
|
: new Map([["mreact:metadata", metadataModule]]),
|
|
@@ -1094,6 +1700,7 @@ export const slots = routeModule.slots;`;
|
|
|
1094
1700
|
cloudflareWorkspaceRuntimePlugin(),
|
|
1095
1701
|
],
|
|
1096
1702
|
resolveDir: dirname(options.filename),
|
|
1703
|
+
root: options.projectRoot,
|
|
1097
1704
|
vitePlugins: options.vitePlugins,
|
|
1098
1705
|
});
|
|
1099
1706
|
}
|
|
@@ -1101,25 +1708,31 @@ async function buildCloudflareStringRouteComponentModule(options) {
|
|
|
1101
1708
|
const shellFiles = await cloudflareShellFilesForPage(options.routesDir, options.filename);
|
|
1102
1709
|
if (shellFiles.length === 0) {
|
|
1103
1710
|
return buildCloudflareServerComponentModule({
|
|
1711
|
+
cacheDir: options.cacheDir,
|
|
1104
1712
|
filename: options.filename,
|
|
1105
1713
|
projectRoot: options.projectRoot,
|
|
1106
1714
|
serverModules: options.serverModules,
|
|
1107
1715
|
serverOutput: "string",
|
|
1716
|
+
sourceAnalysis: options.sourceAnalysis,
|
|
1108
1717
|
vitePlugins: options.vitePlugins,
|
|
1109
1718
|
});
|
|
1110
1719
|
}
|
|
1111
1720
|
const pageModule = await buildCloudflareComponentExportModule({
|
|
1721
|
+
cacheDir: options.cacheDir,
|
|
1112
1722
|
filename: options.filename,
|
|
1113
1723
|
projectRoot: options.projectRoot,
|
|
1114
1724
|
serverModules: options.serverModules,
|
|
1115
1725
|
serverOutput: "string",
|
|
1726
|
+
sourceAnalysis: options.sourceAnalysis,
|
|
1116
1727
|
vitePlugins: options.vitePlugins,
|
|
1117
1728
|
});
|
|
1118
1729
|
const shellModules = await Promise.all(shellFiles.map((shell) => buildCloudflareComponentExportModule({
|
|
1730
|
+
cacheDir: options.cacheDir,
|
|
1119
1731
|
filename: shell.file,
|
|
1120
1732
|
projectRoot: options.projectRoot,
|
|
1121
1733
|
serverModules: options.serverModules,
|
|
1122
1734
|
serverOutput: "string",
|
|
1735
|
+
sourceAnalysis: options.sourceAnalysis,
|
|
1123
1736
|
vitePlugins: options.vitePlugins,
|
|
1124
1737
|
})));
|
|
1125
1738
|
const shellImports = shellFiles.map((_, index) => `import * as shell${index} from "mreact:shell-${index}";`);
|
|
@@ -1175,6 +1788,7 @@ async function renderRouteSlots(slots, props) {
|
|
|
1175
1788
|
${cloudflareShellRuntimeSource()}`;
|
|
1176
1789
|
return bundleCloudflareVirtualModule({
|
|
1177
1790
|
entry,
|
|
1791
|
+
cacheDir: options.cacheDir,
|
|
1178
1792
|
filename: `${options.filename}.mreact-cloudflare-string-route.js`,
|
|
1179
1793
|
modules: new Map([
|
|
1180
1794
|
["mreact:page", pageModule],
|
|
@@ -1182,23 +1796,28 @@ ${cloudflareShellRuntimeSource()}`;
|
|
|
1182
1796
|
]),
|
|
1183
1797
|
plugins: [cloudflareWorkspaceRuntimePlugin()],
|
|
1184
1798
|
resolveDir: dirname(options.filename),
|
|
1799
|
+
root: options.projectRoot,
|
|
1185
1800
|
vitePlugins: options.vitePlugins,
|
|
1186
1801
|
});
|
|
1187
1802
|
}
|
|
1188
1803
|
async function buildCloudflareStreamRouteComponentModule(options) {
|
|
1189
1804
|
const pageModule = await buildCloudflareComponentExportModule({
|
|
1805
|
+
cacheDir: options.cacheDir,
|
|
1190
1806
|
filename: options.filename,
|
|
1191
1807
|
projectRoot: options.projectRoot,
|
|
1192
1808
|
serverModules: options.serverModules,
|
|
1193
1809
|
serverOutput: "stream",
|
|
1810
|
+
sourceAnalysis: options.sourceAnalysis,
|
|
1194
1811
|
vitePlugins: options.vitePlugins,
|
|
1195
1812
|
});
|
|
1196
1813
|
const shellFiles = await cloudflareShellFilesForPage(options.routesDir, options.filename);
|
|
1197
1814
|
const shellModules = await Promise.all(shellFiles.map((shell) => buildCloudflareComponentExportModule({
|
|
1815
|
+
cacheDir: options.cacheDir,
|
|
1198
1816
|
filename: shell.file,
|
|
1199
1817
|
projectRoot: options.projectRoot,
|
|
1200
1818
|
serverModules: options.serverModules,
|
|
1201
1819
|
serverOutput: "string",
|
|
1820
|
+
sourceAnalysis: options.sourceAnalysis,
|
|
1202
1821
|
vitePlugins: options.vitePlugins,
|
|
1203
1822
|
})));
|
|
1204
1823
|
const shellImports = shellFiles.map((_, index) => `import * as shell${index} from "mreact:shell-${index}";`);
|
|
@@ -1270,6 +1889,7 @@ async function renderRouteSlots(slots, props) {
|
|
|
1270
1889
|
${cloudflareShellRuntimeSource()}`;
|
|
1271
1890
|
return bundleCloudflareVirtualModule({
|
|
1272
1891
|
entry,
|
|
1892
|
+
cacheDir: options.cacheDir,
|
|
1273
1893
|
filename: `${options.filename}.mreact-cloudflare-stream-route.js`,
|
|
1274
1894
|
modules: new Map([
|
|
1275
1895
|
["mreact:page", pageModule],
|
|
@@ -1277,6 +1897,7 @@ ${cloudflareShellRuntimeSource()}`;
|
|
|
1277
1897
|
]),
|
|
1278
1898
|
plugins: [cloudflareWorkspaceRuntimePlugin()],
|
|
1279
1899
|
resolveDir: dirname(options.filename),
|
|
1900
|
+
root: options.projectRoot,
|
|
1280
1901
|
vitePlugins: options.vitePlugins,
|
|
1281
1902
|
});
|
|
1282
1903
|
}
|
|
@@ -1529,7 +2150,10 @@ function escapeHtml(value) {
|
|
|
1529
2150
|
}
|
|
1530
2151
|
async function buildCloudflareComponentExportModule(options) {
|
|
1531
2152
|
const metadataModule = await buildCloudflareRouteMetadataExportModule({
|
|
2153
|
+
cacheDir: options.cacheDir,
|
|
1532
2154
|
filename: options.filename,
|
|
2155
|
+
hasMetadata: buildSourceAnalysisForFile(options.sourceAnalysis, options.projectRoot, options.filename)?.hasMetadata,
|
|
2156
|
+
root: options.projectRoot,
|
|
1533
2157
|
vitePlugins: options.vitePlugins,
|
|
1534
2158
|
});
|
|
1535
2159
|
const entry = `import * as routeModule from ${JSON.stringify(options.filename)};
|
|
@@ -1543,6 +2167,7 @@ export const metadata = metadataModule.metadata;
|
|
|
1543
2167
|
export const slots = routeModule.slots;`;
|
|
1544
2168
|
return bundleCloudflareVirtualModule({
|
|
1545
2169
|
entry,
|
|
2170
|
+
cacheDir: options.cacheDir,
|
|
1546
2171
|
filename: `${options.filename}.mreact-cloudflare-${options.serverOutput}-component.js`,
|
|
1547
2172
|
modules: metadataModule === undefined
|
|
1548
2173
|
? new Map()
|
|
@@ -1557,22 +2182,89 @@ export const slots = routeModule.slots;`;
|
|
|
1557
2182
|
cloudflareWorkspaceRuntimePlugin(),
|
|
1558
2183
|
],
|
|
1559
2184
|
resolveDir: dirname(options.filename),
|
|
2185
|
+
root: options.projectRoot,
|
|
1560
2186
|
vitePlugins: options.vitePlugins,
|
|
1561
2187
|
});
|
|
1562
2188
|
}
|
|
1563
|
-
async function
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
2189
|
+
async function writeCloudflareBatchedRouteModuleChunks(cloudflareDir, modules) {
|
|
2190
|
+
if (modules.chunks.some((chunk) => chunk.fileName.startsWith("routes/chunks/"))) {
|
|
2191
|
+
await mkdir(join(cloudflareDir, "routes", "chunks"), { recursive: true });
|
|
2192
|
+
}
|
|
2193
|
+
await Promise.all(modules.chunks.map((chunk) => writeFile(join(cloudflareDir, chunk.fileName), chunk.code)));
|
|
2194
|
+
}
|
|
2195
|
+
async function buildCloudflareRouteLoaderModuleBatch(options) {
|
|
2196
|
+
return await bundleCloudflareModuleBatch({
|
|
2197
|
+
cacheDir: options.cacheDir,
|
|
2198
|
+
entries: options.routes.map((route) => ({
|
|
2199
|
+
code: cloudflareRouteLoaderModuleEntry(route.filename),
|
|
2200
|
+
filename: `${route.filename}.mreact-cloudflare-loader.js`,
|
|
2201
|
+
name: `${route.routeId}.loader`,
|
|
2202
|
+
routeId: route.routeId,
|
|
2203
|
+
})),
|
|
2204
|
+
root: options.root,
|
|
2205
|
+
vitePlugins: options.vitePlugins,
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
export async function __buildCloudflareRouteLoaderModuleBatchForTests(options) {
|
|
2209
|
+
const output = await buildCloudflareRouteLoaderModuleBatch({
|
|
2210
|
+
cacheDir: options.cacheDir,
|
|
2211
|
+
root: options.projectRoot,
|
|
2212
|
+
routes: options.routes,
|
|
2213
|
+
vitePlugins: options.vitePlugins,
|
|
2214
|
+
});
|
|
2215
|
+
return Object.fromEntries(options.routes.map((route) => [route.routeId, output.entries.get(route.routeId)?.code ?? ""]));
|
|
2216
|
+
}
|
|
2217
|
+
async function buildCloudflareServerRouteModuleBatch(options) {
|
|
2218
|
+
return await bundleCloudflareModuleBatch({
|
|
2219
|
+
cacheDir: options.cacheDir,
|
|
2220
|
+
entries: options.routes.map((route) => ({
|
|
2221
|
+
code: cloudflareServerRouteModuleEntry(route.filename),
|
|
2222
|
+
filename: `${route.filename}.mreact-cloudflare-server-route.js`,
|
|
2223
|
+
name: `${route.routeId}.server`,
|
|
2224
|
+
routeId: route.routeId,
|
|
2225
|
+
})),
|
|
2226
|
+
root: options.root,
|
|
2227
|
+
vitePlugins: options.vitePlugins,
|
|
2228
|
+
});
|
|
2229
|
+
}
|
|
2230
|
+
async function bundleCloudflareModuleBatch(options) {
|
|
2231
|
+
if (options.entries.length === 0) {
|
|
2232
|
+
return { chunks: [], entries: new Map() };
|
|
2233
|
+
}
|
|
2234
|
+
const output = await bundleRouterModules({
|
|
2235
|
+
cacheDir: options.cacheDir,
|
|
2236
|
+
chunkFileNames: "routes/chunks/[name].[hash].mjs",
|
|
2237
|
+
entries: options.entries,
|
|
2238
|
+
entryFileNames: "routes/[name].[hash].mjs",
|
|
2239
|
+
minify: true,
|
|
2240
|
+
platform: "node",
|
|
1568
2241
|
plugins: [cloudflareWorkspaceRuntimePlugin()],
|
|
1569
|
-
|
|
2242
|
+
root: options.root,
|
|
2243
|
+
target: "es2022",
|
|
1570
2244
|
vitePlugins: options.vitePlugins,
|
|
1571
2245
|
});
|
|
2246
|
+
const entriesByName = new Map(options.entries.map((entry) => [entry.name, entry]));
|
|
2247
|
+
const entries = new Map();
|
|
2248
|
+
for (const chunk of output.chunks) {
|
|
2249
|
+
if (!chunk.isEntry) {
|
|
2250
|
+
continue;
|
|
2251
|
+
}
|
|
2252
|
+
const entry = entriesByName.get(chunk.name);
|
|
2253
|
+
if (entry === undefined) {
|
|
2254
|
+
continue;
|
|
2255
|
+
}
|
|
2256
|
+
entries.set(entry.routeId, {
|
|
2257
|
+
code: chunk.code,
|
|
2258
|
+
fileName: chunk.fileName,
|
|
2259
|
+
});
|
|
2260
|
+
}
|
|
2261
|
+
return { chunks: output.chunks, entries };
|
|
2262
|
+
}
|
|
2263
|
+
function cloudflareRouteLoaderModuleEntry(filename) {
|
|
2264
|
+
return `export { loader } from ${JSON.stringify(filename)};`;
|
|
1572
2265
|
}
|
|
1573
2266
|
async function buildCloudflareRouteMetadataExportModule(options) {
|
|
1574
|
-
|
|
1575
|
-
if (!hasMetadataExport(source)) {
|
|
2267
|
+
if (options.hasMetadata !== true) {
|
|
1576
2268
|
return undefined;
|
|
1577
2269
|
}
|
|
1578
2270
|
const entry = `import * as routeMetadataModule from ${JSON.stringify(options.filename)};
|
|
@@ -1580,14 +2272,16 @@ export const generateMetadata = routeMetadataModule.generateMetadata;
|
|
|
1580
2272
|
export const metadata = routeMetadataModule.metadata;`;
|
|
1581
2273
|
return bundleCloudflareModule({
|
|
1582
2274
|
entry,
|
|
2275
|
+
cacheDir: options.cacheDir,
|
|
1583
2276
|
filename: `${options.filename}.mreact-cloudflare-metadata.js`,
|
|
1584
2277
|
plugins: [cloudflareWorkspaceRuntimePlugin()],
|
|
1585
2278
|
resolveDir: dirname(options.filename),
|
|
2279
|
+
root: options.root,
|
|
1586
2280
|
vitePlugins: options.vitePlugins,
|
|
1587
2281
|
});
|
|
1588
2282
|
}
|
|
1589
|
-
|
|
1590
|
-
|
|
2283
|
+
function cloudflareServerRouteModuleEntry(filename) {
|
|
2284
|
+
return `import * as routeModule from ${JSON.stringify(filename)};
|
|
1591
2285
|
|
|
1592
2286
|
export const GET = routeModule.GET;
|
|
1593
2287
|
export const HEAD = routeModule.HEAD;
|
|
@@ -1599,22 +2293,17 @@ export const OPTIONS = routeModule.OPTIONS;
|
|
|
1599
2293
|
export const ALL = routeModule.ALL;
|
|
1600
2294
|
const defaultHandler = routeModule.default;
|
|
1601
2295
|
export default defaultHandler;`;
|
|
1602
|
-
return bundleCloudflareModule({
|
|
1603
|
-
entry,
|
|
1604
|
-
filename: `${options.filename}.mreact-cloudflare-server-route.js`,
|
|
1605
|
-
plugins: [cloudflareWorkspaceRuntimePlugin()],
|
|
1606
|
-
resolveDir: dirname(options.filename),
|
|
1607
|
-
vitePlugins: options.vitePlugins,
|
|
1608
|
-
});
|
|
1609
2296
|
}
|
|
1610
2297
|
async function bundleCloudflareModule(options) {
|
|
1611
2298
|
const output = await bundleRouterModule({
|
|
2299
|
+
cacheDir: options.cacheDir,
|
|
1612
2300
|
code: options.entry,
|
|
1613
2301
|
filename: options.filename,
|
|
1614
2302
|
minify: true,
|
|
1615
|
-
platform: "
|
|
2303
|
+
platform: "node",
|
|
1616
2304
|
preserveExports: true,
|
|
1617
2305
|
plugins: options.plugins,
|
|
2306
|
+
root: options.root,
|
|
1618
2307
|
target: "es2022",
|
|
1619
2308
|
vitePlugins: options.vitePlugins,
|
|
1620
2309
|
});
|
|
@@ -1626,6 +2315,7 @@ async function bundleCloudflareModule(options) {
|
|
|
1626
2315
|
}
|
|
1627
2316
|
async function bundleCloudflareVirtualModule(options) {
|
|
1628
2317
|
return bundleCloudflareModule({
|
|
2318
|
+
cacheDir: options.cacheDir,
|
|
1629
2319
|
entry: options.entry,
|
|
1630
2320
|
filename: options.filename,
|
|
1631
2321
|
plugins: [
|
|
@@ -1652,6 +2342,7 @@ async function bundleCloudflareVirtualModule(options) {
|
|
|
1652
2342
|
...options.plugins,
|
|
1653
2343
|
],
|
|
1654
2344
|
resolveDir: options.resolveDir,
|
|
2345
|
+
root: options.root,
|
|
1655
2346
|
vitePlugins: options.vitePlugins,
|
|
1656
2347
|
});
|
|
1657
2348
|
}
|
|
@@ -1686,7 +2377,9 @@ function cloudflareServerSourceTransformPlugin(options) {
|
|
|
1686
2377
|
return undefined;
|
|
1687
2378
|
}
|
|
1688
2379
|
const source = await readFile(args.path, "utf8");
|
|
1689
|
-
const serverSource = isServerComponentFile(args.path)
|
|
2380
|
+
const serverSource = isServerComponentFile(args.path)
|
|
2381
|
+
? stripRouteBuildExports(source, args.path)
|
|
2382
|
+
: source;
|
|
1690
2383
|
const sourceHash = hashText(serverSource);
|
|
1691
2384
|
const routeFile = relative(options.projectRoot, args.path).replaceAll(sep, "/");
|
|
1692
2385
|
const artifact = options.serverModules[routeFile]?.[options.serverOutput];
|
|
@@ -1872,27 +2565,36 @@ function viteManifestFromClientRoutes(routes) {
|
|
|
1872
2565
|
return manifest;
|
|
1873
2566
|
}
|
|
1874
2567
|
async function writeClientRouteBundles(options) {
|
|
2568
|
+
const pageRoutes = options.routes.filter((route) => route.kind === "page");
|
|
2569
|
+
const routeCssAssets = await writeRouteCssAssetBatches({
|
|
2570
|
+
appDir: options.appDir,
|
|
2571
|
+
cacheDir: options.cacheDir,
|
|
2572
|
+
clientDir: options.clientDir,
|
|
2573
|
+
pageRoutes,
|
|
2574
|
+
projectRoot: options.projectRoot,
|
|
2575
|
+
sourceAnalysis: options.sourceAnalysis,
|
|
2576
|
+
vitePlugins: options.vitePlugins,
|
|
2577
|
+
});
|
|
1875
2578
|
const entries = await Promise.all(options.routes.map(async (route) => {
|
|
1876
2579
|
if (route.kind !== "page") {
|
|
1877
2580
|
return {
|
|
1878
2581
|
manifest: { path: route.path, kind: route.kind, client: false },
|
|
1879
2582
|
};
|
|
1880
2583
|
}
|
|
1881
|
-
const css =
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
});
|
|
1889
|
-
const source = await readFile(route.file, "utf8");
|
|
2584
|
+
const css = routeCssAssets.get(route.file) ?? [];
|
|
2585
|
+
const source = buildSourceAnalysisForFile(options.sourceAnalysis, options.projectRoot, route.file)?.source;
|
|
2586
|
+
if (source === undefined) {
|
|
2587
|
+
return {
|
|
2588
|
+
manifest: { path: route.path, kind: route.kind, client: false },
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
1890
2591
|
const clientSource = stripRouteClientSource({ code: source, filename: route.file });
|
|
1891
2592
|
const references = await collectClientRouteReferences({
|
|
1892
2593
|
appDir: options.appDir,
|
|
1893
2594
|
cache: options.clientRouteInferenceCache,
|
|
1894
2595
|
code: clientSource,
|
|
1895
2596
|
filename: route.file,
|
|
2597
|
+
routePath: route.path,
|
|
1896
2598
|
vitePlugins: options.vitePlugins,
|
|
1897
2599
|
});
|
|
1898
2600
|
const navigation = await resolveNavigationRuntime({
|
|
@@ -1900,6 +2602,7 @@ async function writeClientRouteBundles(options) {
|
|
|
1900
2602
|
code: source,
|
|
1901
2603
|
filename: route.file,
|
|
1902
2604
|
references,
|
|
2605
|
+
vitePlugins: options.vitePlugins,
|
|
1903
2606
|
});
|
|
1904
2607
|
for (const diagnostic of references.diagnostics) {
|
|
1905
2608
|
console.warn(formatClientRouteInferenceDiagnostic(diagnostic));
|
|
@@ -1925,6 +2628,7 @@ async function writeClientRouteBundles(options) {
|
|
|
1925
2628
|
clientReferenceImports: references.clientReferenceImports,
|
|
1926
2629
|
clientReferenceManifest: references.clientReferenceManifest,
|
|
1927
2630
|
clientNavigation: detectClientNavigationHint(source),
|
|
2631
|
+
cacheDir: options.cacheDir,
|
|
1928
2632
|
dropConsoleFunctions: options.clientConsolePureFunctions,
|
|
1929
2633
|
filename: route.file,
|
|
1930
2634
|
minify: true,
|
|
@@ -1942,6 +2646,7 @@ async function writeClientRouteBundles(options) {
|
|
|
1942
2646
|
try {
|
|
1943
2647
|
output = await buildClientRouteBatchOutput({
|
|
1944
2648
|
assetBaseUrl: options.assetBaseUrl,
|
|
2649
|
+
cacheDir: options.cacheDir,
|
|
1945
2650
|
dropConsoleFunctions: options.clientConsolePureFunctions,
|
|
1946
2651
|
minify: true,
|
|
1947
2652
|
projectRoot: options.projectRoot,
|
|
@@ -2012,12 +2717,51 @@ async function writeClientRouteBundles(options) {
|
|
|
2012
2717
|
};
|
|
2013
2718
|
});
|
|
2014
2719
|
}
|
|
2015
|
-
async function
|
|
2016
|
-
const
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2720
|
+
async function writeRouteCssAssetBatches(options) {
|
|
2721
|
+
const cssInputs = await mapWithBuildConcurrency(options.pageRoutes, async (route) => {
|
|
2722
|
+
const cssFiles = await collectRouteCssFilesFromSources({
|
|
2723
|
+
appDir: options.appDir,
|
|
2724
|
+
pageFile: route.file,
|
|
2725
|
+
projectRoot: options.projectRoot,
|
|
2726
|
+
readSource: (file) => buildSourceAnalysisForFile(options.sourceAnalysis, options.projectRoot, file)?.source,
|
|
2727
|
+
});
|
|
2728
|
+
return { cssFiles, route };
|
|
2020
2729
|
});
|
|
2730
|
+
const groups = new Map();
|
|
2731
|
+
for (const { cssFiles, route } of cssInputs) {
|
|
2732
|
+
if (cssFiles.length === 0) {
|
|
2733
|
+
continue;
|
|
2734
|
+
}
|
|
2735
|
+
const key = cssFiles.join("\0");
|
|
2736
|
+
const group = groups.get(key) ?? { cssFiles, routeFiles: [], routeIds: [] };
|
|
2737
|
+
group.routeFiles.push(route.file);
|
|
2738
|
+
group.routeIds.push(routeIdForPath(route.path));
|
|
2739
|
+
groups.set(key, group);
|
|
2740
|
+
}
|
|
2741
|
+
const writtenGroups = await mapWithBuildConcurrency([...groups.entries()], async ([key, group]) => [
|
|
2742
|
+
key,
|
|
2743
|
+
await writeRouteCssAssetsForFiles({
|
|
2744
|
+
cacheDir: options.cacheDir,
|
|
2745
|
+
clientDir: options.clientDir,
|
|
2746
|
+
cssFiles: group.cssFiles,
|
|
2747
|
+
pageFile: group.routeFiles[0] ?? options.appDir,
|
|
2748
|
+
projectRoot: options.projectRoot,
|
|
2749
|
+
routeIds: group.routeIds,
|
|
2750
|
+
vitePlugins: options.vitePlugins,
|
|
2751
|
+
}),
|
|
2752
|
+
]);
|
|
2753
|
+
const cssByGroup = new Map(writtenGroups);
|
|
2754
|
+
const cssByRoute = new Map();
|
|
2755
|
+
for (const [key, group] of groups) {
|
|
2756
|
+
const css = cssByGroup.get(key) ?? [];
|
|
2757
|
+
for (const routeFile of group.routeFiles) {
|
|
2758
|
+
cssByRoute.set(routeFile, css);
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
return cssByRoute;
|
|
2762
|
+
}
|
|
2763
|
+
async function writeRouteCssAssetsForFiles(options) {
|
|
2764
|
+
const cssFiles = [...options.cssFiles];
|
|
2021
2765
|
if (cssFiles.length === 0) {
|
|
2022
2766
|
return [];
|
|
2023
2767
|
}
|
|
@@ -2026,20 +2770,25 @@ async function writeRouteCssAssets(options) {
|
|
|
2026
2770
|
"export default undefined;",
|
|
2027
2771
|
].join("\n");
|
|
2028
2772
|
const output = await bundleRouterModule({
|
|
2773
|
+
cacheDir: options.cacheDir,
|
|
2029
2774
|
code,
|
|
2030
2775
|
filename: options.pageFile,
|
|
2031
2776
|
minify: true,
|
|
2032
2777
|
platform: "browser",
|
|
2778
|
+
root: options.projectRoot,
|
|
2033
2779
|
vitePlugins: options.vitePlugins,
|
|
2034
2780
|
});
|
|
2035
2781
|
const cssAssets = (output.assets ?? []).filter((asset) => asset.fileName.endsWith(".css"));
|
|
2036
2782
|
const written = [];
|
|
2037
|
-
|
|
2783
|
+
const routeStem = options.routeIds.length === 1
|
|
2784
|
+
? (options.routeIds[0] ?? "index")
|
|
2785
|
+
: `shared.${hashText(cssFiles.join("\0")).slice(0, 8)}`;
|
|
2786
|
+
for (const [index, asset] of cssAssets.entries()) {
|
|
2038
2787
|
const source = typeof asset.source === "string"
|
|
2039
2788
|
? asset.source
|
|
2040
2789
|
: Buffer.from(asset.source).toString("utf8");
|
|
2041
2790
|
const hash = createHash("sha256").update(source).digest("hex").slice(0, 8);
|
|
2042
|
-
const cssFile = `assets/routes/${
|
|
2791
|
+
const cssFile = `assets/routes/${routeStem}${cssAssets.length === 1 ? "" : `.${index}`}.${hash}.css`;
|
|
2043
2792
|
await mkdir(dirname(join(options.clientDir, cssFile)), { recursive: true });
|
|
2044
2793
|
await writeFile(join(options.clientDir, cssFile), source);
|
|
2045
2794
|
written.push(cssFile);
|
|
@@ -2110,7 +2859,16 @@ export async function packageAwsLambdaArtifact(options) {
|
|
|
2110
2859
|
fromDir: dirname(options.fromDir),
|
|
2111
2860
|
outDir: options.outDir,
|
|
2112
2861
|
});
|
|
2113
|
-
|
|
2862
|
+
if (options.handlerEntry === undefined) {
|
|
2863
|
+
await writeFile(join(options.outDir, "mreact-handler.mjs"), awsLambdaHandlerSource(".mreact"));
|
|
2864
|
+
}
|
|
2865
|
+
else {
|
|
2866
|
+
await writeAwsLambdaCustomHandlerArtifact({
|
|
2867
|
+
entry: options.handlerEntry,
|
|
2868
|
+
projectRoot: dirname(options.fromDir),
|
|
2869
|
+
outDir: options.outDir,
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2114
2872
|
if (options.skipRuntimeDependencyCheck !== true) {
|
|
2115
2873
|
await assertAwsLambdaRuntimeDependencies(options.outDir);
|
|
2116
2874
|
}
|
|
@@ -2125,6 +2883,32 @@ export async function packageAwsLambdaArtifact(options) {
|
|
|
2125
2883
|
await writeFile(join(options.outDir, "mreact-lambda-artifact.json"), JSON.stringify(manifest, null, 2));
|
|
2126
2884
|
return manifest;
|
|
2127
2885
|
}
|
|
2886
|
+
async function writeAwsLambdaCustomHandlerArtifact(options) {
|
|
2887
|
+
const entry = resolve(options.entry);
|
|
2888
|
+
const output = await bundleRouterModule({
|
|
2889
|
+
code: await readFile(entry, "utf8"),
|
|
2890
|
+
filename: entry,
|
|
2891
|
+
outfile: "mreact-handler.mjs",
|
|
2892
|
+
platform: "node",
|
|
2893
|
+
plugins: [externalizePackageImportsPlugin()],
|
|
2894
|
+
preserveExports: true,
|
|
2895
|
+
root: options.projectRoot,
|
|
2896
|
+
target: "node24",
|
|
2897
|
+
});
|
|
2898
|
+
await writeFile(join(options.outDir, "mreact-handler.mjs"), output.code);
|
|
2899
|
+
}
|
|
2900
|
+
function externalizePackageImportsPlugin() {
|
|
2901
|
+
return {
|
|
2902
|
+
name: "mreact-router-externalize-package-imports",
|
|
2903
|
+
setup(buildApi) {
|
|
2904
|
+
buildApi.onResolve({ filter: /^(?:node:|[A-Za-z@])/ }, (args) => isBarePackageImport(args.path) ? { external: true, path: args.path } : undefined);
|
|
2905
|
+
},
|
|
2906
|
+
};
|
|
2907
|
+
}
|
|
2908
|
+
function isBarePackageImport(specifier) {
|
|
2909
|
+
return (specifier.startsWith("node:") ||
|
|
2910
|
+
(!specifier.startsWith(".") && !specifier.startsWith("/") && !specifier.includes("\\")));
|
|
2911
|
+
}
|
|
2128
2912
|
async function assertAwsLambdaRuntimeDependencies(outDir) {
|
|
2129
2913
|
try {
|
|
2130
2914
|
const info = await stat(join(outDir, "node_modules", "@reckona", "mreact-router", "package.json"));
|
|
@@ -2266,22 +3050,18 @@ async function validateProductionRoutes(options) {
|
|
|
2266
3050
|
if (route.kind !== "page") {
|
|
2267
3051
|
continue;
|
|
2268
3052
|
}
|
|
2269
|
-
const source = await readFile(route.file, "utf8");
|
|
2270
3053
|
const filename = relative(options.projectRoot, route.file);
|
|
2271
|
-
const
|
|
2272
|
-
|
|
2273
|
-
|
|
3054
|
+
const analysis = options.sourceAnalysis.byRouteFile.get(filename.split(sep).join("/"));
|
|
3055
|
+
if (analysis === undefined) {
|
|
3056
|
+
continue;
|
|
3057
|
+
}
|
|
3058
|
+
const output = await transformServerRouteSource({
|
|
3059
|
+
cache: options.serverTransformCache,
|
|
3060
|
+
code: analysis.routeCode,
|
|
3061
|
+
clientBoundaryImports: analysis.clientBoundaryImports,
|
|
2274
3062
|
filename: route.file,
|
|
2275
|
-
|
|
2276
|
-
serverOutput:
|
|
2277
|
-
filename,
|
|
2278
|
-
files: options.files,
|
|
2279
|
-
projectRoot: options.projectRoot,
|
|
2280
|
-
source,
|
|
2281
|
-
})
|
|
2282
|
-
? "stream"
|
|
2283
|
-
: "string",
|
|
2284
|
-
target: "server",
|
|
3063
|
+
moduleContextCache: options.clientRouteInferenceCache,
|
|
3064
|
+
serverOutput: analysis.streamRoute ? "stream" : "string",
|
|
2285
3065
|
});
|
|
2286
3066
|
const fatalDiagnostics = output.diagnostics.filter((diagnostic) => diagnostic.code !== "MR_UNSUPPORTED_SERVER_EVENT_HANDLER");
|
|
2287
3067
|
if (fatalDiagnostics.length > 0) {
|
|
@@ -2292,15 +3072,22 @@ async function validateProductionRoutes(options) {
|
|
|
2292
3072
|
async function collectBuildFiles(projectRoot, allowedSourceDirs, appDir) {
|
|
2293
3073
|
const files = {};
|
|
2294
3074
|
for (const directory of allowedSourceDirs) {
|
|
2295
|
-
|
|
3075
|
+
const sourceFiles = (await collectFiles(directory)).flatMap((file) => {
|
|
2296
3076
|
if (isAppFileConventionAsset(file, appDir)) {
|
|
2297
|
-
|
|
3077
|
+
return [];
|
|
2298
3078
|
}
|
|
2299
3079
|
const relativeFile = relative(projectRoot, file);
|
|
2300
3080
|
if (relativeFile === "" || relativeFile.startsWith("..") || relativeFile.startsWith(sep)) {
|
|
2301
|
-
|
|
3081
|
+
return [];
|
|
2302
3082
|
}
|
|
2303
|
-
|
|
3083
|
+
return [{ file, relativeFile }];
|
|
3084
|
+
});
|
|
3085
|
+
const fileContents = await mapWithBuildConcurrency(sourceFiles, async ({ file, relativeFile }) => [
|
|
3086
|
+
relativeFile,
|
|
3087
|
+
await readFile(file, "utf8"),
|
|
3088
|
+
]);
|
|
3089
|
+
for (const [relativeFile, source] of fileContents) {
|
|
3090
|
+
files[relativeFile] = source;
|
|
2304
3091
|
}
|
|
2305
3092
|
}
|
|
2306
3093
|
return files;
|
|
@@ -2314,18 +3101,17 @@ function isAppFileConventionAsset(file, appDir) {
|
|
|
2314
3101
|
}
|
|
2315
3102
|
async function collectFiles(directory) {
|
|
2316
3103
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
2317
|
-
const
|
|
2318
|
-
for (const entry of entries) {
|
|
3104
|
+
const nestedFiles = await mapWithBuildConcurrency(entries, async (entry) => {
|
|
2319
3105
|
const path = join(directory, entry.name);
|
|
2320
3106
|
if (entry.isDirectory()) {
|
|
2321
|
-
|
|
2322
|
-
continue;
|
|
3107
|
+
return await collectFiles(path);
|
|
2323
3108
|
}
|
|
2324
3109
|
if (entry.isFile()) {
|
|
2325
|
-
|
|
3110
|
+
return [path];
|
|
2326
3111
|
}
|
|
2327
|
-
|
|
2328
|
-
|
|
3112
|
+
return [];
|
|
3113
|
+
});
|
|
3114
|
+
return nestedFiles.flat();
|
|
2329
3115
|
}
|
|
2330
3116
|
function hashText(text) {
|
|
2331
3117
|
return createHash("sha256").update(text).digest("hex").slice(0, 16);
|