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.
@@ -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
@@ -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. Default sorts CSS before JS, cold files first.
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
- 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
+ });
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 aIsCSS = a.endsWith(".css");
1815
- const bIsCSS = b.endsWith(".css");
1816
- if (aIsCSS && !bIsCSS) return -1;
1817
- if (!aIsCSS && bIsCSS) return 1;
1818
- const coldA = coldSet.has(a);
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 sorted2 = this.sortFiles(withOverrides);
1940
- this.logInitFileGroup(sorted2, this.overrideMap, this.isColdFile.bind(this));
1941
- 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) {
1942
2013
  await this.fileLoader.loadFile(file);
1943
2014
  if (this.onFileLoaded) this.onFileLoaded(file);
1944
2015
  }
1945
- this.log("success", `HMR client ready (${sorted2.length} files loaded)`);
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} (${sorted.length} files loaded)`);
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