|
1 | | -import { createContext, h } from "preact"; |
| 1 | +import { createContext, h, options } from "preact"; |
2 | 2 | import { hydrate, render } from "preact"; |
3 | 3 | import { useContext } from "preact/hooks"; |
4 | 4 | import type { VNode } from "preact"; |
@@ -149,6 +149,22 @@ export async function initClientRouter(options: InitClientRouterOptions): Promis |
149 | 149 | } |
150 | 150 | } |
151 | 151 |
|
| 152 | + // ------------------------------------------------------------------ |
| 153 | + // Dev-mode hydration mismatch monitor (Preact options.__m hook) |
| 154 | + // ------------------------------------------------------------------ |
| 155 | + |
| 156 | + if ((import.meta as any).env?.DEV) { |
| 157 | + const prev = (options as any).__m; |
| 158 | + (options as any).__m = (vnode: VNode, s: string) => { |
| 159 | + const component = |
| 160 | + typeof vnode.type === "function" ? vnode.type.displayName || vnode.type.name : vnode.type; |
| 161 | + const message = `Hydration mismatch in <${component || "Unknown"}>: ${s}`; |
| 162 | + console.warn(`[viact] ${message}`); |
| 163 | + appendHydrationWarning(message); |
| 164 | + if (prev) prev(vnode, s); |
| 165 | + }; |
| 166 | + } |
| 167 | + |
152 | 168 | // ------------------------------------------------------------------ |
153 | 169 | // Initial hydration — includes NavigateContext so useNavigate works |
154 | 170 | // ------------------------------------------------------------------ |
@@ -250,6 +266,65 @@ export async function initClientRouter(options: InitClientRouterOptions): Promis |
250 | 266 | setupPrefetching(app); |
251 | 267 | } |
252 | 268 |
|
| 269 | +// --------------------------------------------------------------------------- |
| 270 | +// Dev-only: in-page hydration mismatch warning banner |
| 271 | +// --------------------------------------------------------------------------- |
| 272 | + |
| 273 | +const HYDRATION_BANNER_ID = "__viact_hydration_warnings__"; |
| 274 | + |
| 275 | +function appendHydrationWarning(message: string): void { |
| 276 | + let container = document.getElementById(HYDRATION_BANNER_ID); |
| 277 | + if (!container) { |
| 278 | + container = document.createElement("div"); |
| 279 | + container.id = HYDRATION_BANNER_ID; |
| 280 | + Object.assign(container.style, { |
| 281 | + position: "fixed", |
| 282 | + bottom: "0", |
| 283 | + left: "0", |
| 284 | + right: "0", |
| 285 | + maxHeight: "30vh", |
| 286 | + overflow: "auto", |
| 287 | + background: "#2d1b00", |
| 288 | + borderTop: "2px solid #f0ad4e", |
| 289 | + color: "#ffc107", |
| 290 | + fontFamily: "ui-monospace, Consolas, monospace", |
| 291 | + fontSize: "13px", |
| 292 | + padding: "12px 16px", |
| 293 | + zIndex: "2147483647", |
| 294 | + }); |
| 295 | + |
| 296 | + const header = document.createElement("div"); |
| 297 | + Object.assign(header.style, { |
| 298 | + display: "flex", |
| 299 | + justifyContent: "space-between", |
| 300 | + alignItems: "center", |
| 301 | + marginBottom: "8px", |
| 302 | + }); |
| 303 | + header.innerHTML = |
| 304 | + '<strong style="color:#f0ad4e">⚠ Hydration Mismatches</strong>'; |
| 305 | + |
| 306 | + const close = document.createElement("button"); |
| 307 | + close.textContent = "×"; |
| 308 | + Object.assign(close.style, { |
| 309 | + background: "none", |
| 310 | + border: "none", |
| 311 | + color: "#f0ad4e", |
| 312 | + fontSize: "18px", |
| 313 | + cursor: "pointer", |
| 314 | + }); |
| 315 | + close.onclick = () => container!.remove(); |
| 316 | + header.appendChild(close); |
| 317 | + |
| 318 | + container.appendChild(header); |
| 319 | + document.body.appendChild(container); |
| 320 | + } |
| 321 | + |
| 322 | + const entry = document.createElement("div"); |
| 323 | + entry.textContent = message; |
| 324 | + Object.assign(entry.style, { padding: "2px 0" }); |
| 325 | + container.appendChild(entry); |
| 326 | +} |
| 327 | + |
253 | 328 | function deserializeRouteError(error: SerializedRouteError): Error { |
254 | 329 | const result = new Error(error.message); |
255 | 330 | result.name = error.name; |
|
0 commit comments