@tyndall/dev 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/index.d.ts.map +1 -1
- package/dist/index.js +647 -6
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -12,6 +12,9 @@ Development server package with watcher integration, route rendering, and HMR tr
|
|
|
12
12
|
- Emit adapter-driven client runtime scripts for route hydration in dev mode
|
|
13
13
|
- Serve route runtime chunk assets (`/_hyper/dev-client/*`) so client route modules can load on demand after navigation
|
|
14
14
|
- Preserve hydration during HMR fallback by applying route payload updates through a mounted-root-safe rerender bridge
|
|
15
|
+
- Resolve root special files (`_app`, `_document`, `_404`, `_error`, `init.server`) during route graph refresh
|
|
16
|
+
- Collect route data payloads and inject `__HYPER_ROUTE_DATA__` into dev HTML + CSR payloads
|
|
17
|
+
- Emit redirect payloads for client-side navigation requests
|
|
15
18
|
- Filter broad `routesDir` scans so internal artifacts (`.hyper`, cache, `node_modules`, `outDir`, `.git`) never become file routes
|
|
16
19
|
- Stabilize Bun dev runtime bundling by aliasing critical React/runtime dependencies to cache-local concrete files (not workspace symlink paths)
|
|
17
20
|
- Track nested layout files as route dependencies for dev snapshotting and HMR invalidation
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAyBL,KAAK,sBAAsB,EAG3B,KAAK,sBAAsB,EAG3B,KAAK,iBAAiB,EAIvB,MAAM,eAAe,CAAC;AAYvB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAU9C,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE7F,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,sBAAsB,CAAC;IACxC,eAAe,CAAC,EAAE,iBAAiB,CAAC;IACpC,cAAc,CAAC,EAAE,sBAAsB,CAAC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CACxC;AAspBD,eAAO,MAAM,cAAc,GAAU,UAAS,gBAAqB,KAAG,OAAO,CAAC,SAAS,CA8tDtF,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -3,8 +3,8 @@ import { copyFile, cp, mkdir, readFile, rm, stat, writeFile } from "node:fs/prom
|
|
|
3
3
|
import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import { inspect } from "node:util";
|
|
6
|
-
import { CLIENT_ROUTER_BOOTSTRAP_PATH, createRouteGraph, handleDynamicPageApiRequest, loadConfig, renderClientRouterBootstrap, resolvePageModule, resolveRouteWithPolicyFallback, resolveUIAdapter, runGetServerProps, serializeProps, } from "@tyndall/core";
|
|
7
|
-
import { createLogger, computeCacheRootKey, getBunVersion, getOsArch, hash, normalizePath, readFileSafe, resolveCacheRoot, } from "@tyndall/shared";
|
|
6
|
+
import { CLIENT_ROUTER_BOOTSTRAP_PATH, ROUTE_DATA_ERROR_KEY, ROUTE_DATA_INIT_KEY, ROUTE_DATA_SCRIPT_ID, SPECIAL_ROUTE_IDS, collectRouteLayouts, createRouteGraph, handleDynamicPageApiRequest, loadConfig, resolveLayoutRouteId, renderClientRouterBootstrap, resolvePageModule, resolveRouteWithPolicyFallback, resolveUIAdapter, routeDataKeyForLayout, routeDataKeyForPage, runBeforeBundleHooks, runGetRouteData, runGetServerProps, runInitServer, serializeProps, } from "@tyndall/core";
|
|
7
|
+
import { createLogger, buildModuleGraphSnapshot, computeCacheRootKey, getBunVersion, getOsArch, hash, normalizePath, readFileSafe, resolveCacheRoot, } from "@tyndall/shared";
|
|
8
8
|
import { createDynamicModuleGraphManager, DEFAULT_IMPORT_EXTENSIONS } from "@tyndall/dynamic-graph";
|
|
9
9
|
import { createFileWatcher } from "./watcher.js";
|
|
10
10
|
import { createDevInspector, INSPECTOR_API_PATH, INSPECTOR_PATH, renderInspectorHtml, } from "./inspector.js";
|
|
@@ -116,6 +116,192 @@ const renderHeadDescriptor = (head) => {
|
|
|
116
116
|
}
|
|
117
117
|
return parts.join("");
|
|
118
118
|
};
|
|
119
|
+
const renderStyleLinks = (styles) => styles.map((href) => `<link rel=\"stylesheet\" href=\"${escapeHtml(href)}\">`).join("");
|
|
120
|
+
const normalizeRedirect = (redirect) => {
|
|
121
|
+
if (!redirect) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
if (typeof redirect === "string") {
|
|
125
|
+
return { destination: redirect };
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
destination: redirect.destination,
|
|
129
|
+
status: redirect.status,
|
|
130
|
+
replace: redirect.replace,
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
const normalizeError = (error) => {
|
|
134
|
+
if (!error) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
if (typeof error === "string") {
|
|
138
|
+
return { message: error };
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
status: error.status,
|
|
142
|
+
message: error.message,
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
const resolveRouteDataHook = (module) => {
|
|
146
|
+
if (module && typeof module === "object") {
|
|
147
|
+
const record = module;
|
|
148
|
+
if (typeof record.getRouteData === "function") {
|
|
149
|
+
return record.getRouteData;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
};
|
|
154
|
+
const resolveInitServerHook = (module) => {
|
|
155
|
+
if (module && typeof module === "object") {
|
|
156
|
+
const record = module;
|
|
157
|
+
if (typeof record.initServer === "function") {
|
|
158
|
+
return record.initServer;
|
|
159
|
+
}
|
|
160
|
+
if (typeof record.default === "function") {
|
|
161
|
+
return record.default;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return undefined;
|
|
165
|
+
};
|
|
166
|
+
const collectRouteDataForRoute = async (input) => {
|
|
167
|
+
const dataMap = {};
|
|
168
|
+
const headers = {};
|
|
169
|
+
let status;
|
|
170
|
+
let redirect;
|
|
171
|
+
let error;
|
|
172
|
+
let initData;
|
|
173
|
+
let parentData;
|
|
174
|
+
if (input.initServerHook) {
|
|
175
|
+
const initResult = await runInitServer(input.initServerHook, {
|
|
176
|
+
routeId: input.routeId,
|
|
177
|
+
params: input.params,
|
|
178
|
+
request: input.request,
|
|
179
|
+
response: input.response,
|
|
180
|
+
});
|
|
181
|
+
if (initResult.data !== undefined) {
|
|
182
|
+
initData = initResult.data;
|
|
183
|
+
dataMap[ROUTE_DATA_INIT_KEY] = initResult.data;
|
|
184
|
+
}
|
|
185
|
+
if (initResult.headers) {
|
|
186
|
+
Object.assign(headers, initResult.headers);
|
|
187
|
+
}
|
|
188
|
+
if (initResult.status !== undefined) {
|
|
189
|
+
status = initResult.status;
|
|
190
|
+
}
|
|
191
|
+
if (initResult.redirect) {
|
|
192
|
+
redirect = initResult.redirect;
|
|
193
|
+
return { data: dataMap, initData, headers, status, redirect, error };
|
|
194
|
+
}
|
|
195
|
+
if (initResult.error) {
|
|
196
|
+
error = initResult.error;
|
|
197
|
+
const normalizedError = normalizeError(error);
|
|
198
|
+
if (normalizedError) {
|
|
199
|
+
dataMap[ROUTE_DATA_ERROR_KEY] = normalizedError;
|
|
200
|
+
}
|
|
201
|
+
return { data: dataMap, initData, headers, status, redirect, error };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const layoutFiles = input.layoutFiles ?? [];
|
|
205
|
+
for (const layoutFile of layoutFiles) {
|
|
206
|
+
const layoutModule = await input.loadLayoutModule(layoutFile);
|
|
207
|
+
const hook = resolveRouteDataHook(layoutModule);
|
|
208
|
+
if (!hook) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const layoutId = resolveLayoutRouteId(layoutFile, input.routesDir);
|
|
212
|
+
const result = await runGetRouteData(hook, {
|
|
213
|
+
routeId: layoutId,
|
|
214
|
+
params: input.params,
|
|
215
|
+
request: input.request,
|
|
216
|
+
response: input.response,
|
|
217
|
+
init: initData,
|
|
218
|
+
routeData: dataMap,
|
|
219
|
+
parentData,
|
|
220
|
+
});
|
|
221
|
+
if (result.data !== undefined) {
|
|
222
|
+
dataMap[routeDataKeyForLayout(layoutId)] = result.data;
|
|
223
|
+
parentData = result.data;
|
|
224
|
+
}
|
|
225
|
+
if (result.headers) {
|
|
226
|
+
Object.assign(headers, result.headers);
|
|
227
|
+
}
|
|
228
|
+
if (result.status !== undefined) {
|
|
229
|
+
status = result.status;
|
|
230
|
+
}
|
|
231
|
+
if (result.redirect) {
|
|
232
|
+
redirect = result.redirect;
|
|
233
|
+
return { data: dataMap, initData, headers, status, redirect, error };
|
|
234
|
+
}
|
|
235
|
+
if (result.error) {
|
|
236
|
+
error = result.error;
|
|
237
|
+
const normalizedError = normalizeError(error);
|
|
238
|
+
if (normalizedError) {
|
|
239
|
+
dataMap[ROUTE_DATA_ERROR_KEY] = normalizedError;
|
|
240
|
+
}
|
|
241
|
+
return { data: dataMap, initData, headers, status, redirect, error };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (input.pageModule?.getRouteData) {
|
|
245
|
+
const result = await runGetRouteData(input.pageModule.getRouteData, {
|
|
246
|
+
routeId: input.routeId,
|
|
247
|
+
params: input.params,
|
|
248
|
+
request: input.request,
|
|
249
|
+
response: input.response,
|
|
250
|
+
init: initData,
|
|
251
|
+
routeData: dataMap,
|
|
252
|
+
parentData,
|
|
253
|
+
});
|
|
254
|
+
if (result.data !== undefined) {
|
|
255
|
+
dataMap[routeDataKeyForPage(input.routeId)] = result.data;
|
|
256
|
+
}
|
|
257
|
+
if (result.headers) {
|
|
258
|
+
Object.assign(headers, result.headers);
|
|
259
|
+
}
|
|
260
|
+
if (result.status !== undefined) {
|
|
261
|
+
status = result.status;
|
|
262
|
+
}
|
|
263
|
+
if (result.redirect) {
|
|
264
|
+
redirect = result.redirect;
|
|
265
|
+
}
|
|
266
|
+
if (result.error) {
|
|
267
|
+
error = result.error;
|
|
268
|
+
const normalizedError = normalizeError(error);
|
|
269
|
+
if (normalizedError) {
|
|
270
|
+
dataMap[ROUTE_DATA_ERROR_KEY] = normalizedError;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return { data: dataMap, initData, headers, status, redirect, error };
|
|
275
|
+
};
|
|
276
|
+
const renderStaticRedirectDocument = (destination) => {
|
|
277
|
+
const safe = escapeHtml(destination);
|
|
278
|
+
return [
|
|
279
|
+
"<!doctype html>",
|
|
280
|
+
"<html>",
|
|
281
|
+
"<head>",
|
|
282
|
+
`<meta http-equiv=\"refresh\" content=\"0;url=${safe}\">`,
|
|
283
|
+
`<script>window.location.replace(${JSON.stringify(destination)});</script>`,
|
|
284
|
+
"</head>",
|
|
285
|
+
"<body>",
|
|
286
|
+
`<a href=\"${safe}\">Redirecting...</a>`,
|
|
287
|
+
"</body>",
|
|
288
|
+
"</html>",
|
|
289
|
+
].join("");
|
|
290
|
+
};
|
|
291
|
+
const renderStaticErrorDocument = (message) => {
|
|
292
|
+
const safe = escapeHtml(message);
|
|
293
|
+
return [
|
|
294
|
+
"<!doctype html>",
|
|
295
|
+
"<html>",
|
|
296
|
+
"<head>",
|
|
297
|
+
"<title>Route data error</title>",
|
|
298
|
+
"</head>",
|
|
299
|
+
"<body>",
|
|
300
|
+
`<main>${safe}</main>`,
|
|
301
|
+
"</body>",
|
|
302
|
+
"</html>",
|
|
303
|
+
].join("");
|
|
304
|
+
};
|
|
119
305
|
const renderDevDocument = (input) => {
|
|
120
306
|
const htmlAttrs = input.ssrPreview ? ' data-hyper-ssr="true"' : "";
|
|
121
307
|
const buildVersionMeta = input.buildVersion
|
|
@@ -124,19 +310,63 @@ const renderDevDocument = (input) => {
|
|
|
124
310
|
const buildVersionAttr = input.buildVersion
|
|
125
311
|
? ` data-hyper-build-version=\"${escapeHtml(input.buildVersion)}\"`
|
|
126
312
|
: "";
|
|
313
|
+
const styles = input.styles ?? [];
|
|
314
|
+
const stylesHtml = renderStyleLinks(styles);
|
|
127
315
|
const appAttrs = input.ssrPreview
|
|
128
316
|
? ` data-hyper-route=\"${escapeHtml(input.routeId)}\" data-hyper-ssr=\"true\" data-hyper-hydration=\"${input.hydration}\" data-hyper-island-root=\"router\"`
|
|
129
317
|
: ` data-hyper-route=\"${escapeHtml(input.routeId)}\" data-hyper-hydration=\"${input.hydration}\"`;
|
|
318
|
+
const scripts = [
|
|
319
|
+
input.clientBootstrapScriptPath,
|
|
320
|
+
...(input.clientRuntimeScriptPath ? [input.clientRuntimeScriptPath] : []),
|
|
321
|
+
HMR_CLIENT_PATH,
|
|
322
|
+
];
|
|
323
|
+
const documentCtx = {
|
|
324
|
+
html: input.appHtml,
|
|
325
|
+
head: input.head,
|
|
326
|
+
propsPayload: input.propsPayload,
|
|
327
|
+
routeDataPayload: input.routeDataPayload,
|
|
328
|
+
routeId: input.routeId,
|
|
329
|
+
hydration: input.hydration,
|
|
330
|
+
scripts,
|
|
331
|
+
legacyScripts: [],
|
|
332
|
+
styles,
|
|
333
|
+
scriptType: "module",
|
|
334
|
+
buildVersion: input.buildVersion ?? "",
|
|
335
|
+
};
|
|
336
|
+
if (input.renderDocument) {
|
|
337
|
+
try {
|
|
338
|
+
const custom = input.renderDocument(documentCtx);
|
|
339
|
+
if (typeof custom === "string") {
|
|
340
|
+
return custom;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
// Fall back to default document if custom render fails.
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (input.renderDocumentFragments) {
|
|
348
|
+
try {
|
|
349
|
+
const fragments = input.renderDocumentFragments(documentCtx);
|
|
350
|
+
if (fragments && typeof fragments.prefix === "string" && typeof fragments.suffix === "string") {
|
|
351
|
+
return `${fragments.prefix}${input.appHtml}${fragments.suffix}`;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
// Fall back to default document if fragments fail.
|
|
356
|
+
}
|
|
357
|
+
}
|
|
130
358
|
return [
|
|
131
359
|
"<!doctype html>",
|
|
132
360
|
`<html${htmlAttrs}${buildVersionAttr}>`,
|
|
133
361
|
"<head>",
|
|
134
362
|
renderHeadDescriptor(input.head),
|
|
135
363
|
buildVersionMeta,
|
|
364
|
+
stylesHtml,
|
|
136
365
|
"</head>",
|
|
137
366
|
"<body>",
|
|
138
367
|
`<div id=\"app\"${appAttrs}>${input.appHtml}</div>`,
|
|
139
368
|
`<script id=\"__HYPER_PROPS__\" type=\"application/json\">${input.propsPayload}</script>`,
|
|
369
|
+
`<script id=\"${ROUTE_DATA_SCRIPT_ID}\" type=\"application/json\">${input.routeDataPayload}</script>`,
|
|
140
370
|
`<script>window.__HYPER_ROUTE_ID__ = ${JSON.stringify(input.routeId)};</script>`,
|
|
141
371
|
`<script type=\"module\" src=\"${input.clientBootstrapScriptPath}\"></script>`,
|
|
142
372
|
...(input.clientRuntimeScriptPath
|
|
@@ -152,6 +382,7 @@ const buildRoutePayload = (rendered) => ({
|
|
|
152
382
|
routeId: rendered.routeId,
|
|
153
383
|
appHtml: rendered.appHtml,
|
|
154
384
|
propsPayload: rendered.propsPayload,
|
|
385
|
+
routeDataPayload: rendered.routeDataPayload,
|
|
155
386
|
clientRuntimeScriptPath: rendered.clientRuntimeScriptPath,
|
|
156
387
|
head: rendered.head,
|
|
157
388
|
hydration: rendered.hydration,
|
|
@@ -217,6 +448,7 @@ const isJavaScriptOutputAsset = (filePath) => {
|
|
|
217
448
|
const ext = extname(filePath);
|
|
218
449
|
return ext === ".js" || ext === ".mjs";
|
|
219
450
|
};
|
|
451
|
+
const isStyleOutputAsset = (filePath) => extname(filePath) === ".css";
|
|
220
452
|
const isRuntimeOutputAsset = (filePath) => {
|
|
221
453
|
const ext = extname(filePath);
|
|
222
454
|
return ext === ".js" || ext === ".mjs" || ext === ".css";
|
|
@@ -280,6 +512,7 @@ export const startDevServer = async (options = {}) => {
|
|
|
280
512
|
const port = options.port ?? 3000;
|
|
281
513
|
const rootDir = options.rootDir ?? process.cwd();
|
|
282
514
|
const config = await loadConfig(rootDir);
|
|
515
|
+
const hyperPlugins = config.plugins;
|
|
283
516
|
const buildVersion = config.build.version.trim();
|
|
284
517
|
// SSR preview is allowed only when both --ssr and config.ssr are enabled.
|
|
285
518
|
const ssrPreviewEnabled = Boolean(options.ssr) && config.ssr;
|
|
@@ -293,6 +526,7 @@ export const startDevServer = async (options = {}) => {
|
|
|
293
526
|
navigationMode: ssrClientRoutingEnabled ? "client" : config.routing.navigation,
|
|
294
527
|
clientRenderMode: config.routing.clientRender,
|
|
295
528
|
linkInterceptionMode: ssrClientRoutingEnabled ? "all" : "marked",
|
|
529
|
+
scrollRestoration: config.routing.scrollRestoration,
|
|
296
530
|
});
|
|
297
531
|
const routesDir = options.routesDir ?? config.routesDir;
|
|
298
532
|
const publicDir = options.publicDir ?? config.publicDir;
|
|
@@ -318,6 +552,7 @@ export const startDevServer = async (options = {}) => {
|
|
|
318
552
|
const isIgnoredRouteGraphPath = (targetPath) => ignoredRouteGraphRoots.some((ignoredRoot) => isPathWithinRoot(ignoredRoot, targetPath));
|
|
319
553
|
const filterRouteGraph = (graph) => ({
|
|
320
554
|
routes: graph.routes.filter((route) => !isIgnoredRouteGraphPath(route.filePath)),
|
|
555
|
+
specialFiles: graph.specialFiles,
|
|
321
556
|
});
|
|
322
557
|
let routeGraph = filterRouteGraph(await loadRouteGraph(resolvedRoutesDir, config.routing.routeMeta, config.routing.nestedLayouts));
|
|
323
558
|
const inspector = createDevInspector();
|
|
@@ -344,12 +579,34 @@ export const startDevServer = async (options = {}) => {
|
|
|
344
579
|
}
|
|
345
580
|
return { byFile };
|
|
346
581
|
};
|
|
582
|
+
const resolveSpecialRouteRecords = (graph, routesDir) => {
|
|
583
|
+
const records = { notFound: null, error: null };
|
|
584
|
+
if (graph.specialFiles?.notFound) {
|
|
585
|
+
const resolution = collectRouteLayouts(graph.specialFiles.notFound, routesDir);
|
|
586
|
+
records.notFound = {
|
|
587
|
+
id: SPECIAL_ROUTE_IDS.notFound,
|
|
588
|
+
filePath: graph.specialFiles.notFound,
|
|
589
|
+
layoutFiles: resolution.layoutFiles,
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
if (graph.specialFiles?.error) {
|
|
593
|
+
const resolution = collectRouteLayouts(graph.specialFiles.error, routesDir);
|
|
594
|
+
records.error = {
|
|
595
|
+
id: SPECIAL_ROUTE_IDS.error,
|
|
596
|
+
filePath: graph.specialFiles.error,
|
|
597
|
+
layoutFiles: resolution.layoutFiles,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
return records;
|
|
601
|
+
};
|
|
347
602
|
let routeIndex = buildRouteIndex(routeGraph);
|
|
348
603
|
let layoutIndex = buildLayoutIndex(routeGraph);
|
|
604
|
+
let specialRouteRecords = resolveSpecialRouteRecords(routeGraph, resolvedRoutesDir);
|
|
349
605
|
const setRouteGraph = (graph) => {
|
|
350
606
|
routeGraph = graph;
|
|
351
607
|
routeIndex = buildRouteIndex(graph);
|
|
352
608
|
layoutIndex = buildLayoutIndex(graph);
|
|
609
|
+
specialRouteRecords = resolveSpecialRouteRecords(graph, resolvedRoutesDir);
|
|
353
610
|
};
|
|
354
611
|
const uiAdapter = typeof config.ui.adapter === "string"
|
|
355
612
|
? config.ui.adapter
|
|
@@ -506,11 +763,19 @@ export const startDevServer = async (options = {}) => {
|
|
|
506
763
|
const propsPath = await resolveCoreShimModulePath(entryDir, "props");
|
|
507
764
|
const renderPolicyPath = await resolveCoreShimModulePath(entryDir, "render-policy");
|
|
508
765
|
const resolverFallbackPath = await resolveCoreShimModulePath(entryDir, "resolver-fallback");
|
|
766
|
+
const routeDataPath = await resolveCoreShimModulePath(entryDir, "route-data");
|
|
509
767
|
hyperCoreBrowserShimSource = [
|
|
510
768
|
`export { mergeHeadDescriptors } from ${JSON.stringify(headPath)};`,
|
|
511
769
|
`export { serializeProps } from ${JSON.stringify(propsPath)};`,
|
|
512
770
|
`export { evaluateRenderPolicy } from ${JSON.stringify(renderPolicyPath)};`,
|
|
513
771
|
`export { shouldForceDynamicFallback } from ${JSON.stringify(resolverFallbackPath)};`,
|
|
772
|
+
`export {`,
|
|
773
|
+
` ROUTE_DATA_SCRIPT_ID,`,
|
|
774
|
+
` ROUTE_DATA_INIT_KEY,`,
|
|
775
|
+
` ROUTE_DATA_ERROR_KEY,`,
|
|
776
|
+
` routeDataKeyForPage,`,
|
|
777
|
+
` routeDataKeyForLayout,`,
|
|
778
|
+
`} from ${JSON.stringify(routeDataPath)};`,
|
|
514
779
|
].join("\n");
|
|
515
780
|
return hyperCoreBrowserShimSource;
|
|
516
781
|
};
|
|
@@ -564,6 +829,7 @@ export const startDevServer = async (options = {}) => {
|
|
|
564
829
|
routeGraph,
|
|
565
830
|
rootDir: resolvedRootDir,
|
|
566
831
|
routeRoot: loaded.routeRoot,
|
|
832
|
+
appModule: routeGraph.specialFiles?.app,
|
|
567
833
|
routeRender: pageRender,
|
|
568
834
|
routeHead: pageHead,
|
|
569
835
|
}, options.adapterRegistry);
|
|
@@ -579,6 +845,35 @@ export const startDevServer = async (options = {}) => {
|
|
|
579
845
|
}
|
|
580
846
|
return normalizedModules;
|
|
581
847
|
};
|
|
848
|
+
const SERVER_ONLY_MODULE_PATTERN = /\.server\.[cm]?[jt]sx?$/i;
|
|
849
|
+
const collectServerOnlyModules = (modules) => {
|
|
850
|
+
const offenders = [];
|
|
851
|
+
for (const modulePath of modules) {
|
|
852
|
+
if (SERVER_ONLY_MODULE_PATTERN.test(modulePath)) {
|
|
853
|
+
offenders.push(modulePath);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return offenders;
|
|
857
|
+
};
|
|
858
|
+
const assertNoServerOnlyClientImports = (routeId, modules) => {
|
|
859
|
+
const offenders = collectServerOnlyModules(modules);
|
|
860
|
+
const appModule = routeGraph.specialFiles?.app;
|
|
861
|
+
const appOffenders = appModule
|
|
862
|
+
? collectServerOnlyModules(buildModuleGraphSnapshot([{ id: "__hyper_app__", filePath: appModule }])
|
|
863
|
+
.routeToModules.__hyper_app__ ?? [])
|
|
864
|
+
: [];
|
|
865
|
+
if (offenders.length === 0 && appOffenders.length === 0) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
const details = [];
|
|
869
|
+
if (offenders.length > 0) {
|
|
870
|
+
details.push(`route "${routeId}": ${offenders.join(", ")}`);
|
|
871
|
+
}
|
|
872
|
+
if (appOffenders.length > 0) {
|
|
873
|
+
details.push(`app: ${appOffenders.join(", ")}`);
|
|
874
|
+
}
|
|
875
|
+
throw new Error(`Server-only modules (.server.*) cannot be imported into client bundles. Found ${details.join(" | ")}.`);
|
|
876
|
+
};
|
|
582
877
|
const shouldCopyRouteSnapshotModule = (modulePath) => {
|
|
583
878
|
if (!isPathWithinRoot(resolvedRootDir, modulePath)) {
|
|
584
879
|
return false;
|
|
@@ -643,31 +938,41 @@ export const startDevServer = async (options = {}) => {
|
|
|
643
938
|
if (typeof adapter.createClientEntry !== "function") {
|
|
644
939
|
return undefined;
|
|
645
940
|
}
|
|
941
|
+
// Guard: client runtime bundling must not include server-only modules.
|
|
942
|
+
assertNoServerOnlyClientImports(route.id, resolveRouteModulesForRender(route));
|
|
646
943
|
const routeRoot = loaded.routeRoot;
|
|
647
944
|
const pageModule = `./${loaded.routeRelativePath.split(sep).join("/")}`;
|
|
648
945
|
const routeModuleMap = routeGraph.routes.reduce((acc, routeRecord) => {
|
|
649
946
|
acc[routeRecord.id] = toImportSpecifier(routeRoot, routeRecord.filePath);
|
|
650
947
|
return acc;
|
|
651
948
|
}, {});
|
|
949
|
+
const appModule = routeGraph.specialFiles?.app
|
|
950
|
+
? toImportSpecifier(routeRoot, routeGraph.specialFiles.app)
|
|
951
|
+
: undefined;
|
|
652
952
|
const entrySource = adapter.createClientEntry({
|
|
653
953
|
routeId: route.id,
|
|
654
954
|
routeGraph,
|
|
655
955
|
rootDir: resolvedRootDir,
|
|
656
956
|
entryDir: routeRoot,
|
|
657
957
|
routeRoot,
|
|
958
|
+
entryLayoutFiles: route.layoutFiles,
|
|
658
959
|
uiOptions: {
|
|
659
960
|
...config.ui.options,
|
|
660
961
|
pageModule,
|
|
962
|
+
appModule,
|
|
661
963
|
navigationMode: config.routing.navigation,
|
|
662
964
|
clientRenderMode: config.routing.clientRender,
|
|
965
|
+
scrollRestoration: config.routing.scrollRestoration,
|
|
663
966
|
clientRouteModules: routeModuleMap,
|
|
664
967
|
nestedLayouts: config.routing.nestedLayouts,
|
|
665
968
|
},
|
|
666
969
|
adapterOptions: {
|
|
667
970
|
...config.ui.options,
|
|
668
971
|
pageModule,
|
|
972
|
+
appModule,
|
|
669
973
|
navigationMode: config.routing.navigation,
|
|
670
974
|
clientRenderMode: config.routing.clientRender,
|
|
975
|
+
scrollRestoration: config.routing.scrollRestoration,
|
|
671
976
|
clientRouteModules: routeModuleMap,
|
|
672
977
|
nestedLayouts: config.routing.nestedLayouts,
|
|
673
978
|
},
|
|
@@ -689,6 +994,23 @@ export const startDevServer = async (options = {}) => {
|
|
|
689
994
|
const runtimeDependencyAliasPattern = runtimeDependencyAliasSpecifiers.length > 0
|
|
690
995
|
? new RegExp(`^(?:${runtimeDependencyAliasSpecifiers.map((specifier) => escapeRegExp(specifier)).join("|")})$`)
|
|
691
996
|
: null;
|
|
997
|
+
const hookPlugins = [];
|
|
998
|
+
if (hyperPlugins.length > 0) {
|
|
999
|
+
await runBeforeBundleHooks(hyperPlugins, {
|
|
1000
|
+
phase: "dev",
|
|
1001
|
+
entryId: route.id,
|
|
1002
|
+
entryPath: entryFilePath,
|
|
1003
|
+
target: "browser",
|
|
1004
|
+
format: "esm",
|
|
1005
|
+
syntaxTarget: "modern",
|
|
1006
|
+
bundler: "bun",
|
|
1007
|
+
bundleType: "client",
|
|
1008
|
+
bundlerPlugins: hookPlugins,
|
|
1009
|
+
addBundlerPlugin: (plugin) => {
|
|
1010
|
+
hookPlugins.push(plugin);
|
|
1011
|
+
},
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
692
1014
|
const plugins = [
|
|
693
1015
|
{
|
|
694
1016
|
name: "@tyndall/core-browser-shim",
|
|
@@ -721,6 +1043,7 @@ export const startDevServer = async (options = {}) => {
|
|
|
721
1043
|
},
|
|
722
1044
|
]
|
|
723
1045
|
: []),
|
|
1046
|
+
...hookPlugins,
|
|
724
1047
|
];
|
|
725
1048
|
let buildResult;
|
|
726
1049
|
for (let attempt = 0; attempt <= DEV_RUNTIME_BUILD_RETRY_COUNT; attempt += 1) {
|
|
@@ -772,6 +1095,7 @@ export const startDevServer = async (options = {}) => {
|
|
|
772
1095
|
}
|
|
773
1096
|
const assetPrefix = `${getRouteAssetPrefix(route.id)}${loaded.version}/`;
|
|
774
1097
|
let scriptPath;
|
|
1098
|
+
const stylePaths = [];
|
|
775
1099
|
for (const outputPath of outputAssets) {
|
|
776
1100
|
const relativeOutputPath = normalizePath(relative(outDir, outputPath));
|
|
777
1101
|
if (relativeOutputPath.length === 0 ||
|
|
@@ -781,6 +1105,9 @@ export const startDevServer = async (options = {}) => {
|
|
|
781
1105
|
}
|
|
782
1106
|
const publicPath = `${assetPrefix}${relativeOutputPath}`;
|
|
783
1107
|
devClientAssets.set(publicPath, outputPath);
|
|
1108
|
+
if (isStyleOutputAsset(outputPath)) {
|
|
1109
|
+
stylePaths.push(publicPath);
|
|
1110
|
+
}
|
|
784
1111
|
if (!scriptPath &&
|
|
785
1112
|
isJavaScriptOutputAsset(outputPath) &&
|
|
786
1113
|
(relativeOutputPath === "__hyper_client_entry.js" ||
|
|
@@ -800,7 +1127,159 @@ export const startDevServer = async (options = {}) => {
|
|
|
800
1127
|
}
|
|
801
1128
|
trackRouteAssetVersion(route.id, loaded.version);
|
|
802
1129
|
pruneStaleRouteAssetVersions(route.id);
|
|
803
|
-
return scriptPath;
|
|
1130
|
+
return scriptPath ? { scriptPath, stylePaths } : undefined;
|
|
1131
|
+
};
|
|
1132
|
+
const loadInitServerModule = async () => {
|
|
1133
|
+
const initServerPath = routeGraph.specialFiles?.initServer;
|
|
1134
|
+
if (!initServerPath) {
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
try {
|
|
1138
|
+
return await import(`${pathToFileURL(initServerPath).href}?dev=${Date.now()}`);
|
|
1139
|
+
}
|
|
1140
|
+
catch {
|
|
1141
|
+
return null;
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
const loadDocumentModule = async () => {
|
|
1145
|
+
const documentPath = routeGraph.specialFiles?.document;
|
|
1146
|
+
if (!documentPath) {
|
|
1147
|
+
return null;
|
|
1148
|
+
}
|
|
1149
|
+
try {
|
|
1150
|
+
return await import(`${pathToFileURL(documentPath).href}?dev=${Date.now()}`);
|
|
1151
|
+
}
|
|
1152
|
+
catch {
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
const resolveDocumentRenderers = async () => {
|
|
1157
|
+
const module = await loadDocumentModule();
|
|
1158
|
+
if (!module || typeof module !== "object") {
|
|
1159
|
+
return { renderDocument: undefined, renderDocumentFragments: undefined };
|
|
1160
|
+
}
|
|
1161
|
+
const record = module;
|
|
1162
|
+
const renderDocument = typeof record.renderDocument === "function"
|
|
1163
|
+
? record.renderDocument
|
|
1164
|
+
: typeof record.default === "function"
|
|
1165
|
+
? record.default
|
|
1166
|
+
: undefined;
|
|
1167
|
+
const renderDocumentFragments = typeof record.renderDocumentFragments === "function"
|
|
1168
|
+
? record.renderDocumentFragments
|
|
1169
|
+
: undefined;
|
|
1170
|
+
return { renderDocument, renderDocumentFragments };
|
|
1171
|
+
};
|
|
1172
|
+
const renderSpecialRoute = async (input) => {
|
|
1173
|
+
const specialRoute = {
|
|
1174
|
+
id: input.record.id,
|
|
1175
|
+
filePath: input.record.filePath,
|
|
1176
|
+
segments: [],
|
|
1177
|
+
layoutFiles: input.record.layoutFiles,
|
|
1178
|
+
};
|
|
1179
|
+
const loaded = await loadRouteModule(specialRoute);
|
|
1180
|
+
const page = resolvePageModule(loaded.module);
|
|
1181
|
+
const adapter = resolveAdapterForRoute(specialRoute, loaded, (props) => page.default(props), page.head ? (props) => page.head?.(props) ?? {} : undefined);
|
|
1182
|
+
if (typeof adapter.renderToHtml !== "function") {
|
|
1183
|
+
throw new Error(`UI adapter \"${adapter.name}\" does not support renderToHtml.`);
|
|
1184
|
+
}
|
|
1185
|
+
const loadLayoutModule = async (layoutFile) => {
|
|
1186
|
+
if (loaded.routeRoot) {
|
|
1187
|
+
const relativePath = relative(resolvedRootDir, layoutFile);
|
|
1188
|
+
if (relativePath && !relativePath.startsWith(`..${sep}`) && relativePath !== "..") {
|
|
1189
|
+
const snapshotPath = join(loaded.routeRoot, relativePath);
|
|
1190
|
+
try {
|
|
1191
|
+
return await import(pathToFileURL(snapshotPath).href);
|
|
1192
|
+
}
|
|
1193
|
+
catch {
|
|
1194
|
+
// Fall back to direct import.
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
return await import(`${pathToFileURL(layoutFile).href}?dev=${Date.now()}`);
|
|
1199
|
+
};
|
|
1200
|
+
const initServerModule = await loadInitServerModule();
|
|
1201
|
+
const initServerHook = resolveInitServerHook(initServerModule);
|
|
1202
|
+
const routeDataResult = await collectRouteDataForRoute({
|
|
1203
|
+
routeId: specialRoute.id,
|
|
1204
|
+
routesDir: resolvedRoutesDir,
|
|
1205
|
+
params: undefined,
|
|
1206
|
+
layoutFiles: specialRoute.layoutFiles,
|
|
1207
|
+
pageModule: page,
|
|
1208
|
+
initServerHook,
|
|
1209
|
+
request: input.request,
|
|
1210
|
+
response: input.response,
|
|
1211
|
+
loadLayoutModule,
|
|
1212
|
+
});
|
|
1213
|
+
const mergedRouteData = { ...routeDataResult.data, ...(input.extraRouteData ?? {}) };
|
|
1214
|
+
const serverProps = page.getServerProps
|
|
1215
|
+
? await runGetServerProps(page.getServerProps, {
|
|
1216
|
+
routeId: specialRoute.id,
|
|
1217
|
+
params: undefined,
|
|
1218
|
+
request: input.request,
|
|
1219
|
+
response: input.response,
|
|
1220
|
+
init: routeDataResult.initData,
|
|
1221
|
+
routeData: mergedRouteData,
|
|
1222
|
+
})
|
|
1223
|
+
: null;
|
|
1224
|
+
const props = {
|
|
1225
|
+
...(serverProps?.props ?? {}),
|
|
1226
|
+
...(input.extraProps ?? {}),
|
|
1227
|
+
};
|
|
1228
|
+
const rendered = await adapter.renderToHtml({
|
|
1229
|
+
routeId: specialRoute.id,
|
|
1230
|
+
params: undefined,
|
|
1231
|
+
props,
|
|
1232
|
+
routeData: mergedRouteData,
|
|
1233
|
+
});
|
|
1234
|
+
const payload = adapter.serializeProps?.(props) ?? serializeProps(props);
|
|
1235
|
+
const routeDataPayload = adapter.serializeProps?.(mergedRouteData) ?? serializeProps(mergedRouteData);
|
|
1236
|
+
let clientRuntimeScriptPath;
|
|
1237
|
+
let clientRuntimeStyles = [];
|
|
1238
|
+
try {
|
|
1239
|
+
const runtimeAssets = await buildClientRuntimeScript(specialRoute, loaded, adapter);
|
|
1240
|
+
clientRuntimeScriptPath = runtimeAssets?.scriptPath;
|
|
1241
|
+
clientRuntimeStyles = runtimeAssets?.stylePaths ?? [];
|
|
1242
|
+
}
|
|
1243
|
+
catch (error) {
|
|
1244
|
+
const info = toErrorInfo(error);
|
|
1245
|
+
logger.warn("Dev client runtime build failed for special route", {
|
|
1246
|
+
routeId: specialRoute.id,
|
|
1247
|
+
message: info.message,
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
const hydration = ssrPreviewEnabled ? "islands" : "full";
|
|
1251
|
+
const documentRenderers = await resolveDocumentRenderers();
|
|
1252
|
+
const html = renderDevDocument({
|
|
1253
|
+
routeId: specialRoute.id,
|
|
1254
|
+
appHtml: rendered.html,
|
|
1255
|
+
head: rendered.head ?? {},
|
|
1256
|
+
propsPayload: payload,
|
|
1257
|
+
routeDataPayload,
|
|
1258
|
+
ssrPreview: ssrPreviewEnabled,
|
|
1259
|
+
hydration,
|
|
1260
|
+
clientBootstrapScriptPath: CLIENT_ROUTER_BOOTSTRAP_PATH,
|
|
1261
|
+
clientRuntimeScriptPath,
|
|
1262
|
+
styles: clientRuntimeStyles,
|
|
1263
|
+
buildVersion,
|
|
1264
|
+
...documentRenderers,
|
|
1265
|
+
});
|
|
1266
|
+
return {
|
|
1267
|
+
html,
|
|
1268
|
+
routeId: specialRoute.id,
|
|
1269
|
+
appHtml: rendered.html,
|
|
1270
|
+
head: rendered.head ?? {},
|
|
1271
|
+
propsPayload: payload,
|
|
1272
|
+
routeDataPayload,
|
|
1273
|
+
clientRuntimeScriptPath,
|
|
1274
|
+
styles: clientRuntimeStyles,
|
|
1275
|
+
hydration,
|
|
1276
|
+
status: input.statusOverride ?? rendered.status ?? serverProps?.status ?? routeDataResult.status ?? 200,
|
|
1277
|
+
headers: {
|
|
1278
|
+
...routeDataResult.headers,
|
|
1279
|
+
...(serverProps?.headers ?? {}),
|
|
1280
|
+
...(rendered.headers ?? {}),
|
|
1281
|
+
},
|
|
1282
|
+
};
|
|
804
1283
|
};
|
|
805
1284
|
const renderRoute = async (match, request, response) => {
|
|
806
1285
|
const loaded = await loadRouteModule(match.route);
|
|
@@ -809,6 +1288,99 @@ export const startDevServer = async (options = {}) => {
|
|
|
809
1288
|
if (typeof adapter.renderToHtml !== "function") {
|
|
810
1289
|
throw new Error(`UI adapter "${adapter.name}" does not support renderToHtml.`);
|
|
811
1290
|
}
|
|
1291
|
+
const loadLayoutModule = async (layoutFile) => {
|
|
1292
|
+
if (loaded.routeRoot) {
|
|
1293
|
+
const relativePath = relative(resolvedRootDir, layoutFile);
|
|
1294
|
+
if (relativePath && !relativePath.startsWith(`..${sep}`) && relativePath !== "..") {
|
|
1295
|
+
const snapshotPath = join(loaded.routeRoot, relativePath);
|
|
1296
|
+
try {
|
|
1297
|
+
return await import(pathToFileURL(snapshotPath).href);
|
|
1298
|
+
}
|
|
1299
|
+
catch {
|
|
1300
|
+
// Fall back to direct import.
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
return await import(`${pathToFileURL(layoutFile).href}?dev=${Date.now()}`);
|
|
1305
|
+
};
|
|
1306
|
+
const initServerModule = await loadInitServerModule();
|
|
1307
|
+
const initServerHook = resolveInitServerHook(initServerModule);
|
|
1308
|
+
const routeDataResult = await collectRouteDataForRoute({
|
|
1309
|
+
routeId: match.route.id,
|
|
1310
|
+
routesDir: resolvedRoutesDir,
|
|
1311
|
+
params: match.params,
|
|
1312
|
+
layoutFiles: match.route.layoutFiles,
|
|
1313
|
+
pageModule: page,
|
|
1314
|
+
initServerHook,
|
|
1315
|
+
request,
|
|
1316
|
+
response,
|
|
1317
|
+
loadLayoutModule,
|
|
1318
|
+
});
|
|
1319
|
+
const redirect = normalizeRedirect(routeDataResult.redirect);
|
|
1320
|
+
if (redirect) {
|
|
1321
|
+
return {
|
|
1322
|
+
html: renderStaticRedirectDocument(redirect.destination),
|
|
1323
|
+
routeId: match.route.id,
|
|
1324
|
+
appHtml: "",
|
|
1325
|
+
head: { title: `Redirect ${match.route.id}` },
|
|
1326
|
+
propsPayload: serializeProps({}),
|
|
1327
|
+
routeDataPayload: serializeProps(routeDataResult.data),
|
|
1328
|
+
hydration: ssrPreviewEnabled ? "islands" : "full",
|
|
1329
|
+
status: redirect.status ?? routeDataResult.status ?? 302,
|
|
1330
|
+
headers: {
|
|
1331
|
+
...routeDataResult.headers,
|
|
1332
|
+
location: redirect.destination,
|
|
1333
|
+
},
|
|
1334
|
+
redirect,
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
if (routeDataResult.error) {
|
|
1338
|
+
const normalizedError = normalizeError(routeDataResult.error);
|
|
1339
|
+
if (specialRouteRecords.error) {
|
|
1340
|
+
return await renderSpecialRoute({
|
|
1341
|
+
record: specialRouteRecords.error,
|
|
1342
|
+
request,
|
|
1343
|
+
response,
|
|
1344
|
+
extraProps: {
|
|
1345
|
+
error: normalizedError,
|
|
1346
|
+
routeId: match.route.id,
|
|
1347
|
+
},
|
|
1348
|
+
extraRouteData: normalizedError
|
|
1349
|
+
? {
|
|
1350
|
+
[ROUTE_DATA_ERROR_KEY]: normalizedError,
|
|
1351
|
+
}
|
|
1352
|
+
: undefined,
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
const message = normalizedError?.message ?? "Route data error";
|
|
1356
|
+
const appErrorHtml = `<main><h1>Route Data Error</h1><pre>${escapeHtml(message)}</pre></main>`;
|
|
1357
|
+
const documentRenderers = await resolveDocumentRenderers();
|
|
1358
|
+
const html = renderDevDocument({
|
|
1359
|
+
routeId: match.route.id,
|
|
1360
|
+
appHtml: appErrorHtml,
|
|
1361
|
+
head: { title: `Dev Error ${match.route.id}` },
|
|
1362
|
+
propsPayload: serializeProps({}),
|
|
1363
|
+
routeDataPayload: serializeProps(routeDataResult.data),
|
|
1364
|
+
ssrPreview: ssrPreviewEnabled,
|
|
1365
|
+
hydration: ssrPreviewEnabled ? "islands" : "full",
|
|
1366
|
+
clientBootstrapScriptPath: CLIENT_ROUTER_BOOTSTRAP_PATH,
|
|
1367
|
+
buildVersion,
|
|
1368
|
+
...documentRenderers,
|
|
1369
|
+
});
|
|
1370
|
+
return {
|
|
1371
|
+
html,
|
|
1372
|
+
routeId: match.route.id,
|
|
1373
|
+
appHtml: appErrorHtml,
|
|
1374
|
+
head: { title: `Dev Error ${match.route.id}` },
|
|
1375
|
+
propsPayload: serializeProps({}),
|
|
1376
|
+
routeDataPayload: serializeProps(routeDataResult.data),
|
|
1377
|
+
hydration: ssrPreviewEnabled ? "islands" : "full",
|
|
1378
|
+
status: normalizedError?.status ?? routeDataResult.status ?? 500,
|
|
1379
|
+
headers: {
|
|
1380
|
+
...routeDataResult.headers,
|
|
1381
|
+
},
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
812
1384
|
// Run server props in dev whenever the route exports it so page contracts stay consistent.
|
|
813
1385
|
const serverProps = page.getServerProps
|
|
814
1386
|
? await runGetServerProps(page.getServerProps, {
|
|
@@ -816,6 +1388,8 @@ export const startDevServer = async (options = {}) => {
|
|
|
816
1388
|
params: match.params,
|
|
817
1389
|
request,
|
|
818
1390
|
response,
|
|
1391
|
+
init: routeDataResult.initData,
|
|
1392
|
+
routeData: routeDataResult.data,
|
|
819
1393
|
})
|
|
820
1394
|
: null;
|
|
821
1395
|
const props = serverProps?.props ?? {};
|
|
@@ -823,11 +1397,16 @@ export const startDevServer = async (options = {}) => {
|
|
|
823
1397
|
routeId: match.route.id,
|
|
824
1398
|
params: match.params,
|
|
825
1399
|
props,
|
|
1400
|
+
routeData: routeDataResult.data,
|
|
826
1401
|
});
|
|
827
1402
|
const payload = adapter.serializeProps?.(props) ?? serializeProps(props);
|
|
1403
|
+
const routeDataPayload = adapter.serializeProps?.(routeDataResult.data) ?? serializeProps(routeDataResult.data);
|
|
828
1404
|
let clientRuntimeScriptPath;
|
|
1405
|
+
let clientRuntimeStyles = [];
|
|
829
1406
|
try {
|
|
830
|
-
|
|
1407
|
+
const runtimeAssets = await buildClientRuntimeScript(match.route, loaded, adapter);
|
|
1408
|
+
clientRuntimeScriptPath = runtimeAssets?.scriptPath;
|
|
1409
|
+
clientRuntimeStyles = runtimeAssets?.stylePaths ?? [];
|
|
831
1410
|
}
|
|
832
1411
|
catch (error) {
|
|
833
1412
|
const info = toErrorInfo(error);
|
|
@@ -837,16 +1416,20 @@ export const startDevServer = async (options = {}) => {
|
|
|
837
1416
|
});
|
|
838
1417
|
}
|
|
839
1418
|
const hydration = ssrPreviewEnabled ? "islands" : "full";
|
|
1419
|
+
const documentRenderers = await resolveDocumentRenderers();
|
|
840
1420
|
const html = renderDevDocument({
|
|
841
1421
|
routeId: match.route.id,
|
|
842
1422
|
appHtml: rendered.html,
|
|
843
1423
|
head: rendered.head ?? {},
|
|
844
1424
|
propsPayload: payload,
|
|
1425
|
+
routeDataPayload,
|
|
845
1426
|
ssrPreview: ssrPreviewEnabled,
|
|
846
1427
|
hydration,
|
|
847
1428
|
clientBootstrapScriptPath: CLIENT_ROUTER_BOOTSTRAP_PATH,
|
|
848
1429
|
clientRuntimeScriptPath,
|
|
1430
|
+
styles: clientRuntimeStyles,
|
|
849
1431
|
buildVersion,
|
|
1432
|
+
...documentRenderers,
|
|
850
1433
|
});
|
|
851
1434
|
return {
|
|
852
1435
|
html,
|
|
@@ -854,10 +1437,13 @@ export const startDevServer = async (options = {}) => {
|
|
|
854
1437
|
appHtml: rendered.html,
|
|
855
1438
|
head: rendered.head ?? {},
|
|
856
1439
|
propsPayload: payload,
|
|
1440
|
+
routeDataPayload,
|
|
857
1441
|
clientRuntimeScriptPath,
|
|
1442
|
+
styles: clientRuntimeStyles,
|
|
858
1443
|
hydration,
|
|
859
|
-
status: rendered.status ?? serverProps?.status ?? 200,
|
|
1444
|
+
status: rendered.status ?? serverProps?.status ?? routeDataResult.status ?? 200,
|
|
860
1445
|
headers: {
|
|
1446
|
+
...routeDataResult.headers,
|
|
861
1447
|
...(serverProps?.headers ?? {}),
|
|
862
1448
|
...(rendered.headers ?? {}),
|
|
863
1449
|
},
|
|
@@ -1050,18 +1636,48 @@ export const startDevServer = async (options = {}) => {
|
|
|
1050
1636
|
}
|
|
1051
1637
|
response.statusCode = 200;
|
|
1052
1638
|
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
1639
|
+
const documentRenderers = await resolveDocumentRenderers();
|
|
1053
1640
|
response.end(renderDevDocument({
|
|
1054
1641
|
routeId: pathname,
|
|
1055
1642
|
appHtml: `<main data-hyper-dynamic=\"true\">Dynamic Route: ${escapeHtml(pathname)}</main>`,
|
|
1056
1643
|
head: { title: `Dynamic ${pathname}` },
|
|
1057
1644
|
propsPayload: serializeProps({}),
|
|
1645
|
+
routeDataPayload: serializeProps({}),
|
|
1058
1646
|
ssrPreview: ssrPreviewEnabled,
|
|
1059
1647
|
hydration: ssrPreviewEnabled ? "islands" : "full",
|
|
1060
1648
|
clientBootstrapScriptPath: CLIENT_ROUTER_BOOTSTRAP_PATH,
|
|
1061
1649
|
buildVersion,
|
|
1650
|
+
...documentRenderers,
|
|
1062
1651
|
}));
|
|
1063
1652
|
return true;
|
|
1064
1653
|
}
|
|
1654
|
+
if (resolved.type === "notfound") {
|
|
1655
|
+
if (!specialRouteRecords.notFound) {
|
|
1656
|
+
return false;
|
|
1657
|
+
}
|
|
1658
|
+
kind = "route";
|
|
1659
|
+
routeId = SPECIAL_ROUTE_IDS.notFound;
|
|
1660
|
+
const navigationRequest = isCsrNavigationRequest(request);
|
|
1661
|
+
const rendered = await renderSpecialRoute({
|
|
1662
|
+
record: specialRouteRecords.notFound,
|
|
1663
|
+
request,
|
|
1664
|
+
response,
|
|
1665
|
+
statusOverride: 404,
|
|
1666
|
+
});
|
|
1667
|
+
response.statusCode = rendered.status;
|
|
1668
|
+
for (const [header, value] of Object.entries(rendered.headers)) {
|
|
1669
|
+
response.setHeader(header, value);
|
|
1670
|
+
}
|
|
1671
|
+
if (navigationRequest) {
|
|
1672
|
+
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
1673
|
+
response.setHeader("x-hyper-navigation", NAVIGATION_MODE);
|
|
1674
|
+
response.end(JSON.stringify(buildRoutePayload(rendered)));
|
|
1675
|
+
return true;
|
|
1676
|
+
}
|
|
1677
|
+
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
1678
|
+
response.end(rendered.html);
|
|
1679
|
+
return true;
|
|
1680
|
+
}
|
|
1065
1681
|
return false;
|
|
1066
1682
|
};
|
|
1067
1683
|
const respondWithRoute = async (match) => {
|
|
@@ -1107,7 +1723,29 @@ export const startDevServer = async (options = {}) => {
|
|
|
1107
1723
|
for (const [header, value] of Object.entries(rendered.headers)) {
|
|
1108
1724
|
response.setHeader(header, value);
|
|
1109
1725
|
}
|
|
1110
|
-
|
|
1726
|
+
if (!rendered.redirect) {
|
|
1727
|
+
routeCache.set(cacheKey, { ...rendered, updatedAt: Date.now() });
|
|
1728
|
+
}
|
|
1729
|
+
if (rendered.redirect) {
|
|
1730
|
+
if (navigationRequest) {
|
|
1731
|
+
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
1732
|
+
response.setHeader("x-hyper-navigation", NAVIGATION_MODE);
|
|
1733
|
+
response.setHeader("x-hyper-route-cache", "miss");
|
|
1734
|
+
response.end(JSON.stringify({
|
|
1735
|
+
kind: "hyper-route-redirect",
|
|
1736
|
+
destination: rendered.redirect.destination,
|
|
1737
|
+
status: rendered.redirect.status ?? rendered.status ?? 302,
|
|
1738
|
+
replace: rendered.redirect.replace ?? false,
|
|
1739
|
+
}));
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
response.statusCode = rendered.redirect.status ?? rendered.status ?? 302;
|
|
1743
|
+
response.setHeader("location", rendered.redirect.destination);
|
|
1744
|
+
response.setHeader("x-hyper-route-cache", "miss");
|
|
1745
|
+
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
1746
|
+
response.end(rendered.html);
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1111
1749
|
if (navigationRequest) {
|
|
1112
1750
|
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
1113
1751
|
response.setHeader("x-hyper-navigation", NAVIGATION_MODE);
|
|
@@ -1135,15 +1773,18 @@ export const startDevServer = async (options = {}) => {
|
|
|
1135
1773
|
hmr.broadcastError(message, stack);
|
|
1136
1774
|
response.statusCode = 500;
|
|
1137
1775
|
response.setHeader("x-hyper-route-cache", "miss");
|
|
1776
|
+
const documentRenderers = await resolveDocumentRenderers();
|
|
1138
1777
|
response.end(renderDevDocument({
|
|
1139
1778
|
routeId: match.route.id,
|
|
1140
1779
|
appHtml: `<main><h1>Dev Render Error</h1><pre>${escapeHtml(message)}</pre></main>`,
|
|
1141
1780
|
head: { title: `Dev Error ${match.route.id}` },
|
|
1142
1781
|
propsPayload: serializeProps({}),
|
|
1782
|
+
routeDataPayload: serializeProps({}),
|
|
1143
1783
|
ssrPreview: ssrPreviewEnabled,
|
|
1144
1784
|
hydration: ssrPreviewEnabled ? "islands" : "full",
|
|
1145
1785
|
clientBootstrapScriptPath: CLIENT_ROUTER_BOOTSTRAP_PATH,
|
|
1146
1786
|
buildVersion,
|
|
1787
|
+
...documentRenderers,
|
|
1147
1788
|
}));
|
|
1148
1789
|
}
|
|
1149
1790
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tyndall/dev",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
"build": "tsc -p tsconfig.json"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@tyndall/core": "
|
|
25
|
-
"@tyndall/dynamic-graph": "
|
|
26
|
-
"@tyndall/shared": "
|
|
24
|
+
"@tyndall/core": "^0.0.2",
|
|
25
|
+
"@tyndall/dynamic-graph": "^0.0.2",
|
|
26
|
+
"@tyndall/shared": "^0.0.2",
|
|
27
27
|
"ws": "^8.18.0"
|
|
28
28
|
}
|
|
29
29
|
}
|