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.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
|
|
@@ -1762,7 +1814,12 @@ var HMR = (() => {
|
|
|
1762
1814
|
* @param {function(string): boolean} [options.filterCold] - Custom cold file logic. Receives `(filePath)`. Combined with `cold` via OR.
|
|
1763
1815
|
* @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.
|
|
1764
1816
|
* @param {function(string): void} [options.onFileLoaded] - Called after each file loads or reloads. Receives `(filePath)`.
|
|
1765
|
-
* @param {function(string[]): string[]} [options.sortFiles] - Custom sort for the initial file load order.
|
|
1817
|
+
* @param {function(string[]): string[]} [options.sortFiles] - Custom sort for the initial file load order. When provided, replaces `defaultSortFiles` entirely and `loadOrder` is ignored.
|
|
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.
|
|
1766
1823
|
*/
|
|
1767
1824
|
constructor(options) {
|
|
1768
1825
|
const opts = typeof options === "object" && !Array.isArray(options) ? options : {};
|
|
@@ -1783,6 +1840,7 @@ var HMR = (() => {
|
|
|
1783
1840
|
this.allFiles = [];
|
|
1784
1841
|
this.getOverrideTarget = opts.getOverrideTarget || null;
|
|
1785
1842
|
this.onFileLoaded = opts.onFileLoaded || null;
|
|
1843
|
+
this.loadOrder = opts.loadOrder || [];
|
|
1786
1844
|
this.sortFiles = opts.sortFiles || this.defaultSortFiles.bind(this);
|
|
1787
1845
|
this.socket = null;
|
|
1788
1846
|
this.reconnectAttempts = 0;
|
|
@@ -1791,7 +1849,18 @@ var HMR = (() => {
|
|
|
1791
1849
|
this._reconnectTimer = null;
|
|
1792
1850
|
this._messageQueue = [];
|
|
1793
1851
|
this._processingMessages = false;
|
|
1794
|
-
|
|
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
|
+
});
|
|
1795
1864
|
this.overrideMap = /* @__PURE__ */ new Map();
|
|
1796
1865
|
this._reverseOverrideMap = /* @__PURE__ */ new Map();
|
|
1797
1866
|
this.logStyles = {
|
|
@@ -1810,16 +1879,18 @@ var HMR = (() => {
|
|
|
1810
1879
|
}
|
|
1811
1880
|
defaultSortFiles(files) {
|
|
1812
1881
|
const coldSet = new Set(files.filter((f) => this.isColdFile(f)));
|
|
1882
|
+
const builtinStages = [
|
|
1883
|
+
(f) => f.endsWith(".css"),
|
|
1884
|
+
(f) => coldSet.has(f),
|
|
1885
|
+
(a, b) => a.localeCompare(b)
|
|
1886
|
+
];
|
|
1887
|
+
const stages = [...this.loadOrder, ...builtinStages];
|
|
1813
1888
|
return [...files].sort((a, b) => {
|
|
1814
|
-
const
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
const coldB = coldSet.has(b);
|
|
1820
|
-
if (coldA && !coldB) return -1;
|
|
1821
|
-
if (!coldA && coldB) return 1;
|
|
1822
|
-
return a.localeCompare(b);
|
|
1889
|
+
for (const stage of stages) {
|
|
1890
|
+
const result = stage.length === 2 ? stage(a, b) : stage(b) - stage(a);
|
|
1891
|
+
if (result !== 0) return result;
|
|
1892
|
+
}
|
|
1893
|
+
return 0;
|
|
1823
1894
|
});
|
|
1824
1895
|
}
|
|
1825
1896
|
makeFilter(patterns, callback) {
|
|
@@ -1936,13 +2007,13 @@ var HMR = (() => {
|
|
|
1936
2007
|
}
|
|
1937
2008
|
}
|
|
1938
2009
|
const withOverrides = this.buildOverrideMap(toLoad);
|
|
1939
|
-
const
|
|
1940
|
-
this.logInitFileGroup(
|
|
1941
|
-
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) {
|
|
1942
2013
|
await this.fileLoader.loadFile(file);
|
|
1943
2014
|
if (this.onFileLoaded) this.onFileLoaded(file);
|
|
1944
2015
|
}
|
|
1945
|
-
this.log("success", `HMR client ready (${
|
|
2016
|
+
this.log("success", `HMR client ready (${sorted.length} files loaded)`);
|
|
1946
2017
|
}
|
|
1947
2018
|
async handleFileChange(file, action, serverCold = false) {
|
|
1948
2019
|
if (this.shouldSkipFile(file, this.allFiles)) {
|
|
@@ -2046,7 +2117,7 @@ var HMR = (() => {
|
|
|
2046
2117
|
await this.processInitFiles(data.files);
|
|
2047
2118
|
} else {
|
|
2048
2119
|
const modeLabel = this.watchFiles ? "HMR ready" : "Static snapshot ready";
|
|
2049
|
-
this.log("success", `${modeLabel} (
|
|
2120
|
+
this.log("success", `${modeLabel} (0 files loaded)`);
|
|
2050
2121
|
}
|
|
2051
2122
|
return;
|
|
2052
2123
|
}
|
|
@@ -2141,6 +2212,48 @@ var HMR = (() => {
|
|
|
2141
2212
|
}
|
|
2142
2213
|
this._processingMessages = false;
|
|
2143
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
|
+
}
|
|
2144
2257
|
/**
|
|
2145
2258
|
* Connect to the HMR server
|
|
2146
2259
|
* @returns {Promise<void>}
|
|
@@ -2163,14 +2276,27 @@ var HMR = (() => {
|
|
|
2163
2276
|
let settled = false;
|
|
2164
2277
|
try {
|
|
2165
2278
|
this.socket = new WebSocket(this.wsUrl);
|
|
2166
|
-
this.socket.onopen = () => {
|
|
2279
|
+
this.socket.onopen = async () => {
|
|
2167
2280
|
settled = true;
|
|
2168
2281
|
this.isConnected = true;
|
|
2169
2282
|
this.reconnectAttempts = 0;
|
|
2170
|
-
this._messageQueue = [];
|
|
2171
|
-
this._processingMessages = false;
|
|
2172
2283
|
this.log("success", "HMR connected");
|
|
2173
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();
|
|
2174
2300
|
resolve();
|
|
2175
2301
|
};
|
|
2176
2302
|
this.socket.onclose = () => {
|
|
@@ -2226,12 +2352,54 @@ var HMR = (() => {
|
|
|
2226
2352
|
this.isConnected = false;
|
|
2227
2353
|
clearTimeout(this._reconnectTimer);
|
|
2228
2354
|
this._reconnectTimer = null;
|
|
2355
|
+
if (this._onReattach) {
|
|
2356
|
+
window.removeEventListener("message", this._onReattach);
|
|
2357
|
+
this._onReattach = null;
|
|
2358
|
+
}
|
|
2229
2359
|
if (this.socket) {
|
|
2230
2360
|
this.socket.close();
|
|
2231
2361
|
this.socket = null;
|
|
2232
2362
|
}
|
|
2233
2363
|
}
|
|
2234
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
|
+
}
|
|
2235
2403
|
return __toCommonJS(client_exports);
|
|
2236
2404
|
})();
|
|
2237
2405
|
//# sourceMappingURL=client.iife.js.map
|