cindel 1.0.3 → 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 +115 -22
- 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 +15 -1
- 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 +195 -27
- 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 +195 -27
- 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) => {
|
|
1576
|
+
this._inject("css", code, path);
|
|
1577
|
+
})
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
if (toParent) {
|
|
1581
|
+
ops.push(this._loadCSSInParent(path, url));
|
|
1582
|
+
}
|
|
1583
|
+
await Promise.all(ops);
|
|
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
|
+
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
|
+
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,15 @@ 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
|
+
this._post({ type: "hmr:remove", file: path });
|
|
1676
|
+
await Promise.resolve();
|
|
1677
|
+
} else {
|
|
1678
|
+
const el = document.querySelector(`[data-file="${path}"]`);
|
|
1679
|
+
if (el) {
|
|
1680
|
+
el.remove();
|
|
1681
|
+
await Promise.resolve();
|
|
1682
|
+
}
|
|
1639
1683
|
}
|
|
1640
1684
|
this.versions.delete(path);
|
|
1641
1685
|
}
|
|
@@ -1645,6 +1689,14 @@ var FileLoader = class {
|
|
|
1645
1689
|
this.versions.set(path, v);
|
|
1646
1690
|
return `${this.httpUrl}${path}?v=${v}`;
|
|
1647
1691
|
}
|
|
1692
|
+
// Send a raw postMessage to the iframe target
|
|
1693
|
+
_post(message) {
|
|
1694
|
+
this.iframeTarget.postMessage(message, this.iframeOrigin);
|
|
1695
|
+
}
|
|
1696
|
+
// Forward a file payload to the iframe target
|
|
1697
|
+
_inject(kind, code, file) {
|
|
1698
|
+
this._post({ type: "hmr:inject", kind, code, file });
|
|
1699
|
+
}
|
|
1648
1700
|
};
|
|
1649
1701
|
|
|
1650
1702
|
// src/shared/constants.js
|
|
@@ -1748,7 +1800,12 @@ var HMRClient = class {
|
|
|
1748
1800
|
* @param {function(string): boolean} [options.filterCold] - Custom cold file logic. Receives `(filePath)`. Combined with `cold` via OR.
|
|
1749
1801
|
* @param {function(string, string[]): string|null} [options.getOverrideTarget] - Given a changed file, return the path of the original it replaces, or `null`. Receives `(filePath, allFiles)`. When matched, the original is unloaded before the override loads.
|
|
1750
1802
|
* @param {function(string): void} [options.onFileLoaded] - Called after each file loads or reloads. Receives `(filePath)`.
|
|
1751
|
-
* @param {function(string[]): string[]} [options.sortFiles] - Custom sort for the initial file load order.
|
|
1803
|
+
* @param {function(string[]): string[]} [options.sortFiles] - Custom sort for the initial file load order. When provided, replaces `defaultSortFiles` entirely and `loadOrder` is ignored.
|
|
1804
|
+
* @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.
|
|
1805
|
+
* @param {boolean | Object} [options.iframe] - Forward files to an iframe via `postMessage` (for Private Network Access restricted environments). Pass `true` for defaults.
|
|
1806
|
+
* @param {Window | HTMLIFrameElement} [options.iframe.target] - Target a specific same-origin iframe directly, skipping auto-discovery. Reattachment is not automatic.
|
|
1807
|
+
* @param {string} [options.iframe.origin] - The iframe's origin used to validate incoming handshake responses. Defaults to `'*'`.
|
|
1808
|
+
* @param {'iframe'|'parent'|'both'} [options.iframe.css='iframe'] - Where CSS files are loaded when `iframe` is set.
|
|
1752
1809
|
*/
|
|
1753
1810
|
constructor(options) {
|
|
1754
1811
|
const opts = typeof options === "object" && !Array.isArray(options) ? options : {};
|
|
@@ -1769,6 +1826,7 @@ var HMRClient = class {
|
|
|
1769
1826
|
this.allFiles = [];
|
|
1770
1827
|
this.getOverrideTarget = opts.getOverrideTarget || null;
|
|
1771
1828
|
this.onFileLoaded = opts.onFileLoaded || null;
|
|
1829
|
+
this.loadOrder = opts.loadOrder || [];
|
|
1772
1830
|
this.sortFiles = opts.sortFiles || this.defaultSortFiles.bind(this);
|
|
1773
1831
|
this.socket = null;
|
|
1774
1832
|
this.reconnectAttempts = 0;
|
|
@@ -1777,7 +1835,18 @@ var HMRClient = class {
|
|
|
1777
1835
|
this._reconnectTimer = null;
|
|
1778
1836
|
this._messageQueue = [];
|
|
1779
1837
|
this._processingMessages = false;
|
|
1780
|
-
|
|
1838
|
+
const iframeOpts = opts.iframe === true ? {} : opts.iframe;
|
|
1839
|
+
const iframeTarget = iframeOpts?.target ? iframeOpts.target?.contentWindow ?? null : null;
|
|
1840
|
+
const iframeOrigin = iframeOpts?.origin ?? "*";
|
|
1841
|
+
this._iframeTarget = iframeTarget;
|
|
1842
|
+
this._iframeOrigin = iframeOrigin;
|
|
1843
|
+
this._stubManaged = !!iframeOpts && !iframeTarget;
|
|
1844
|
+
this._onReattach = null;
|
|
1845
|
+
this.fileLoader = new FileLoader(this.httpUrl, {
|
|
1846
|
+
iframeTarget,
|
|
1847
|
+
iframeOrigin,
|
|
1848
|
+
css: iframeOpts?.css ?? "iframe"
|
|
1849
|
+
});
|
|
1781
1850
|
this.overrideMap = /* @__PURE__ */ new Map();
|
|
1782
1851
|
this._reverseOverrideMap = /* @__PURE__ */ new Map();
|
|
1783
1852
|
this.logStyles = {
|
|
@@ -1796,16 +1865,18 @@ var HMRClient = class {
|
|
|
1796
1865
|
}
|
|
1797
1866
|
defaultSortFiles(files) {
|
|
1798
1867
|
const coldSet = new Set(files.filter((f) => this.isColdFile(f)));
|
|
1868
|
+
const builtinStages = [
|
|
1869
|
+
(f) => f.endsWith(".css"),
|
|
1870
|
+
(f) => coldSet.has(f),
|
|
1871
|
+
(a, b) => a.localeCompare(b)
|
|
1872
|
+
];
|
|
1873
|
+
const stages = [...this.loadOrder, ...builtinStages];
|
|
1799
1874
|
return [...files].sort((a, b) => {
|
|
1800
|
-
const
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
const coldB = coldSet.has(b);
|
|
1806
|
-
if (coldA && !coldB) return -1;
|
|
1807
|
-
if (!coldA && coldB) return 1;
|
|
1808
|
-
return a.localeCompare(b);
|
|
1875
|
+
for (const stage of stages) {
|
|
1876
|
+
const result = stage.length === 2 ? stage(a, b) : stage(b) - stage(a);
|
|
1877
|
+
if (result !== 0) return result;
|
|
1878
|
+
}
|
|
1879
|
+
return 0;
|
|
1809
1880
|
});
|
|
1810
1881
|
}
|
|
1811
1882
|
makeFilter(patterns, callback) {
|
|
@@ -1922,13 +1993,13 @@ var HMRClient = class {
|
|
|
1922
1993
|
}
|
|
1923
1994
|
}
|
|
1924
1995
|
const withOverrides = this.buildOverrideMap(toLoad);
|
|
1925
|
-
const
|
|
1926
|
-
this.logInitFileGroup(
|
|
1927
|
-
for (const file of
|
|
1996
|
+
const sorted = this.sortFiles(withOverrides);
|
|
1997
|
+
this.logInitFileGroup(sorted, this.overrideMap, this.isColdFile.bind(this));
|
|
1998
|
+
for (const file of sorted) {
|
|
1928
1999
|
await this.fileLoader.loadFile(file);
|
|
1929
2000
|
if (this.onFileLoaded) this.onFileLoaded(file);
|
|
1930
2001
|
}
|
|
1931
|
-
this.log("success", `HMR client ready (${
|
|
2002
|
+
this.log("success", `HMR client ready (${sorted.length} files loaded)`);
|
|
1932
2003
|
}
|
|
1933
2004
|
async handleFileChange(file, action, serverCold = false) {
|
|
1934
2005
|
if (this.shouldSkipFile(file, this.allFiles)) {
|
|
@@ -2032,7 +2103,7 @@ var HMRClient = class {
|
|
|
2032
2103
|
await this.processInitFiles(data.files);
|
|
2033
2104
|
} else {
|
|
2034
2105
|
const modeLabel = this.watchFiles ? "HMR ready" : "Static snapshot ready";
|
|
2035
|
-
this.log("success", `${modeLabel} (
|
|
2106
|
+
this.log("success", `${modeLabel} (0 files loaded)`);
|
|
2036
2107
|
}
|
|
2037
2108
|
return;
|
|
2038
2109
|
}
|
|
@@ -2127,6 +2198,48 @@ var HMRClient = class {
|
|
|
2127
2198
|
}
|
|
2128
2199
|
this._processingMessages = false;
|
|
2129
2200
|
}
|
|
2201
|
+
// Wait for stub's hmr:ready signal. Stub fires it proactively on run.
|
|
2202
|
+
// Times out after 5s if stub was never injected.
|
|
2203
|
+
_waitForStub() {
|
|
2204
|
+
return new Promise((resolve, reject) => {
|
|
2205
|
+
const timer = setTimeout(() => {
|
|
2206
|
+
window.removeEventListener("message", onReady);
|
|
2207
|
+
reject(new Error("Timed out waiting for hmr:ready. Was HMR.stub() called in the iframe?"));
|
|
2208
|
+
}, 5e3);
|
|
2209
|
+
const onReady = (e) => {
|
|
2210
|
+
if (e.data?.type !== "hmr:ready") return;
|
|
2211
|
+
const originOk = this._iframeOrigin === "*" || e.origin === this._iframeOrigin;
|
|
2212
|
+
if (!originOk) return;
|
|
2213
|
+
clearTimeout(timer);
|
|
2214
|
+
window.removeEventListener("message", onReady);
|
|
2215
|
+
if (this._stubManaged) {
|
|
2216
|
+
this._iframeTarget = e.source;
|
|
2217
|
+
this.fileLoader.iframeTarget = e.source;
|
|
2218
|
+
}
|
|
2219
|
+
resolve();
|
|
2220
|
+
};
|
|
2221
|
+
window.addEventListener("message", onReady);
|
|
2222
|
+
});
|
|
2223
|
+
}
|
|
2224
|
+
// Persistent listener for hmr:ready that handles iframe reattachment. If the
|
|
2225
|
+
// iframe reloads or is replaced, the stub fires hmr:ready again so we can
|
|
2226
|
+
// update the target and re-inject all currently loaded files.
|
|
2227
|
+
_listenForReattach() {
|
|
2228
|
+
if (this._onReattach) return;
|
|
2229
|
+
this._onReattach = async (e) => {
|
|
2230
|
+
if (e.data?.type !== "hmr:ready") return;
|
|
2231
|
+
const originOk = this._iframeOrigin === "*" || e.origin === this._iframeOrigin;
|
|
2232
|
+
if (!originOk) return;
|
|
2233
|
+
if (e.source === this._iframeTarget) return;
|
|
2234
|
+
this._iframeTarget = e.source;
|
|
2235
|
+
this.fileLoader.iframeTarget = e.source;
|
|
2236
|
+
this.log("success", "HMR reattached to new iframe");
|
|
2237
|
+
for (const path of this.fileLoader.versions.keys()) {
|
|
2238
|
+
await this.fileLoader.loadFile(path);
|
|
2239
|
+
}
|
|
2240
|
+
};
|
|
2241
|
+
window.addEventListener("message", this._onReattach);
|
|
2242
|
+
}
|
|
2130
2243
|
/**
|
|
2131
2244
|
* Connect to the HMR server
|
|
2132
2245
|
* @returns {Promise<void>}
|
|
@@ -2149,14 +2262,27 @@ var HMRClient = class {
|
|
|
2149
2262
|
let settled = false;
|
|
2150
2263
|
try {
|
|
2151
2264
|
this.socket = new WebSocket(this.wsUrl);
|
|
2152
|
-
this.socket.onopen = () => {
|
|
2265
|
+
this.socket.onopen = async () => {
|
|
2153
2266
|
settled = true;
|
|
2154
2267
|
this.isConnected = true;
|
|
2155
2268
|
this.reconnectAttempts = 0;
|
|
2156
|
-
this._messageQueue = [];
|
|
2157
|
-
this._processingMessages = false;
|
|
2158
2269
|
this.log("success", "HMR connected");
|
|
2159
2270
|
this.emit("connect");
|
|
2271
|
+
this._messageQueue = [];
|
|
2272
|
+
this._processingMessages = true;
|
|
2273
|
+
if (this._iframeTarget || this._stubManaged) {
|
|
2274
|
+
try {
|
|
2275
|
+
await this._waitForStub();
|
|
2276
|
+
if (this._stubManaged) this._listenForReattach();
|
|
2277
|
+
} catch (e) {
|
|
2278
|
+
this.log("error", e.message);
|
|
2279
|
+
this._processingMessages = false;
|
|
2280
|
+
reject(e);
|
|
2281
|
+
return;
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
this._processingMessages = false;
|
|
2285
|
+
if (this._messageQueue.length > 0) this._drainMessageQueue();
|
|
2160
2286
|
resolve();
|
|
2161
2287
|
};
|
|
2162
2288
|
this.socket.onclose = () => {
|
|
@@ -2212,15 +2338,57 @@ var HMRClient = class {
|
|
|
2212
2338
|
this.isConnected = false;
|
|
2213
2339
|
clearTimeout(this._reconnectTimer);
|
|
2214
2340
|
this._reconnectTimer = null;
|
|
2341
|
+
if (this._onReattach) {
|
|
2342
|
+
window.removeEventListener("message", this._onReattach);
|
|
2343
|
+
this._onReattach = null;
|
|
2344
|
+
}
|
|
2215
2345
|
if (this.socket) {
|
|
2216
2346
|
this.socket.close();
|
|
2217
2347
|
this.socket = null;
|
|
2218
2348
|
}
|
|
2219
2349
|
}
|
|
2220
2350
|
};
|
|
2351
|
+
|
|
2352
|
+
// src/client/stub.js
|
|
2353
|
+
function stub() {
|
|
2354
|
+
const byFile = (tag, file) => document.querySelector(`${tag}[data-file="${CSS.escape(file)}"]`);
|
|
2355
|
+
const removeIfExists = (el) => {
|
|
2356
|
+
if (el) el.remove();
|
|
2357
|
+
};
|
|
2358
|
+
const injectScript = (kind, code, file) => {
|
|
2359
|
+
removeIfExists(byFile("script", file));
|
|
2360
|
+
const script = document.createElement("script");
|
|
2361
|
+
if (kind === "module") script.type = "module";
|
|
2362
|
+
script.textContent = code;
|
|
2363
|
+
script.dataset.file = file;
|
|
2364
|
+
document.documentElement.appendChild(script);
|
|
2365
|
+
script.remove();
|
|
2366
|
+
};
|
|
2367
|
+
const injectStyle = (code, file) => {
|
|
2368
|
+
const existing = byFile("style", file);
|
|
2369
|
+
const style = document.createElement("style");
|
|
2370
|
+
style.textContent = code;
|
|
2371
|
+
style.dataset.file = file;
|
|
2372
|
+
document.head.appendChild(style);
|
|
2373
|
+
removeIfExists(existing);
|
|
2374
|
+
};
|
|
2375
|
+
window.addEventListener("message", (e) => {
|
|
2376
|
+
const data = e.data;
|
|
2377
|
+
if (!data?.type) return;
|
|
2378
|
+
if (data.type === "hmr:remove") {
|
|
2379
|
+
removeIfExists(document.querySelector(`[data-file="${CSS.escape(data.file)}"]`));
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
if (data.type !== "hmr:inject") return;
|
|
2383
|
+
const { kind, code, file } = data;
|
|
2384
|
+
if (kind === "script" || kind === "module") injectScript(kind, code, file);
|
|
2385
|
+
else if (kind === "css") injectStyle(code, file);
|
|
2386
|
+
});
|
|
2387
|
+
window.parent.postMessage({ type: "hmr:ready" }, "*");
|
|
2388
|
+
}
|
|
2221
2389
|
export {
|
|
2222
|
-
FileLoader,
|
|
2223
2390
|
HMRClient,
|
|
2224
|
-
HMRClient as default
|
|
2391
|
+
HMRClient as default,
|
|
2392
|
+
stub
|
|
2225
2393
|
};
|
|
2226
2394
|
//# sourceMappingURL=client.js.map
|