cindel 1.0.5 → 1.1.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.
package/README.md CHANGED
@@ -23,16 +23,17 @@
23
23
  - HTTP CORS proxy with configurable header injection
24
24
  - WebSocket proxy with header forwarding and message interception
25
25
  - Static file server and automatic `index.html` loader injection
26
+ - Custom route handlers and directory mirroring via Bun's native routing
26
27
  - TLS/HTTPS + WSS support
27
28
  - `/files` endpoint exposing the live watched file list as JSON
28
29
 
29
30
  **Client**
30
31
 
32
+ - No runtime dependencies, works in any modern browser
31
33
  - Exponential backoff with automatic reconnect
32
- - No runtime dependencies, so it works in any modern browser
33
34
  - Event system with `on`, `once`, and `off` for connect, disconnect, reload, add, remove, etc.
34
- - IIFE build compatible with userscript managers (Tampermonkey, Greasemonkey) via `@require`
35
35
  - Iframe injection via `postMessage` for Private Network Access restricted environments
36
+ - IIFE build compatible with userscript managers (Tampermonkey, Greasemonkey) via `@require`
36
37
 
37
38
  ---
38
39
 
@@ -140,8 +141,10 @@ You can even load it through a user script on any domain:
140
141
  | `static` | `string \| false` | `'.'` | Directory to serve static files from. Pass `false` to disable static serving |
141
142
  | `indexPath` | `string` | `'index.html'` | Path to `index.html` |
142
143
  | `injectLoader` | `string` | | Script path injected into `index.html` before `</head>` |
144
+ | `injectPaths` | `Array<string\|{path: string, html: string}>` | `['/']` | Paths where the loader script is injected |
143
145
  | `corsProxy` | `boolean \| string\| CORSProxyConfig` | | Enable the HTTP CORS proxy |
144
146
  | `wsProxy` | `WSProxyConfig` | | Proxy WebSocket connections to an upstream server |
147
+ | `routes` | `Object` | | Custom routes passed directly to `Bun.serve` |
145
148
  | `filesEndpoint` | `boolean \| string` | `'/files'` | Expose the watched file list as JSON. `true` mounts at `/files` |
146
149
  | `configEndpoint` | `boolean \| string` | `'/config'` | Expose the server config as JSON. `false` to disable |
147
150
  | `getFiles` | `() => string[]` | | Override the file list sent to connecting clients |
