@tyndall/build 0.0.1 → 0.0.2
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 +3 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +968 -29
- package/dist/manifest.d.ts +4 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +14 -0
- package/dist/renderer.d.ts +3 -0
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +5 -1
- package/dist/ssg-cache.d.ts +3 -0
- package/dist/ssg-cache.d.ts.map +1 -1
- package/dist/ssg-cache.js +8 -1
- package/package.json +3 -3
package/dist/build.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { copyFile, mkdir, rm, stat, writeFile } from "node:fs/promises";
|
|
2
|
-
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
2
|
+
import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
-
import { createRouteGraph, loadConfig, renderClientRouterBootstrap, resolvePageModule, resolveUIAdapter, serializeProps, } from "@tyndall/core";
|
|
5
|
-
import { buildModuleGraphSnapshot, computeGraphKey, computeCacheRootKey, getBunVersion, getOsArch, hash, normalizePath, readFileSafe, readModuleGraphCache, resolveCacheRoot, stableStringify, writeModuleGraphCache, } from "@tyndall/shared";
|
|
4
|
+
import { createRouteGraph, loadConfig, resolveLayoutRouteId, routeDataKeyForLayout, routeDataKeyForPage, ROUTE_DATA_ERROR_KEY, ROUTE_DATA_INIT_KEY, SPECIAL_ROUTE_IDS, collectRouteLayouts, renderClientRouterBootstrap, resolvePageModule, resolveUIAdapter, runBeforeBundleHooks, runGetRouteData, runInitServer, serializeProps, } from "@tyndall/core";
|
|
5
|
+
import { buildModuleGraphSnapshot, computeGraphKey, computeCacheRootKey, DEFAULT_IMPORT_EXTENSIONS, getBunVersion, getOsArch, hash, normalizePath, readFileSafe, readModuleGraphCache, resolveCacheRoot, stableStringify, writeModuleGraphCache, } from "@tyndall/shared";
|
|
6
6
|
import { bundleClient, bundleServer, } from "./bundler.js";
|
|
7
7
|
import { build as esbuild } from "esbuild";
|
|
8
8
|
import { transform } from "@swc/core";
|
|
@@ -61,6 +61,16 @@ const resolveHyperVersion = async (rootDir) => {
|
|
|
61
61
|
return "unknown";
|
|
62
62
|
}
|
|
63
63
|
};
|
|
64
|
+
const STYLE_MODULE_EXTENSIONS = new Set([".css", ".scss", ".sass"]);
|
|
65
|
+
const STYLE_MODULE_SUFFIXES = [".css.ts", ".css.tsx"];
|
|
66
|
+
const isStyleModule = (filePath) => {
|
|
67
|
+
const lower = filePath.toLowerCase();
|
|
68
|
+
if (STYLE_MODULE_SUFFIXES.some((suffix) => lower.endsWith(suffix))) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return STYLE_MODULE_EXTENSIONS.has(extname(lower));
|
|
72
|
+
};
|
|
73
|
+
const MODULE_GRAPH_EXTENSIONS = [...DEFAULT_IMPORT_EXTENSIONS, ".css", ".scss", ".sass"];
|
|
64
74
|
const isStaticRoute = (route) => route.segments.every((segment) => segment.type === "static");
|
|
65
75
|
const isDynamicRoute = (route) => route.segments.some((segment) => segment.type === "dynamic" || segment.type === "catchAll");
|
|
66
76
|
const routeIdToPath = (routeId, params) => {
|
|
@@ -87,6 +97,183 @@ const writeFileSafe = async (filePath, contents) => {
|
|
|
87
97
|
await mkdir(dirname(filePath), { recursive: true });
|
|
88
98
|
await writeFile(filePath, contents, "utf-8");
|
|
89
99
|
};
|
|
100
|
+
const resolveSpecialRouteRecords = (routeGraph, routesDir) => {
|
|
101
|
+
const specialFiles = routeGraph.specialFiles;
|
|
102
|
+
const records = {
|
|
103
|
+
notFound: null,
|
|
104
|
+
error: null,
|
|
105
|
+
};
|
|
106
|
+
if (specialFiles?.notFound) {
|
|
107
|
+
const resolution = collectRouteLayouts(specialFiles.notFound, routesDir);
|
|
108
|
+
records.notFound = {
|
|
109
|
+
id: SPECIAL_ROUTE_IDS.notFound,
|
|
110
|
+
filePath: specialFiles.notFound,
|
|
111
|
+
layoutFiles: resolution.layoutFiles,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (specialFiles?.error) {
|
|
115
|
+
const resolution = collectRouteLayouts(specialFiles.error, routesDir);
|
|
116
|
+
records.error = {
|
|
117
|
+
id: SPECIAL_ROUTE_IDS.error,
|
|
118
|
+
filePath: specialFiles.error,
|
|
119
|
+
layoutFiles: resolution.layoutFiles,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return records;
|
|
123
|
+
};
|
|
124
|
+
const resolveInitServerHook = (module) => {
|
|
125
|
+
if (module && typeof module === "object") {
|
|
126
|
+
const record = module;
|
|
127
|
+
if (typeof record.initServer === "function") {
|
|
128
|
+
return record.initServer;
|
|
129
|
+
}
|
|
130
|
+
if (typeof record.default === "function") {
|
|
131
|
+
return record.default;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
};
|
|
136
|
+
const resolveRouteDataHook = (module) => {
|
|
137
|
+
if (module && typeof module === "object" && typeof module.getRouteData === "function") {
|
|
138
|
+
return module.getRouteData;
|
|
139
|
+
}
|
|
140
|
+
return undefined;
|
|
141
|
+
};
|
|
142
|
+
const normalizeRedirect = (redirect) => {
|
|
143
|
+
if (!redirect) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
if (typeof redirect === "string") {
|
|
147
|
+
return { destination: redirect };
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
destination: redirect.destination,
|
|
151
|
+
status: redirect.status,
|
|
152
|
+
replace: redirect.replace,
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
const normalizeError = (error) => {
|
|
156
|
+
if (!error) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
if (typeof error === "string") {
|
|
160
|
+
return { message: error };
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
status: error.status,
|
|
164
|
+
message: error.message,
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
const collectRouteDataForRoute = async (input) => {
|
|
168
|
+
const dataMap = {};
|
|
169
|
+
const headers = {};
|
|
170
|
+
let status;
|
|
171
|
+
let redirect;
|
|
172
|
+
let error;
|
|
173
|
+
let initData;
|
|
174
|
+
let parentData;
|
|
175
|
+
if (input.initServerHook) {
|
|
176
|
+
const initResult = await runInitServer(input.initServerHook, {
|
|
177
|
+
routeId: input.routeId,
|
|
178
|
+
params: input.params,
|
|
179
|
+
request: input.request,
|
|
180
|
+
response: input.response,
|
|
181
|
+
});
|
|
182
|
+
if (initResult.data !== undefined) {
|
|
183
|
+
initData = initResult.data;
|
|
184
|
+
dataMap[ROUTE_DATA_INIT_KEY] = initResult.data;
|
|
185
|
+
}
|
|
186
|
+
if (initResult.headers) {
|
|
187
|
+
Object.assign(headers, initResult.headers);
|
|
188
|
+
}
|
|
189
|
+
if (initResult.status !== undefined) {
|
|
190
|
+
status = initResult.status;
|
|
191
|
+
}
|
|
192
|
+
if (initResult.redirect) {
|
|
193
|
+
redirect = initResult.redirect;
|
|
194
|
+
return { data: dataMap, initData, headers, status, redirect, error };
|
|
195
|
+
}
|
|
196
|
+
if (initResult.error) {
|
|
197
|
+
error = initResult.error;
|
|
198
|
+
const normalizedError = normalizeError(error);
|
|
199
|
+
if (normalizedError) {
|
|
200
|
+
dataMap[ROUTE_DATA_ERROR_KEY] = normalizedError;
|
|
201
|
+
}
|
|
202
|
+
return { data: dataMap, initData, headers, status, redirect, error };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const layoutFiles = input.layoutFiles ?? [];
|
|
206
|
+
for (const layoutFile of layoutFiles) {
|
|
207
|
+
const layoutModule = await input.loadLayoutModule(layoutFile);
|
|
208
|
+
const hook = resolveRouteDataHook(layoutModule);
|
|
209
|
+
if (!hook) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const layoutId = resolveLayoutRouteId(layoutFile, input.routesDir);
|
|
213
|
+
const result = await runGetRouteData(hook, {
|
|
214
|
+
routeId: layoutId,
|
|
215
|
+
params: input.params,
|
|
216
|
+
request: input.request,
|
|
217
|
+
response: input.response,
|
|
218
|
+
init: initData,
|
|
219
|
+
routeData: dataMap,
|
|
220
|
+
parentData,
|
|
221
|
+
});
|
|
222
|
+
if (result.data !== undefined) {
|
|
223
|
+
dataMap[routeDataKeyForLayout(layoutId)] = result.data;
|
|
224
|
+
parentData = result.data;
|
|
225
|
+
}
|
|
226
|
+
if (result.headers) {
|
|
227
|
+
Object.assign(headers, result.headers);
|
|
228
|
+
}
|
|
229
|
+
if (result.status !== undefined) {
|
|
230
|
+
status = result.status;
|
|
231
|
+
}
|
|
232
|
+
if (result.redirect) {
|
|
233
|
+
redirect = result.redirect;
|
|
234
|
+
return { data: dataMap, initData, headers, status, redirect, error };
|
|
235
|
+
}
|
|
236
|
+
if (result.error) {
|
|
237
|
+
error = result.error;
|
|
238
|
+
const normalizedError = normalizeError(error);
|
|
239
|
+
if (normalizedError) {
|
|
240
|
+
dataMap[ROUTE_DATA_ERROR_KEY] = normalizedError;
|
|
241
|
+
}
|
|
242
|
+
return { data: dataMap, initData, headers, status, redirect, error };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (input.pageModule?.getRouteData) {
|
|
246
|
+
const result = await runGetRouteData(input.pageModule.getRouteData, {
|
|
247
|
+
routeId: input.routeId,
|
|
248
|
+
params: input.params,
|
|
249
|
+
request: input.request,
|
|
250
|
+
response: input.response,
|
|
251
|
+
init: initData,
|
|
252
|
+
routeData: dataMap,
|
|
253
|
+
parentData,
|
|
254
|
+
});
|
|
255
|
+
if (result.data !== undefined) {
|
|
256
|
+
dataMap[routeDataKeyForPage(input.routeId)] = result.data;
|
|
257
|
+
}
|
|
258
|
+
if (result.headers) {
|
|
259
|
+
Object.assign(headers, result.headers);
|
|
260
|
+
}
|
|
261
|
+
if (result.status !== undefined) {
|
|
262
|
+
status = result.status;
|
|
263
|
+
}
|
|
264
|
+
if (result.redirect) {
|
|
265
|
+
redirect = result.redirect;
|
|
266
|
+
}
|
|
267
|
+
if (result.error) {
|
|
268
|
+
error = result.error;
|
|
269
|
+
const normalizedError = normalizeError(error);
|
|
270
|
+
if (normalizedError) {
|
|
271
|
+
dataMap[ROUTE_DATA_ERROR_KEY] = normalizedError;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return { data: dataMap, initData, headers, status, redirect, error };
|
|
276
|
+
};
|
|
90
277
|
const toPosixPath = (value) => value.split(sep).join("/");
|
|
91
278
|
const toImportSpecifier = (fromDir, targetPath) => {
|
|
92
279
|
const relativePath = toPosixPath(relative(fromDir, targetPath));
|
|
@@ -114,6 +301,40 @@ const appendBuildVersion = (value, buildVersion) => {
|
|
|
114
301
|
const separator = value.includes("?") ? "&" : "?";
|
|
115
302
|
return `${value}${separator}v=${encodeURIComponent(buildVersion)}`;
|
|
116
303
|
};
|
|
304
|
+
const escapeHtml = (value) => value
|
|
305
|
+
.replace(/&/g, "&")
|
|
306
|
+
.replace(/</g, "<")
|
|
307
|
+
.replace(/>/g, ">")
|
|
308
|
+
.replace(/\"/g, """);
|
|
309
|
+
const renderStaticRedirectDocument = (destination) => {
|
|
310
|
+
const safe = escapeHtml(destination);
|
|
311
|
+
return [
|
|
312
|
+
"<!doctype html>",
|
|
313
|
+
"<html>",
|
|
314
|
+
"<head>",
|
|
315
|
+
`<meta http-equiv=\"refresh\" content=\"0;url=${safe}\">`,
|
|
316
|
+
`<script>window.location.replace(${JSON.stringify(destination)});</script>`,
|
|
317
|
+
"</head>",
|
|
318
|
+
"<body>",
|
|
319
|
+
`<a href=\"${safe}\">Redirecting...</a>`,
|
|
320
|
+
"</body>",
|
|
321
|
+
"</html>",
|
|
322
|
+
].join("");
|
|
323
|
+
};
|
|
324
|
+
const renderStaticErrorDocument = (message) => {
|
|
325
|
+
const safe = escapeHtml(message);
|
|
326
|
+
return [
|
|
327
|
+
"<!doctype html>",
|
|
328
|
+
"<html>",
|
|
329
|
+
"<head>",
|
|
330
|
+
"<title>Route data error</title>",
|
|
331
|
+
"</head>",
|
|
332
|
+
"<body>",
|
|
333
|
+
`<main>${safe}</main>`,
|
|
334
|
+
"</body>",
|
|
335
|
+
"</html>",
|
|
336
|
+
].join("");
|
|
337
|
+
};
|
|
117
338
|
const resolveBundleScripts = (bundle, routeId, basePath, assetsDir, buildVersion) => {
|
|
118
339
|
const entryKey = bundle.entryChunks[routeId];
|
|
119
340
|
if (!entryKey) {
|
|
@@ -138,6 +359,20 @@ const resolveBundleScripts = (bundle, routeId, basePath, assetsDir, buildVersion
|
|
|
138
359
|
walk(entryKey);
|
|
139
360
|
return scripts;
|
|
140
361
|
};
|
|
362
|
+
const resolveBundleStyleFile = (bundle, routeId) => {
|
|
363
|
+
const entryKey = bundle.entryChunks[routeId];
|
|
364
|
+
if (!entryKey) {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
const chunk = bundle.chunks[entryKey];
|
|
368
|
+
if (!chunk) {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
if (chunk.file.endsWith(".js")) {
|
|
372
|
+
return chunk.file.replace(/\.js$/, ".css");
|
|
373
|
+
}
|
|
374
|
+
return `${chunk.file}.css`;
|
|
375
|
+
};
|
|
141
376
|
const LEGACY_AUTO_POLYFILLS_FILE = "hyper-legacy-polyfills.js";
|
|
142
377
|
const renderLegacyClientBootstrap = () => [
|
|
143
378
|
"(function () {",
|
|
@@ -268,12 +503,20 @@ const resolveHyperCoreBrowserShimSource = async () => {
|
|
|
268
503
|
const propsPath = await resolveCoreShimModulePath(entryDir, "props");
|
|
269
504
|
const renderPolicyPath = await resolveCoreShimModulePath(entryDir, "render-policy");
|
|
270
505
|
const resolverFallbackPath = await resolveCoreShimModulePath(entryDir, "resolver-fallback");
|
|
506
|
+
const routeDataPath = await resolveCoreShimModulePath(entryDir, "route-data");
|
|
271
507
|
hyperCoreBrowserShimSource = {
|
|
272
508
|
source: [
|
|
273
509
|
`export { mergeHeadDescriptors } from ${JSON.stringify(headPath)};`,
|
|
274
510
|
`export { serializeProps } from ${JSON.stringify(propsPath)};`,
|
|
275
511
|
`export { evaluateRenderPolicy } from ${JSON.stringify(renderPolicyPath)};`,
|
|
276
512
|
`export { shouldForceDynamicFallback } from ${JSON.stringify(resolverFallbackPath)};`,
|
|
513
|
+
`export {`,
|
|
514
|
+
` ROUTE_DATA_SCRIPT_ID,`,
|
|
515
|
+
` ROUTE_DATA_INIT_KEY,`,
|
|
516
|
+
` ROUTE_DATA_ERROR_KEY,`,
|
|
517
|
+
` routeDataKeyForPage,`,
|
|
518
|
+
` routeDataKeyForLayout,`,
|
|
519
|
+
`} from ${JSON.stringify(routeDataPath)};`,
|
|
277
520
|
].join("\n"),
|
|
278
521
|
// Required so esbuild resolves filesystem imports from a virtual namespace.
|
|
279
522
|
resolveDir: entryDir,
|
|
@@ -287,6 +530,8 @@ const emitBundleAssets = async (bundle, outDir, assetsDir, chunkSource) => {
|
|
|
287
530
|
}
|
|
288
531
|
};
|
|
289
532
|
const buildChunkFile = async (entryPath, outputPath, options) => {
|
|
533
|
+
const bundlerPlugins = options.plugins ?? [];
|
|
534
|
+
const shouldEmitCss = Boolean(options.emitCss && options.cssOutputPath);
|
|
290
535
|
const shimSource = options.shimHyperCore ? await resolveHyperCoreBrowserShimSource() : null;
|
|
291
536
|
const shimPlugin = shimSource
|
|
292
537
|
? {
|
|
@@ -309,6 +554,7 @@ const buildChunkFile = async (entryPath, outputPath, options) => {
|
|
|
309
554
|
: null;
|
|
310
555
|
if (options.syntaxTarget === "es5") {
|
|
311
556
|
try {
|
|
557
|
+
const plugins = shimPlugin ? [shimPlugin, ...bundlerPlugins] : [...bundlerPlugins];
|
|
312
558
|
const bundled = await esbuild({
|
|
313
559
|
entryPoints: [entryPath],
|
|
314
560
|
bundle: true,
|
|
@@ -320,10 +566,13 @@ const buildChunkFile = async (entryPath, outputPath, options) => {
|
|
|
320
566
|
minify: false,
|
|
321
567
|
logLevel: "silent",
|
|
322
568
|
jsx: "automatic",
|
|
323
|
-
plugins:
|
|
569
|
+
plugins: plugins.length > 0 ? plugins : undefined,
|
|
324
570
|
write: false,
|
|
325
571
|
});
|
|
326
|
-
const
|
|
572
|
+
const outputFiles = bundled.outputFiles ?? [];
|
|
573
|
+
const jsOutput = outputFiles.find((file) => file.path.endsWith(".js") || file.path.endsWith(".mjs")) ??
|
|
574
|
+
outputFiles[0];
|
|
575
|
+
const outputText = jsOutput?.text;
|
|
327
576
|
if (!outputText) {
|
|
328
577
|
throw new Error("ES5 bundling produced no output.");
|
|
329
578
|
}
|
|
@@ -338,6 +587,15 @@ const buildChunkFile = async (entryPath, outputPath, options) => {
|
|
|
338
587
|
});
|
|
339
588
|
await mkdir(dirname(outputPath), { recursive: true });
|
|
340
589
|
await writeFile(outputPath, transformed.code, "utf-8");
|
|
590
|
+
if (shouldEmitCss && options.cssOutputPath) {
|
|
591
|
+
const cssChunks = outputFiles
|
|
592
|
+
.filter((file) => file.path.endsWith(".css"))
|
|
593
|
+
.map((file) => file.text)
|
|
594
|
+
.filter((text) => typeof text === "string" && text.length > 0);
|
|
595
|
+
if (cssChunks.length > 0) {
|
|
596
|
+
await writeFileSafe(options.cssOutputPath, cssChunks.join("\n"));
|
|
597
|
+
}
|
|
598
|
+
}
|
|
341
599
|
}
|
|
342
600
|
catch (error) {
|
|
343
601
|
const messages = typeof error === "object" && error !== null && "errors" in error
|
|
@@ -355,6 +613,7 @@ const buildChunkFile = async (entryPath, outputPath, options) => {
|
|
|
355
613
|
}
|
|
356
614
|
const tempOutDir = join(dirname(outputPath), `.@tyndall/build-tmp-${hash(`${outputPath}-${Date.now()}`)}`);
|
|
357
615
|
await mkdir(tempOutDir, { recursive: true });
|
|
616
|
+
const plugins = shimPlugin ? [shimPlugin, ...bundlerPlugins] : [...bundlerPlugins];
|
|
358
617
|
const result = await bun.build({
|
|
359
618
|
entrypoints: [entryPath],
|
|
360
619
|
outdir: tempOutDir,
|
|
@@ -364,7 +623,7 @@ const buildChunkFile = async (entryPath, outputPath, options) => {
|
|
|
364
623
|
splitting: false,
|
|
365
624
|
sourcemap: "none",
|
|
366
625
|
minify: false,
|
|
367
|
-
plugins:
|
|
626
|
+
plugins: plugins.length > 0 ? plugins : undefined,
|
|
368
627
|
});
|
|
369
628
|
if (!result.success) {
|
|
370
629
|
const errors = (result.logs ?? [])
|
|
@@ -380,10 +639,59 @@ const buildChunkFile = async (entryPath, outputPath, options) => {
|
|
|
380
639
|
await rm(tempOutDir, { recursive: true, force: true }).catch(() => { });
|
|
381
640
|
throw new Error(`Build chunk output is missing (${entryPath}).`);
|
|
382
641
|
}
|
|
642
|
+
if (shouldEmitCss && options.cssOutputPath) {
|
|
643
|
+
const cssOutputs = (result.outputs ?? [])
|
|
644
|
+
.map((output) => output.path)
|
|
645
|
+
.filter((path) => typeof path === "string" && path.endsWith(".css"));
|
|
646
|
+
if (cssOutputs.length > 0) {
|
|
647
|
+
const cssChunks = [];
|
|
648
|
+
const ordered = [...cssOutputs].sort((left, right) => left.localeCompare(right));
|
|
649
|
+
for (const cssPath of ordered) {
|
|
650
|
+
const content = await readFileSafe(cssPath, "utf-8");
|
|
651
|
+
if (typeof content === "string" && content.length > 0) {
|
|
652
|
+
cssChunks.push(content);
|
|
653
|
+
}
|
|
654
|
+
else if (content instanceof Uint8Array && content.length > 0) {
|
|
655
|
+
cssChunks.push(Buffer.from(content).toString("utf-8"));
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
if (cssChunks.length > 0) {
|
|
659
|
+
await writeFileSafe(options.cssOutputPath, cssChunks.join("\n"));
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
383
663
|
await mkdir(dirname(outputPath), { recursive: true });
|
|
384
664
|
await copyFile(emittedPath, outputPath);
|
|
385
665
|
await rm(tempOutDir, { recursive: true, force: true }).catch(() => { });
|
|
386
666
|
};
|
|
667
|
+
const SERVER_ONLY_MODULE_PATTERN = /\.server\.[cm]?[jt]sx?$/i;
|
|
668
|
+
const collectServerOnlyModules = (modules) => modules.filter((modulePath) => SERVER_ONLY_MODULE_PATTERN.test(modulePath));
|
|
669
|
+
const assertNoServerOnlyClientImports = async (snapshot, appModule) => {
|
|
670
|
+
const offenders = [];
|
|
671
|
+
const routeIds = Object.keys(snapshot.routeToModules).sort((a, b) => a.localeCompare(b));
|
|
672
|
+
for (const routeId of routeIds) {
|
|
673
|
+
const modules = snapshot.routeToModules[routeId] ?? [];
|
|
674
|
+
const hits = collectServerOnlyModules(modules);
|
|
675
|
+
if (hits.length > 0) {
|
|
676
|
+
offenders.push({ scope: `route "${routeId}"`, modules: hits });
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (appModule) {
|
|
680
|
+
const appSnapshot = buildModuleGraphSnapshot([{ id: "__hyper_app__", filePath: appModule }]);
|
|
681
|
+
const appModules = appSnapshot.routeToModules["__hyper_app__"] ?? [];
|
|
682
|
+
const hits = collectServerOnlyModules(appModules);
|
|
683
|
+
if (hits.length > 0) {
|
|
684
|
+
offenders.push({ scope: "app", modules: hits });
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (offenders.length === 0) {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
const details = offenders
|
|
691
|
+
.map((entry) => `${entry.scope}: ${entry.modules.join(", ")}`)
|
|
692
|
+
.join(" | ");
|
|
693
|
+
throw new Error(`Server-only modules (.server.*) cannot be imported into client bundles. Found ${details}.`);
|
|
694
|
+
};
|
|
387
695
|
const dynamicImportPattern = /import\s*\(/;
|
|
388
696
|
const findDynamicImportModules = async (modulePaths) => {
|
|
389
697
|
const offenders = [];
|
|
@@ -406,6 +714,7 @@ export const build = async (options) => {
|
|
|
406
714
|
const outDir = resolvePath(rootDir, options.outDir ?? "dist");
|
|
407
715
|
const mode = options.mode ?? "ssg";
|
|
408
716
|
const config = await loadConfig(rootDir);
|
|
717
|
+
const hyperPlugins = config.plugins;
|
|
409
718
|
const buildVersion = config.build.version.trim();
|
|
410
719
|
const adapterRegistry = options.adapterRegistry ?? {};
|
|
411
720
|
const ssrClientRoutingEnabled = mode === "ssr" && config.routing.ssrClientRouting;
|
|
@@ -414,6 +723,7 @@ export const build = async (options) => {
|
|
|
414
723
|
navigationMode: resolvedNavigationMode,
|
|
415
724
|
clientRenderMode: config.routing.clientRender,
|
|
416
725
|
linkInterceptionMode: ssrClientRoutingEnabled ? "all" : "marked",
|
|
726
|
+
scrollRestoration: config.routing.scrollRestoration,
|
|
417
727
|
});
|
|
418
728
|
const legacyBootstrapChunk = renderLegacyClientBootstrap();
|
|
419
729
|
const legacyEnabled = config.legacy.enabled;
|
|
@@ -445,19 +755,36 @@ export const build = async (options) => {
|
|
|
445
755
|
? await resolveGraphKey(rootDir, {
|
|
446
756
|
client: { format: "esm", target: modernTarget },
|
|
447
757
|
server: mode === "ssr" ? { format: "cjs", target: serverTarget } : null,
|
|
758
|
+
moduleGraphExtensions: MODULE_GRAPH_EXTENSIONS,
|
|
448
759
|
})
|
|
449
760
|
: null;
|
|
450
761
|
const envHash = cache.enabled ? hash(stableStringify(process.env)) : "disabled";
|
|
451
762
|
let depsHashByEntry = null;
|
|
452
763
|
let legacyBundle = null;
|
|
764
|
+
let routeHasStyles = {};
|
|
453
765
|
const routeModules = new Map();
|
|
454
766
|
const routeById = new Map();
|
|
455
767
|
const clientRuntimeEntries = new Map();
|
|
456
768
|
const serverRuntimeEntries = new Map();
|
|
769
|
+
const layoutModuleCache = new Map();
|
|
770
|
+
let specialRouteFiles = null;
|
|
771
|
+
let specialRouteRecords = {
|
|
772
|
+
notFound: null,
|
|
773
|
+
error: null,
|
|
774
|
+
};
|
|
775
|
+
let initServerHook;
|
|
776
|
+
let documentRenderers = null;
|
|
457
777
|
const entryWorkspaceRoot = join(cacheRoot, "build-runtime-entries", hash(`${Date.now()}-${Math.random()}`).slice(0, 12));
|
|
458
778
|
// Important: clear output to avoid stale HTML/chunks when route render policy changes.
|
|
459
779
|
await rm(outDir, { recursive: true, force: true }).catch(() => { });
|
|
460
780
|
await mkdir(outDir, { recursive: true });
|
|
781
|
+
const computeRouteStyleMap = (snapshot) => {
|
|
782
|
+
const map = {};
|
|
783
|
+
for (const [routeId, modules] of Object.entries(snapshot.routeToModules)) {
|
|
784
|
+
map[routeId] = modules.some((modulePath) => isStyleModule(modulePath));
|
|
785
|
+
}
|
|
786
|
+
routeHasStyles = map;
|
|
787
|
+
};
|
|
461
788
|
const assertLegacyChunkingSafe = async (snapshot) => {
|
|
462
789
|
if (!legacyEnabled || config.legacy.chunking !== "safe") {
|
|
463
790
|
return;
|
|
@@ -489,6 +816,48 @@ export const build = async (options) => {
|
|
|
489
816
|
}
|
|
490
817
|
return result;
|
|
491
818
|
};
|
|
819
|
+
const loadLayoutModule = async (filePath) => {
|
|
820
|
+
const cached = layoutModuleCache.get(filePath);
|
|
821
|
+
if (cached) {
|
|
822
|
+
return cached;
|
|
823
|
+
}
|
|
824
|
+
const loaded = await import(`${pathToFileURL(filePath).href}?build=${Date.now()}-${hash(filePath).slice(0, 6)}`);
|
|
825
|
+
layoutModuleCache.set(filePath, loaded);
|
|
826
|
+
return loaded;
|
|
827
|
+
};
|
|
828
|
+
const resolveInitServer = async () => {
|
|
829
|
+
if (initServerHook !== undefined) {
|
|
830
|
+
return initServerHook;
|
|
831
|
+
}
|
|
832
|
+
if (!specialRouteFiles?.initServer) {
|
|
833
|
+
initServerHook = undefined;
|
|
834
|
+
return undefined;
|
|
835
|
+
}
|
|
836
|
+
const loaded = await import(`${pathToFileURL(specialRouteFiles.initServer).href}?build=${Date.now()}-${hash("init").slice(0, 6)}`);
|
|
837
|
+
initServerHook = resolveInitServerHook(loaded);
|
|
838
|
+
return initServerHook;
|
|
839
|
+
};
|
|
840
|
+
const resolveDocumentRenderers = async () => {
|
|
841
|
+
if (documentRenderers) {
|
|
842
|
+
return documentRenderers;
|
|
843
|
+
}
|
|
844
|
+
if (!specialRouteFiles?.document) {
|
|
845
|
+
documentRenderers = { renderDocument: undefined, renderDocumentFragments: undefined };
|
|
846
|
+
return documentRenderers;
|
|
847
|
+
}
|
|
848
|
+
const loaded = await import(`${pathToFileURL(specialRouteFiles.document).href}?build=${Date.now()}-${hash("document").slice(0, 6)}`);
|
|
849
|
+
const record = loaded;
|
|
850
|
+
const renderDocument = typeof record.renderDocument === "function"
|
|
851
|
+
? record.renderDocument
|
|
852
|
+
: typeof record.default === "function"
|
|
853
|
+
? record.default
|
|
854
|
+
: undefined;
|
|
855
|
+
const renderDocumentFragments = typeof record.renderDocumentFragments === "function"
|
|
856
|
+
? record.renderDocumentFragments
|
|
857
|
+
: undefined;
|
|
858
|
+
documentRenderers = { renderDocument, renderDocumentFragments };
|
|
859
|
+
return documentRenderers;
|
|
860
|
+
};
|
|
492
861
|
try {
|
|
493
862
|
const result = await runBuildPipeline({ rootDir, routesDir, outDir, publicDir, cache }, {
|
|
494
863
|
scanRoutes: () => createRouteGraph({
|
|
@@ -498,14 +867,33 @@ export const build = async (options) => {
|
|
|
498
867
|
}),
|
|
499
868
|
analyzeGraph: async (_ctx, state) => {
|
|
500
869
|
const routes = state.routeGraph?.routes ?? [];
|
|
870
|
+
specialRouteFiles = state.routeGraph?.specialFiles ?? null;
|
|
871
|
+
specialRouteRecords = state.routeGraph
|
|
872
|
+
? resolveSpecialRouteRecords(state.routeGraph, routesDir)
|
|
873
|
+
: { notFound: null, error: null };
|
|
501
874
|
routeById.clear();
|
|
502
875
|
for (const route of routes) {
|
|
503
876
|
routeById.set(route.id, route);
|
|
504
877
|
}
|
|
505
878
|
const entries = routes.map((route) => ({ id: route.id, filePath: route.filePath }));
|
|
879
|
+
if (specialRouteRecords.notFound) {
|
|
880
|
+
entries.push({
|
|
881
|
+
id: SPECIAL_ROUTE_IDS.notFound,
|
|
882
|
+
filePath: specialRouteRecords.notFound.filePath,
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
if (specialRouteRecords.error) {
|
|
886
|
+
entries.push({
|
|
887
|
+
id: SPECIAL_ROUTE_IDS.error,
|
|
888
|
+
filePath: specialRouteRecords.error.filePath,
|
|
889
|
+
});
|
|
890
|
+
}
|
|
506
891
|
if (!cache.enabled || !graphKey) {
|
|
507
|
-
const snapshot = buildModuleGraphSnapshot(entries);
|
|
892
|
+
const snapshot = buildModuleGraphSnapshot(entries, { extensions: MODULE_GRAPH_EXTENSIONS });
|
|
893
|
+
// Guard: client bundles must never include server-only modules.
|
|
894
|
+
await assertNoServerOnlyClientImports(snapshot, specialRouteFiles?.app);
|
|
508
895
|
await assertLegacyChunkingSafe(snapshot);
|
|
896
|
+
computeRouteStyleMap(snapshot);
|
|
509
897
|
depsHashByEntry = cache.enabled ? await computeDepsHashByEntry(snapshot) : null;
|
|
510
898
|
return snapshot;
|
|
511
899
|
}
|
|
@@ -514,12 +902,16 @@ export const build = async (options) => {
|
|
|
514
902
|
// Important: cached graph must cover all current routes to be valid.
|
|
515
903
|
const hasAllRoutes = entries.every((route) => cached.routeToModules[route.id]);
|
|
516
904
|
if (hasAllRoutes) {
|
|
905
|
+
await assertNoServerOnlyClientImports(cached, specialRouteFiles?.app);
|
|
517
906
|
await assertLegacyChunkingSafe(cached);
|
|
907
|
+
computeRouteStyleMap(cached);
|
|
518
908
|
depsHashByEntry = await computeDepsHashByEntry(cached);
|
|
519
909
|
return cached;
|
|
520
910
|
}
|
|
521
911
|
}
|
|
522
|
-
const snapshot = buildModuleGraphSnapshot(entries);
|
|
912
|
+
const snapshot = buildModuleGraphSnapshot(entries, { extensions: MODULE_GRAPH_EXTENSIONS });
|
|
913
|
+
await assertNoServerOnlyClientImports(snapshot, specialRouteFiles?.app);
|
|
914
|
+
computeRouteStyleMap(snapshot);
|
|
523
915
|
await writeModuleGraphCache(cacheRoot, graphKey, snapshot);
|
|
524
916
|
await assertLegacyChunkingSafe(snapshot);
|
|
525
917
|
depsHashByEntry = await computeDepsHashByEntry(snapshot);
|
|
@@ -535,11 +927,15 @@ export const build = async (options) => {
|
|
|
535
927
|
navigationMode: resolvedNavigationMode,
|
|
536
928
|
clientRenderMode: config.routing.clientRender,
|
|
537
929
|
}, adapterRegistry);
|
|
538
|
-
|
|
930
|
+
const createClientEntry = entryAdapter.createClientEntry;
|
|
931
|
+
if (typeof createClientEntry !== "function") {
|
|
539
932
|
throw new Error(`UI adapter \"${entryAdapter.name}\" does not support createClientEntry.`);
|
|
540
933
|
}
|
|
541
934
|
const clientEntryDir = join(entryWorkspaceRoot, "client");
|
|
542
935
|
await mkdir(clientEntryDir, { recursive: true });
|
|
936
|
+
const appModule = specialRouteFiles?.app
|
|
937
|
+
? toImportSpecifier(clientEntryDir, specialRouteFiles.app)
|
|
938
|
+
: undefined;
|
|
543
939
|
const buildClientRouteModules = routeGraph.routes.reduce((acc, route) => {
|
|
544
940
|
acc[route.id] = toImportSpecifier(clientEntryDir, route.filePath);
|
|
545
941
|
return acc;
|
|
@@ -549,25 +945,30 @@ export const build = async (options) => {
|
|
|
549
945
|
for (const route of routeGraph.routes) {
|
|
550
946
|
const entryFilePath = join(clientEntryDir, `entry-${hash(route.id).slice(0, 12)}.tsx`);
|
|
551
947
|
const pageModule = toImportSpecifier(clientEntryDir, route.filePath);
|
|
552
|
-
const entrySource =
|
|
948
|
+
const entrySource = createClientEntry({
|
|
553
949
|
routeId: route.id,
|
|
554
950
|
routeGraph,
|
|
555
951
|
basePath: config.basePath,
|
|
556
952
|
rootDir,
|
|
557
953
|
entryDir: clientEntryDir,
|
|
954
|
+
entryLayoutFiles: route.layoutFiles,
|
|
558
955
|
uiOptions: {
|
|
559
956
|
...config.ui.options,
|
|
560
957
|
pageModule,
|
|
958
|
+
appModule,
|
|
561
959
|
navigationMode: resolvedNavigationMode,
|
|
562
960
|
clientRenderMode: config.routing.clientRender,
|
|
961
|
+
scrollRestoration: config.routing.scrollRestoration,
|
|
563
962
|
clientRouteModules: buildClientRouteModules,
|
|
564
963
|
nestedLayouts: config.routing.nestedLayouts,
|
|
565
964
|
},
|
|
566
965
|
adapterOptions: {
|
|
567
966
|
...config.ui.options,
|
|
568
967
|
pageModule,
|
|
968
|
+
appModule,
|
|
569
969
|
navigationMode: resolvedNavigationMode,
|
|
570
970
|
clientRenderMode: config.routing.clientRender,
|
|
971
|
+
scrollRestoration: config.routing.scrollRestoration,
|
|
571
972
|
clientRouteModules: buildClientRouteModules,
|
|
572
973
|
nestedLayouts: config.routing.nestedLayouts,
|
|
573
974
|
},
|
|
@@ -579,6 +980,49 @@ export const build = async (options) => {
|
|
|
579
980
|
clientRuntimeEntries.set(route.id, entryFilePath);
|
|
580
981
|
entries.push({ id: route.id, input: entryFilePath });
|
|
581
982
|
}
|
|
983
|
+
const addSpecialClientEntry = async (record) => {
|
|
984
|
+
if (!record) {
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
const entryFilePath = join(clientEntryDir, `entry-${hash(record.id).slice(0, 12)}.tsx`);
|
|
988
|
+
const pageModule = toImportSpecifier(clientEntryDir, record.filePath);
|
|
989
|
+
const entrySource = createClientEntry({
|
|
990
|
+
routeId: record.id,
|
|
991
|
+
routeGraph,
|
|
992
|
+
basePath: config.basePath,
|
|
993
|
+
rootDir,
|
|
994
|
+
entryDir: clientEntryDir,
|
|
995
|
+
entryLayoutFiles: record.layoutFiles,
|
|
996
|
+
uiOptions: {
|
|
997
|
+
...config.ui.options,
|
|
998
|
+
pageModule,
|
|
999
|
+
appModule,
|
|
1000
|
+
navigationMode: resolvedNavigationMode,
|
|
1001
|
+
clientRenderMode: config.routing.clientRender,
|
|
1002
|
+
scrollRestoration: config.routing.scrollRestoration,
|
|
1003
|
+
clientRouteModules: buildClientRouteModules,
|
|
1004
|
+
nestedLayouts: config.routing.nestedLayouts,
|
|
1005
|
+
},
|
|
1006
|
+
adapterOptions: {
|
|
1007
|
+
...config.ui.options,
|
|
1008
|
+
pageModule,
|
|
1009
|
+
appModule,
|
|
1010
|
+
navigationMode: resolvedNavigationMode,
|
|
1011
|
+
clientRenderMode: config.routing.clientRender,
|
|
1012
|
+
scrollRestoration: config.routing.scrollRestoration,
|
|
1013
|
+
clientRouteModules: buildClientRouteModules,
|
|
1014
|
+
nestedLayouts: config.routing.nestedLayouts,
|
|
1015
|
+
},
|
|
1016
|
+
});
|
|
1017
|
+
if (typeof entrySource !== "string" || entrySource.trim().length === 0) {
|
|
1018
|
+
throw new Error(`Client entry generation failed for route ${record.id}`);
|
|
1019
|
+
}
|
|
1020
|
+
await writeFileSafe(entryFilePath, `${entrySource}\n\n${clientBootstrapChunk}\n`);
|
|
1021
|
+
clientRuntimeEntries.set(record.id, entryFilePath);
|
|
1022
|
+
entries.push({ id: record.id, input: entryFilePath });
|
|
1023
|
+
};
|
|
1024
|
+
await addSpecialClientEntry(specialRouteRecords.notFound);
|
|
1025
|
+
await addSpecialClientEntry(specialRouteRecords.error);
|
|
582
1026
|
const clientFormat = isEs5Target ? "iife" : "esm";
|
|
583
1027
|
const modernBundle = await bundleWithCache("client", entries, clientFormat, bundleClient, modernTarget);
|
|
584
1028
|
if (legacyEnabled && !isEs5Target) {
|
|
@@ -597,18 +1041,28 @@ export const build = async (options) => {
|
|
|
597
1041
|
navigationMode: resolvedNavigationMode,
|
|
598
1042
|
clientRenderMode: config.routing.clientRender,
|
|
599
1043
|
}, adapterRegistry);
|
|
600
|
-
|
|
1044
|
+
const createServerEntry = entryAdapter.createServerEntry;
|
|
1045
|
+
if (typeof createServerEntry !== "function") {
|
|
601
1046
|
throw new Error(`UI adapter \"${entryAdapter.name}\" does not support createServerEntry.`);
|
|
602
1047
|
}
|
|
603
1048
|
const serverEntryDir = join(entryWorkspaceRoot, "server");
|
|
604
1049
|
await mkdir(serverEntryDir, { recursive: true });
|
|
605
1050
|
const entries = [];
|
|
606
1051
|
serverRuntimeEntries.clear();
|
|
1052
|
+
const appModule = specialRouteFiles?.app
|
|
1053
|
+
? toImportSpecifier(serverEntryDir, specialRouteFiles.app)
|
|
1054
|
+
: undefined;
|
|
1055
|
+
const documentModule = specialRouteFiles?.document
|
|
1056
|
+
? toImportSpecifier(serverEntryDir, specialRouteFiles.document)
|
|
1057
|
+
: undefined;
|
|
1058
|
+
const initServerModule = specialRouteFiles?.initServer
|
|
1059
|
+
? toImportSpecifier(serverEntryDir, specialRouteFiles.initServer)
|
|
1060
|
+
: undefined;
|
|
607
1061
|
for (const route of routeGraph.routes) {
|
|
608
1062
|
const pageModule = toImportSpecifier(serverEntryDir, route.filePath);
|
|
609
1063
|
const rawEntryFilePath = join(serverEntryDir, `entry-${hash(route.id).slice(0, 12)}.raw.tsx`);
|
|
610
1064
|
const wrappedEntryFilePath = join(serverEntryDir, `entry-${hash(route.id).slice(0, 12)}.ts`);
|
|
611
|
-
const serverEntrySource =
|
|
1065
|
+
const serverEntrySource = createServerEntry({
|
|
612
1066
|
routeId: route.id,
|
|
613
1067
|
routeGraph,
|
|
614
1068
|
basePath: config.basePath,
|
|
@@ -617,6 +1071,7 @@ export const build = async (options) => {
|
|
|
617
1071
|
uiOptions: {
|
|
618
1072
|
...config.ui.options,
|
|
619
1073
|
pageModule,
|
|
1074
|
+
appModule,
|
|
620
1075
|
navigationMode: resolvedNavigationMode,
|
|
621
1076
|
clientRenderMode: config.routing.clientRender,
|
|
622
1077
|
nestedLayouts: config.routing.nestedLayouts,
|
|
@@ -624,6 +1079,7 @@ export const build = async (options) => {
|
|
|
624
1079
|
adapterOptions: {
|
|
625
1080
|
...config.ui.options,
|
|
626
1081
|
pageModule,
|
|
1082
|
+
appModule,
|
|
627
1083
|
navigationMode: resolvedNavigationMode,
|
|
628
1084
|
clientRenderMode: config.routing.clientRender,
|
|
629
1085
|
nestedLayouts: config.routing.nestedLayouts,
|
|
@@ -634,9 +1090,27 @@ export const build = async (options) => {
|
|
|
634
1090
|
}
|
|
635
1091
|
await writeFileSafe(rawEntryFilePath, serverEntrySource);
|
|
636
1092
|
const rawSpecifier = toImportSpecifier(serverEntryDir, rawEntryFilePath);
|
|
1093
|
+
const layoutFiles = route.layoutFiles ?? [];
|
|
1094
|
+
const layoutImports = layoutFiles.map((layoutFile, index) => {
|
|
1095
|
+
const specifier = toImportSpecifier(serverEntryDir, layoutFile);
|
|
1096
|
+
return `import * as layoutModule${index} from ${JSON.stringify(specifier)};`;
|
|
1097
|
+
});
|
|
1098
|
+
const layoutEntries = layoutFiles.map((layoutFile, index) => {
|
|
1099
|
+
const layoutId = resolveLayoutRouteId(layoutFile, routesDir);
|
|
1100
|
+
return ` { key: ${JSON.stringify(routeDataKeyForLayout(layoutId))}, routeId: ${JSON.stringify(layoutId)}, hook: typeof layoutModule${index}.getRouteData === \"function\" ? layoutModule${index}.getRouteData : undefined },`;
|
|
1101
|
+
});
|
|
1102
|
+
const initServerImport = initServerModule
|
|
1103
|
+
? `import * as initServerModule from ${JSON.stringify(initServerModule)};`
|
|
1104
|
+
: "";
|
|
1105
|
+
const documentImport = documentModule
|
|
1106
|
+
? `import * as documentModule from ${JSON.stringify(documentModule)};`
|
|
1107
|
+
: "";
|
|
637
1108
|
const wrapperSource = [
|
|
638
1109
|
`import * as pageModule from ${JSON.stringify(pageModule)};`,
|
|
639
1110
|
`import * as serverEntryModule from ${JSON.stringify(rawSpecifier)};`,
|
|
1111
|
+
initServerImport,
|
|
1112
|
+
documentImport,
|
|
1113
|
+
...layoutImports,
|
|
640
1114
|
"export const renderToHtml = serverEntryModule.renderToHtml;",
|
|
641
1115
|
"export const renderToStream = serverEntryModule.renderToStream;",
|
|
642
1116
|
"export const hydration =",
|
|
@@ -647,12 +1121,118 @@ export const build = async (options) => {
|
|
|
647
1121
|
" typeof pageModule.getServerProps === \"function\" ? pageModule.getServerProps : undefined;",
|
|
648
1122
|
"export const getServerSideProps =",
|
|
649
1123
|
" typeof pageModule.getServerSideProps === \"function\" ? pageModule.getServerSideProps : undefined;",
|
|
1124
|
+
"export const getRouteData =",
|
|
1125
|
+
" typeof pageModule.getRouteData === \"function\" ? pageModule.getRouteData : undefined;",
|
|
1126
|
+
initServerModule
|
|
1127
|
+
? "export const initServer = typeof initServerModule.initServer === \"function\" ? initServerModule.initServer : (typeof initServerModule.default === \"function\" ? initServerModule.default : undefined);"
|
|
1128
|
+
: "export const initServer = undefined;",
|
|
1129
|
+
documentModule
|
|
1130
|
+
? "export const renderDocument = typeof documentModule.renderDocument === \"function\" ? documentModule.renderDocument : (typeof documentModule.default === \"function\" ? documentModule.default : undefined);"
|
|
1131
|
+
: "export const renderDocument = undefined;",
|
|
1132
|
+
documentModule
|
|
1133
|
+
? "export const renderDocumentFragments = typeof documentModule.renderDocumentFragments === \"function\" ? documentModule.renderDocumentFragments : undefined;"
|
|
1134
|
+
: "export const renderDocumentFragments = undefined;",
|
|
1135
|
+
"export const routeDataEntries = [",
|
|
1136
|
+
...layoutEntries,
|
|
1137
|
+
` { key: ${JSON.stringify(routeDataKeyForPage(route.id))}, routeId: ${JSON.stringify(route.id)}, hook: typeof pageModule.getRouteData === \"function\" ? pageModule.getRouteData : undefined },`,
|
|
1138
|
+
"];",
|
|
650
1139
|
"",
|
|
651
1140
|
].join("\n");
|
|
652
1141
|
await writeFileSafe(wrappedEntryFilePath, wrapperSource);
|
|
653
1142
|
serverRuntimeEntries.set(route.id, wrappedEntryFilePath);
|
|
654
1143
|
entries.push({ id: route.id, input: wrappedEntryFilePath });
|
|
655
1144
|
}
|
|
1145
|
+
const addSpecialServerEntry = async (record) => {
|
|
1146
|
+
if (!record) {
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
const pageModule = toImportSpecifier(serverEntryDir, record.filePath);
|
|
1150
|
+
const rawEntryFilePath = join(serverEntryDir, `entry-${hash(record.id).slice(0, 12)}.raw.tsx`);
|
|
1151
|
+
const wrappedEntryFilePath = join(serverEntryDir, `entry-${hash(record.id).slice(0, 12)}.ts`);
|
|
1152
|
+
const serverEntrySource = createServerEntry({
|
|
1153
|
+
routeId: record.id,
|
|
1154
|
+
routeGraph,
|
|
1155
|
+
basePath: config.basePath,
|
|
1156
|
+
rootDir,
|
|
1157
|
+
entryDir: serverEntryDir,
|
|
1158
|
+
uiOptions: {
|
|
1159
|
+
...config.ui.options,
|
|
1160
|
+
pageModule,
|
|
1161
|
+
appModule,
|
|
1162
|
+
entryLayoutFiles: record.layoutFiles,
|
|
1163
|
+
navigationMode: resolvedNavigationMode,
|
|
1164
|
+
clientRenderMode: config.routing.clientRender,
|
|
1165
|
+
nestedLayouts: config.routing.nestedLayouts,
|
|
1166
|
+
},
|
|
1167
|
+
adapterOptions: {
|
|
1168
|
+
...config.ui.options,
|
|
1169
|
+
pageModule,
|
|
1170
|
+
appModule,
|
|
1171
|
+
entryLayoutFiles: record.layoutFiles,
|
|
1172
|
+
navigationMode: resolvedNavigationMode,
|
|
1173
|
+
clientRenderMode: config.routing.clientRender,
|
|
1174
|
+
nestedLayouts: config.routing.nestedLayouts,
|
|
1175
|
+
},
|
|
1176
|
+
});
|
|
1177
|
+
if (typeof serverEntrySource !== "string" || serverEntrySource.trim().length === 0) {
|
|
1178
|
+
throw new Error(`Server entry generation failed for route ${record.id}`);
|
|
1179
|
+
}
|
|
1180
|
+
await writeFileSafe(rawEntryFilePath, serverEntrySource);
|
|
1181
|
+
const rawSpecifier = toImportSpecifier(serverEntryDir, rawEntryFilePath);
|
|
1182
|
+
const layoutFiles = record.layoutFiles ?? [];
|
|
1183
|
+
const layoutImports = layoutFiles.map((layoutFile, index) => {
|
|
1184
|
+
const specifier = toImportSpecifier(serverEntryDir, layoutFile);
|
|
1185
|
+
return `import * as layoutModule${index} from ${JSON.stringify(specifier)};`;
|
|
1186
|
+
});
|
|
1187
|
+
const layoutEntries = layoutFiles.map((layoutFile, index) => {
|
|
1188
|
+
const layoutId = resolveLayoutRouteId(layoutFile, routesDir);
|
|
1189
|
+
return ` { key: ${JSON.stringify(routeDataKeyForLayout(layoutId))}, routeId: ${JSON.stringify(layoutId)}, hook: typeof layoutModule${index}.getRouteData === \"function\" ? layoutModule${index}.getRouteData : undefined },`;
|
|
1190
|
+
});
|
|
1191
|
+
const initServerImport = initServerModule
|
|
1192
|
+
? `import * as initServerModule from ${JSON.stringify(initServerModule)};`
|
|
1193
|
+
: "";
|
|
1194
|
+
const documentImport = documentModule
|
|
1195
|
+
? `import * as documentModule from ${JSON.stringify(documentModule)};`
|
|
1196
|
+
: "";
|
|
1197
|
+
const wrapperSource = [
|
|
1198
|
+
`import * as pageModule from ${JSON.stringify(pageModule)};`,
|
|
1199
|
+
`import * as serverEntryModule from ${JSON.stringify(rawSpecifier)};`,
|
|
1200
|
+
initServerImport,
|
|
1201
|
+
documentImport,
|
|
1202
|
+
...layoutImports,
|
|
1203
|
+
"export const renderToHtml = serverEntryModule.renderToHtml;",
|
|
1204
|
+
"export const renderToStream = serverEntryModule.renderToStream;",
|
|
1205
|
+
"export const hydration =",
|
|
1206
|
+
" serverEntryModule.hydration === \"full\" || serverEntryModule.hydration === \"islands\"",
|
|
1207
|
+
" ? serverEntryModule.hydration",
|
|
1208
|
+
" : \"islands\";",
|
|
1209
|
+
"export const getServerProps =",
|
|
1210
|
+
" typeof pageModule.getServerProps === \"function\" ? pageModule.getServerProps : undefined;",
|
|
1211
|
+
"export const getServerSideProps =",
|
|
1212
|
+
" typeof pageModule.getServerSideProps === \"function\" ? pageModule.getServerSideProps : undefined;",
|
|
1213
|
+
"export const getRouteData =",
|
|
1214
|
+
" typeof pageModule.getRouteData === \"function\" ? pageModule.getRouteData : undefined;",
|
|
1215
|
+
initServerModule
|
|
1216
|
+
? "export const initServer = typeof initServerModule.initServer === \"function\" ? initServerModule.initServer : (typeof initServerModule.default === \"function\" ? initServerModule.default : undefined);"
|
|
1217
|
+
: "export const initServer = undefined;",
|
|
1218
|
+
documentModule
|
|
1219
|
+
? "export const renderDocument = typeof documentModule.renderDocument === \"function\" ? documentModule.renderDocument : (typeof documentModule.default === \"function\" ? documentModule.default : undefined);"
|
|
1220
|
+
: "export const renderDocument = undefined;",
|
|
1221
|
+
documentModule
|
|
1222
|
+
? "export const renderDocumentFragments = typeof documentModule.renderDocumentFragments === \"function\" ? documentModule.renderDocumentFragments : undefined;"
|
|
1223
|
+
: "export const renderDocumentFragments = undefined;",
|
|
1224
|
+
"export const routeDataEntries = [",
|
|
1225
|
+
...layoutEntries,
|
|
1226
|
+
` { key: ${JSON.stringify(routeDataKeyForPage(record.id))}, routeId: ${JSON.stringify(record.id)}, hook: typeof pageModule.getRouteData === \"function\" ? pageModule.getRouteData : undefined },`,
|
|
1227
|
+
"];",
|
|
1228
|
+
"",
|
|
1229
|
+
].join("\n");
|
|
1230
|
+
await writeFileSafe(wrappedEntryFilePath, wrapperSource);
|
|
1231
|
+
serverRuntimeEntries.set(record.id, wrappedEntryFilePath);
|
|
1232
|
+
entries.push({ id: record.id, input: wrappedEntryFilePath });
|
|
1233
|
+
};
|
|
1234
|
+
await addSpecialServerEntry(specialRouteRecords.notFound);
|
|
1235
|
+
await addSpecialServerEntry(specialRouteRecords.error);
|
|
656
1236
|
return bundleWithCache("server", entries, "cjs", bundleServer, serverTarget);
|
|
657
1237
|
}
|
|
658
1238
|
: undefined,
|
|
@@ -741,37 +1321,160 @@ export const build = async (options) => {
|
|
|
741
1321
|
return {};
|
|
742
1322
|
}
|
|
743
1323
|
const outputs = {};
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
1324
|
+
const initServer = await resolveInitServer();
|
|
1325
|
+
const errorRecord = specialRouteRecords.error;
|
|
1326
|
+
const notFoundRecord = specialRouteRecords.notFound;
|
|
1327
|
+
let errorPageModule = null;
|
|
1328
|
+
if (errorRecord) {
|
|
1329
|
+
const loaded = await import(`${pathToFileURL(errorRecord.filePath).href}?build=${Date.now()}-${hash("error").slice(0, 6)}`);
|
|
1330
|
+
errorPageModule = resolvePageModule(loaded);
|
|
1331
|
+
}
|
|
1332
|
+
let notFoundPageModule = null;
|
|
1333
|
+
if (notFoundRecord) {
|
|
1334
|
+
const loaded = await import(`${pathToFileURL(notFoundRecord.filePath).href}?build=${Date.now()}-${hash("not-found").slice(0, 6)}`);
|
|
1335
|
+
notFoundPageModule = resolvePageModule(loaded);
|
|
1336
|
+
}
|
|
1337
|
+
const resolveAssetsForRoute = (routeId) => {
|
|
1338
|
+
const scripts = resolveBundleScripts(clientBundle, routeId, config.basePath, assetsDir, buildVersion);
|
|
1339
|
+
const styleFile = routeHasStyles[routeId]
|
|
1340
|
+
? resolveBundleStyleFile(clientBundle, routeId)
|
|
1341
|
+
: null;
|
|
1342
|
+
const styles = styleFile
|
|
1343
|
+
? [appendBuildVersion(resolveAssetUrl(config.basePath, assetsDir, styleFile), buildVersion)]
|
|
1344
|
+
: [];
|
|
753
1345
|
const legacyPolyfillScripts = legacyEnabled
|
|
754
1346
|
? legacyPolyfillPlan.scripts.map((script) => appendBuildVersion(script, buildVersion))
|
|
755
1347
|
: [];
|
|
756
1348
|
const legacyBundleScripts = legacyEnabled && legacyBundle
|
|
757
|
-
? resolveBundleScripts(legacyBundle,
|
|
1349
|
+
? resolveBundleScripts(legacyBundle, routeId, config.basePath, assetsDir, buildVersion)
|
|
758
1350
|
: [];
|
|
759
1351
|
const legacyScripts = legacyEnabled && !isEs5Target
|
|
760
1352
|
? [...legacyPolyfillScripts, ...legacyBundleScripts]
|
|
761
1353
|
: [];
|
|
762
|
-
|
|
763
|
-
scripts
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
1354
|
+
return {
|
|
1355
|
+
scripts,
|
|
1356
|
+
assets: {
|
|
1357
|
+
scripts: isEs5Target ? [...legacyPolyfillScripts, ...scripts] : scripts,
|
|
1358
|
+
legacyScripts,
|
|
1359
|
+
styles,
|
|
1360
|
+
scriptType: clientScriptType,
|
|
1361
|
+
buildVersion,
|
|
1362
|
+
},
|
|
767
1363
|
};
|
|
768
|
-
|
|
1364
|
+
};
|
|
1365
|
+
const documentRenderers = await resolveDocumentRenderers();
|
|
1366
|
+
const documentTemplate = documentRenderers.renderDocument || documentRenderers.renderDocumentFragments
|
|
1367
|
+
? (ctx) => {
|
|
1368
|
+
if (documentRenderers.renderDocument) {
|
|
1369
|
+
try {
|
|
1370
|
+
const custom = documentRenderers.renderDocument(ctx);
|
|
1371
|
+
if (typeof custom === "string") {
|
|
1372
|
+
return custom;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
catch {
|
|
1376
|
+
// Fall back to default template if custom document fails.
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
if (documentRenderers.renderDocumentFragments) {
|
|
1380
|
+
try {
|
|
1381
|
+
const fragments = documentRenderers.renderDocumentFragments(ctx);
|
|
1382
|
+
if (fragments &&
|
|
1383
|
+
typeof fragments.prefix === "string" &&
|
|
1384
|
+
typeof fragments.suffix === "string") {
|
|
1385
|
+
return `${fragments.prefix}${ctx.html}${fragments.suffix}`;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
catch {
|
|
1389
|
+
// Fall back to default template if fragments fail.
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
return defaultHtmlTemplate(ctx);
|
|
1393
|
+
}
|
|
1394
|
+
: defaultHtmlTemplate;
|
|
1395
|
+
for (const entry of entries) {
|
|
1396
|
+
const route = routeById.get(entry.routeId);
|
|
1397
|
+
const pageModule = routeModules.get(entry.routeId);
|
|
1398
|
+
if (!route || !pageModule) {
|
|
1399
|
+
continue;
|
|
1400
|
+
}
|
|
1401
|
+
const routeDataResult = await collectRouteDataForRoute({
|
|
1402
|
+
routeId: entry.routeId,
|
|
1403
|
+
routesDir,
|
|
1404
|
+
params: entry.params,
|
|
1405
|
+
layoutFiles: route.layoutFiles,
|
|
1406
|
+
pageModule,
|
|
1407
|
+
initServerHook: initServer,
|
|
1408
|
+
loadLayoutModule,
|
|
1409
|
+
});
|
|
1410
|
+
const redirect = normalizeRedirect(routeDataResult.redirect);
|
|
1411
|
+
if (redirect) {
|
|
1412
|
+
const redirectHtml = renderStaticRedirectDocument(redirect.destination);
|
|
1413
|
+
const routePath = routeIdToPath(entry.routeId, entry.params);
|
|
1414
|
+
const htmlPath = routePath
|
|
1415
|
+
? join(outDir, routePath, "index.html")
|
|
1416
|
+
: join(outDir, "index.html");
|
|
1417
|
+
await writeFileSafe(htmlPath, redirectHtml);
|
|
1418
|
+
outputs[routePath || "/"] = redirectHtml;
|
|
1419
|
+
continue;
|
|
1420
|
+
}
|
|
1421
|
+
if (routeDataResult.error) {
|
|
1422
|
+
const normalizedError = normalizeError(routeDataResult.error);
|
|
1423
|
+
if (normalizedError && errorRecord && errorPageModule) {
|
|
1424
|
+
const errorAdapter = resolveUIAdapter(config.ui.adapter, {
|
|
1425
|
+
...config.ui.options,
|
|
1426
|
+
navigationMode: resolvedNavigationMode,
|
|
1427
|
+
clientRenderMode: config.routing.clientRender,
|
|
1428
|
+
nestedLayouts: config.routing.nestedLayouts,
|
|
1429
|
+
routeGraph,
|
|
1430
|
+
rootDir,
|
|
1431
|
+
appModule: specialRouteFiles?.app,
|
|
1432
|
+
routeRender: (props) => errorPageModule.default(props),
|
|
1433
|
+
routeHead: errorPageModule.head
|
|
1434
|
+
? (props) => errorPageModule.head(props)
|
|
1435
|
+
: undefined,
|
|
1436
|
+
}, adapterRegistry);
|
|
1437
|
+
const { assets } = resolveAssetsForRoute(errorRecord.id);
|
|
1438
|
+
const errorRendered = await renderHtml({
|
|
1439
|
+
renderToHtml: (ctx) => errorAdapter.renderToHtml(ctx),
|
|
1440
|
+
serializeProps: errorAdapter.serializeProps ?? serializeProps,
|
|
1441
|
+
}, {
|
|
1442
|
+
routeId: errorRecord.id,
|
|
1443
|
+
props: {
|
|
1444
|
+
error: normalizedError,
|
|
1445
|
+
routeId: entry.routeId,
|
|
1446
|
+
},
|
|
1447
|
+
routeData: routeDataResult.data,
|
|
1448
|
+
}, documentTemplate, assets);
|
|
1449
|
+
const routePath = routeIdToPath(entry.routeId, entry.params);
|
|
1450
|
+
const htmlPath = routePath
|
|
1451
|
+
? join(outDir, routePath, "index.html")
|
|
1452
|
+
: join(outDir, "index.html");
|
|
1453
|
+
await writeFileSafe(htmlPath, errorRendered.finalHtml);
|
|
1454
|
+
outputs[routePath || "/"] = errorRendered.finalHtml;
|
|
1455
|
+
continue;
|
|
1456
|
+
}
|
|
1457
|
+
const fallbackHtml = renderStaticErrorDocument(normalizedError?.message ?? "Route data error");
|
|
1458
|
+
const routePath = routeIdToPath(entry.routeId, entry.params);
|
|
1459
|
+
const htmlPath = routePath
|
|
1460
|
+
? join(outDir, routePath, "index.html")
|
|
1461
|
+
: join(outDir, "index.html");
|
|
1462
|
+
await writeFileSafe(htmlPath, fallbackHtml);
|
|
1463
|
+
outputs[routePath || "/"] = fallbackHtml;
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
const depsHash = depsHashByEntry?.[entry.routeId] ?? "missing";
|
|
1467
|
+
const propsHash = computePropsHash(entry.props);
|
|
1468
|
+
const { assets } = resolveAssetsForRoute(entry.routeId);
|
|
1469
|
+
const templateHash = computeTemplateHash(documentTemplate, assets);
|
|
1470
|
+
const routeDataHash = computePropsHash(routeDataResult.data);
|
|
769
1471
|
const renderKey = computeRenderKey({
|
|
770
1472
|
routeId: entry.routeId,
|
|
771
1473
|
paramsKey: entry.paramsKey,
|
|
772
1474
|
templateHash,
|
|
773
1475
|
depsHash,
|
|
774
1476
|
propsHash,
|
|
1477
|
+
routeDataHash,
|
|
775
1478
|
});
|
|
776
1479
|
if (renderCacheEnabled) {
|
|
777
1480
|
const cached = await readRenderCache(cacheRoot, {
|
|
@@ -780,6 +1483,7 @@ export const build = async (options) => {
|
|
|
780
1483
|
templateHash,
|
|
781
1484
|
depsHash,
|
|
782
1485
|
propsHash,
|
|
1486
|
+
routeDataHash,
|
|
783
1487
|
});
|
|
784
1488
|
if (cached) {
|
|
785
1489
|
const routePath = routeIdToPath(entry.routeId, entry.params);
|
|
@@ -798,6 +1502,7 @@ export const build = async (options) => {
|
|
|
798
1502
|
nestedLayouts: config.routing.nestedLayouts,
|
|
799
1503
|
routeGraph,
|
|
800
1504
|
rootDir,
|
|
1505
|
+
appModule: specialRouteFiles?.app,
|
|
801
1506
|
routeRender: (props) => pageModule.default(props),
|
|
802
1507
|
routeHead: pageModule.head
|
|
803
1508
|
? (props) => pageModule.head(props)
|
|
@@ -809,7 +1514,12 @@ export const build = async (options) => {
|
|
|
809
1514
|
const rendered = await renderHtml({
|
|
810
1515
|
renderToHtml: (ctx) => routeAdapter.renderToHtml(ctx),
|
|
811
1516
|
serializeProps: routeAdapter.serializeProps ?? serializeProps,
|
|
812
|
-
}, {
|
|
1517
|
+
}, {
|
|
1518
|
+
routeId: entry.routeId,
|
|
1519
|
+
params: entry.params,
|
|
1520
|
+
props: entry.props,
|
|
1521
|
+
routeData: routeDataResult.data,
|
|
1522
|
+
}, documentTemplate, assets);
|
|
813
1523
|
const routePath = routeIdToPath(entry.routeId, entry.params);
|
|
814
1524
|
const htmlPath = routePath
|
|
815
1525
|
? join(outDir, routePath, "index.html")
|
|
@@ -823,22 +1533,124 @@ export const build = async (options) => {
|
|
|
823
1533
|
renderKey,
|
|
824
1534
|
templateHash: rendered.templateHash,
|
|
825
1535
|
propsHash,
|
|
1536
|
+
routeDataHash,
|
|
826
1537
|
depsHash,
|
|
827
1538
|
generatedAt: Date.now(),
|
|
828
1539
|
head: rendered.head,
|
|
829
1540
|
propsPayload: rendered.propsPayload,
|
|
1541
|
+
routeDataPayload: rendered.routeDataPayload,
|
|
830
1542
|
status: rendered.status,
|
|
831
1543
|
headers: rendered.headers,
|
|
832
1544
|
finalHtml: rendered.finalHtml,
|
|
833
1545
|
});
|
|
834
1546
|
}
|
|
835
1547
|
}
|
|
1548
|
+
if (notFoundRecord && notFoundPageModule) {
|
|
1549
|
+
const notFoundData = await collectRouteDataForRoute({
|
|
1550
|
+
routeId: notFoundRecord.id,
|
|
1551
|
+
routesDir,
|
|
1552
|
+
layoutFiles: notFoundRecord.layoutFiles,
|
|
1553
|
+
pageModule: notFoundPageModule,
|
|
1554
|
+
initServerHook: initServer,
|
|
1555
|
+
loadLayoutModule,
|
|
1556
|
+
});
|
|
1557
|
+
const notFoundAdapter = resolveUIAdapter(config.ui.adapter, {
|
|
1558
|
+
...config.ui.options,
|
|
1559
|
+
navigationMode: resolvedNavigationMode,
|
|
1560
|
+
clientRenderMode: config.routing.clientRender,
|
|
1561
|
+
nestedLayouts: config.routing.nestedLayouts,
|
|
1562
|
+
routeGraph,
|
|
1563
|
+
rootDir,
|
|
1564
|
+
appModule: specialRouteFiles?.app,
|
|
1565
|
+
routeRender: (props) => notFoundPageModule.default(props),
|
|
1566
|
+
routeHead: notFoundPageModule.head
|
|
1567
|
+
? (props) => notFoundPageModule.head(props)
|
|
1568
|
+
: undefined,
|
|
1569
|
+
}, adapterRegistry);
|
|
1570
|
+
const { assets } = resolveAssetsForRoute(notFoundRecord.id);
|
|
1571
|
+
const rendered = await renderHtml({
|
|
1572
|
+
renderToHtml: (ctx) => notFoundAdapter.renderToHtml(ctx),
|
|
1573
|
+
serializeProps: notFoundAdapter.serializeProps ?? serializeProps,
|
|
1574
|
+
}, {
|
|
1575
|
+
routeId: notFoundRecord.id,
|
|
1576
|
+
props: {},
|
|
1577
|
+
routeData: notFoundData.data,
|
|
1578
|
+
}, documentTemplate, assets);
|
|
1579
|
+
await writeFileSafe(join(outDir, "404.html"), rendered.finalHtml);
|
|
1580
|
+
outputs["/404"] = rendered.finalHtml;
|
|
1581
|
+
}
|
|
836
1582
|
return outputs;
|
|
837
1583
|
},
|
|
838
1584
|
emitManifest: async (_ctx, state) => {
|
|
839
1585
|
const routeGraph = state.routeGraph;
|
|
840
1586
|
const clientBundle = state.clientBundle;
|
|
841
1587
|
const serverBundle = state.serverBundle;
|
|
1588
|
+
const collectBundlerPlugins = async (input) => {
|
|
1589
|
+
if (!hyperPlugins.length) {
|
|
1590
|
+
return [];
|
|
1591
|
+
}
|
|
1592
|
+
const plugins = [];
|
|
1593
|
+
await runBeforeBundleHooks(hyperPlugins, {
|
|
1594
|
+
phase: "build",
|
|
1595
|
+
entryId: input.entryId,
|
|
1596
|
+
entryPath: input.entryPath,
|
|
1597
|
+
target: input.target,
|
|
1598
|
+
format: input.format,
|
|
1599
|
+
syntaxTarget: input.syntaxTarget,
|
|
1600
|
+
bundler: input.syntaxTarget === "es5" ? "esbuild" : "bun",
|
|
1601
|
+
bundleType: input.bundleType,
|
|
1602
|
+
bundlerPlugins: plugins,
|
|
1603
|
+
addBundlerPlugin: (plugin) => {
|
|
1604
|
+
plugins.push(plugin);
|
|
1605
|
+
},
|
|
1606
|
+
});
|
|
1607
|
+
return plugins;
|
|
1608
|
+
};
|
|
1609
|
+
const special = {};
|
|
1610
|
+
if (specialRouteRecords.notFound) {
|
|
1611
|
+
const clientEntry = clientBundle.entryChunks[specialRouteRecords.notFound.id];
|
|
1612
|
+
if (!clientEntry) {
|
|
1613
|
+
throw new Error("Missing client entry for special notFound route.");
|
|
1614
|
+
}
|
|
1615
|
+
special.notFound = {
|
|
1616
|
+
clientEntry,
|
|
1617
|
+
serverEntry: serverBundle?.entryChunks[specialRouteRecords.notFound.id],
|
|
1618
|
+
html: "404.html",
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
if (specialRouteRecords.error) {
|
|
1622
|
+
const clientEntry = clientBundle.entryChunks[specialRouteRecords.error.id];
|
|
1623
|
+
if (!clientEntry) {
|
|
1624
|
+
throw new Error("Missing client entry for special error route.");
|
|
1625
|
+
}
|
|
1626
|
+
special.error = {
|
|
1627
|
+
clientEntry,
|
|
1628
|
+
serverEntry: serverBundle?.entryChunks[specialRouteRecords.error.id],
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
const routeStyles = {};
|
|
1632
|
+
for (const route of routeGraph.routes) {
|
|
1633
|
+
if (!routeHasStyles[route.id]) {
|
|
1634
|
+
continue;
|
|
1635
|
+
}
|
|
1636
|
+
const styleFile = resolveBundleStyleFile(clientBundle, route.id);
|
|
1637
|
+
if (styleFile) {
|
|
1638
|
+
routeStyles[route.id] = [styleFile];
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
const specialStyles = {};
|
|
1642
|
+
if (specialRouteRecords.notFound && routeHasStyles[specialRouteRecords.notFound.id]) {
|
|
1643
|
+
const styleFile = resolveBundleStyleFile(clientBundle, specialRouteRecords.notFound.id);
|
|
1644
|
+
if (styleFile) {
|
|
1645
|
+
specialStyles.notFound = [styleFile];
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
if (specialRouteRecords.error && routeHasStyles[specialRouteRecords.error.id]) {
|
|
1649
|
+
const styleFile = resolveBundleStyleFile(clientBundle, specialRouteRecords.error.id);
|
|
1650
|
+
if (styleFile) {
|
|
1651
|
+
specialStyles.error = [styleFile];
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
842
1654
|
const manifest = generateManifest({
|
|
843
1655
|
version: buildVersion,
|
|
844
1656
|
basePath: config.basePath,
|
|
@@ -849,6 +1661,9 @@ export const build = async (options) => {
|
|
|
849
1661
|
assetsDir,
|
|
850
1662
|
scriptType: clientScriptType,
|
|
851
1663
|
},
|
|
1664
|
+
routeStyles: Object.keys(routeStyles).length > 0 ? routeStyles : undefined,
|
|
1665
|
+
specialStyles: Object.keys(specialStyles).length > 0 ? specialStyles : undefined,
|
|
1666
|
+
special: Object.keys(special).length > 0 ? special : undefined,
|
|
852
1667
|
});
|
|
853
1668
|
await mkdir(join(outDir, assetsDir), { recursive: true });
|
|
854
1669
|
for (const route of routeGraph.routes) {
|
|
@@ -861,13 +1676,84 @@ export const build = async (options) => {
|
|
|
861
1676
|
if (!clientChunk || !clientEntryPath) {
|
|
862
1677
|
throw new Error(`Missing client runtime metadata for route ${route.id}`);
|
|
863
1678
|
}
|
|
1679
|
+
const styleFile = routeHasStyles[route.id]
|
|
1680
|
+
? resolveBundleStyleFile(clientBundle, route.id)
|
|
1681
|
+
: null;
|
|
1682
|
+
const cssOutputPath = styleFile ? join(outDir, assetsDir, styleFile) : undefined;
|
|
1683
|
+
const bundlerPlugins = await collectBundlerPlugins({
|
|
1684
|
+
entryId: route.id,
|
|
1685
|
+
entryPath: clientEntryPath,
|
|
1686
|
+
target: "browser",
|
|
1687
|
+
format: clientScriptType === "classic" ? "iife" : "esm",
|
|
1688
|
+
syntaxTarget: isEs5Target ? "es5" : "modern",
|
|
1689
|
+
bundleType: "client",
|
|
1690
|
+
});
|
|
864
1691
|
await buildChunkFile(clientEntryPath, join(outDir, assetsDir, clientChunk.file), {
|
|
865
1692
|
target: "browser",
|
|
866
1693
|
format: clientScriptType === "classic" ? "iife" : "esm",
|
|
867
1694
|
shimHyperCore: true,
|
|
868
1695
|
syntaxTarget: isEs5Target ? "es5" : "modern",
|
|
1696
|
+
plugins: bundlerPlugins,
|
|
1697
|
+
emitCss: Boolean(cssOutputPath),
|
|
1698
|
+
cssOutputPath,
|
|
869
1699
|
});
|
|
870
1700
|
}
|
|
1701
|
+
if (specialRouteRecords.notFound) {
|
|
1702
|
+
const clientEntryKey = clientBundle.entryChunks[specialRouteRecords.notFound.id];
|
|
1703
|
+
const clientChunk = clientEntryKey ? clientBundle.chunks[clientEntryKey] : null;
|
|
1704
|
+
const clientEntryPath = clientRuntimeEntries.get(specialRouteRecords.notFound.id);
|
|
1705
|
+
if (clientChunk && clientEntryPath) {
|
|
1706
|
+
const styleFile = routeHasStyles[specialRouteRecords.notFound.id]
|
|
1707
|
+
? resolveBundleStyleFile(clientBundle, specialRouteRecords.notFound.id)
|
|
1708
|
+
: null;
|
|
1709
|
+
const cssOutputPath = styleFile ? join(outDir, assetsDir, styleFile) : undefined;
|
|
1710
|
+
const bundlerPlugins = await collectBundlerPlugins({
|
|
1711
|
+
entryId: specialRouteRecords.notFound.id,
|
|
1712
|
+
entryPath: clientEntryPath,
|
|
1713
|
+
target: "browser",
|
|
1714
|
+
format: clientScriptType === "classic" ? "iife" : "esm",
|
|
1715
|
+
syntaxTarget: isEs5Target ? "es5" : "modern",
|
|
1716
|
+
bundleType: "client",
|
|
1717
|
+
});
|
|
1718
|
+
await buildChunkFile(clientEntryPath, join(outDir, assetsDir, clientChunk.file), {
|
|
1719
|
+
target: "browser",
|
|
1720
|
+
format: clientScriptType === "classic" ? "iife" : "esm",
|
|
1721
|
+
shimHyperCore: true,
|
|
1722
|
+
syntaxTarget: isEs5Target ? "es5" : "modern",
|
|
1723
|
+
plugins: bundlerPlugins,
|
|
1724
|
+
emitCss: Boolean(cssOutputPath),
|
|
1725
|
+
cssOutputPath,
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
if (specialRouteRecords.error) {
|
|
1730
|
+
const clientEntryKey = clientBundle.entryChunks[specialRouteRecords.error.id];
|
|
1731
|
+
const clientChunk = clientEntryKey ? clientBundle.chunks[clientEntryKey] : null;
|
|
1732
|
+
const clientEntryPath = clientRuntimeEntries.get(specialRouteRecords.error.id);
|
|
1733
|
+
if (clientChunk && clientEntryPath) {
|
|
1734
|
+
const styleFile = routeHasStyles[specialRouteRecords.error.id]
|
|
1735
|
+
? resolveBundleStyleFile(clientBundle, specialRouteRecords.error.id)
|
|
1736
|
+
: null;
|
|
1737
|
+
const cssOutputPath = styleFile ? join(outDir, assetsDir, styleFile) : undefined;
|
|
1738
|
+
const bundlerPlugins = await collectBundlerPlugins({
|
|
1739
|
+
entryId: specialRouteRecords.error.id,
|
|
1740
|
+
entryPath: clientEntryPath,
|
|
1741
|
+
target: "browser",
|
|
1742
|
+
format: clientScriptType === "classic" ? "iife" : "esm",
|
|
1743
|
+
syntaxTarget: isEs5Target ? "es5" : "modern",
|
|
1744
|
+
bundleType: "client",
|
|
1745
|
+
});
|
|
1746
|
+
await buildChunkFile(clientEntryPath, join(outDir, assetsDir, clientChunk.file), {
|
|
1747
|
+
target: "browser",
|
|
1748
|
+
format: clientScriptType === "classic" ? "iife" : "esm",
|
|
1749
|
+
shimHyperCore: true,
|
|
1750
|
+
syntaxTarget: isEs5Target ? "es5" : "modern",
|
|
1751
|
+
plugins: bundlerPlugins,
|
|
1752
|
+
emitCss: Boolean(cssOutputPath),
|
|
1753
|
+
cssOutputPath,
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
871
1757
|
if (serverBundle) {
|
|
872
1758
|
for (const route of routeGraph.routes) {
|
|
873
1759
|
const serverEntryKey = serverBundle.entryChunks[route.id];
|
|
@@ -879,13 +1765,66 @@ export const build = async (options) => {
|
|
|
879
1765
|
if (!serverChunk || !serverEntryPath) {
|
|
880
1766
|
throw new Error(`Missing server runtime metadata for route ${route.id}`);
|
|
881
1767
|
}
|
|
1768
|
+
const bundlerPlugins = await collectBundlerPlugins({
|
|
1769
|
+
entryId: route.id,
|
|
1770
|
+
entryPath: serverEntryPath,
|
|
1771
|
+
target: "node",
|
|
1772
|
+
format: "cjs",
|
|
1773
|
+
syntaxTarget: isEs5Target ? "es5" : "modern",
|
|
1774
|
+
bundleType: "server",
|
|
1775
|
+
});
|
|
882
1776
|
await buildChunkFile(serverEntryPath, join(outDir, assetsDir, serverChunk.file), {
|
|
883
1777
|
target: "node",
|
|
884
1778
|
format: "cjs",
|
|
885
1779
|
shimHyperCore: false,
|
|
886
1780
|
syntaxTarget: isEs5Target ? "es5" : "modern",
|
|
1781
|
+
plugins: bundlerPlugins,
|
|
887
1782
|
});
|
|
888
1783
|
}
|
|
1784
|
+
if (specialRouteRecords.notFound) {
|
|
1785
|
+
const serverEntryKey = serverBundle.entryChunks[specialRouteRecords.notFound.id];
|
|
1786
|
+
const serverChunk = serverEntryKey ? serverBundle.chunks[serverEntryKey] : null;
|
|
1787
|
+
const serverEntryPath = serverRuntimeEntries.get(specialRouteRecords.notFound.id);
|
|
1788
|
+
if (serverChunk && serverEntryPath) {
|
|
1789
|
+
const bundlerPlugins = await collectBundlerPlugins({
|
|
1790
|
+
entryId: specialRouteRecords.notFound.id,
|
|
1791
|
+
entryPath: serverEntryPath,
|
|
1792
|
+
target: "node",
|
|
1793
|
+
format: "cjs",
|
|
1794
|
+
syntaxTarget: isEs5Target ? "es5" : "modern",
|
|
1795
|
+
bundleType: "server",
|
|
1796
|
+
});
|
|
1797
|
+
await buildChunkFile(serverEntryPath, join(outDir, assetsDir, serverChunk.file), {
|
|
1798
|
+
target: "node",
|
|
1799
|
+
format: "cjs",
|
|
1800
|
+
shimHyperCore: false,
|
|
1801
|
+
syntaxTarget: isEs5Target ? "es5" : "modern",
|
|
1802
|
+
plugins: bundlerPlugins,
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
if (specialRouteRecords.error) {
|
|
1807
|
+
const serverEntryKey = serverBundle.entryChunks[specialRouteRecords.error.id];
|
|
1808
|
+
const serverChunk = serverEntryKey ? serverBundle.chunks[serverEntryKey] : null;
|
|
1809
|
+
const serverEntryPath = serverRuntimeEntries.get(specialRouteRecords.error.id);
|
|
1810
|
+
if (serverChunk && serverEntryPath) {
|
|
1811
|
+
const bundlerPlugins = await collectBundlerPlugins({
|
|
1812
|
+
entryId: specialRouteRecords.error.id,
|
|
1813
|
+
entryPath: serverEntryPath,
|
|
1814
|
+
target: "node",
|
|
1815
|
+
format: "cjs",
|
|
1816
|
+
syntaxTarget: isEs5Target ? "es5" : "modern",
|
|
1817
|
+
bundleType: "server",
|
|
1818
|
+
});
|
|
1819
|
+
await buildChunkFile(serverEntryPath, join(outDir, assetsDir, serverChunk.file), {
|
|
1820
|
+
target: "node",
|
|
1821
|
+
format: "cjs",
|
|
1822
|
+
shimHyperCore: false,
|
|
1823
|
+
syntaxTarget: isEs5Target ? "es5" : "modern",
|
|
1824
|
+
plugins: bundlerPlugins,
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
889
1828
|
}
|
|
890
1829
|
await writeFileSafe(join(outDir, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
891
1830
|
if (legacyEnabled && legacyBundle) {
|