cindel 1.0.4 → 1.0.5

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
@@ -32,6 +32,7 @@
32
32
  - No runtime dependencies, so it works in any modern browser
33
33
  - Event system with `on`, `once`, and `off` for connect, disconnect, reload, add, remove, etc.
34
34
  - IIFE build compatible with userscript managers (Tampermonkey, Greasemonkey) via `@require`
35
+ - Iframe injection via `postMessage` for Private Network Access restricted environments
35
36
 
36
37
  ---
37
38
 
@@ -408,6 +409,10 @@ process.on("SIGINT", () => server.stop().then(() => process.exit(0)));
408
409
  | `onFileLoaded` | `(file) => void` | | Called after each file is loaded or reloaded |
409
410
  | `loadOrder` | `Stage[]` | | Extra stages prepended before the built-in sorting |
410
411
  | `sortFiles` | `(files) => string[]` | | Fully replaces the default sort. When set, `loadOrder` is ignored |
412
+ | `iframe` | `boolean \| Object` | | Forward files to an iframe via `postMessage` |
413
+ | `iframe.target` | `Window \| HTMLIFrameElement` | | Target a specific same-origin iframe, skips auto-discovery |
414
+ | `iframe.origin` | `string` | `'*'` | Validates incoming handshake responses |
415
+ | `iframe.css` | `'iframe' \| 'parent' \| 'both'` | `'iframe'` | Where CSS files are loaded when `iframe` is set |
411
416
 
412
417
  ### Methods
413
418
 
