@tyndall/dev 0.0.1
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 +37 -0
- package/dist/hmr.d.ts +57 -0
- package/dist/hmr.d.ts.map +1 -0
- package/dist/hmr.js +384 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1424 -0
- package/dist/inspector.d.ts +29 -0
- package/dist/inspector.d.ts.map +1 -0
- package/dist/inspector.js +76 -0
- package/dist/watcher.d.ts +13 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +64 -0
- package/package.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @tyndall/dev
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
Development server package with watcher integration, route rendering, and HMR transport orchestration.
|
|
5
|
+
|
|
6
|
+
## Responsibilities
|
|
7
|
+
- Serve development routes and public assets
|
|
8
|
+
- Coordinate file invalidation and route cache invalidation
|
|
9
|
+
- Broadcast HMR updates and dev errors
|
|
10
|
+
- Surface dev/runtime failures through structured console logging with configurable severity
|
|
11
|
+
- Support SSR-first entry with optional client-side navigation takeover via bootstrap policy
|
|
12
|
+
- Emit adapter-driven client runtime scripts for route hydration in dev mode
|
|
13
|
+
- Serve route runtime chunk assets (`/_hyper/dev-client/*`) so client route modules can load on demand after navigation
|
|
14
|
+
- Preserve hydration during HMR fallback by applying route payload updates through a mounted-root-safe rerender bridge
|
|
15
|
+
- Filter broad `routesDir` scans so internal artifacts (`.hyper`, cache, `node_modules`, `outDir`, `.git`) never become file routes
|
|
16
|
+
- Stabilize Bun dev runtime bundling by aliasing critical React/runtime dependencies to cache-local concrete files (not workspace symlink paths)
|
|
17
|
+
- Track nested layout files as route dependencies for dev snapshotting and HMR invalidation
|
|
18
|
+
- Inject build version metadata into dev HTML when configured
|
|
19
|
+
|
|
20
|
+
## Public API Highlights
|
|
21
|
+
- startDevServer
|
|
22
|
+
- createFileWatcher
|
|
23
|
+
- createHmrServer
|
|
24
|
+
|
|
25
|
+
## Development
|
|
26
|
+
- Build: bun run --filter @tyndall/dev build
|
|
27
|
+
- Test (from workspace root): bun test
|
|
28
|
+
|
|
29
|
+
## Documentation
|
|
30
|
+
- Package specification: [spec.md](./spec.md)
|
|
31
|
+
- Package architecture: [architecture.md](./architecture.md)
|
|
32
|
+
- Package changes: [CHANGELOG.md](./CHANGELOG.md)
|
|
33
|
+
|
|
34
|
+
## Maintenance Rules
|
|
35
|
+
- Keep this document aligned with implemented package behavior.
|
|
36
|
+
- Update spec.md and architecture.md whenever package contracts or design boundaries change.
|
|
37
|
+
- Record user-visible package changes in CHANGELOG.md.
|
package/dist/hmr.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Server } from "node:http";
|
|
2
|
+
export declare const HMR_WS_PATH = "/__hyper_hmr";
|
|
3
|
+
export declare const HMR_CLIENT_PATH = "/_hyper/hmr.js";
|
|
4
|
+
export type HmrMessage = {
|
|
5
|
+
type: "connected";
|
|
6
|
+
sessionId: string;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
} | {
|
|
9
|
+
type: "full-reload";
|
|
10
|
+
paths?: string[];
|
|
11
|
+
routes?: string[];
|
|
12
|
+
timestamp: number;
|
|
13
|
+
} | {
|
|
14
|
+
type: "css-update";
|
|
15
|
+
paths: string[];
|
|
16
|
+
timestamp: number;
|
|
17
|
+
} | {
|
|
18
|
+
type: "react-refresh";
|
|
19
|
+
paths?: string[];
|
|
20
|
+
routes?: string[];
|
|
21
|
+
timestamp: number;
|
|
22
|
+
} | {
|
|
23
|
+
type: "error";
|
|
24
|
+
message: string;
|
|
25
|
+
stack?: string;
|
|
26
|
+
timestamp: number;
|
|
27
|
+
};
|
|
28
|
+
export interface HmrServer {
|
|
29
|
+
path: string;
|
|
30
|
+
broadcast: (message: HmrMessage) => void;
|
|
31
|
+
broadcastFullReload: (paths?: string[], routes?: string[]) => void;
|
|
32
|
+
broadcastCssUpdate: (paths: string[]) => void;
|
|
33
|
+
broadcastReactRefresh: (paths?: string[], routes?: string[]) => void;
|
|
34
|
+
broadcastError: (message: string, stack?: string) => void;
|
|
35
|
+
clientCount: () => number;
|
|
36
|
+
close: () => void;
|
|
37
|
+
}
|
|
38
|
+
export interface WebSocketLike {
|
|
39
|
+
readyState: number;
|
|
40
|
+
send: (payload: string) => void;
|
|
41
|
+
close: () => void;
|
|
42
|
+
on: (event: "close" | "error", handler: () => void) => void;
|
|
43
|
+
}
|
|
44
|
+
export interface WebSocketServerLike {
|
|
45
|
+
on: (event: "connection", handler: (socket: WebSocketLike) => void) => void;
|
|
46
|
+
close: () => void;
|
|
47
|
+
}
|
|
48
|
+
export interface HmrServerOptions {
|
|
49
|
+
path?: string;
|
|
50
|
+
createWebSocketServer?: (server: Server, path: string) => WebSocketServerLike;
|
|
51
|
+
}
|
|
52
|
+
export declare const createHmrServer: (server: Server, options?: HmrServerOptions) => HmrServer;
|
|
53
|
+
export interface HmrClientScriptOptions {
|
|
54
|
+
wsPath?: string;
|
|
55
|
+
}
|
|
56
|
+
export declare const getHmrClientScript: (options?: HmrClientScriptOptions) => string;
|
|
57
|
+
//# sourceMappingURL=hmr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hmr.d.ts","sourceRoot":"","sources":["../src/hmr.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGxC,eAAO,MAAM,WAAW,iBAAiB,CAAC;AAC1C,eAAO,MAAM,eAAe,mBAAmB,CAAC;AAEhD,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC/E;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACjF;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1E,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,IAAI,CAAC;IACzC,mBAAmB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACnE,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC9C,qBAAqB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACrE,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,EAAE,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CAC7D;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,KAAK,IAAI,CAAC;IAC5E,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qBAAqB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,mBAAmB,CAAC;CAC/E;AAED,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,EAAE,UAAS,gBAAqB,KAAG,SA6EhF,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,kBAAkB,GAAI,UAAS,sBAA2B,KAAG,MAuTzE,CAAC"}
|
package/dist/hmr.js
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import WebSocket, { WebSocketServer } from "ws";
|
|
3
|
+
export const HMR_WS_PATH = "/__hyper_hmr";
|
|
4
|
+
export const HMR_CLIENT_PATH = "/_hyper/hmr.js";
|
|
5
|
+
export const createHmrServer = (server, options = {}) => {
|
|
6
|
+
const path = options.path ?? HMR_WS_PATH;
|
|
7
|
+
const createWebSocketServer = options.createWebSocketServer ??
|
|
8
|
+
((srv, wsPath) => new WebSocketServer({
|
|
9
|
+
server: srv,
|
|
10
|
+
path: wsPath,
|
|
11
|
+
}));
|
|
12
|
+
const wss = createWebSocketServer(server, path);
|
|
13
|
+
const clients = new Set();
|
|
14
|
+
const openState = WebSocket.OPEN ?? 1;
|
|
15
|
+
wss.on("connection", (socket) => {
|
|
16
|
+
clients.add(socket);
|
|
17
|
+
socket.on("close", () => clients.delete(socket));
|
|
18
|
+
socket.on("error", () => clients.delete(socket));
|
|
19
|
+
const sessionId = randomUUID();
|
|
20
|
+
const payload = JSON.stringify({
|
|
21
|
+
type: "connected",
|
|
22
|
+
sessionId,
|
|
23
|
+
timestamp: Date.now(),
|
|
24
|
+
});
|
|
25
|
+
if (socket.readyState === openState) {
|
|
26
|
+
socket.send(payload);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
const broadcast = (message) => {
|
|
30
|
+
const payload = JSON.stringify(message);
|
|
31
|
+
for (const socket of clients) {
|
|
32
|
+
if (socket.readyState === openState) {
|
|
33
|
+
socket.send(payload);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
path,
|
|
39
|
+
broadcast,
|
|
40
|
+
broadcastFullReload: (paths, routes) => broadcast({
|
|
41
|
+
type: "full-reload",
|
|
42
|
+
paths,
|
|
43
|
+
routes,
|
|
44
|
+
timestamp: Date.now(),
|
|
45
|
+
}),
|
|
46
|
+
broadcastCssUpdate: (paths) => broadcast({
|
|
47
|
+
type: "css-update",
|
|
48
|
+
paths,
|
|
49
|
+
timestamp: Date.now(),
|
|
50
|
+
}),
|
|
51
|
+
broadcastReactRefresh: (paths, routes) => broadcast({
|
|
52
|
+
type: "react-refresh",
|
|
53
|
+
paths,
|
|
54
|
+
routes,
|
|
55
|
+
timestamp: Date.now(),
|
|
56
|
+
}),
|
|
57
|
+
broadcastError: (message, stack) => broadcast({
|
|
58
|
+
type: "error",
|
|
59
|
+
message,
|
|
60
|
+
stack,
|
|
61
|
+
timestamp: Date.now(),
|
|
62
|
+
}),
|
|
63
|
+
clientCount: () => Array.from(clients).filter((socket) => socket.readyState === WebSocket.OPEN).length,
|
|
64
|
+
close: () => {
|
|
65
|
+
for (const socket of clients) {
|
|
66
|
+
socket.close();
|
|
67
|
+
}
|
|
68
|
+
clients.clear();
|
|
69
|
+
wss.close();
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
export const getHmrClientScript = (options = {}) => {
|
|
74
|
+
const wsPath = options.wsPath ?? HMR_WS_PATH;
|
|
75
|
+
return `(() => {
|
|
76
|
+
if (window.__HYPER_HMR__) return;
|
|
77
|
+
window.__HYPER_HMR__ = true;
|
|
78
|
+
|
|
79
|
+
const wsPath = ${JSON.stringify(wsPath)};
|
|
80
|
+
const protocol = location.protocol === "https:" ? "wss" : "ws";
|
|
81
|
+
const socketUrl = protocol + "://" + location.host + wsPath;
|
|
82
|
+
|
|
83
|
+
const log = (...args) => console.log("[hyper-hmr]", ...args);
|
|
84
|
+
const warn = (...args) => console.warn("[hyper-hmr]", ...args);
|
|
85
|
+
const resolveRouteId = () => {
|
|
86
|
+
const direct = window.__HYPER_ROUTE_ID__;
|
|
87
|
+
if (typeof direct === "string") {
|
|
88
|
+
return direct;
|
|
89
|
+
}
|
|
90
|
+
const el = document.querySelector("[data-hyper-route]");
|
|
91
|
+
if (el) {
|
|
92
|
+
return el.getAttribute("data-hyper-route");
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
};
|
|
96
|
+
const shouldApply = (message) => {
|
|
97
|
+
if (message && Array.isArray(message.routes) && message.routes.length > 0) {
|
|
98
|
+
const current = resolveRouteId();
|
|
99
|
+
if (!current) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
return message.routes.includes(current);
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const ensureOverlay = () => {
|
|
108
|
+
let overlay = document.getElementById("hyper-error-overlay");
|
|
109
|
+
if (overlay) return overlay;
|
|
110
|
+
overlay = document.createElement("div");
|
|
111
|
+
overlay.id = "hyper-error-overlay";
|
|
112
|
+
overlay.style.position = "fixed";
|
|
113
|
+
overlay.style.inset = "0";
|
|
114
|
+
overlay.style.background = "rgba(0, 0, 0, 0.85)";
|
|
115
|
+
overlay.style.color = "#f8f8f2";
|
|
116
|
+
overlay.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace";
|
|
117
|
+
overlay.style.fontSize = "13px";
|
|
118
|
+
overlay.style.lineHeight = "1.5";
|
|
119
|
+
overlay.style.zIndex = "99999";
|
|
120
|
+
overlay.style.padding = "24px";
|
|
121
|
+
overlay.style.display = "none";
|
|
122
|
+
overlay.style.overflow = "auto";
|
|
123
|
+
document.body.appendChild(overlay);
|
|
124
|
+
return overlay;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const showErrorOverlay = (message, stack) => {
|
|
128
|
+
const overlay = ensureOverlay();
|
|
129
|
+
overlay.innerHTML = "";
|
|
130
|
+
const title = document.createElement("div");
|
|
131
|
+
title.textContent = "Hyper Dev Error";
|
|
132
|
+
title.style.fontWeight = "600";
|
|
133
|
+
title.style.marginBottom = "12px";
|
|
134
|
+
const body = document.createElement("div");
|
|
135
|
+
body.textContent = message || "Unknown error";
|
|
136
|
+
body.style.marginBottom = "12px";
|
|
137
|
+
overlay.appendChild(title);
|
|
138
|
+
overlay.appendChild(body);
|
|
139
|
+
if (stack) {
|
|
140
|
+
const pre = document.createElement("pre");
|
|
141
|
+
pre.textContent = stack;
|
|
142
|
+
pre.style.whiteSpace = "pre-wrap";
|
|
143
|
+
overlay.appendChild(pre);
|
|
144
|
+
}
|
|
145
|
+
overlay.style.display = "block";
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const clearErrorOverlay = () => {
|
|
149
|
+
const overlay = document.getElementById("hyper-error-overlay");
|
|
150
|
+
if (overlay) {
|
|
151
|
+
overlay.style.display = "none";
|
|
152
|
+
overlay.innerHTML = "";
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const installRefreshHooks = () => {
|
|
157
|
+
const runtime = window.$RefreshRuntime$ || window.__REACT_REFRESH_RUNTIME__;
|
|
158
|
+
if (!runtime) {
|
|
159
|
+
window.$RefreshReg$ = () => {};
|
|
160
|
+
window.$RefreshSig$ = () => (type) => type;
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (runtime.injectIntoGlobalHook) {
|
|
164
|
+
runtime.injectIntoGlobalHook(window);
|
|
165
|
+
}
|
|
166
|
+
window.$RefreshReg$ = (type, id) => runtime.register?.(type, id);
|
|
167
|
+
window.$RefreshSig$ =
|
|
168
|
+
runtime.createSignatureFunctionForTransform?.bind(runtime) || (() => (type) => type);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const updateStyles = (paths) => {
|
|
172
|
+
const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]'));
|
|
173
|
+
for (const path of paths) {
|
|
174
|
+
for (const link of links) {
|
|
175
|
+
const href = link.getAttribute("href");
|
|
176
|
+
if (!href || !href.includes(path)) continue;
|
|
177
|
+
const url = new URL(href, location.origin);
|
|
178
|
+
url.searchParams.set("t", String(Date.now()));
|
|
179
|
+
link.setAttribute("href", url.toString());
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const performReactRefresh = () => {
|
|
185
|
+
const runtime = window.$RefreshRuntime$ || window.__REACT_REFRESH_RUNTIME__;
|
|
186
|
+
if (runtime && typeof runtime.performReactRefresh === "function") {
|
|
187
|
+
runtime.performReactRefresh();
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const HEAD_ATTR = "data-hyper-head";
|
|
194
|
+
const ROUTE_PAYLOAD_EVENT = "hyper:route-payload-applied";
|
|
195
|
+
const removeManagedHead = () => {
|
|
196
|
+
const head = document.head;
|
|
197
|
+
if (!head) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const managed = head.querySelectorAll('[' + HEAD_ATTR + ']');
|
|
201
|
+
for (const node of managed) {
|
|
202
|
+
node.remove();
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
const appendHeadEntries = (tag, entries) => {
|
|
206
|
+
if (!Array.isArray(entries)) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
for (const entry of entries) {
|
|
210
|
+
if (!entry || typeof entry !== "object") {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const element = document.createElement(tag);
|
|
214
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
215
|
+
element.setAttribute(key, String(value));
|
|
216
|
+
}
|
|
217
|
+
element.setAttribute(HEAD_ATTR, "");
|
|
218
|
+
document.head.appendChild(element);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
const applyHeadDescriptor = (descriptor) => {
|
|
222
|
+
if (!descriptor || typeof descriptor !== "object") {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (typeof descriptor.title === "string") {
|
|
226
|
+
document.title = descriptor.title;
|
|
227
|
+
}
|
|
228
|
+
removeManagedHead();
|
|
229
|
+
appendHeadEntries("meta", descriptor.meta);
|
|
230
|
+
appendHeadEntries("link", descriptor.link);
|
|
231
|
+
appendHeadEntries("script", descriptor.script);
|
|
232
|
+
};
|
|
233
|
+
const applyRoutePayload = (payload, message) => {
|
|
234
|
+
if (
|
|
235
|
+
!payload ||
|
|
236
|
+
payload.kind !== "hyper-route-payload" ||
|
|
237
|
+
typeof payload.routeId !== "string" ||
|
|
238
|
+
typeof payload.appHtml !== "string" ||
|
|
239
|
+
typeof payload.propsPayload !== "string"
|
|
240
|
+
) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
const app = document.getElementById("app");
|
|
244
|
+
const hasMountedClientApp =
|
|
245
|
+
payload.hydration !== "islands" && Boolean(window.__HYPER_CLIENT_APP_MOUNTED__);
|
|
246
|
+
if (app) {
|
|
247
|
+
// Keep React-owned root stable: mounted full hydration root should rerender via event bridge.
|
|
248
|
+
if (!hasMountedClientApp) {
|
|
249
|
+
app.innerHTML = payload.appHtml;
|
|
250
|
+
}
|
|
251
|
+
app.setAttribute("data-hyper-route", payload.routeId);
|
|
252
|
+
app.setAttribute("data-hyper-hydrated", "false");
|
|
253
|
+
if (payload.hydration === "islands" || payload.hydration === "full") {
|
|
254
|
+
app.setAttribute("data-hyper-hydration", payload.hydration);
|
|
255
|
+
if (payload.hydration === "islands") {
|
|
256
|
+
app.setAttribute("data-hyper-island-root", "router");
|
|
257
|
+
} else {
|
|
258
|
+
app.removeAttribute("data-hyper-island-root");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const propsScript = document.getElementById("__HYPER_PROPS__");
|
|
263
|
+
if (propsScript) {
|
|
264
|
+
propsScript.textContent = payload.propsPayload;
|
|
265
|
+
}
|
|
266
|
+
applyHeadDescriptor(payload.head);
|
|
267
|
+
window.__HYPER_ROUTE_ID__ = payload.routeId;
|
|
268
|
+
window.__HYPER_HYDRATED__ = false;
|
|
269
|
+
try {
|
|
270
|
+
window.dispatchEvent(
|
|
271
|
+
new CustomEvent(ROUTE_PAYLOAD_EVENT, {
|
|
272
|
+
detail: {
|
|
273
|
+
routeId: payload.routeId,
|
|
274
|
+
paths: Array.isArray(message?.paths) ? message.paths : undefined,
|
|
275
|
+
routes: Array.isArray(message?.routes) ? message.routes : undefined,
|
|
276
|
+
source: "hmr",
|
|
277
|
+
},
|
|
278
|
+
}),
|
|
279
|
+
);
|
|
280
|
+
} catch {
|
|
281
|
+
// Keep HMR fallback resilient when event APIs are unavailable.
|
|
282
|
+
}
|
|
283
|
+
return true;
|
|
284
|
+
};
|
|
285
|
+
const loadRuntimeEntry = async (scriptPath) => {
|
|
286
|
+
if (typeof scriptPath !== "string" || scriptPath.length === 0) {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
const url = new URL(scriptPath, location.origin);
|
|
291
|
+
url.searchParams.set("hmr", String(Date.now()));
|
|
292
|
+
await import(url.toString());
|
|
293
|
+
return true;
|
|
294
|
+
} catch {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
const refreshCurrentRoute = async (message) => {
|
|
299
|
+
try {
|
|
300
|
+
const response = await fetch(location.href, {
|
|
301
|
+
method: "GET",
|
|
302
|
+
headers: {
|
|
303
|
+
"x-hyper-navigation": "csr",
|
|
304
|
+
accept: "application/json",
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
if (!response.ok) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
const contentType = response.headers.get("content-type") || "";
|
|
311
|
+
if (!contentType.includes("application/json")) {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
const payload = await response.json();
|
|
315
|
+
const runtimeLoaded = await loadRuntimeEntry(payload?.clientRuntimeScriptPath);
|
|
316
|
+
if (!runtimeLoaded) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
return applyRoutePayload(payload, message);
|
|
320
|
+
} catch {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const handleMessage = (message) => {
|
|
326
|
+
switch (message?.type) {
|
|
327
|
+
case "connected":
|
|
328
|
+
log("connected", message.sessionId);
|
|
329
|
+
clearErrorOverlay();
|
|
330
|
+
return;
|
|
331
|
+
case "css-update":
|
|
332
|
+
if (Array.isArray(message.paths)) {
|
|
333
|
+
updateStyles(message.paths);
|
|
334
|
+
}
|
|
335
|
+
return;
|
|
336
|
+
case "react-refresh":
|
|
337
|
+
clearErrorOverlay();
|
|
338
|
+
if (!shouldApply(message)) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (!performReactRefresh()) {
|
|
342
|
+
void refreshCurrentRoute(message).then((applied) => {
|
|
343
|
+
if (!applied) {
|
|
344
|
+
location.reload();
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
return;
|
|
349
|
+
case "full-reload":
|
|
350
|
+
if (!shouldApply(message)) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
location.reload();
|
|
354
|
+
return;
|
|
355
|
+
case "error":
|
|
356
|
+
warn(message.message || "Unknown HMR error", message.stack || "");
|
|
357
|
+
showErrorOverlay(message.message, message.stack);
|
|
358
|
+
return;
|
|
359
|
+
default:
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const connect = () => {
|
|
365
|
+
const socket = new WebSocket(socketUrl);
|
|
366
|
+
socket.addEventListener("open", installRefreshHooks);
|
|
367
|
+
socket.addEventListener("message", (event) => {
|
|
368
|
+
try {
|
|
369
|
+
handleMessage(JSON.parse(event.data));
|
|
370
|
+
} catch (error) {
|
|
371
|
+
warn("Failed to parse HMR message", error);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
socket.addEventListener("close", () => {
|
|
375
|
+
setTimeout(connect, 1000);
|
|
376
|
+
});
|
|
377
|
+
socket.addEventListener("error", () => {
|
|
378
|
+
socket.close();
|
|
379
|
+
});
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
connect();
|
|
383
|
+
})();`;
|
|
384
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type DynamicPageApiProvider, type ResolverFallbackPolicy, type UIAdapterRegistry } from "@tyndall/core";
|
|
2
|
+
import type { Logger } from "@tyndall/shared";
|
|
3
|
+
import { type HmrServer } from "./hmr.js";
|
|
4
|
+
export { createFileWatcher } from "./watcher.js";
|
|
5
|
+
export { createHmrServer, getHmrClientScript, HMR_CLIENT_PATH, HMR_WS_PATH } from "./hmr.js";
|
|
6
|
+
export interface DevServerOptions {
|
|
7
|
+
port?: number;
|
|
8
|
+
host?: string;
|
|
9
|
+
open?: boolean;
|
|
10
|
+
ssr?: boolean;
|
|
11
|
+
rootDir?: string;
|
|
12
|
+
routesDir?: string;
|
|
13
|
+
publicDir?: string;
|
|
14
|
+
prefetchPath?: string;
|
|
15
|
+
dynamicPageApi?: DynamicPageApiProvider;
|
|
16
|
+
adapterRegistry?: UIAdapterRegistry;
|
|
17
|
+
fallbackPolicy?: ResolverFallbackPolicy;
|
|
18
|
+
logger?: Logger;
|
|
19
|
+
}
|
|
20
|
+
export interface DevServer {
|
|
21
|
+
host: string;
|
|
22
|
+
port: number;
|
|
23
|
+
url: string;
|
|
24
|
+
close: () => Promise<void>;
|
|
25
|
+
hmr?: HmrServer;
|
|
26
|
+
invalidate?: (paths: string[]) => void;
|
|
27
|
+
}
|
|
28
|
+
export declare const startDevServer: (options?: DevServerOptions) => Promise<DevServer>;
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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"}
|