mithril-lynx 0.0.9 → 2.0.0

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 (61) hide show
  1. package/.omo/plans/m-request-fetch-lynx.md +306 -0
  2. package/.omo/plans/m-route-en-memoria.md +397 -0
  3. package/.omo/plans/mithril-lynx-v2-desde-cero.md +548 -0
  4. package/FETCH_INVESTIGATION.md +307 -0
  5. package/README.md +32 -302
  6. package/REQUEST.md +71 -0
  7. package/ROUTE.md +71 -0
  8. package/package.json +24 -80
  9. package/plugin.d.ts +4 -33
  10. package/plugin.js +108 -438
  11. package/rstest.config.ts +27 -0
  12. package/src/apply-patch.js +179 -0
  13. package/src/backends/virtual-backend.js +80 -0
  14. package/src/background.d.ts +11 -0
  15. package/src/background.js +79 -0
  16. package/src/channel.js +41 -0
  17. package/src/commit.js +67 -0
  18. package/src/dev-reload-client.js +171 -187
  19. package/src/dev-transport-noop.js +10 -0
  20. package/src/fake-dom.js +374 -0
  21. package/src/main-thread.d.ts +1 -0
  22. package/src/main-thread.js +68 -0
  23. package/src/mount-redraw.js +67 -0
  24. package/src/patch-protocol.js +40 -0
  25. package/src/reload/version.js +28 -0
  26. package/src/request.d.ts +37 -0
  27. package/src/request.js +181 -0
  28. package/src/route.d.ts +33 -0
  29. package/src/route.js +207 -0
  30. package/test/end-to-end.test.ts +86 -0
  31. package/test/reload-version.test.ts +17 -0
  32. package/test/request.test.ts +182 -0
  33. package/test/route-hot-reload.test.ts +40 -0
  34. package/test/route.test.ts +152 -0
  35. package/test/setup.ts +25 -0
  36. package/test/structural-reload.test.ts +95 -0
  37. package/CONTRACT.md +0 -151
  38. package/LICENSE +0 -21
  39. package/background.d.ts +0 -54
  40. package/background.js +0 -169
  41. package/element.d.ts +0 -34
  42. package/element.js +0 -83
  43. package/gesture.d.ts +0 -40
  44. package/gesture.js +0 -117
  45. package/internal/constants.js +0 -26
  46. package/internal/virtual-node.js +0 -388
  47. package/list.d.ts +0 -31
  48. package/list.js +0 -185
  49. package/main-thread.d.ts +0 -43
  50. package/main-thread.js +0 -165
  51. package/navigation.d.ts +0 -35
  52. package/navigation.js +0 -76
  53. package/renderer/background.d.ts +0 -21
  54. package/renderer/background.js +0 -84
  55. package/renderer/main-thread.d.ts +0 -12
  56. package/renderer/main-thread.js +0 -175
  57. package/src/lynx-mithril-shim.d.ts +0 -16
  58. package/src/lynx-mithril-shim.js +0 -1505
  59. package/src/worklet-runtime.js +0 -82
  60. package/testing.d.ts +0 -10
  61. package/testing.js +0 -91
@@ -1,138 +1,121 @@
1
- // Development-only client for Lynx Go.
1
+ // Development-only client for Lynx Go — background thread only.
2
2
  //
3
- // Mithril view code runs in the main-thread/Lepus bundle, while WebSocket and
4
- // Native Module access only exist in the background JS bundle. Consequently
5
- // normal module HMR cannot patch the view code. On a successful rebuild this
6
- // client triggers an in-place reload of the current page.
3
+ // Adapted from mithril-lynx v1's src/dev-reload-client.js, with the F0
4
+ // monkey-patch of `lynx.requireModuleAsync` REMOVED: that patch existed
5
+ // only because v1's `RuntimeWrapperWebpackPlugin` config never matched
6
+ // `.hot-update.js` chunks, so the native eval context had no `module`/
7
+ // `exports` bindings for them (see mithril-lynx-v2-desde-cero.md §F0.2 —
8
+ // the real fix is the widened `test` regex in this package's plugin.js,
9
+ // applied at build time to every chunk including hot-update ones, not a
10
+ // runtime patch of the module loader).
7
11
  //