@@ -552,6 +557,38 @@ new HMRClient({
552
557
 
553
558
  ---
554
559
 
560
+ ### Iframe Injection
561
+
562
+ When your project runs inside an `<iframe>` on a third-party domain, the browser's Private Network Access policy blocks the iframe from reaching `localhost` directly. The `iframe` option works around this by fetching files in the parent page and forwarding them to the iframe via `postMessage`.
563
+
564
+ Call `stub()` once inside the iframe via a userscript or existing inline script:
565
+
566
+ ```js
567
+ HMR.stub();
568
+ ```
569
+
570
+ Then configure the client in the parent:
571
+
572
+ ```js
573
+ new HMR.HMRClient({
574
+ port: 1338,
575
+ iframe: true,
576
+ });
577
+ ```
578
+
579
+ The client listens for the stub's ready signal and attaches automatically. Reattachment is automatic if the iframe reloads or is replaced. In the rare case of multiple same-origin iframes all running stub, pass `iframe.target` to target a specific one, but reattachment is then your responsibility.
580
+
581
+ ```js
582
+ iframe: {
583
+ target: document.querySelector('iframe#html5game'),
584
+ css: 'both', // 'iframe' (default) | 'parent' | 'both'
585
+ }
586
+ ```
587
+
588
+ > `.mjs` files are forwarded as `<script type="module">` blocks, preserving ES module semantics. Bare specifiers and relative imports will not resolve, only self-contained modules are supported.
589
+
590
+ ---
591
+
555
592
  ### Override Detection
556
593
 
557
594
  Override detection lets you maintain a parallel directory of replacement files that shadow originals without modifying them. When an override changes, the client unloads the original before loading the override.
@@ -592,7 +629,7 @@ new HMRClient({
592
629
  | Import path | Environment | Description |
593
630
  | ------------------------------------- | ----------- | -------------------- |
594
631
  | `cindel` or `cindel/server` | Node / Bun | `HMRServer` |
595
- | `cindel/client` | Browser ESM | `HMRClient` |
632
+ | `cindel/client` | Browser ESM | `HMRClient`, `stub` |
596
633
  | `https://cdn.jsdelivr.net/npm/cindel` | Browser CDN | Exposes `window.HMR` |
597
634
 
598
635
  ---
@@ -1,7 +1,28 @@
1
1
  /** Handles loading and hot reloading of JavaScript and CSS files via blob URLs. */
2
2
  export class FileLoader {
3
- constructor(httpUrl: any);
3
+ constructor(httpUrl: any, { iframeTarget, iframeOrigin, css }?: {
4
+ iframeTarget?: any;
5
+ iframeOrigin?: string;
6
+ css?: string;
7
+ });
4
8
  httpUrl: any;
9
+ /**
10
+ * When set, JS and module files are fetched in the parent context and forwarded
11
+ * to this window via postMessage instead of being injected as DOM elements.
12
+ * CSS routing is controlled separately by the `css` option.
13
+ * @type {Window | null}
14
+ */
15
+ iframeTarget: Window | null;
16
+ /** @type {string} */
17
+ iframeOrigin: string;
18
+ /**
19
+ * Controls where CSS files go when `iframeTarget` is set.
20
+ * - `'iframe'` -> forward only, skip parent injection (default)
21
+ * - `'parent'` -> load normally in parent, do not forward
22
+ * - `'both'` -> load in parent via `<link>` and forward to iframe
23
+ * @type {'iframe' | 'parent' | 'both'}
24
+ */
25
+ css: "iframe" | "parent" | "both";
5
26
  /**
6
27
  * Debounce state per file. Stores { timeout, resolvers[] } so that
7
28
  * when a rapid second change clears the first timeout, the first
@@ -20,12 +41,15 @@ export class FileLoader {
20
41
  */
21
42
  versions: Map<string, number>;
22
43
  loadFile(path: any): Promise<any>;
23
- loadCSS(path: any): Promise<any>;
44
+ loadCSS(path: any): Promise<boolean>;
45
+ _loadCSSInParent(path: any, url: any): Promise<any>;
24
46
  loadModule(path: any): Promise<boolean>;
25
47
  loadScript(path: any): Promise<any>;
26
48
  reloadFile(path: any): Promise<any>;
27
49
  _flushReload(path: any): Promise<void>;
28
50
  removeFile(path: any): Promise<void>;
29
51
  makeUrl(path: any): string;
52
+ _post(message: any): void;
53
+ _inject(kind: any, code: any, file: any): void;
30
54
  }
31
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,0BAgBC;IAfC,aAAsB;IACtB;;;;;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,iCAoBC;IAED,wCAIC;IAED,oCAeC;IAKD,oCAeC;IAED,uCAUC;IAED,qCAiBC;IAGD,2BAIC;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,oCAwBC;IAKD,oCAeC;IAED,uCAUC;IAED,qCAsBC;IAGD,2BAIC;IAGD,0BAEC;IAGD,+CAEC;CACF"}
@@ -48,6 +48,10 @@ export class HMRClient {
48
48
  * @param {function(string): void} [options.onFileLoaded] - Called after each file loads or reloads. Receives `(filePath)`.
49
49
  * @param {function(string[]): string[]} [options.sortFiles] - Custom sort for the initial file load order. When provided, replaces `defaultSortFiles` entirely and `loadOrder` is ignored.
50
50
  * @param {Array<Function>} [options.loadOrder] - Stages prepended before the built-in sort (CSS-first, cold-first, alphabetical). One argument: return true to load that file first. Two arguments: works like a normal sort callback.
51
+ * @param {boolean | Object} [options.iframe] - Forward files to an iframe via `postMessage` (for Private Network Access restricted environments). Pass `true` for defaults.
52
+ * @param {Window | HTMLIFrameElement} [options.iframe.target] - Target a specific same-origin iframe directly, skipping auto-discovery. Reattachment is not automatic.
53
+ * @param {string} [options.iframe.origin] - The iframe's origin used to validate incoming handshake responses. Defaults to `'*'`.
54
+ * @param {'iframe'|'parent'|'both'} [options.iframe.css='iframe'] - Where CSS files are loaded when `iframe` is set.
51
55
  */
52
56
  constructor(options: {
53
57
  wsUrl?: string;
@@ -68,6 +72,7 @@ export class HMRClient {
68
72
  onFileLoaded?: (arg0: string) => void;
69
73
  sortFiles?: (arg0: string[]) => string[];
70
74
  loadOrder?: Array<Function>;
75
+ iframe?: boolean | any;
71
76
  });
72
77
  wsUrl: any;
73
78
  httpUrl: any;
@@ -93,6 +98,10 @@ export class HMRClient {
93
98
  _reconnectTimer: NodeJS.Timeout;
94
99
  _messageQueue: any[];
95
100
  _processingMessages: boolean;
101
+ _iframeTarget: any;
102
+ _iframeOrigin: any;
103
+ _stubManaged: boolean;
104
+ _onReattach: (e: any) => Promise<void>;
96
105
  fileLoader: FileLoader;
97
106
  /** @type {Map<string, string>} - Maps override file -> original file */
98
107
  overrideMap: Map<string, string>;
@@ -177,6 +186,8 @@ export class HMRClient {
177
186
  emit(event: any, ...args: any[]): void;
178
187
  _enqueueMessage(data: any): void;
179
188
  _drainMessageQueue(): Promise<void>;
189
+ _waitForStub(): Promise<any>;
190
+ _listenForReattach(): void;
180
191
  /**
181
192
  * Connect to the HMR server
182
193
  * @returns {Promise<void>}
@@ -1 +1 @@
1
- {"version":3,"file":"hmr-client.d.ts","sourceRoot":"","sources":["../../src/client/hmr-client.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH;IACE;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,qBAnBG;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;KACzB,EAgEA;IAzDC,WAAkB;IAClB,aAAsB;IACtB,oBAAsB;IAEtB,+BAAyD;IACzD,uBAA+C;IAC/C,uBAAiD;IACjD,0BAAwD;IACxD,yBAAqD;IAGrD,wBAAsC;IACtC,oBAxBkB,MAAM,KAAG,OAAO,CAwBQ;IAG1C,oBAAiF;IACjF,gBAAuE;IAGvE,gBAAkB;IAElB,0BAhCkB,MAAM,QAAE,MAAM,EAAE,KAAG,MAAM,GAAC,IAAI,CAgCO;IACvD,qBAhCkB,MAAM,KAAG,IAAI,CAgCc;IAC7C,sBAAqC;IACrC,eAAmE;IAEnE,kBAAkB;IAClB,0BAA0B;IAC1B,qBAAwB;IACxB,6BAA8B;IAC9B,gCAA2B;IAI3B,qBAAuB;IACvB,6BAAgC;IAEhC,uBAA8C;IAE9C,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;IAED;;;OAGG;IACH,WAFa,OAAO,CAAC,IAAI,CAAC,CAuFzB;IAED;;OAEG;IACH,mBAUC;CACF;2BAvoB0B,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,6BAsBC;IAKD,2BAoBC;IAED;;;OAGG;IACH,WAFa,OAAO,CAAC,IAAI,CAAC,CAwGzB;IAED;;OAEG;IACH,mBAgBC;CACF;2BA1uB0B,kBAAkB"}
@@ -0,0 +1,2 @@
1
+ export function stub(): void;
2
+ //# sourceMappingURL=stub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stub.d.ts","sourceRoot":"","sources":["../../src/client/stub.js"],"names":[],"mappings":"AAAA,6BAmCC"}
package/dist/client.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { FileLoader } from "./client/file-loader.js";
1
+ export { stub } from "./client/stub.js";
2
2
  export { HMRClient as default, HMRClient } from "./client/hmr-client.js";
3
3
  //# sourceMappingURL=client.d.ts.map
@@ -1551,15 +1551,18 @@ var HMR = (() => {
1551
1551
  // src/client.js
1552
1552
  var client_exports = {};
1553
1553
  __export(client_exports, {
1554
- FileLoader: () => FileLoader,
1555
1554
  HMRClient: () => HMRClient,
1556
- default: () => HMRClient
1555
+ default: () => HMRClient,
1556
+ stub: () => stub
1557
1557
  });
1558
1558
 
1559
1559
  // src/client/file-loader.js
1560
1560
  var FileLoader = class {
1561
- constructor(httpUrl) {
1561
+ constructor(httpUrl, { iframeTarget = null, iframeOrigin = "*", css = "iframe" } = {}) {
1562
1562
  this.httpUrl = httpUrl;
1563
+ this.iframeTarget = iframeTarget;
1564
+ this.iframeOrigin = iframeOrigin;
1565
+ this.css = css;
1563
1566
  this.loadQueue = /* @__PURE__ */ new Map();
1564
1567
  this.versions = /* @__PURE__ */ new Map();
1565
1568
  }
@@ -1574,8 +1577,28 @@ var HMR = (() => {
1574
1577
  // the old one. This fixes the brief flash of unstyled content that
1575
1578
  // happens when you remove the old sheet before the new one is parsed.
1576
1579
  async loadCSS(path) {
1577
- const existing = document.querySelector(`link[data-file="${path}"]`);
1578
1580
  const url = this.makeUrl(path);
1581
+ const toIframe = this.iframeTarget && this.css !== "parent";
1582
+ const toParent = !this.iframeTarget || this.css !== "iframe";
1583
+ const ops = [];
1584
+ if (toIframe) {
1585
+ ops.push(
1586
+ fetch(url).then((r) => {
1587
+ if (!r.ok) throw new Error(`Failed to fetch CSS: ${path} (${r.status})`);
1588
+ return r.text();
1589
+ }).then((code) => {
1590
+ this._inject("css", code, path);
1591
+ })
1592
+ );
1593
+ }
1594
+ if (toParent) {
1595
+ ops.push(this._loadCSSInParent(path, url));
1596
+ }
1597
+ await Promise.all(ops);
1598
+ return true;
1599
+ }
1600
+ _loadCSSInParent(path, url) {
1601
+ const existing = document.querySelector(`link[data-file="${path}"]`);
1579
1602
  const link = document.createElement("link");
1580
1603
  link.rel = "stylesheet";
1581
1604
  link.href = url;
@@ -1594,6 +1617,14 @@ var HMR = (() => {
1594
1617
  }
1595
1618
  async loadModule(path) {
1596
1619
  const url = this.makeUrl(path);
1620
+ if (this.iframeTarget) {
1621
+ const code = await fetch(url).then((r) => {
1622
+ if (!r.ok) throw new Error(`Failed to fetch module: ${path} (${r.status})`);
1623
+ return r.text();
1624
+ });
1625
+ this._inject("module", code, path);
1626
+ return true;
1627
+ }
1597
1628
  await import(url);
1598
1629
  return true;
1599
1630
  }
@@ -1601,6 +1632,14 @@ var HMR = (() => {
1601
1632
  const url = this.makeUrl(path);
1602
1633
  const existing = document.querySelector(`script[data-file="${path}"]`);
1603
1634
  if (existing) existing.remove();
1635
+ if (this.iframeTarget) {
1636
+ const code = await fetch(url).then((r) => {
1637
+ if (!r.ok) throw new Error(`Failed to fetch script: ${path} (${r.status})`);
1638
+ return r.text();
1639
+ });
1640
+ this._inject("script", code, path);
1641
+ return true;
1642
+ }
1604
1643
  const script = document.createElement("script");
1605
1644
  script.src = url;
1606
1645
  script.setAttribute("data-file", path);
@@ -1646,10 +1685,15 @@ var HMR = (() => {
1646
1685
  for (const { reject } of entry.resolvers) reject(new Error(`File removed: ${path}`));
1647
1686
  this.loadQueue.delete(path);
1648
1687
  }
1649
- const el = document.querySelector(`[data-file="${path}"]`);
1650
- if (el) {
1651
- el.remove();
1652
- await new Promise((r) => setTimeout(r, 0));
1688
+ if (this.iframeTarget) {
1689
+ this._post({ type: "hmr:remove", file: path });
1690
+ await Promise.resolve();
1691
+ } else {
1692
+ const el = document.querySelector(`[data-file="${path}"]`);
1693
+ if (el) {
1694
+ el.remove();
1695
+ await Promise.resolve();
1696
+ }
1653
1697
  }
1654
1698
  this.versions.delete(path);
1655
1699
  }
@@ -1659,6 +1703,14 @@ var HMR = (() => {
1659
1703
  this.versions.set(path, v);
1660
1704
  return `${this.httpUrl}${path}?v=${v}`;
1661
1705
  }
1706
+ // Send a raw postMessage to the iframe target
1707
+ _post(message) {
1708
+ this.iframeTarget.postMessage(message, this.iframeOrigin);
1709
+ }
1710
+ // Forward a file payload to the iframe target
1711
+ _inject(kind, code, file) {
1712
+ this._post({ type: "hmr:inject", kind, code, file });
1713
+ }
1662
1714
  };
1663
1715
 
1664
1716
  // src/shared/constants.js
@@ -1764,6 +1816,10 @@ var HMR = (() => {
1764
1816
  * @param {function(string): void} [options.onFileLoaded] - Called after each file loads or reloads. Receives `(filePath)`.
1765
1817
  * @param {function(string[]): string[]} [options.sortFiles] - Custom sort for the initial file load order. When provided, replaces `defaultSortFiles` entirely and `loadOrder` is ignored.
1766
1818
  * @param {Array<Function>} [options.loadOrder] - Stages prepended before the built-in sort (CSS-first, cold-first, alphabetical). One argument: return true to load that file first. Two arguments: works like a normal sort callback.
1819
+ * @param {boolean | Object} [options.iframe] - Forward files to an iframe via `postMessage` (for Private Network Access restricted environments). Pass `true` for defaults.
1820
+ * @param {Window | HTMLIFrameElement} [options.iframe.target] - Target a specific same-origin iframe directly, skipping auto-discovery. Reattachment is not automatic.
1821
+ * @param {string} [options.iframe.origin] - The iframe's origin used to validate incoming handshake responses. Defaults to `'*'`.
1822
+ * @param {'iframe'|'parent'|'both'} [options.iframe.css='iframe'] - Where CSS files are loaded when `iframe` is set.
1767
1823
  */
1768
1824
  constructor(options) {
1769
1825
  const opts = typeof options === "object" && !Array.isArray(options) ? options : {};
@@ -1793,7 +1849,18 @@ var HMR = (() => {
1793
1849
  this._reconnectTimer = null;
1794
1850
  this._messageQueue = [];
1795
1851
  this._processingMessages = false;
1796
- this.fileLoader = new FileLoader(this.httpUrl);
1852
+ const iframeOpts = opts.iframe === true ? {} : opts.iframe;
1853
+ const iframeTarget = iframeOpts?.target ? iframeOpts.target?.contentWindow ?? null : null;
1854
+ const iframeOrigin = iframeOpts?.origin ?? "*";
1855
+ this._iframeTarget = iframeTarget;
1856
+ this._iframeOrigin = iframeOrigin;
1857
+ this._stubManaged = !!iframeOpts && !iframeTarget;
1858
+ this._onReattach = null;
1859
+ this.fileLoader = new FileLoader(this.httpUrl, {
1860
+ iframeTarget,
1861
+ iframeOrigin,
1862
+ css: iframeOpts?.css ?? "iframe"
1863
+ });
1797
1864
  this.overrideMap = /* @__PURE__ */ new Map();
1798
1865
  this._reverseOverrideMap = /* @__PURE__ */ new Map();
1799
1866
  this.logStyles = {
@@ -1940,13 +2007,13 @@ var HMR = (() => {
1940
2007
  }
1941
2008
  }
1942
2009
  const withOverrides = this.buildOverrideMap(toLoad);
1943
- const sorted2 = this.sortFiles(withOverrides);
1944
- this.logInitFileGroup(sorted2, this.overrideMap, this.isColdFile.bind(this));
1945
- for (const file of sorted2) {
2010
+ const sorted = this.sortFiles(withOverrides);
2011
+ this.logInitFileGroup(sorted, this.overrideMap, this.isColdFile.bind(this));
2012
+ for (const file of sorted) {
1946
2013
  await this.fileLoader.loadFile(file);
1947
2014
  if (this.onFileLoaded) this.onFileLoaded(file);
1948
2015
  }
1949
- this.log("success", `HMR client ready (${sorted2.length} files loaded)`);
2016
+ this.log("success", `HMR client ready (${sorted.length} files loaded)`);
1950
2017
  }
1951
2018
  async handleFileChange(file, action, serverCold = false) {
1952
2019
  if (this.shouldSkipFile(file, this.allFiles)) {
@@ -2050,7 +2117,7 @@ var HMR = (() => {
2050
2117
  await this.processInitFiles(data.files);
2051
2118
  } else {
2052
2119
  const modeLabel = this.watchFiles ? "HMR ready" : "Static snapshot ready";
2053
- this.log("success", `${modeLabel} (${sorted.length} files loaded)`);
2120
+ this.log("success", `${modeLabel} (0 files loaded)`);
2054
2121
  }
2055
2122
  return;
2056
2123
  }
@@ -2145,6 +2212,48 @@ var HMR = (() => {
2145
2212
  }
2146
2213
  this._processingMessages = false;
2147
2214
  }
2215
+ // Wait for stub's hmr:ready signal. Stub fires it proactively on run.
2216
+ // Times out after 5s if stub was never injected.
2217
+ _waitForStub() {
2218
+ return new Promise((resolve, reject) => {
2219
+ const timer = setTimeout(() => {
2220
+ window.removeEventListener("message", onReady);
2221
+ reject(new Error("Timed out waiting for hmr:ready. Was HMR.stub() called in the iframe?"));
2222
+ }, 5e3);
2223
+ const onReady = (e) => {
2224
+ if (e.data?.type !== "hmr:ready") return;
2225
+ const originOk = this._iframeOrigin === "*" || e.origin === this._iframeOrigin;
2226
+ if (!originOk) return;
2227
+ clearTimeout(timer);
2228
+ window.removeEventListener("message", onReady);
2229
+ if (this._stubManaged) {
2230
+ this._iframeTarget = e.source;
2231
+ this.fileLoader.iframeTarget = e.source;
2232
+ }
2233
+ resolve();
2234
+ };
2235
+ window.addEventListener("message", onReady);
2236
+ });
2237
+ }
2238
+ // Persistent listener for hmr:ready that handles iframe reattachment. If the
2239
+ // iframe reloads or is replaced, the stub fires hmr:ready again so we can
2240
+ // update the target and re-inject all currently loaded files.
2241
+ _listenForReattach() {
2242
+ if (this._onReattach) return;
2243
+ this._onReattach = async (e) => {
2244
+ if (e.data?.type !== "hmr:ready") return;
2245
+ const originOk = this._iframeOrigin === "*" || e.origin === this._iframeOrigin;
2246
+ if (!originOk) return;
2247
+ if (e.source === this._iframeTarget) return;
2248
+ this._iframeTarget = e.source;
2249
+ this.fileLoader.iframeTarget = e.source;
2250
+ this.log("success", "HMR reattached to new iframe");
2251
+ for (const path of this.fileLoader.versions.keys()) {
2252
+ await this.fileLoader.loadFile(path);
2253
+ }
2254
+ };
2255
+ window.addEventListener("message", this._onReattach);
2256
+ }
2148
2257
  /**
2149
2258
  * Connect to the HMR server
2150
2259
  * @returns {Promise<void>}
@@ -2167,14 +2276,27 @@ var HMR = (() => {
2167
2276
  let settled = false;
2168
2277
  try {
2169
2278
  this.socket = new WebSocket(this.wsUrl);
2170
- this.socket.onopen = () => {
2279
+ this.socket.onopen = async () => {
2171
2280
  settled = true;
2172
2281
  this.isConnected = true;
2173
2282
  this.reconnectAttempts = 0;
2174
- this._messageQueue = [];
2175
- this._processingMessages = false;
2176
2283
  this.log("success", "HMR connected");
2177
2284
  this.emit("connect");
2285
+ this._messageQueue = [];
2286
+ this._processingMessages = true;
2287
+ 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
+ }
2297
+ }
2298
+ this._processingMessages = false;
2299
+ if (this._messageQueue.length > 0) this._drainMessageQueue();
2178
2300
  resolve();
2179
2301
  };
2180
2302
  this.socket.onclose = () => {
@@ -2230,12 +2352,54 @@ var HMR = (() => {
2230
2352
  this.isConnected = false;
2231
2353
  clearTimeout(this._reconnectTimer);
2232
2354
  this._reconnectTimer = null;
2355
+ if (this._onReattach) {
2356
+ window.removeEventListener("message", this._onReattach);
2357
+ this._onReattach = null;
2358
+ }
2233
2359
  if (this.socket) {
2234
2360
  this.socket.close();
2235
2361
  this.socket = null;
2236
2362
  }
2237
2363
  }
2238
2364
  };
2365
+
2366
+ // src/client/stub.js
2367
+ function stub() {
2368
+ const byFile = (tag, file) => document.querySelector(`${tag}[data-file="${CSS.escape(file)}"]`);
2369
+ const removeIfExists = (el) => {
2370
+ if (el) el.remove();
2371
+ };
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();
2380
+ };
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);
2387
+ removeIfExists(existing);
2388
+ };
2389
+ window.addEventListener("message", (e) => {
2390
+ const data = e.data;
2391
+ if (!data?.type) return;
2392
+ if (data.type === "hmr:remove") {
2393
+ removeIfExists(document.querySelector(`[data-file="${CSS.escape(data.file)}"]`));
2394
+ return;
2395
+ }
2396
+ if (data.type !== "hmr:inject") return;
2397
+ const { kind, code, file } = data;
2398
+ if (kind === "script" || kind === "module") injectScript(kind, code, file);
2399
+ else if (kind === "css") injectStyle(code, file);
2400
+ });
2401
+ window.parent.postMessage({ type: "hmr:ready" }, "*");
2402
+ }
2239
2403
  return __toCommonJS(client_exports);
2240
2404
  })();
2241
2405
  //# sourceMappingURL=client.iife.js.map