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 +38 -1
- package/dist/client/file-loader.d.ts +26 -2
- package/dist/client/file-loader.d.ts.map +1 -1
- package/dist/client/hmr-client.d.ts +11 -0
- package/dist/client/hmr-client.d.ts.map +1 -1
- package/dist/client/stub.d.ts +2 -0
- package/dist/client/stub.d.ts.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.iife.js +181 -17
- package/dist/client.iife.js.map +4 -4
- package/dist/client.iife.min.js +1 -1
- package/dist/client.iife.min.js.map +4 -4
- package/dist/client.js +181 -17
- package/dist/client.js.map +4 -4
- package/package.json +1 -1
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<
|
|
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
|
|
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
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"stub.d.ts","sourceRoot":"","sources":["../../src/client/stub.js"],"names":[],"mappings":"AAAA,6BAmCC"}
|
package/dist/client.d.ts
CHANGED
package/dist/client.iife.js
CHANGED
|
@@ -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
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
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
|
-
|
|
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
|
|
1944
|
-
this.logInitFileGroup(
|
|
1945
|
-
for (const file of
|
|
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 (${
|
|
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} (
|
|
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
|