8
- // Mechanism: CDP `Page.reload` via the DevTool native module
9
- // (`NativeModules.LynxDevToolSetModule.invokeCdp`) — the same mechanism
10
- // Rspeedy's own official live-reload client uses
11
- // (`@lynx-js/webpack-dev-transport/lib/client/reloadApp.js`). It reloads the
12
- // CURRENT page in place (no new Activity on the back stack) and it is invoked
13
- // from inside the bundle itself, so the only network hop is the WebSocket the
14
- // client runs on: Wi-Fi only, no adb, no cable.
12
+ // Also REMOVED: the `__mithril_redraw` global v1 called after a successful
13
+ // check(). It isn't needed — the app's own background.ts calls
14
+ // `module.hot.accept("./index.js", ...)` and re-renders from ITS OWN
15
+ // closure when that fires (see the demo app's background.ts for the
16
+ // pattern). This client's only job is: decide whether a rebuild can be
17
+ // applied via HMR at all, and fall back to a full CDP `Page.reload` when
18
+ // it can't (structural changes, `module.hot.decline()`, or a check()
19
+ // rejection).
15
20
  //
16
- // Why not the alternatives (each verified on-device):
17
- // - `lynx.reload({})` (v0.0.9's original mechanism): on the device's engine it
18
- // re-executes the main-thread bundle against the live session, but the
19
- // runtime helpers `removeComponents`/`updatePage` are not re-injected, so
20
- // the render crashes with two TypeErrors (captured via lynx-devtool) and the
21
- // background chunk never re-runs — the socket dies and later builds are
22
- // silently ignored.
23
- // - `ExplorerModule.openSchema(url)` (0.0.7's mechanism): real navigation —
24
- // Lynx Go starts a NEW LynxViewShellActivity every time and never finishes
25
- // the one it replaces, so Back steps through one frozen snapshot per reload.
26
- // - `lynx.fetchBundle(url)` + `lynx.loadScript(sectionName, {bundleName})`:
27
- // in lynx-core these are the lazy-bundle path (`LazyBundleLoader` /
28
- // `LoadCustomSectionScript`) — they load a customSection of a SEPARATE
29
- // lazily-fetched bundle, not the page's own main-thread/Lepus template, so
30
- // they cannot re-render the current page.
31
- //
32
- // The reload passes a cache-busted `url` (`?t=<ts>`) because Lynx keys both
33
- // its HTTP layer and its bytecode cache by URL: measured on-device in 0.0.8,
34
- // a `Page.reload` without a changed URL re-runs the PREVIOUS bundle
35
- // byte-for-byte even with `ignoreCache:true` (see `cacheBustedUrl()` in
36
- // plugin.js).
37
- //
38
- // This client runs on the background/JS thread (where WebSocket + the
39
- // `lynx`/`NativeModules` globals are available), is only ever imported in dev
40
- // builds (see plugin.js's synthetic background chunk), and reads its config
41
- // from a resource query string injected at build time.
21
+ // One real fix over v1 that IS new here: the "race de doble-build" (two
22
+ // rebuilds landing close together made v1 see `hotStatus !== "idle"` and
23
+ // give up to a full reload even though each edit alone was light) is
24
+ // fixed by coalescing instead of by a version counter — see
25
+ // `pendingRecheckHash` below. `module.hot.check()` itself already refuses
26
+ // to run when not idle (real webpack behavior), so the fix has to live on
27
+ // this side of that call, not inside it.
28
+
29
+ var MRL = "[mrl-trace]";
42
30
 
