@webjsdev/server 0.8.22 → 0.8.23

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/ssr.js +17 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/server",
3
- "version": "0.8.22",
3
+ "version": "0.8.23",
4
4
  "type": "module",
5
5
  "description": "webjs dev/prod server: SSR, router, API, server actions, live reload",
6
6
  "main": "index.js",
package/src/ssr.js CHANGED
@@ -82,7 +82,7 @@ export async function ssrPage(route, params, url, opts) {
82
82
  const metadata = await collectMetadata(route, ctx, opts.dev);
83
83
 
84
84
  try {
85
- const suspenseCtx = { pending: [], nextId: 1, usedComponents: new Set() };
85
+ const suspenseCtx = { pending: [], nextId: 1, usedComponents: new Set(), dev: opts.dev };
86
86
  // Parse the partial-nav "have" header from the client. The header
87
87
  // lists comma-separated marker paths the client already has rendered
88
88
  // in its DOM. The server walks the target route's layout chain
@@ -183,6 +183,7 @@ export async function ssrPage(route, params, url, opts) {
183
183
  url,
184
184
  metadata,
185
185
  nonce,
186
+ opts.dev,
186
187
  );
187
188
  // Server HTML cache write (#241). The page opted in via `revalidate`, so
188
189
  // FLAG this candidate for the response funnel rather than writing here: the
@@ -233,7 +234,7 @@ export async function ssrPage(route, params, url, opts) {
233
234
  const mod = await loadModule(route.errors[i], opts.dev);
234
235
  if (!mod.default) continue;
235
236
  const tree = await mod.default({ ...ctx, error: err });
236
- const body = await renderToString(tree);
237
+ const body = await renderToString(tree, { ssr: true, dev: opts.dev });
237
238
  const moduleUrls = [route.file, ...route.layouts].map((f) => toUrlPath(f, opts.appDir));
238
239
  const html = wrapInDocument(body, { metadata, moduleUrls, dev: opts.dev, nonce: errNonce });
239
240
  return htmlResponse(html, 500, opts.req, url);
@@ -333,7 +334,7 @@ async function ssrNotFoundHtml(notFoundFile, opts) {
333
334
  if (notFoundFile) {
334
335
  try {
335
336
  const mod = await loadModule(notFoundFile, opts.dev);
336
- if (mod.default) body = await renderToString(await mod.default({}));
337
+ if (mod.default) body = await renderToString(await mod.default({}), { ssr: true, dev: opts.dev });
337
338
  } catch (e) {
338
339
  body = `<h1>404: Not found</h1><pre>${escapeHtml(String(e))}</pre>`;
339
340
  }
@@ -430,7 +431,7 @@ async function loadingTemplates(route, ctx, dev) {
430
431
  const mod = await loadModule(file, dev);
431
432
  if (!mod.default) continue;
432
433
  const tree = await mod.default(ctx);
433
- const html = await renderToString(tree, { ssr: true });
434
+ const html = await renderToString(tree, { ssr: true, dev });
434
435
  const segmentPath = loadingSegmentPath(file);
435
436
  parts.push(`<template id="wj-loading:${segmentPath}">${html}</template>`);
436
437
  } catch { /* skip broken loading file */ }
@@ -1444,15 +1445,18 @@ function deduplicatedPreloads(componentUrls, moduleUrls, graph, entryFiles, appD
1444
1445
  * Build a streaming Response. Degrades to a single-flush response when
1445
1446
  * there are no pending Suspense boundaries.
1446
1447
  *
1447
- * @param {string} headHtml
1448
+ * @param {string} prefix
1448
1449
  * @param {string} bodyHtml
1450
+ * @param {string} closer
1449
1451
  * @param {{ pending: {id: string, promise: Promise<unknown>}[], nextId: number }} ctx
1450
1452
  * @param {number} status
1451
1453
  * @param {Request | undefined} req
1452
1454
  * @param {URL | undefined} url
1453
1455
  * @param {Record<string, any>} [metadata]
1456
+ * @param {string} [nonce]
1457
+ * @param {boolean} [dev] dev surfaces a streamed-boundary error message; prod stays silent
1454
1458
  */
1455
- function streamingHtmlResponse(prefix, bodyHtml, closer, ctx, status, req, url, metadata, nonce) {
1459
+ function streamingHtmlResponse(prefix, bodyHtml, closer, ctx, status, req, url, metadata, nonce, dev) {
1456
1460
  const encoder = new TextEncoder();
1457
1461
  const headers = new Headers({ 'content-type': 'text/html; charset=utf-8' });
1458
1462
  // Default: no caching. Pages are dynamic by default: the developer
@@ -1499,14 +1503,19 @@ function streamingHtmlResponse(prefix, bodyHtml, closer, ctx, status, req, url,
1499
1503
  batch.map(async (p) => {
1500
1504
  try {
1501
1505
  const resolved = await p.promise;
1502
- const sub = { pending: [], nextId: ctx.nextId };
1506
+ const sub = { pending: [], nextId: ctx.nextId, dev: ctx.dev };
1503
1507
  const html = await renderToString(resolved, { ssr: true, suspenseCtx: sub });
1504
1508
  ctx.nextId = sub.nextId;
1505
1509
  for (const n of sub.pending) ctx.pending.push(n);
1506
1510
  return { id: p.id, html };
1507
1511
  } catch (e) {
1512
+ // Match the SSR error-isolation policy (render-server.js's
1513
+ // defaultSSRErrorTemplate): dev surfaces the message so the
1514
+ // failure is obvious, prod stays SILENT so no internal detail
1515
+ // (a DB error, a stack-derived path) leaks to the client (#478).
1508
1516
  const msg = e instanceof Error ? e.message : String(e);
1509
- return { id: p.id, html: `<p>error: ${escapeHtml(msg)}</p>` };
1517
+ const html = dev ? `<p>error: ${escapeHtml(msg)}</p>` : '';
1518
+ return { id: p.id, html };
1510
1519
  }
1511
1520
  })
1512
1521
  );