@@ -262,6 +265,43 @@ new HMRServer({
262
265
 
263
266
  ---
264
267
 
268
+ ### Routing
269
+
270
+ The `routes` option is passed directly to `Bun.serve`. Useful for mocking APIs, serving specific files, or mirroring directories without a separate server process. See [Bun's routing docs](https://bun.sh/docs/api/http#routing) for the full API.
271
+
272
+ ```js
273
+ routes: {
274
+ // Serve a specific file
275
+ '/favicon.ico': new Response(Bun.file('./public/favicon.ico')),
276
+
277
+ // Mock API responses
278
+ '/api/session': Response.json({ user: 'dev', role: 'admin' }),
279
+ '/api/flags': Response.json({ darkMode: true, betaFeatures: false }),
280
+
281
+ // Named params
282
+ '/api/users/:id': req => Response.json({ id: req.params.id }),
283
+
284
+ // Per-method handlers
285
+ '/api/posts': {
286
+ GET: () => new Response("List posts"),
287
+ POST: async req => {
288
+ const body = await req.json();
289
+ return Response.json({ created: true, ...body });
290
+ },
291
+ },
292
+
293
+ // Mirror directory /assets/* served from /dist/assets/*
294
+ '/assets/*': req => new Response(Bun.file(`./dist/assets/${req.params['*']}`)),
295
+
296
+ // SPA fallback
297
+ '/*': new Response(Bun.file('./index.html')),
298
+ }
299
+ ```
300
+
301
+ > **Note:** Avoid paths that overlap with `wsPath`, `filesEndpoint`, `configEndpoint`, or any proxy paths, the server will warn on startup if a collision is detected.
302
+
303
+ ---
304
+
265
305
  ### TLS
266
306
 
267
307
  Pass `tls` to switch the server to HTTPS and WSS. The client's `secure` option or a `wss://` URL flips the client to match.
@@ -442,14 +482,14 @@ client
442
482
  console.log(`Loaded ${files.length} files`);
443
483
  console.log("Server cold patterns:", config.cold);
444
484
  })
445
- .on("reload", ({ file }) => {
485
+ .on("reload", (file) => {
446
486
  console.log(`Hot-reloaded: ${file}`);
447
- applyChanges(file);
487
+ swapPrototype(file);
448
488
  })
449
- .on("add", ({ file }) => {
489
+ .on("add", (file) => {
450
490
  console.log(`New file available: ${file}`);
451
491
  })
452
- .on("remove", ({ file }) => {
492
+ .on("remove", (file) => {
453
493
  console.log(`File removed: ${file}`);
454
494
  cleanupForFile(file);
455
495
  })
@@ -467,9 +507,9 @@ client
467
507
  | `connect` | | WebSocket connection established |
468
508
  | `disconnect` | | WebSocket disconnected |
469
509
  | `init` | `{ files, config }` | Server sent the initial file list |
470
- | `reload` | `{ file }` | A file was changed and hot-reloaded |
471
- | `add` | `{ file }` | A new file was detected |
472
- | `remove` | `{ file }` | A file was removed |
510
+ | `reload` | `file: string` | A file was changed and hot-reloaded |
511
+ | `add` | `file: string` | A new file was detected |
512
+ | `remove` | `file: string` | A file was removed |
473
513
  | `cold` | `file: string` | A cold file changed |
474
514
  | `error` | `Error` | A connection or message error occurred |
475
515
 
@@ -49,7 +49,7 @@ export class FileLoader {
49
49
  _flushReload(path: any): Promise<void>;
50
50
  removeFile(path: any): Promise<void>;
51
51
  makeUrl(path: any): string;
52
- _post(message: any): void;
53
- _inject(kind: any, code: any, file: any): void;
52
+ _postAndAwaitAck(message: any): Promise<any>;
53
+ _inject(kind: any, code: any, file: any): Promise<any>;
54
54
  }
55
55
  //# sourceMappingURL=file-loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-loader.d.ts","sourceRoot":"","sources":["../../src/client/file-loader.js"],"names":[],"mappings":"AAAA,mFAAmF;AACnF;IACE;;;;OAiCC;IAhCC,aAAsB;IACtB;;;;;OAKG;IACH,cAFU,MAAM,GAAG,IAAI,CAES;IAChC,qBAAqB;IACrB,cADW,MAAM,CACe;IAChC;;;;;;OAMG;IACH,KAFU,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAExB;IACd;;;;;OAKG;IACH,WAFU,GAAG,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,UAAU,CAAA;KAAE,CAAC,CAEvC;IAC1B;;;;;OAKG;IACH,UAFU,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAEJ;IAG3B,kCAOC;IAKD,qCAwBC;IAED,oDAmBC;IAED,wCAcC;IAED,oCAwBC;IAKD,oCAeC;IAED,uCAUC;IAED,qCAsBC;IAGD,2BAIC;IAGD,0BAEC;IAGD,+CAEC;CACF"}
1
+ {"version":3,"file":"file-loader.d.ts","sourceRoot":"","sources":["../../src/client/file-loader.js"],"names":[],"mappings":"AAAA,mFAAmF;AACnF;IACE;;;;OAiCC;IAhCC,aAAsB;IACtB;;;;;OAKG;IACH,cAFU,MAAM,GAAG,IAAI,CAES;IAChC,qBAAqB;IACrB,cADW,MAAM,CACe;IAChC;;;;;;OAMG;IACH,KAFU,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAExB;IACd;;;;;OAKG;IACH,WAFU,GAAG,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,UAAU,CAAA;KAAE,CAAC,CAEvC;IAC1B;;;;;OAKG;IACH,UAFU,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAEJ;IAG3B,kCAOC;IAKD,qCAwBC;IAED,oDAmBC;IAED,wCAcC;IAED,oCA4BC;IAKD,oCAeC;IAED,uCAUC;IAED,qCAqBC;IAGD,2BAIC;IAED,6CAaC;IAED,uDAEC;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"hmr-client.d.ts","sourceRoot":"","sources":["../../src/client/hmr-client.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH;IACE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,qBAvBG;QAAyB,KAAK,GAAtB,MAAM;QACW,OAAO,GAAxB,MAAM;QACY,UAAU,GAA5B,OAAO;QACU,IAAI,GAArB,MAAM;QACW,IAAI,GAArB,MAAM;QACY,MAAM,GAAxB,OAAO;QACW,aAAa,GAA/B,OAAO;QACU,cAAc,GAA/B,MAAM;QACW,iBAAiB,GAAlC,MAAM;QACY,eAAe,GAAjC,OAAO;QACY,IAAI,GAAvB,MAAM,EAAE;QACsC,UAAU,GAAxD,CAAS,IAAM,EAAN,MAAM,EAAE,IAAQ,EAAR,MAAM,EAAE,KAAG,OAAO;QAChB,IAAI,GAAvB,MAAM,EAAE;QAC4B,UAAU,GAA9C,CAAS,IAAM,EAAN,MAAM,KAAG,OAAO;QACyB,iBAAiB,GAAnE,CAAS,IAAM,EAAN,MAAM,EAAE,IAAQ,EAAR,MAAM,EAAE,KAAG,MAAM,GAAC,IAAI;QACN,YAAY,GAA7C,CAAS,IAAM,EAAN,MAAM,KAAG,IAAI;QACiB,SAAS,GAAhD,CAAS,IAAQ,EAAR,MAAM,EAAE,KAAG,MAAM,EAAE;QACF,SAAS,GAAnC,KAAK,UAAU;QACY,MAAM,GAAjC,OAAO,MAAS;KACxB,EAwFF;IA9EC,WAAkB;IAClB,aAAsB;IACtB,oBAAsB;IAEtB,+BAAyD;IACzD,uBAA+C;IAC/C,uBAAiD;IACjD,0BAAwD;IACxD,yBAAqD;IAGrD,wBAAsC;IACtC,oBA5BkB,MAAM,KAAG,OAAO,CA4BQ;IAG1C,oBAAiF;IACjF,gBAAuE;IAGvE,gBAAkB;IAElB,0BApCkB,MAAM,QAAE,MAAM,EAAE,KAAG,MAAM,GAAC,IAAI,CAoCO;IACvD,qBApCkB,MAAM,KAAG,IAAI,CAoCc;IAC7C,sBAAqC;IACrC,eAAmE;IAEnE,kBAAkB;IAClB,0BAA0B;IAC1B,qBAAwB;IACxB,6BAA8B;IAC9B,gCAA2B;IAI3B,qBAAuB;IACvB,6BAAgC;IAShC,mBAAiC;IACjC,mBAAiC;IAIjC,sBAAiD;IAGjD,uCAAuB;IAEvB,uBAIE;IAEF,wEAAwE;IACxE,aADW,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CACF;IAC5B,uFAAuF;IACvF,qBADW,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CACC;IAEpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAYC;IAGH,oCAkBC;IAED,8CAcC;IAED,mCAIC;IAED,sEAoDC;IAED,kCA0BC;IAED,4CAgDC;IAED,8EAkEC;IAED,2CA4CC;IAED,wCA6CC;IAED;;;;;OAKG;IACH,UAJW,MAAM,GAAC,QAAQ,GAAC,KAAK,GAAC,QAAQ,GAAC,MAAM,GAAC,SAAS,GAAC,YAAY,GAAC,OAAO,sBAElE,SAAS,CAQrB;IAED;;;;;OAKG;IACH,YAJW,MAAM,GAAC,QAAQ,GAAC,KAAK,GAAC,QAAQ,GAAC,MAAM,GAAC,SAAS,GAAC,YAAY,GAAC,OAAO,sBAElE,SAAS,CASrB;IAED;;;;;OAKG;IACH,WAJW,MAAM,GAAC,QAAQ,GAAC,KAAK,GAAC,QAAQ,GAAC,MAAM,GAAC,SAAS,GAAC,YAAY,GAAC,OAAO,sBAElE,SAAS,CAoBrB;IAED,uCAQC;IAMD,iCAGC;IAED,oCAWC;IAID,6BAsBC;IAKD,2BAoBC;IAED;;;OAGG;IACH,WAFa,OAAO,CAAC,IAAI,CAAC,CAwGzB;IAED;;OAEG;IACH,mBAgBC;CACF;2BA1uB0B,kBAAkB"}
1
+ {"version":3,"file":"hmr-client.d.ts","sourceRoot":"","sources":["../../src/client/hmr-client.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH;IACE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,qBAvBG;QAAyB,KAAK,GAAtB,MAAM;QACW,OAAO,GAAxB,MAAM;QACY,UAAU,GAA5B,OAAO;QACU,IAAI,GAArB,MAAM;QACW,IAAI,GAArB,MAAM;QACY,MAAM,GAAxB,OAAO;QACW,aAAa,GAA/B,OAAO;QACU,cAAc,GAA/B,MAAM;QACW,iBAAiB,GAAlC,MAAM;QACY,eAAe,GAAjC,OAAO;QACY,IAAI,GAAvB,MAAM,EAAE;QACsC,UAAU,GAAxD,CAAS,IAAM,EAAN,MAAM,EAAE,IAAQ,EAAR,MAAM,EAAE,KAAG,OAAO;QAChB,IAAI,GAAvB,MAAM,EAAE;QAC4B,UAAU,GAA9C,CAAS,IAAM,EAAN,MAAM,KAAG,OAAO;QACyB,iBAAiB,GAAnE,CAAS,IAAM,EAAN,MAAM,EAAE,IAAQ,EAAR,MAAM,EAAE,KAAG,MAAM,GAAC,IAAI;QACN,YAAY,GAA7C,CAAS,IAAM,EAAN,MAAM,KAAG,IAAI;QACiB,SAAS,GAAhD,CAAS,IAAQ,EAAR,MAAM,EAAE,KAAG,MAAM,EAAE;QACF,SAAS,GAAnC,KAAK,UAAU;QACY,MAAM,GAAjC,OAAO,MAAS;KACxB,EAwFF;IA9EC,WAAkB;IAClB,aAAsB;IACtB,oBAAsB;IAEtB,+BAAyD;IACzD,uBAA+C;IAC/C,uBAAiD;IACjD,0BAAwD;IACxD,yBAAqD;IAGrD,wBAAsC;IACtC,oBA5BkB,MAAM,KAAG,OAAO,CA4BQ;IAG1C,oBAAiF;IACjF,gBAAuE;IAGvE,gBAAkB;IAElB,0BApCkB,MAAM,QAAE,MAAM,EAAE,KAAG,MAAM,GAAC,IAAI,CAoCO;IACvD,qBApCkB,MAAM,KAAG,IAAI,CAoCc;IAC7C,sBAAqC;IACrC,eAAmE;IAEnE,kBAAkB;IAClB,0BAA0B;IAC1B,qBAAwB;IACxB,6BAA8B;IAC9B,gCAA2B;IAI3B,qBAAuB;IACvB,6BAAgC;IAShC,mBAAiC;IACjC,mBAAiC;IAIjC,sBAAiD;IAGjD,uCAAuB;IAEvB,uBAIE;IAEF,wEAAwE;IACxE,aADW,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CACF;IAC5B,uFAAuF;IACvF,qBADW,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CACC;IAEpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAYC;IAGH,oCAkBC;IAED,8CAcC;IAED,mCAIC;IAED,sEAoDC;IAED,kCA0BC;IAED,4CAgDC;IAED,8EAkEC;IAED,2CA4CC;IAED,wCA6CC;IAED;;;;;OAKG;IACH,UAJW,MAAM,GAAC,QAAQ,GAAC,KAAK,GAAC,QAAQ,GAAC,MAAM,GAAC,SAAS,GAAC,YAAY,GAAC,OAAO,sBAElE,SAAS,CAQrB;IAED;;;;;OAKG;IACH,YAJW,MAAM,GAAC,QAAQ,GAAC,KAAK,GAAC,QAAQ,GAAC,MAAM,GAAC,SAAS,GAAC,YAAY,GAAC,OAAO,sBAElE,SAAS,CASrB;IAED;;;;;OAKG;IACH,WAJW,MAAM,GAAC,QAAQ,GAAC,KAAK,GAAC,QAAQ,GAAC,MAAM,GAAC,SAAS,GAAC,YAAY,GAAC,OAAO,sBAElE,SAAS,CAoBrB;IAED,uCAQC;IAMD,iCAGC;IAED,oCAWC;IAID,6BAyBC;IAKD,2BA0BC;IAED;;;OAGG;IACH,WAFa,OAAO,CAAC,IAAI,CAAC,CAiGzB;IAED;;OAEG;IACH,mBAgBC;CACF;2BA5uB0B,kBAAkB"}
@@ -1 +1 @@
1
- {"version":3,"file":"stub.d.ts","sourceRoot":"","sources":["../../src/client/stub.js"],"names":[],"mappings":"AAAA,6BAmCC"}
1
+ {"version":3,"file":"stub.d.ts","sourceRoot":"","sources":["../../src/client/stub.js"],"names":[],"mappings":"AAAA,6BAsEC"}
@@ -1586,15 +1586,15 @@ var HMR = (() => {
1586
1586
  fetch(url).then((r) => {
1587
1587
  if (!r.ok) throw new Error(`Failed to fetch CSS: ${path} (${r.status})`);
1588
1588
  return r.text();
1589
- }).then((code) => {
1590
- this._inject("css", code, path);
1591
- })
1589
+ }).then((code) => this._inject("css", code, path))
1592
1590
  );
1593
1591
  }
1594
1592
  if (toParent) {
1595
1593
  ops.push(this._loadCSSInParent(path, url));
1596
1594
  }
1597
- await Promise.all(ops);
1595
+ const results = await Promise.allSettled(ops);
1596
+ const failed = results.find((r) => r.status === "rejected");
1597
+ if (failed) throw failed.reason;
1598
1598
  return true;
1599
1599
  }
1600
1600
  _loadCSSInParent(path, url) {
@@ -1622,7 +1622,7 @@ var HMR = (() => {
1622
1622
  if (!r.ok) throw new Error(`Failed to fetch module: ${path} (${r.status})`);
1623
1623
  return r.text();
1624
1624
  });
1625
- this._inject("module", code, path);
1625
+ await this._inject("module", code, path);
1626
1626
  return true;
1627
1627
  }
1628
1628
  await import(url);
@@ -1637,14 +1637,18 @@ var HMR = (() => {
1637
1637
  if (!r.ok) throw new Error(`Failed to fetch script: ${path} (${r.status})`);
1638
1638
  return r.text();
1639
1639
  });
1640
- this._inject("script", code, path);
1640
+ await this._inject("script", code, path);
1641
1641
  return true;
1642
1642
  }
1643
1643
  const script = document.createElement("script");
1644
1644
  script.src = url;
1645
1645
  script.setAttribute("data-file", path);
1646
1646
  return new Promise((resolve, reject) => {
1647
- script.onload = () => resolve(true);
1647
+ script.onload = () => {
1648
+ const previous = document.querySelector(`script[data-file="${path}"]:not([src="${url}"])`);
1649
+ if (previous) previous.remove();
1650
+ resolve(true);
1651
+ };
1648
1652
  script.onerror = () => reject(new Error(`Failed to load script: ${path}`));
1649
1653
  document.head.appendChild(script);
1650
1654
  });
@@ -1686,8 +1690,7 @@ var HMR = (() => {
1686
1690
  this.loadQueue.delete(path);
1687
1691
  }
1688
1692
  if (this.iframeTarget) {
1689
- this._post({ type: "hmr:remove", file: path });
1690
- await Promise.resolve();
1693
+ await this._postAndAwaitAck({ type: "hmr:remove", file: path });
1691
1694
  } else {
1692
1695
  const el = document.querySelector(`[data-file="${path}"]`);
1693
1696
  if (el) {
@@ -1703,13 +1706,27 @@ var HMR = (() => {
1703
1706
  this.versions.set(path, v);
1704
1707
  return `${this.httpUrl}${path}?v=${v}`;
1705
1708
  }
1706
- // Send a raw postMessage to the iframe target
1707
- _post(message) {
1708
- this.iframeTarget.postMessage(message, this.iframeOrigin);
1709
+ // Post a message and resolve once the stub sends back hmr:ack
1710
+ _postAndAwaitAck(message) {
1711
+ return new Promise((resolve) => {
1712
+ const onAck = (e) => {
1713
+ if (e.source !== this.iframeTarget) return;
1714
+ let data;
1715
+ try {
1716
+ data = JSON.parse(e.data);
1717
+ } catch {
1718
+ return;
1719
+ }
1720
+ if (data?.type !== "hmr:ack") return;
1721
+ window.removeEventListener("message", onAck);
1722
+ resolve();
1723
+ };
1724
+ window.addEventListener("message", onAck);
1725
+ this.iframeTarget.postMessage(JSON.stringify(message), this.iframeOrigin);
1726
+ });
1709
1727
  }
1710
- // Forward a file payload to the iframe target
1711
1728
  _inject(kind, code, file) {
1712
- this._post({ type: "hmr:inject", kind, code, file });
1729
+ return this._postAndAwaitAck({ type: "hmr:inject", kind, code, file });
1713
1730
  }
1714
1731
  };
1715
1732
 
@@ -2213,15 +2230,22 @@ var HMR = (() => {
2213
2230
  this._processingMessages = false;
2214
2231
  }
2215
2232
  // Wait for stub's hmr:ready signal. Stub fires it proactively on run.
2216
- // Times out after 5s if stub was never injected.
2233
+ // Times out after 5s and resolves anyway, a missing stub degrades gracefully.
2217
2234
  _waitForStub() {
2218
- return new Promise((resolve, reject) => {
2235
+ return new Promise((resolve) => {
2219
2236
  const timer = setTimeout(() => {
2220
2237
  window.removeEventListener("message", onReady);
2221
- reject(new Error("Timed out waiting for hmr:ready. Was HMR.stub() called in the iframe?"));
2238
+ this.log("warning", "Timed out waiting for hmr:ready. Was HMR.stub() called in the iframe?");
2239
+ resolve();
2222
2240
  }, 5e3);
2223
2241
  const onReady = (e) => {
2224
- if (e.data?.type !== "hmr:ready") return;
2242
+ let data;
2243
+ try {
2244
+ data = JSON.parse(e.data);
2245
+ } catch {
2246
+ return;
2247
+ }
2248
+ if (data?.type !== "hmr:ready") return;
2225
2249
  const originOk = this._iframeOrigin === "*" || e.origin === this._iframeOrigin;
2226
2250
  if (!originOk) return;
2227
2251
  clearTimeout(timer);
@@ -2241,15 +2265,25 @@ var HMR = (() => {
2241
2265
  _listenForReattach() {
2242
2266
  if (this._onReattach) return;
2243
2267
  this._onReattach = async (e) => {
2244
- if (e.data?.type !== "hmr:ready") return;
2268
+ let data;
2269
+ try {
2270
+ data = JSON.parse(e.data);
2271
+ } catch {
2272
+ return;
2273
+ }
2274
+ if (data?.type !== "hmr:ready") return;
2245
2275
  const originOk = this._iframeOrigin === "*" || e.origin === this._iframeOrigin;
2246
2276
  if (!originOk) return;
2247
2277
  if (e.source === this._iframeTarget) return;
2248
2278
  this._iframeTarget = e.source;
2249
2279
  this.fileLoader.iframeTarget = e.source;
2250
2280
  this.log("success", "HMR reattached to new iframe");
2251
- for (const path of this.fileLoader.versions.keys()) {
2252
- await this.fileLoader.loadFile(path);
2281
+ for (const path of this.sortFiles([...this.fileLoader.versions.keys()])) {
2282
+ try {
2283
+ await this.fileLoader.loadFile(path);
2284
+ } catch (e2) {
2285
+ this.log("error", `Reattach failed to load ${path}: ${e2.message}`);
2286
+ }
2253
2287
  }
2254
2288
  };
2255
2289
  window.addEventListener("message", this._onReattach);
@@ -2285,15 +2319,8 @@ var HMR = (() => {
2285
2319
  this._messageQueue = [];
2286
2320
  this._processingMessages = true;
2287
2321
  if (this._iframeTarget || this._stubManaged) {
2288
- try {
2289
- await this._waitForStub();
2290
- if (this._stubManaged) this._listenForReattach();
2291
- } catch (e) {
2292
- this.log("error", e.message);
2293
- this._processingMessages = false;
2294
- reject(e);
2295
- return;
2296
- }
2322
+ await this._waitForStub();
2323
+ if (this._stubManaged) this._listenForReattach();
2297
2324
  }
2298
2325
  this._processingMessages = false;
2299
2326
  if (this._messageQueue.length > 0) this._drainMessageQueue();
@@ -2369,36 +2396,72 @@ var HMR = (() => {
2369
2396
  const removeIfExists = (el) => {
2370
2397
  if (el) el.remove();
2371
2398
  };
2372
- const injectScript = (kind, code, file) => {
2373
- removeIfExists(byFile("script", file));
2374
- const script = document.createElement("script");
2375
- if (kind === "module") script.type = "module";
2376
- script.textContent = code;
2377
- script.dataset.file = file;
2378
- document.documentElement.appendChild(script);
2379
- script.remove();
2399
+ const injectScript = async (kind, code, file) => {
2400
+ const url = URL.createObjectURL(new Blob([code + `
2401
+ //# sourceURL=${file}`], { type: "text/javascript" }));
2402
+ try {
2403
+ if (kind === "module") {
2404
+ await import(url);
2405
+ } else {
2406
+ const script = document.createElement("script");
2407
+ script.src = url;
2408
+ await new Promise((resolve, reject) => {
2409
+ script.onload = resolve;
2410
+ script.onerror = () => reject(new Error(`Failed to execute script: ${file}`));
2411
+ document.documentElement.appendChild(script);
2412
+ });
2413
+ }
2414
+ } finally {
2415
+ URL.revokeObjectURL(url);
2416
+ }
2380
2417
  };
2381
- const injectStyle = (code, file) => {
2382
- const existing = byFile("style", file);
2383
- const style = document.createElement("style");
2384
- style.textContent = code;
2385
- style.dataset.file = file;
2386
- document.head.appendChild(style);
2418
+ const injectStyle = async (code, file) => {
2419
+ const existing = byFile("link", file);
2420
+ const url = URL.createObjectURL(new Blob([code + `
2421
+ /*# sourceURL=${file} */`], { type: "text/css" }));
2422
+ const link = document.createElement("link");
2423
+ link.rel = "stylesheet";
2424
+ link.href = url;
2425
+ link.dataset.file = file;
2426
+ await new Promise((resolve, reject) => {
2427
+ link.onload = () => {
2428
+ URL.revokeObjectURL(url);
2429
+ resolve();
2430
+ };
2431
+ link.onerror = () => {
2432
+ URL.revokeObjectURL(url);
2433
+ reject(new Error(`Failed to load CSS: ${file}`));
2434
+ };
2435
+ document.head.appendChild(link);
2436
+ });
2387
2437
  removeIfExists(existing);
2388
2438
  };
2389
- window.addEventListener("message", (e) => {
2390
- const data = e.data;
2439
+ window.addEventListener("message", async (e) => {
2440
+ let data;
2441
+ try {
2442
+ data = JSON.parse(e.data);
2443
+ } catch {
2444
+ return;
2445
+ }
2391
2446
  if (!data?.type) return;
2447
+ const ackOrigin = e.origin && e.origin !== "null" ? e.origin : "*";
2448
+ const ack = () => e.source?.postMessage(JSON.stringify({ type: "hmr:ack" }), ackOrigin);
2392
2449
  if (data.type === "hmr:remove") {
2393
2450
  removeIfExists(document.querySelector(`[data-file="${CSS.escape(data.file)}"]`));
2451
+ ack();
2394
2452
  return;
2395
2453
  }
2396
2454
  if (data.type !== "hmr:inject") return;
2397
2455
  const { kind, code, file } = data;
2398
- if (kind === "script" || kind === "module") injectScript(kind, code, file);
2399
- else if (kind === "css") injectStyle(code, file);
2456
+ try {
2457
+ if (kind === "script" || kind === "module") await injectScript(kind, code, file);
2458
+ else if (kind === "css") await injectStyle(code, file);
2459
+ } catch (err) {
2460
+ console.error(`Failed to inject ${file}:`, err.message ?? err, "\n" + err.stack);
2461
+ }
2462
+ ack();
2400
2463
  });
2401
- window.parent.postMessage({ type: "hmr:ready" }, "*");
2464
+ window.parent.postMessage(JSON.stringify({ type: "hmr:ready" }), "*");
2402
2465
  }
2403
2466
  return __toCommonJS(client_exports);
2404
2467
  })();