43
31
  function parseResourceQuery(query) {
44
- const values = {};
32
+ var values = {};
45
33
  if (typeof query !== "string" || !query.startsWith("?")) return values;
46
- for (const pair of query.slice(1).split("&")) {
47
- const index = pair.indexOf("=");
48
- const key = index === -1 ? pair : pair.slice(0, index);
49
- const value = index === -1 ? "" : pair.slice(index + 1);
34
+ for (var i = 0, pairs = query.slice(1).split("&"); i < pairs.length; i++) {
35
+ var pair = pairs[i];
36
+ var index = pair.indexOf("=");
37
+ var key = index === -1 ? pair : pair.slice(0, index);
38
+ var value = index === -1 ? "" : pair.slice(index + 1);
50
39
  values[key] = decodeURIComponent(value);
51
40
  }
52
41
  return values;
53
42
  }
54
43
 
55
44
  function socketURL(options) {
56
- const hostname = options.hostname || "";
57
- const port = options.port ? `:${options.port}` : "";
58
- const pathname = options.pathname || "/rsbuild-hmr";
59
- const token = options.token ? `?token=${encodeURIComponent(options.token)}` : "";
60
- return `${options.protocol || "ws"}://${hostname}${port}${pathname}${token}`;
45
+ var hostname = options.hostname || "";
46
+ var port = options.port ? ":" + options.port : "";
47
+ var pathname = options.pathname || "/rsbuild-hmr";
48
+ var token = options.token ? "?token=" + encodeURIComponent(options.token) : "";
49
+ return (options.protocol || "ws") + "://" + hostname + port + pathname + token;
61
50
  }
62
51
 
63
- const options = parseResourceQuery(__resourceQuery);
64
- const bundleUrl = options["bundle-url"];
65
-
66
52
  /**
67
- * Appends a unique query parameter to a bundle URL. Lynx keys both the HTTP
68
- * layer and its bytecode cache by URL, so `Page.reload` with the SAME URL
69
- * re-runs the previous bundle byte-for-byte even with `ignoreCache: true`
70
- * (measured on-device in 0.0.8). Changing the URL is what actually forces a
71
- * fresh fetch — same reasoning as `cacheBustedUrl()` in plugin.js.
72
- *
73
- * Returns undefined for anything that isn't an http(s) URL, so the caller can
74
- * fall back to letting Page.reload use its existing URL.
53
+ * Lynx keys both the HTTP layer and its bytecode cache by URL — a
54
+ * `Page.reload` with the SAME url re-runs the previous bundle byte-for-byte
55
+ * even with `ignoreCache: true` (measured on-device against v1, 0.0.8).
75
56
  */
76
- function cacheBustedUrl(url, now = Date.now()) {
57
+ function cacheBustedUrl(url, now) {
77
58
  if (typeof url !== "string" || !/^https?:\/\//i.test(url)) return undefined;
78
- const [base, query = ""] = url.split("?");
79
- const params = new URLSearchParams(query);
59
+ if (now === undefined) now = Date.now();
60
+ var parts = url.split("?");
61
+ var base = parts[0];
62
+ var query = parts[1] || "";
63
+ var params = new URLSearchParams(query);
80
64
  params.set("t", String(now));
81
- return `${base}?${params.toString()}`;
65
+ return base + "?" + params.toString();
82
66
  }
83
67
 
84
- let currentHash;
85
- let initialBuild = true;
86
- let socket;
87
- // Reloading the current page — including re-executing this very module from
88
- // scratch would naturally replace this stale instance's socket with a fresh
89
- // one, except the OLD connection is otherwise left open: confirmed on-device
90
- // (0.0.7) that every earlier reload's socket stays alive and keeps receiving
91
- // server broadcasts, so a build a few reloads in fires the reload several
92
- // times at once (one call per still-open stale socket), and the resulting
93
- // reloads race. Closing this socket ourselves right before reloading, and
94
- // suppressing the reconnect-on-close it would otherwise trigger, keeps exactly
95
- // one socket alive at a time. Same lifecycle bug as 0.0.7, identical fix.
96
- let reloading = false;
97
-
98
- console.info(
99
- "[mithril-lynx] LynxDevToolSetModule.invokeCdp:",
100
- typeof NativeModules !== "undefined" && typeof NativeModules.LynxDevToolSetModule?.invokeCdp,
101
- "| lynx.reload:",
102
- typeof lynx !== "undefined" && typeof lynx?.reload,
103
- "| ExplorerModule.openSchema:",
104
- typeof NativeModules !== "undefined" && typeof NativeModules.ExplorerModule?.openSchema,
105
- );
68
+ var options = parseResourceQuery(__resourceQuery);
69
+ var bundleUrl = options["bundle-url"];
70
+
71
+ console.info(MRL + ":0 client-boot", JSON.stringify({
72
+ hostname: options.hostname,
73
+ port: options.port,
74
+ protocol: options.protocol,
75
+ pathname: options.pathname,
76
+ hasToken: !!options.token,
77
+ bundleUrl: bundleUrl,
78
+ }));
79
+
80
+ var currentHash;
81
+ var initialBuild = true;
82
+ // Anti-loop guard: without comparing against the hash the running bundle
83
+ // was actually built with, every boot would trigger check() for the SAME
84
+ // hash it already carries -> manifest 404 -> full reload -> boot -> loop.
85
+ var runtimeHash = typeof __webpack_require__ !== "undefined"
86
+ && typeof __webpack_require__.h === "function"
87
+ ? __webpack_require__.h()
88
+ : null;
89
+ var socket;
90
+ var reloading = false;
91
+ // Set when an "ok" for a NEW hash arrives while a check() from a PREVIOUS
92
+ // hash is still in flight. Instead of giving up to a full reload (v1's
93
+ // race), the in-flight check's `.then`/`.catch` re-drives the check logic
94
+ // against this hash once it settles.
95
+ var pendingRecheckHash;
106
96
 
107
97
  function invokeCdpReload() {
108
- // Mirrors @lynx-js/webpack-dev-transport/lib/client/reloadApp.js:
109
- // Lynx Go exposes the DevTool CDP bridge under two module casings;
110
- // either one accepts the Page.reload CDP message. Note the older
111
- // casing binds "Page.reload" as a leading argument, like the official
112
- // client does.
113
- const upperCase = typeof NativeModules !== "undefined" ? NativeModules.LynxDevToolSetModule : undefined;
114
- const lowerCase = typeof NativeModules !== "undefined" ? NativeModules.LynxDevtoolSetModule : undefined;
115
- const invokeCdp =
116
- (upperCase?.invokeCdp?.bind(upperCase)) ??
117
- (typeof lowerCase?.invokeCdp === "function" ? lowerCase.invokeCdp.bind(lowerCase, "Page.reload") : undefined);
98
+ var upperCase = typeof NativeModules !== "undefined" ? NativeModules.LynxDevToolSetModule : undefined;
99
+ var lowerCase = typeof NativeModules !== "undefined" ? NativeModules.LynxDevtoolSetModule : undefined;
100
+ var invokeCdp =
101
+ (upperCase && upperCase.invokeCdp ? upperCase.invokeCdp.bind(upperCase) : undefined) ??
102
+ (typeof (lowerCase && lowerCase.invokeCdp) === "function" ? lowerCase.invokeCdp.bind(lowerCase, "Page.reload") : undefined);
118
103
  if (typeof invokeCdp !== "function") return false;
119
104
 
120
- // Page.reload with the SAME URL re-runs the previous bundle (URL-keyed
121
- // bytecode cache — measured in 0.0.8), so pass the cache-busted URL. If
122
- // the URL is unusable for some reason, fall back to the bare CDP reload
123
- // rather than skipping the reload entirely.
124
- const params = { ignoreCache: true };
125
- const url = cacheBustedUrl(bundleUrl);
105
+ var params = { ignoreCache: true };
106
+ var url = cacheBustedUrl(bundleUrl);
126
107
  if (url != null) params.url = url;
127
108
 
109
+ console.info(MRL + ":9 cdp-page-reload", JSON.stringify({ url: url, bundleUrl: bundleUrl }));
110
+
128
111
  invokeCdp(
129
112
  JSON.stringify({ method: "Page.reload", params }),
130
- (data) => {
113
+ function (data) {
131
114
  if (!data) return;
132
115
  try {
133
- const { error } = JSON.parse(data);
134
- if (error) console.error("[mithril-lynx] Page.reload failed:", error.message);
135
- } catch {
116
+ var parsed = JSON.parse(data);
117
+ if (parsed.error) console.error("[mithril-lynx-v2] Page.reload failed:", parsed.error.message);
118
+ } catch (e) {
136
119
  // response is not JSON — ignore
137
120
  }
138
121
  },
@@ -140,121 +123,122 @@ function invokeCdpReload() {
140
123
  return true;
141
124
  }
142
125
 
143
- function reload() {
144
- // Preferred: CDP Page.reload through the DevTool native module — the
145
- // same mechanism Rspeedy's own live-reload client uses. Page.reload
146
- // reloads the current page in place (no new Activity, no back-stack
147
- // corruption — verified on-device in 0.0.8), and because it is invoked
148
- // from inside the bundle it works over Wi-Fi alone.
149
- const cdpModule =
150
- (typeof NativeModules !== "undefined" && NativeModules.LynxDevToolSetModule?.invokeCdp) ||
151
- (typeof NativeModules !== "undefined" && NativeModules.LynxDevtoolSetModule?.invokeCdp);
152
- if (typeof cdpModule === "function") {
153
- console.info("[mithril-lynx] Reloading in place via CDP Page.reload.");
154
- reloading = true;
155
- socket?.close();
156
- invokeCdpReload();
157
- return;
126
+ function reload(reason) {
127
+ console.info(MRL + ":8 reload-called", JSON.stringify({ reason: reason || "unspecified" }));
128
+ reloading = true;
129
+ if (socket) socket.close();
130
+ if (!invokeCdpReload()) {
131
+ console.warn(
132
+ "[mithril-lynx-v2] Live reload unavailable: NativeModules.LynxDevToolSetModule.invokeCdp was not found.",
133
+ );
158
134
  }
135
+ }
159
136
 
160
- // Fallback: lynx.reload() reloads the current page in place — no new
161
- // Activity, no back-stack corruption. `value` is the new init data for the
162
- // page; pass {} to keep the same (no new initData), mirroring openSchema's
163
- // full reload. The callback fires when the reload completes. NOTE: on the
164
- // engine this project targets (reported 3.5 on-device), lynx.reload
165
- // re-executes the main-thread bundle against the live session without
166
- // re-injecting the runtime helpers removeComponents/updatePage, crashing
167
- // with two TypeErrors and never re-running the background chunk — that is
168
- // why CDP is preferred and lynx.reload stays only as a fallback for
169
- // viewers/SDKs whose reload path is intact.
170
- const reloadFn = typeof lynx !== "undefined" && typeof lynx?.reload === "function" ? lynx.reload : null;
171
- if (reloadFn) {
172
- console.info("[mithril-lynx] Reloading in place via lynx.reload().");
173
- reloading = true;
174
- socket?.close();
175
- try {
176
- reloadFn.call(lynx, {}, () => {
177
- console.info("[mithril-lynx] Reload complete.");
178
- });
179
- } catch (error) {
180
- console.error("[mithril-lynx] lynx.reload threw:", error);
137
+ function runCheck(hash) {
138
+ console.info(MRL + ":5 hmr-check-calling", "module.hot.check(true)");
139
+ module.hot.check(true).then(function (updatedModules) {
140
+ console.info(MRL + ":6 hmr-check-resolved", JSON.stringify({
141
+ updatedModulesLength: updatedModules ? updatedModules.length : 0,
142
+ }));
143
+ var recheck = pendingRecheckHash;
144
+ pendingRecheckHash = undefined;
145
+ if (recheck && recheck !== hash) {
146
+ console.info(MRL + ":6d coalesced-recheck", JSON.stringify({ hash: recheck }));
147
+ maybeCheck(recheck);
148
+ return;
181
149
  }
182
- return;
183
- }
150
+ if (!updatedModules || updatedModules.length === 0) {
151
+ console.info(MRL + ":6c hmr-no-modules", "falling back to full reload");
152
+ reload("hmr-check-returned-no-modules");
153
+ }
154
+ }).catch(function (err) {
155
+ console.warn(MRL + ":7 hmr-check-rejected", JSON.stringify({
156
+ message: err && err.message,
157
+ }));
158
+ var recheck = pendingRecheckHash;
159
+ pendingRecheckHash = undefined;
160
+ if (recheck) {
161
+ maybeCheck(recheck);
162
+ return;
163
+ }
164
+ reload("hmr-check-rejected:" + (err && err.message ? err.message : String(err)));
165
+ });
166
+ }
184
167
 
185
- // Last resort: ExplorerModule.openSchema(url) — the 0.0.7 mechanism. Works
186
- // over Wi-Fi too, but it is a real navigation and stacks a new
187
- // LynxViewShellActivity per reload (Back then walks frozen snapshots). Kept
188
- // only for viewers/SDKs where lynx.reload is unavailable.
189
- const openSchema =
190
- typeof NativeModules !== "undefined" && NativeModules.ExplorerModule?.openSchema;
191
- if (typeof openSchema !== "function") {
192
- console.warn(
193
- "[mithril-lynx] Live reload unavailable: neither CDP invokeCdp, lynx.reload, nor ExplorerModule.openSchema was found.",
194
- );
168
+ function maybeCheck(hash) {
169
+ var hasHot = typeof module !== "undefined" && module.hot;
170
+ var hotStatus = hasHot ? module.hot.status() : "n/a";
171
+
172
+ console.info(MRL + ":4 hmr-check", JSON.stringify({ hasModuleHot: hasHot, hotStatus: hotStatus }));
173
+
174
+ if (!hasHot) {
175
+ console.info(MRL + ":4a hmr-unavailable", JSON.stringify({ hasHot: hasHot }));
176
+ reload("hmr-unavailable:no-module-hot");
195
177
  return;
196
178
  }
197
- if (!bundleUrl) {
198
- console.warn("[mithril-lynx] Live reload unavailable: the bundle URL was not configured.");
179
+ if (hotStatus !== "idle") {
180
+ // A check for an OLDER hash is still in flight — coalesce instead of
181
+ // bailing to a full reload (the v1 "race de doble-build" fix).
182
+ pendingRecheckHash = hash;
183
+ console.info(MRL + ":4b hmr-check-in-flight", JSON.stringify({ queuedHash: hash }));
199
184
  return;
200
185
  }
201
- console.info("[mithril-lynx] Reloading updated bundle via ExplorerModule.openSchema (fallback).");
202
- reloading = true;
203
- socket?.close();
204
- openSchema.call(NativeModules.ExplorerModule, bundleUrl);
186
+ runCheck(hash);
205
187
  }
206
188
 
207
189
  function handleMessage(rawMessage) {
208
- let message;
190
+ var message;
209
191
  try {
210
192
  message = JSON.parse(rawMessage);
211
- } catch {
212
- console.warn("[mithril-lynx] Ignoring an invalid dev-server message.");
193
+ } catch (e) {
194
+ console.warn("[mithril-lynx-v2] Ignoring an invalid dev-server message.");
213
195
  return;
214
196
  }
215
197
 
216
198
  switch (message.type) {
217
199
  case "hash":
218
200
  currentHash = message.data;
201
+ console.info(MRL + ":2a hash-received", JSON.stringify({ hash: message.data }));
219
202
  break;
220
203
  case "ok":
221
- // The server sends hash + ok immediately after connecting. Reloading
222
- // then would loop forever, so only act after a later successful build.
223
- if (initialBuild) initialBuild = false;
224
- else if (currentHash) reload();
225
- break;
226
- case "still-ok":
227
- console.info("[mithril-lynx] Nothing changed.");
204
+ console.info(MRL + ":3 ok-received", JSON.stringify({ initialBuild: initialBuild }));
205
+ if (initialBuild) {
206
+ initialBuild = false;
207
+ console.info(MRL + ":3a initial-build-skipped");
208
+ } else if (currentHash) {
209
+ if (runtimeHash && currentHash === runtimeHash) {
210
+ console.info(MRL + ":3b hash-equal-same-build");
211
+ break;
212
+ }
213
+ maybeCheck(currentHash);
214
+ }
228
215
  break;
229
216
  case "warnings":
230
- console.warn("[mithril-lynx] Build completed with warnings.", message.data);
231
- if (!message.params?.preventReloading && !initialBuild) reload();
217
+ if (!message.params || !message.params.preventReloading) {
218
+ if (!initialBuild) reload("build-warnings");
219
+ }
232
220
  break;
233
221
  case "errors":
234
- console.warn("[mithril-lynx] Build failed; waiting for the next successful build.", message.data);
235
- break;
236
- case "invalid":
237
- console.info("[mithril-lynx] App updated. Recompiling...");
238
- break;
239
- case "error":
240
- console.error("[mithril-lynx] Dev server error:", message.data);
222
+ console.warn("[mithril-lynx-v2] Build failed; waiting for the next successful build.", message.data);
241
223
  break;
242
224
  }
243
225
  }
244
226
 
245
- function connect(retries = 0) {
227
+ function connect(retries) {
228
+ if (retries === undefined) retries = 0;
229
+ console.info(MRL + ":1 ws-connecting", JSON.stringify({ url: socketURL(options), retry: retries }));
230
+
246
231
  socket = new WebSocket(socketURL(options));
247
- socket.onmessage = (event) => handleMessage(event.data);
248
- socket.onerror = (error) => console.error("[mithril-lynx] Dev server connection error:", error);
249
- socket.onclose = () => {
250
- if (reloading) return; // Deliberately closed by reload() — a fresh socket is on its way already.
232
+ socket.onmessage = function (event) { handleMessage(event.data); };
233
+ socket.onerror = function (error) { console.error("[mithril-lynx-v2] Dev server connection error:", error); };
234
+ socket.onclose = function () {
235
+ if (reloading) return;
251
236
  if (retries >= 10) {
252
- console.error("[mithril-lynx] Unable to reconnect to the dev server.");
237
+ console.error("[mithril-lynx-v2] Unable to reconnect to the dev server.");
253
238
  return;
254
239
  }
255
- const delay = 1000 * 2 ** retries + Math.random() * 100;
256
- console.info("[mithril-lynx] Dev server disconnected; reconnecting...");
257
- setTimeout(() => connect(retries + 1), delay);
240
+ var delay = 1000 * Math.pow(2, retries) + Math.random() * 100;
241
+ setTimeout(function () { connect(retries + 1); }, delay);
258
242
  };
259
243
  }
260
244
 
@@ -0,0 +1,10 @@
1
+ // No-op stand-in for @lynx-js/webpack-dev-transport/client.
2
+ //
3
+ // `dev.hmr: true` makes Rsbuild/Rspeedy inject the official transport
4
+ // client, which opens its OWN WebSocket to /rsbuild-hmr and full-reloads
5
+ // via reloadApp.js — a second reload channel on top of this package's
6
+ // in-bundle dev-reload-client.js. plugin.js re-aliases the module here
7
+ // (order "post", so it wins over the alias @lynx-js/rsbuild-plugin
8
+ // registers) so `module.hot` stays live without a competing channel. Same
9
+ // fix as mithril-lynx v1's F2 (src/dev-transport-noop.js).
10
+ export default class DevTransportClientNoop {}