@tyndall/dev 0.0.1 → 0.0.3

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 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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAaL,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAG3B,KAAK,iBAAiB,EAGvB,MAAM,eAAe,CAAC;AAWvB,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;AA4WD,eAAO,MAAM,cAAc,GAAU,UAAS,gBAAqB,KAAG,OAAO,CAAC,SAAS,CAyyCtF,CAAC"}
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
- clientRuntimeScriptPath = await buildClientRuntimeScript(match.route, loaded, adapter);
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
- routeCache.set(cacheKey, { ...rendered, updatedAt: Date.now() });
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.1",
3
+ "version": "0.0.3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -10,7 +10,7 @@
10
10
  "exports": {
11
11
  ".": {
12
12
  "types": "./dist/index.d.ts",
13
- "bun": "./src/index.ts",
13
+ "bun": "./dist/index.js",
14
14
  "default": "./dist/index.js"
15
15
  }
16
16
  },
@@ -21,9 +21,9 @@
21
21
  "build": "tsc -p tsconfig.json"
22
22
  },
23
23
  "dependencies": {
24
- "@tyndall/core": "workspace:*",
25
- "@tyndall/dynamic-graph": "workspace:*",
26
- "@tyndall/shared": "workspace:*",
24
+ "@tyndall/core": "^0.0.3",
25
+ "@tyndall/dynamic-graph": "^0.0.3",
26
+ "@tyndall/shared": "^0.0.3",
27
27
  "ws": "^8.18.0"
28
28
  }
29
29
  }