cindel 1.0.4 → 1.0.6
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 +39 -2
- 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 +240 -17
- package/dist/client.iife.js.map +4 -4
- package/dist/client.iife.min.js +4 -1
- package/dist/client.iife.min.js.map +4 -4
- package/dist/client.js +240 -17
- package/dist/client.js.map +4 -4
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -1544,8 +1544,11 @@ var require_picomatch2 = __commonJS({
|
|
|
1544
1544
|
|
|
1545
1545
|
// src/client/file-loader.js
|
|
1546
1546
|
var FileLoader = class {
|
|
1547
|
-
constructor(httpUrl) {
|
|
1547
|
+
constructor(httpUrl, { iframeTarget = null, iframeOrigin = "*", css = "iframe" } = {}) {
|
|
1548
1548
|
this.httpUrl = httpUrl;
|
|
1549
|
+
this.iframeTarget = iframeTarget;
|
|
1550
|
+
this.iframeOrigin = iframeOrigin;
|
|
1551
|
+
this.css = css;
|
|
1549
1552
|
this.loadQueue = /* @__PURE__ */ new Map();
|
|
1550
1553
|
this.versions = /* @__PURE__ */ new Map();
|
|
1551
1554
|
}
|
|
@@ -1560,8 +1563,28 @@ var FileLoader = class {
|
|
|
1560
1563
|
// the old one. This fixes the brief flash of unstyled content that
|
|
1561
1564
|
// happens when you remove the old sheet before the new one is parsed.
|
|
1562
1565
|
async loadCSS(path) {
|
|
1563
|
-
const existing = document.querySelector(`link[data-file="${path}"]`);
|
|
1564
1566
|
const url = this.makeUrl(path);
|
|
1567
|
+
const toIframe = this.iframeTarget && this.css !== "parent";
|
|
1568
|
+
const toParent = !this.iframeTarget || this.css !== "iframe";
|
|
1569
|
+
const ops = [];
|
|
1570
|
+
if (toIframe) {
|
|
1571
|
+
ops.push(
|
|
1572
|
+
fetch(url).then((r) => {
|
|
1573
|
+
if (!r.ok) throw new Error(`Failed to fetch CSS: ${path} (${r.status})`);
|
|
1574
|
+
return r.text();
|
|
1575
|
+
}).then((code) => this._inject("css", code, path))
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
if (toParent) {
|
|
1579
|
+
ops.push(this._loadCSSInParent(path, url));
|
|
1580
|
+
}
|
|
1581
|
+
const results = await Promise.allSettled(ops);
|
|
1582
|
+
const failed = results.find((r) => r.status === "rejected");
|
|
1583
|
+
if (failed) throw failed.reason;
|
|
1584
|
+
return true;
|
|
1585
|
+
}
|
|
1586
|
+
_loadCSSInParent(path, url) {
|
|
1587
|
+
const existing = document.querySelector(`link[data-file="${path}"]`);
|
|
1565
1588
|
const link = document.createElement("link");
|
|
1566
1589
|
link.rel = "stylesheet";
|
|
1567
1590
|
link.href = url;
|
|
@@ -1580,6 +1603,14 @@ var FileLoader = class {
|
|
|
1580
1603
|
}
|
|
1581
1604
|
async loadModule(path) {
|
|
1582
1605
|
const url = this.makeUrl(path);
|
|
1606
|
+
if (this.iframeTarget) {
|
|
1607
|
+
const code = await fetch(url).then((r) => {
|
|
1608
|
+
if (!r.ok) throw new Error(`Failed to fetch module: ${path} (${r.status})`);
|
|
1609
|
+
return r.text();
|
|
1610
|
+
});
|
|
1611
|
+
await this._inject("module", code, path);
|
|
1612
|
+
return true;
|
|
1613
|
+
}
|
|
1583
1614
|
await import(url);
|
|
1584
1615
|
return true;
|
|
1585
1616
|
}
|
|
@@ -1587,6 +1618,14 @@ var FileLoader = class {
|
|
|
1587
1618
|
const url = this.makeUrl(path);
|
|
1588
1619
|
const existing = document.querySelector(`script[data-file="${path}"]`);
|
|
1589
1620
|
if (existing) existing.remove();
|
|
1621
|
+
if (this.iframeTarget) {
|
|
1622
|
+
const code = await fetch(url).then((r) => {
|
|
1623
|
+
if (!r.ok) throw new Error(`Failed to fetch script: ${path} (${r.status})`);
|
|
1624
|
+
return r.text();
|
|
1625
|
+
});
|
|
1626
|
+
await this._inject("script", code, path);
|
|
1627
|
+
return true;
|
|
1628
|
+
}
|
|
1590
1629
|
const script = document.createElement("script");
|
|
1591
1630
|
script.src = url;
|
|
1592
1631
|
script.setAttribute("data-file", path);
|
|
@@ -1632,10 +1671,14 @@ var FileLoader = class {
|
|
|
1632
1671
|
for (const { reject } of entry.resolvers) reject(new Error(`File removed: ${path}`));
|
|
1633
1672
|
this.loadQueue.delete(path);
|
|
1634
1673
|
}
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1674
|
+
if (this.iframeTarget) {
|
|
1675
|
+
await this._postAndAwaitAck({ type: "hmr:remove", file: path });
|
|
1676
|
+
} else {
|
|
1677
|
+
const el = document.querySelector(`[data-file="${path}"]`);
|
|
1678
|
+
if (el) {
|
|
1679
|
+
el.remove();
|
|
1680
|
+
await Promise.resolve();
|
|
1681
|
+
}
|
|
1639
1682
|
}
|
|
1640
1683
|
this.versions.delete(path);
|
|
1641
1684
|
}
|
|
@@ -1645,6 +1688,28 @@ var FileLoader = class {
|
|
|
1645
1688
|
this.versions.set(path, v);
|
|
1646
1689
|
return `${this.httpUrl}${path}?v=${v}`;
|
|
1647
1690
|
}
|
|
1691
|
+
// Post a message and resolve once the stub sends back hmr:ack
|
|
1692
|
+
_postAndAwaitAck(message) {
|
|
1693
|
+
return new Promise((resolve) => {
|
|
1694
|
+
const onAck = (e) => {
|
|
1695
|
+
if (e.source !== this.iframeTarget) return;
|
|
1696
|
+
let data;
|
|
1697
|
+
try {
|
|
1698
|
+
data = JSON.parse(e.data);
|
|
1699
|
+
} catch {
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
if (data?.type !== "hmr:ack") return;
|
|
1703
|
+
window.removeEventListener("message", onAck);
|
|
1704
|
+
resolve();
|
|
1705
|
+
};
|
|
1706
|
+
window.addEventListener("message", onAck);
|
|
1707
|
+
this.iframeTarget.postMessage(JSON.stringify(message), this.iframeOrigin);
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
_inject(kind, code, file) {
|
|
1711
|
+
return this._postAndAwaitAck({ type: "hmr:inject", kind, code, file });
|
|
1712
|
+
}
|
|
1648
1713
|
};
|
|
1649
1714
|
|
|
1650
1715
|
// src/shared/constants.js
|
|
@@ -1750,6 +1815,10 @@ var HMRClient = class {
|
|
|
1750
1815
|
* @param {function(string): void} [options.onFileLoaded] - Called after each file loads or reloads. Receives `(filePath)`.
|
|
1751
1816
|
* @param {function(string[]): string[]} [options.sortFiles] - Custom sort for the initial file load order. When provided, replaces `defaultSortFiles` entirely and `loadOrder` is ignored.
|
|
1752
1817
|
* @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.
|
|
1818
|
+
* @param {boolean | Object} [options.iframe] - Forward files to an iframe via `postMessage` (for Private Network Access restricted environments). Pass `true` for defaults.
|
|
1819
|
+
* @param {Window | HTMLIFrameElement} [options.iframe.target] - Target a specific same-origin iframe directly, skipping auto-discovery. Reattachment is not automatic.
|
|
1820
|
+
* @param {string} [options.iframe.origin] - The iframe's origin used to validate incoming handshake responses. Defaults to `'*'`.
|
|
1821
|
+
* @param {'iframe'|'parent'|'both'} [options.iframe.css='iframe'] - Where CSS files are loaded when `iframe` is set.
|
|
1753
1822
|
*/
|
|
1754
1823
|
constructor(options) {
|
|
1755
1824
|
const opts = typeof options === "object" && !Array.isArray(options) ? options : {};
|
|
@@ -1779,7 +1848,18 @@ var HMRClient = class {
|
|
|
1779
1848
|
this._reconnectTimer = null;
|
|
1780
1849
|
this._messageQueue = [];
|
|
1781
1850
|
this._processingMessages = false;
|
|
1782
|
-
|
|
1851
|
+
const iframeOpts = opts.iframe === true ? {} : opts.iframe;
|
|
1852
|
+
const iframeTarget = iframeOpts?.target ? iframeOpts.target?.contentWindow ?? null : null;
|
|
1853
|
+
const iframeOrigin = iframeOpts?.origin ?? "*";
|
|
1854
|
+
this._iframeTarget = iframeTarget;
|
|
1855
|
+
this._iframeOrigin = iframeOrigin;
|
|
1856
|
+
this._stubManaged = !!iframeOpts && !iframeTarget;
|
|
1857
|
+
this._onReattach = null;
|
|
1858
|
+
this.fileLoader = new FileLoader(this.httpUrl, {
|
|
1859
|
+
iframeTarget,
|
|
1860
|
+
iframeOrigin,
|
|
1861
|
+
css: iframeOpts?.css ?? "iframe"
|
|
1862
|
+
});
|
|
1783
1863
|
this.overrideMap = /* @__PURE__ */ new Map();
|
|
1784
1864
|
this._reverseOverrideMap = /* @__PURE__ */ new Map();
|
|
1785
1865
|
this.logStyles = {
|
|
@@ -1926,13 +2006,13 @@ var HMRClient = class {
|
|
|
1926
2006
|
}
|
|
1927
2007
|
}
|
|
1928
2008
|
const withOverrides = this.buildOverrideMap(toLoad);
|
|
1929
|
-
const
|
|
1930
|
-
this.logInitFileGroup(
|
|
1931
|
-
for (const file of
|
|
2009
|
+
const sorted = this.sortFiles(withOverrides);
|
|
2010
|
+
this.logInitFileGroup(sorted, this.overrideMap, this.isColdFile.bind(this));
|
|
2011
|
+
for (const file of sorted) {
|
|
1932
2012
|
await this.fileLoader.loadFile(file);
|
|
1933
2013
|
if (this.onFileLoaded) this.onFileLoaded(file);
|
|
1934
2014
|
}
|
|
1935
|
-
this.log("success", `HMR client ready (${
|
|
2015
|
+
this.log("success", `HMR client ready (${sorted.length} files loaded)`);
|
|
1936
2016
|
}
|
|
1937
2017
|
async handleFileChange(file, action, serverCold = false) {
|
|
1938
2018
|
if (this.shouldSkipFile(file, this.allFiles)) {
|
|
@@ -2036,7 +2116,7 @@ var HMRClient = class {
|
|
|
2036
2116
|
await this.processInitFiles(data.files);
|
|
2037
2117
|
} else {
|
|
2038
2118
|
const modeLabel = this.watchFiles ? "HMR ready" : "Static snapshot ready";
|
|
2039
|
-
this.log("success", `${modeLabel} (
|
|
2119
|
+
this.log("success", `${modeLabel} (0 files loaded)`);
|
|
2040
2120
|
}
|
|
2041
2121
|
return;
|
|
2042
2122
|
}
|
|
@@ -2131,6 +2211,65 @@ var HMRClient = class {
|
|
|
2131
2211
|
}
|
|
2132
2212
|
this._processingMessages = false;
|
|
2133
2213
|
}
|
|
2214
|
+
// Wait for stub's hmr:ready signal. Stub fires it proactively on run.
|
|
2215
|
+
// Times out after 5s and resolves anyway, a missing stub degrades gracefully.
|
|
2216
|
+
_waitForStub() {
|
|
2217
|
+
return new Promise((resolve) => {
|
|
2218
|
+
const timer = setTimeout(() => {
|
|
2219
|
+
window.removeEventListener("message", onReady);
|
|
2220
|
+
this.log("warning", "Timed out waiting for hmr:ready. Was HMR.stub() called in the iframe?");
|
|
2221
|
+
resolve();
|
|
2222
|
+
}, 5e3);
|
|
2223
|
+
const onReady = (e) => {
|
|
2224
|
+
let data;
|
|
2225
|
+
try {
|
|
2226
|
+
data = JSON.parse(e.data);
|
|
2227
|
+
} catch {
|
|
2228
|
+
return;
|
|
2229
|
+
}
|
|
2230
|
+
if (data?.type !== "hmr:ready") return;
|
|
2231
|
+
const originOk = this._iframeOrigin === "*" || e.origin === this._iframeOrigin;
|
|
2232
|
+
if (!originOk) return;
|
|
2233
|
+
clearTimeout(timer);
|
|
2234
|
+
window.removeEventListener("message", onReady);
|
|
2235
|
+
if (this._stubManaged) {
|
|
2236
|
+
this._iframeTarget = e.source;
|
|
2237
|
+
this.fileLoader.iframeTarget = e.source;
|
|
2238
|
+
}
|
|
2239
|
+
resolve();
|
|
2240
|
+
};
|
|
2241
|
+
window.addEventListener("message", onReady);
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
// Persistent listener for hmr:ready that handles iframe reattachment. If the
|
|
2245
|
+
// iframe reloads or is replaced, the stub fires hmr:ready again so we can
|
|
2246
|
+
// update the target and re-inject all currently loaded files.
|
|
2247
|
+
_listenForReattach() {
|
|
2248
|
+
if (this._onReattach) return;
|
|
2249
|
+
this._onReattach = async (e) => {
|
|
2250
|
+
let data;
|
|
2251
|
+
try {
|
|
2252
|
+
data = JSON.parse(e.data);
|
|
2253
|
+
} catch {
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
if (data?.type !== "hmr:ready") return;
|
|
2257
|
+
const originOk = this._iframeOrigin === "*" || e.origin === this._iframeOrigin;
|
|
2258
|
+
if (!originOk) return;
|
|
2259
|
+
if (e.source === this._iframeTarget) return;
|
|
2260
|
+
this._iframeTarget = e.source;
|
|
2261
|
+
this.fileLoader.iframeTarget = e.source;
|
|
2262
|
+
this.log("success", "HMR reattached to new iframe");
|
|
2263
|
+
for (const path of this.sortFiles([...this.fileLoader.versions.keys()])) {
|
|
2264
|
+
try {
|
|
2265
|
+
await this.fileLoader.loadFile(path);
|
|
2266
|
+
} catch (e2) {
|
|
2267
|
+
this.log("error", `Reattach failed to load ${path}: ${e2.message}`);
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
};
|
|
2271
|
+
window.addEventListener("message", this._onReattach);
|
|
2272
|
+
}
|
|
2134
2273
|
/**
|
|
2135
2274
|
* Connect to the HMR server
|
|
2136
2275
|
* @returns {Promise<void>}
|
|
@@ -2153,14 +2292,20 @@ var HMRClient = class {
|
|
|
2153
2292
|
let settled = false;
|
|
2154
2293
|
try {
|
|
2155
2294
|
this.socket = new WebSocket(this.wsUrl);
|
|
2156
|
-
this.socket.onopen = () => {
|
|
2295
|
+
this.socket.onopen = async () => {
|
|
2157
2296
|
settled = true;
|
|
2158
2297
|
this.isConnected = true;
|
|
2159
2298
|
this.reconnectAttempts = 0;
|
|
2160
|
-
this._messageQueue = [];
|
|
2161
|
-
this._processingMessages = false;
|
|
2162
2299
|
this.log("success", "HMR connected");
|
|
2163
2300
|
this.emit("connect");
|
|
2301
|
+
this._messageQueue = [];
|
|
2302
|
+
this._processingMessages = true;
|
|
2303
|
+
if (this._iframeTarget || this._stubManaged) {
|
|
2304
|
+
await this._waitForStub();
|
|
2305
|
+
if (this._stubManaged) this._listenForReattach();
|
|
2306
|
+
}
|
|
2307
|
+
this._processingMessages = false;
|
|
2308
|
+
if (this._messageQueue.length > 0) this._drainMessageQueue();
|
|
2164
2309
|
resolve();
|
|
2165
2310
|
};
|
|
2166
2311
|
this.socket.onclose = () => {
|
|
@@ -2216,15 +2361,93 @@ var HMRClient = class {
|
|
|
2216
2361
|
this.isConnected = false;
|
|
2217
2362
|
clearTimeout(this._reconnectTimer);
|
|
2218
2363
|
this._reconnectTimer = null;
|
|
2364
|
+
if (this._onReattach) {
|
|
2365
|
+
window.removeEventListener("message", this._onReattach);
|
|
2366
|
+
this._onReattach = null;
|
|
2367
|
+
}
|
|
2219
2368
|
if (this.socket) {
|
|
2220
2369
|
this.socket.close();
|
|
2221
2370
|
this.socket = null;
|
|
2222
2371
|
}
|
|
2223
2372
|
}
|
|
2224
2373
|
};
|
|
2374
|
+
|
|
2375
|
+
// src/client/stub.js
|
|
2376
|
+
function stub() {
|
|
2377
|
+
const byFile = (tag, file) => document.querySelector(`${tag}[data-file="${CSS.escape(file)}"]`);
|
|
2378
|
+
const removeIfExists = (el) => {
|
|
2379
|
+
if (el) el.remove();
|
|
2380
|
+
};
|
|
2381
|
+
const injectScript = async (kind, code, file) => {
|
|
2382
|
+
const url = URL.createObjectURL(new Blob([code + `
|
|
2383
|
+
//# sourceURL=${file}`], { type: "text/javascript" }));
|
|
2384
|
+
try {
|
|
2385
|
+
if (kind === "module") {
|
|
2386
|
+
await import(url);
|
|
2387
|
+
} else {
|
|
2388
|
+
const script = document.createElement("script");
|
|
2389
|
+
script.src = url;
|
|
2390
|
+
await new Promise((resolve, reject) => {
|
|
2391
|
+
script.onload = resolve;
|
|
2392
|
+
script.onerror = () => reject(new Error(`Failed to execute script: ${file}`));
|
|
2393
|
+
document.documentElement.appendChild(script);
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2396
|
+
} finally {
|
|
2397
|
+
URL.revokeObjectURL(url);
|
|
2398
|
+
}
|
|
2399
|
+
};
|
|
2400
|
+
const injectStyle = async (code, file) => {
|
|
2401
|
+
const existing = byFile("link", file);
|
|
2402
|
+
const url = URL.createObjectURL(new Blob([code + `
|
|
2403
|
+
/*# sourceURL=${file} */`], { type: "text/css" }));
|
|
2404
|
+
const link = document.createElement("link");
|
|
2405
|
+
link.rel = "stylesheet";
|
|
2406
|
+
link.href = url;
|
|
2407
|
+
link.dataset.file = file;
|
|
2408
|
+
await new Promise((resolve, reject) => {
|
|
2409
|
+
link.onload = () => {
|
|
2410
|
+
URL.revokeObjectURL(url);
|
|
2411
|
+
resolve();
|
|
2412
|
+
};
|
|
2413
|
+
link.onerror = () => {
|
|
2414
|
+
URL.revokeObjectURL(url);
|
|
2415
|
+
reject(new Error(`Failed to load CSS: ${file}`));
|
|
2416
|
+
};
|
|
2417
|
+
document.head.appendChild(link);
|
|
2418
|
+
});
|
|
2419
|
+
removeIfExists(existing);
|
|
2420
|
+
};
|
|
2421
|
+
window.addEventListener("message", async (e) => {
|
|
2422
|
+
let data;
|
|
2423
|
+
try {
|
|
2424
|
+
data = JSON.parse(e.data);
|
|
2425
|
+
} catch {
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
if (!data?.type) return;
|
|
2429
|
+
const ackOrigin = e.origin && e.origin !== "null" ? e.origin : "*";
|
|
2430
|
+
const ack = () => e.source?.postMessage(JSON.stringify({ type: "hmr:ack" }), ackOrigin);
|
|
2431
|
+
if (data.type === "hmr:remove") {
|
|
2432
|
+
removeIfExists(document.querySelector(`[data-file="${CSS.escape(data.file)}"]`));
|
|
2433
|
+
ack();
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2436
|
+
if (data.type !== "hmr:inject") return;
|
|
2437
|
+
const { kind, code, file } = data;
|
|
2438
|
+
try {
|
|
2439
|
+
if (kind === "script" || kind === "module") await injectScript(kind, code, file);
|
|
2440
|
+
else if (kind === "css") await injectStyle(code, file);
|
|
2441
|
+
} catch (err) {
|
|
2442
|
+
console.error(`Failed to inject ${file}:`, err.message ?? err, "\n" + err.stack);
|
|
2443
|
+
}
|
|
2444
|
+
ack();
|
|
2445
|
+
});
|
|
2446
|
+
window.parent.postMessage(JSON.stringify({ type: "hmr:ready" }), "*");
|
|
2447
|
+
}
|
|
2225
2448
|
export {
|
|
2226
|
-
FileLoader,
|
|
2227
2449
|
HMRClient,
|
|
2228
|
-
HMRClient as default
|
|
2450
|
+
HMRClient as default,
|
|
2451
|
+
stub
|
|
2229
2452
|
};
|
|
2230
2453
|
//# sourceMappingURL=client.js.map
|