@vertz/ui-server 0.2.31 → 0.2.33

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
@@ -85,14 +85,14 @@ const html = await renderToHTML(App, {
85
85
 
86
86
  **Options:**
87
87
 
88
- | Option | Type | Description |
89
- |---|---|---|
90
- | `app` | `() => VNode` | App component function |
91
- | `url` | `string` | Request URL for SSR routing |
92
- | `theme` | `Theme` | Theme definition for CSS vars |
93
- | `styles` | `string[]` | Global CSS strings to inject |
94
- | `head` | `object` | Head config: `title`, `meta[]`, `links[]` |
95
- | `container` | `string` | Container selector (default `'#app'`) |
88
+ | Option | Type | Description |
89
+ | ----------- | ------------- | ----------------------------------------- |
90
+ | `app` | `() => VNode` | App component function |
91
+ | `url` | `string` | Request URL for SSR routing |
92
+ | `theme` | `Theme` | Theme definition for CSS vars |
93
+ | `styles` | `string[]` | Global CSS strings to inject |
94
+ | `head` | `object` | Head config: `title`, `meta[]`, `links[]` |
95
+ | `container` | `string` | Container selector (default `'#app'`) |
96
96
 
97
97
  ### `renderPage(vnode, options?)`
98
98
 
@@ -113,18 +113,18 @@ return renderPage(<App />, {
113
113
 
114
114
  **Options:**
115
115
 
116
- | Option | Type | Description |
117
- |---|---|---|
118
- | `status` | `number` | HTTP status code (default `200`) |
119
- | `title` | `string` | Page title |
120
- | `description` | `string` | Meta description |
121
- | `lang` | `string` | HTML lang attribute (default `'en'`) |
122
- | `favicon` | `string` | Favicon URL |
123
- | `og` | `object` | Open Graph: `title`, `description`, `image`, `url`, `type` |
124
- | `twitter` | `object` | Twitter card: `card`, `site` |
125
- | `scripts` | `string[]` | Script URLs for end of body |
126
- | `styles` | `string[]` | Stylesheet URLs for head |
127
- | `head` | `string` | Raw HTML escape hatch for head |
116
+ | Option | Type | Description |
117
+ | ------------- | ---------- | ---------------------------------------------------------- |
118
+ | `status` | `number` | HTTP status code (default `200`) |
119
+ | `title` | `string` | Page title |
120
+ | `description` | `string` | Meta description |
121
+ | `lang` | `string` | HTML lang attribute (default `'en'`) |
122
+ | `favicon` | `string` | Favicon URL |
123
+ | `og` | `object` | Open Graph: `title`, `description`, `image`, `url`, `type` |
124
+ | `twitter` | `object` | Twitter card: `card`, `site` |
125
+ | `scripts` | `string[]` | Script URLs for end of body |
126
+ | `styles` | `string[]` | Stylesheet URLs for head |
127
+ | `head` | `string` | Raw HTML escape hatch for head |
128
128
 
129
129
  ### `renderToStream(tree, options?)`
130
130
 
@@ -148,6 +148,7 @@ return new Response(stream, {
148
148
  ```
149
149
 
150
150
  **Options:**
151
+
151
152
  - `nonce?: string` — CSP nonce for inline scripts
152
153
 
153
154
  ### `serializeToHtml(node)`
@@ -203,11 +204,11 @@ removeDomShim();
203
204
 
204
205
  **Note:** `renderToHTML` handles DOM shim setup and teardown automatically. You only need these when using lower-level rendering APIs.
205
206
 
206
- | Export | Description |
207
- |---|---|
207
+ | Export | Description |
208
+ | ------------------ | -------------------------------------------- |
208
209
  | `installDomShim()` | Install the minimal DOM shim on `globalThis` |
209
- | `removeDomShim()` | Remove the DOM shim from `globalThis` |
210
- | `toVNode(element)` | Convert an SSR element to a VNode |
210
+ | `removeDomShim()` | Remove the DOM shim from `globalThis` |
211
+ | `toVNode(element)` | Convert an SSR element to a VNode |
211
212
 
212
213
  ---
213
214
 
@@ -228,6 +229,7 @@ const headHtml = renderHeadToHtml(headCollector.getEntries());
228
229
  ```
229
230
 
230
231
  **`HeadCollector` methods:**
232
+
231
233
  - `addTitle(text)` — Add a `<title>` tag
232
234
  - `addMeta(attrs)` — Add a `<meta>` tag
233
235
  - `addLink(attrs)` — Add a `<link>` tag
@@ -266,7 +268,9 @@ const hydratedNode = wrapWithHydrationMarkers(counterNode, {
266
268
  <div data-v-id="Counter" data-v-key="counter-0">
267
269
  <span>Count: 0</span>
268
270
  <button>+</button>
269
- <script type="application/json">{"initial":0}</script>
271
+ <script type="application/json">
272
+ { "initial": 0 }
273
+ </script>
270
274
  </div>
271
275
  ```
272
276
 
@@ -363,24 +367,24 @@ await server.listen();
363
367
 
364
368
  **Options:**
365
369
 
366
- | Option | Type | Default | Description |
367
- |---|---|---|---|
368
- | `entry` | `string` | (required) | Path to the SSR entry module |
369
- | `port` | `number` | `5173` | Port to listen on |
370
- | `host` | `string` | `'0.0.0.0'` | Host to bind to |
371
- | `viteConfig` | `InlineConfig` | `{}` | Custom Vite configuration |
372
- | `middleware` | `function` | — | Custom middleware before SSR handler |
373
- | `skipModuleInvalidation` | `boolean` | `false` | Skip invalidating modules on each request |
374
- | `logRequests` | `boolean` | `true` | Log requests to console |
370
+ | Option | Type | Default | Description |
371
+ | ------------------------ | -------------- | ----------- | ----------------------------------------- |
372
+ | `entry` | `string` | (required) | Path to the SSR entry module |
373
+ | `port` | `number` | `5173` | Port to listen on |
374
+ | `host` | `string` | `'0.0.0.0'` | Host to bind to |
375
+ | `viteConfig` | `InlineConfig` | `{}` | Custom Vite configuration |
376
+ | `middleware` | `function` | — | Custom middleware before SSR handler |
377
+ | `skipModuleInvalidation` | `boolean` | `false` | Skip invalidating modules on each request |
378
+ | `logRequests` | `boolean` | `true` | Log requests to console |
375
379
 
376
380
  **`DevServer` interface:**
377
381
 
378
- | Property/Method | Description |
379
- |---|---|
380
- | `listen()` | Start the server |
381
- | `close()` | Stop the server |
382
- | `vite` | The underlying `ViteDevServer` |
383
- | `httpServer` | The underlying `http.Server` |
382
+ | Property/Method | Description |
383
+ | --------------- | ------------------------------ |
384
+ | `listen()` | Start the server |
385
+ | `close()` | Stop the server |
386
+ | `vite` | The underlying `ViteDevServer` |
387
+ | `httpServer` | The underlying `http.Server` |
384
388
 
385
389
  The entry module should export a `renderToString(url: string)` function that returns HTML.
386
390
 
@@ -390,11 +394,11 @@ The entry module should export a `renderToString(url: string)` function that ret
390
394
 
391
395
  The `@vertz/ui-server/jsx-runtime` subpath provides a server-side JSX factory that produces VNode trees compatible with `renderToStream`. During SSR, Vite's `ssrLoadModule` swaps this in automatically.
392
396
 
393
- | Export | Description |
394
- |---|---|
395
- | `jsx` | JSX factory for single-child elements |
396
- | `jsxs` | JSX factory for multi-child elements |
397
- | `Fragment` | Fragment component |
397
+ | Export | Description |
398
+ | ---------- | ------------------------------------- |
399
+ | `jsx` | JSX factory for single-child elements |
400
+ | `jsxs` | JSX factory for multi-child elements |
401
+ | `Fragment` | Fragment component |
398
402
 
399
403
  ---
400
404
 
@@ -430,11 +434,11 @@ import type {
430
434
 
431
435
  ## Utilities
432
436
 
433
- | Export | Description |
434
- |---|---|
435
- | `streamToString(stream)` | Convert a `ReadableStream<Uint8Array>` to a string |
436
- | `collectStreamChunks(stream)` | Collect stream chunks as a `string[]` |
437
- | `encodeChunk(html)` | Encode a string to a `Uint8Array` chunk |
437
+ | Export | Description |
438
+ | ----------------------------- | -------------------------------------------------- |
439
+ | `streamToString(stream)` | Convert a `ReadableStream<Uint8Array>` to a string |
440
+ | `collectStreamChunks(stream)` | Collect stream chunks as a `string[]` |
441
+ | `encodeChunk(html)` | Encode a string to a `Uint8Array` chunk |
438
442
 
439
443
  ---
440
444
 
@@ -133,6 +133,19 @@ declare function isStaleGraphError(message: string): boolean;
133
133
  * addEventListener("DOMContentLoaded",function(event){location.reload()})
134
134
  */
135
135
  declare function isReloadStub(text: string): boolean;
136
+ /**
137
+ * Determine whether the dev server should check for a stale dev bundler
138
+ * (and potentially restart) after a file change.
139
+ *
140
+ * When the bundle hash changed, the dev bundler processed the update
141
+ * successfully — HMR will deliver it. Checking for a reload stub would
142
+ * race with Bun's internal update and produce false positives.
143
+ *
144
+ * When the hash did NOT change, the dev bundler may be stuck (e.g. new
145
+ * file imported for the first time). The stale check is needed to detect
146
+ * this and auto-restart.
147
+ */
148
+ declare function shouldCheckStaleBundler(hashChanged: boolean): boolean;
136
149
  /** A resolved stack frame for terminal logging. */
137
150
  interface TerminalStackFrame {
138
151
  functionName: string | null;
@@ -244,4 +257,4 @@ declare function clearSSRRequireCache(): number;
244
257
  * SSR is always on. HMR always works. No mode toggle needed.
245
258
  */
246
259
  declare function createBunDevServer(options: BunDevServerOptions): BunDevServer;
247
- export { parseHMRAssets, isStaleGraphError, isReloadStub, generateSSRPageHtml, formatTerminalRuntimeError, detectFaviconTag, createRuntimeErrorDeduplicator, createFetchInterceptor, createBunDevServer, clearSSRRequireCache, buildScriptTag, SSRPageHtmlOptions, HMRAssets, FetchInterceptorOptions, ErrorDetail, ErrorCategory, BunDevServerOptions, BunDevServer };
260
+ export { shouldCheckStaleBundler, parseHMRAssets, isStaleGraphError, isReloadStub, generateSSRPageHtml, formatTerminalRuntimeError, detectFaviconTag, createRuntimeErrorDeduplicator, createFetchInterceptor, createBunDevServer, clearSSRRequireCache, buildScriptTag, SSRPageHtmlOptions, HMRAssets, FetchInterceptorOptions, ErrorDetail, ErrorCategory, BunDevServerOptions, BunDevServer };
@@ -2556,6 +2556,9 @@ function isStaleGraphError(message) {
2556
2556
  function isReloadStub(text) {
2557
2557
  return text.trimStart().startsWith("try{location.reload()}");
2558
2558
  }
2559
+ function shouldCheckStaleBundler(hashChanged) {
2560
+ return !hashChanged;
2561
+ }
2559
2562
  var MAX_TERMINAL_STACK_FRAMES = 5;
2560
2563
  function formatTerminalRuntimeError(errors, parsedStack) {
2561
2564
  const primary = errors[0];
@@ -2883,6 +2886,7 @@ var BUILD_ERROR_LOADER = [
2883
2886
  "fetch(src).then(function(r){return r.text()}).then(function(t){",
2884
2887
  "if(t.trimStart().startsWith('try{location.reload()}')){",
2885
2888
  "fetch('/__vertz_build_check').then(function(r){return r.json()}).then(function(j){",
2889
+ `if(j.restarting){showOverlay('Restarting dev server','<p style="margin:0;color:#666;font-size:12px">Stale module graph detected. Restarting...</p>');sessionStorage.removeItem('__vertz_stub_retry');return}`,
2886
2890
  "if(j.errors&&j.errors.length>0){showOverlay('Build failed',formatErrors(j.errors),j)}",
2887
2891
  "else{var V2=window.__vertz_overlay;",
2888
2892
  "if(V2&&V2._autoRestart&&V2._canAutoRestart&&V2._canAutoRestart()){",
@@ -3045,6 +3049,7 @@ function createBunDevServer(options) {
3045
3049
  pendingRuntimeError = null;
3046
3050
  }
3047
3051
  clearGraceUntil = Date.now() + 5000;
3052
+ autoRestartTimestamps.length = 0;
3048
3053
  const msg = JSON.stringify({ type: "clear" });
3049
3054
  for (const ws of wsClients) {
3050
3055
  ws.sendText(msg);
@@ -3061,6 +3066,7 @@ function createBunDevServer(options) {
3061
3066
  pendingRuntimeError = null;
3062
3067
  }
3063
3068
  clearGraceUntil = 0;
3069
+ autoRestartTimestamps.length = 0;
3064
3070
  const msg = JSON.stringify({ type: "clear" });
3065
3071
  for (const ws of wsClients) {
3066
3072
  ws.sendText(msg);
@@ -3488,6 +3494,20 @@ if (import.meta.hot) import.meta.hot.accept();
3488
3494
  if (lastBuildError) {
3489
3495
  return Response.json({ errors: [{ message: lastBuildError }] });
3490
3496
  }
3497
+ if (bundledScriptUrl && !isRestarting && canAutoRestart()) {
3498
+ try {
3499
+ const bundleRes = await fetch(`http://${host}:${server?.port}${bundledScriptUrl}`);
3500
+ const bundleText = await bundleRes.text();
3501
+ if (isReloadStub(bundleText)) {
3502
+ autoRestartTimestamps.push(Date.now());
3503
+ if (logRequests) {
3504
+ console.log("[Server] Build check: dev bundler stale \u2014 restarting");
3505
+ }
3506
+ devServer.restart();
3507
+ return Response.json({ errors: [], restarting: true });
3508
+ }
3509
+ } catch {}
3510
+ }
3491
3511
  return Response.json({ errors: [] });
3492
3512
  } catch (e) {
3493
3513
  const msg = e instanceof Error ? e.message : String(e);
@@ -3852,6 +3872,27 @@ data: {}
3852
3872
  console.warn("[Server] Could not discover HMR bundled URL:", e);
3853
3873
  }
3854
3874
  }
3875
+ async function checkAndRestartIfBundlerStale() {
3876
+ if (!bundledScriptUrl || !server || isRestarting)
3877
+ return false;
3878
+ try {
3879
+ const bundleRes = await fetch(`http://${host}:${server.port}${bundledScriptUrl}`);
3880
+ const bundleText = await bundleRes.text();
3881
+ if (isReloadStub(bundleText)) {
3882
+ if (canAutoRestart()) {
3883
+ autoRestartTimestamps.push(Date.now());
3884
+ if (logRequests) {
3885
+ console.log("[Server] Dev bundler still serving reload stub after SSR recovery \u2014 restarting");
3886
+ }
3887
+ devServer.restart();
3888
+ return true;
3889
+ } else if (logRequests) {
3890
+ console.log("[Server] Dev bundler stale but auto-restart cap reached");
3891
+ }
3892
+ }
3893
+ } catch {}
3894
+ return false;
3895
+ }
3855
3896
  stopped = false;
3856
3897
  if (existsSync2(srcDir)) {
3857
3898
  srcWatcherRef = watch2(srcDir, { recursive: true }, (_event, filename) => {
@@ -3875,6 +3916,7 @@ data: {}
3875
3916
  await discoverHMRAssets();
3876
3917
  if (stopped)
3877
3918
  return;
3919
+ let hashChanged = false;
3878
3920
  try {
3879
3921
  const clientRelative = rawClientSrc.replace(/^\//, "");
3880
3922
  const result = await Bun.build({
@@ -3906,10 +3948,12 @@ data: {}
3906
3948
  if (stopped)
3907
3949
  return;
3908
3950
  await discoverHMRAssets();
3909
- if (bundledScriptUrl !== prevUrl)
3951
+ if (bundledScriptUrl !== prevUrl) {
3952
+ hashChanged = true;
3910
3953
  break;
3954
+ }
3911
3955
  }
3912
- if (bundledScriptUrl && server && !isRestarting) {
3956
+ if (!hashChanged && bundledScriptUrl && server && !isRestarting) {
3913
3957
  try {
3914
3958
  const bundleRes = await fetch(`http://${host}:${server.port}${bundledScriptUrl}`);
3915
3959
  const bundleText = await bundleRes.text();
@@ -3927,7 +3971,6 @@ data: {}
3927
3971
  }
3928
3972
  } catch {}
3929
3973
  }
3930
- clearErrorForFileChange();
3931
3974
  }
3932
3975
  } catch {}
3933
3976
  if (stopped)
@@ -3977,8 +4020,10 @@ data: {}
3977
4020
  return;
3978
4021
  }
3979
4022
  if (logRequests) {
3980
- console.log("[Server] SSR in fallback mode \u2014 attempting re-import (best effort)");
4023
+ console.log("[Server] SSR in fallback mode \u2014 restarting dev server");
3981
4024
  }
4025
+ devServer.restart();
4026
+ return;
3982
4027
  }
3983
4028
  const cacheCleared = clearSSRRequireCache();
3984
4029
  logger.log("watcher", "cache-cleared", { entries: cacheCleared });
@@ -4002,6 +4047,9 @@ data: {}
4002
4047
  if (logRequests) {
4003
4048
  console.log("[Server] SSR module refreshed");
4004
4049
  }
4050
+ if (!hashChanged && await checkAndRestartIfBundlerStale())
4051
+ return;
4052
+ clearErrorForFileChange();
4005
4053
  } catch {
4006
4054
  logger.log("watcher", "ssr-reload", { status: "retry" });
4007
4055
  if (stopped)
@@ -4028,6 +4076,9 @@ data: {}
4028
4076
  if (logRequests) {
4029
4077
  console.log("[Server] SSR module refreshed (retry)");
4030
4078
  }
4079
+ if (!hashChanged && await checkAndRestartIfBundlerStale())
4080
+ return;
4081
+ clearErrorForFileChange();
4031
4082
  } catch (e2) {
4032
4083
  console.error("[Server] Failed to refresh SSR module:", e2);
4033
4084
  const errMsg = e2 instanceof Error ? e2.message : String(e2);
@@ -4138,6 +4189,7 @@ data: {}
4138
4189
  return devServer;
4139
4190
  }
4140
4191
  export {
4192
+ shouldCheckStaleBundler,
4141
4193
  parseHMRAssets,
4142
4194
  isStaleGraphError,
4143
4195
  isReloadStub,