cindel 1.0.0 → 1.0.2

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 CHANGED
@@ -3,9 +3,8 @@
3
3
  > Hot module replacement server and client with file watching, static file serving, CORS proxy and WebSocket proxy support
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/cindel.svg)](https://www.npmjs.com/package/cindel)
6
+ [![npm bundle size](https://img.shields.io/bundlephobia/minzip/cindel?style=round-square)](https://bundlephobia.com/package/cindel@latest)
6
7
  [![license](https://img.shields.io/npm/l/cindel.svg)](LICENSE)
7
- [![bundle size](https://img.shields.io/bundlephobia/minzip/cindel)](https://bundlephobia.com/package/cindel)
8
-
9
8
  ---
10
9
 
11
10
  ## Features
@@ -13,10 +12,10 @@
13
12
  **HMR & File Watching**
14
13
 
15
14
  - Instant push driven HMR over WebSocket on file change
16
- - Atomic CSS hot swap (no flash of unstyled content), script execution, and ES module reload
17
15
  - Glob pattern support for watch, ignore, and cold file configuration
18
16
  - Cold file patterns that can trigger a full page reload instead of HMR
19
17
  - Override detection to map replacement files onto their originals
18
+ - Atomic CSS hot swap (no flash of unstyled content), script execution, and ES module reload
20
19
 
21
20
  **Server**
22
21
 
@@ -399,6 +398,7 @@ process.on("SIGINT", () => server.stop().then(() => process.exit(0)));
399
398
  | `autoReconnect` | `boolean` | `true` | Reconnect on disconnect with exponential backoff |
400
399
  | `reconnectDelay` | `number` | `2000` | Base reconnect delay in ms |
401
400
  | `maxReconnectDelay` | `number` | `30000` | Maximum reconnect delay cap in ms |
401
+ | `skipOnReconnect` | `boolean` | `true` | Skip files already loaded in the page when the server reconnects |
402
402
  | `skip` | `string[]` | | Glob patterns for files to never load |
403
403
  | `filterSkip` | `(file, allFiles) => boolean` | | Custom skip logic, OR'd with `skip` |
404
404
  | `cold` | `string[]` | | Glob patterns that trigger a full page reload. Merged with the server's `cold` config on connect |
@@ -12,9 +12,16 @@ export class FileLoader {
12
12
  timeout: number;
13
13
  resolvers: Function[];
14
14
  }>;
15
+ /**
16
+ * Load counter per file used for cache busting.
17
+ * Produces short URLs like Logger.js?v=3 which keeps
18
+ * browser stack traces readable.
19
+ * @type {Map<string, number>}
20
+ */
21
+ versions: Map<string, number>;
15
22
  loadFile(path: any): Promise<any>;
16
23
  loadCSS(path: any): Promise<any>;
17
- loadModule(path: any): Promise<any>;
24
+ loadModule(path: any): Promise<boolean>;
18
25
  loadScript(path: any): Promise<any>;
19
26
  reloadFile(path: any): Promise<any>;
20
27
  _flushReload(path: any): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"file-loader.d.ts","sourceRoot":"","sources":["../../src/client/file-loader.js"],"names":[],"mappings":"AAAA,mFAAmF;AACnF;IACE,0BASC;IARC,aAAsB;IACtB;;;;;OAKG;IACH,WAFU,GAAG,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,UAAU,CAAA;KAAE,CAAC,CAEvC;IAG5B,kCAOC;IAKD,iCAoBC;IAKD,oCAgBC;IAED,oCAeC;IAKD,oCAeC;IAED,uCAUC;IAED,qCAcC;IAGD,2BAGC;CACF"}
1
+ {"version":3,"file":"file-loader.d.ts","sourceRoot":"","sources":["../../src/client/file-loader.js"],"names":[],"mappings":"AAAA,mFAAmF;AACnF;IACE,0BAgBC;IAfC,aAAsB;IACtB;;;;;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,iCAoBC;IAED,wCAIC;IAED,oCAeC;IAKD,oCAeC;IAED,uCAUC;IAED,qCAiBC;IAGD,2BAIC;CACF"}
@@ -39,6 +39,7 @@ export class HMRClient {
39
39
  * @param {boolean} [options.autoReconnect=true] - Reconnect on disconnect with exponential backoff.
40
40
  * @param {number} [options.reconnectDelay=2000] - Base reconnect delay in ms
41
41
  * @param {number} [options.maxReconnectDelay=30000] - Maximum reconnect delay cap in ms
42
+ * @param {boolean} [options.skipOnReconnect=true] - Skip files already present in the page on reconnect, preventing them from being loaded again.
42
43
  * @param {string[]} [options.skip] - Glob patterns for files that should never be loaded (e.g. `['_*\/**']`)
43
44
  * @param {function(string, string[]): boolean} [options.filterSkip] - Custom skip logic. Receives `(filePath, allFiles)`. Combined with `skip` via OR.
44
45
  * @param {string[]} [options.cold] - Glob patterns for files that require a full page reload. Merged with the server's `cold` config on connect. A `cold` event is emitted instead of hot reloading.
@@ -57,6 +58,7 @@ export class HMRClient {
57
58
  autoReconnect?: boolean;
58
59
  reconnectDelay?: number;
59
60
  maxReconnectDelay?: number;
61
+ skipOnReconnect?: boolean;
60
62
  skip?: string[];
61
63
  filterSkip?: (arg0: string, arg1: string[]) => boolean;
62
64
  cold?: string[];
@@ -72,6 +74,7 @@ export class HMRClient {
72
74
  autoReconnect: boolean;
73
75
  reconnectDelay: number;
74
76
  maxReconnectDelay: number;
77
+ skipOnReconnect: boolean;
75
78
  _coldPatterns: string[];
76
79
  _filterCold: (arg0: string) => boolean;
77
80
  shouldSkipFile: any;
@@ -1 +1 @@
1
- {"version":3,"file":"hmr-client.d.ts","sourceRoot":"","sources":["../../src/client/hmr-client.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH;IACE;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,qBAjBG;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;QACa,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;KACtC,EA8DA;IAvDC,WAAkB;IAClB,aAAsB;IACtB,oBAAsB;IAEtB,+BAAyD;IACzD,uBAA+C;IAC/C,uBAAiD;IACjD,0BAAwD;IAGxD,wBAAsC;IACtC,oBAtBkB,MAAM,KAAG,OAAO,CAsBQ;IAG1C,oBAAiF;IACjF,gBAAuE;IAGvE,gBAAkB;IAElB,0BA9BkB,MAAM,QAAE,MAAM,EAAE,KAAG,MAAM,GAAC,IAAI,CA8BO;IACvD,qBA9BkB,MAAM,KAAG,IAAI,CA8Bc;IAC7C,eAAmE;IAEnE,kBAAkB;IAClB,0BAA0B;IAC1B,qBAAwB;IACxB,6BAA8B;IAC9B,gCAA2B;IAI3B,qBAAuB;IACvB,6BAAgC;IAEhC,uBAA8C;IAE9C,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,oCAmBC;IAED,8CAcC;IAED,mCAIC;IAED,sEAoDC;IAED,kCA0BC;IAED,4CAgCC;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;IAED;;;OAGG;IACH,WAFa,OAAO,CAAC,IAAI,CAAC,CAuFzB;IAED;;OAEG;IACH,mBAUC;CACF;2BApnB0B,kBAAkB"}
1
+ {"version":3,"file":"hmr-client.d.ts","sourceRoot":"","sources":["../../src/client/hmr-client.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH;IACE;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,qBAlBG;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;KACtC,EA+DA;IAxDC,WAAkB;IAClB,aAAsB;IACtB,oBAAsB;IAEtB,+BAAyD;IACzD,uBAA+C;IAC/C,uBAAiD;IACjD,0BAAwD;IACxD,yBAAqD;IAGrD,wBAAsC;IACtC,oBAvBkB,MAAM,KAAG,OAAO,CAuBQ;IAG1C,oBAAiF;IACjF,gBAAuE;IAGvE,gBAAkB;IAElB,0BA/BkB,MAAM,QAAE,MAAM,EAAE,KAAG,MAAM,GAAC,IAAI,CA+BO;IACvD,qBA/BkB,MAAM,KAAG,IAAI,CA+Bc;IAC7C,eAAmE;IAEnE,kBAAkB;IAClB,0BAA0B;IAC1B,qBAAwB;IACxB,6BAA8B;IAC9B,gCAA2B;IAI3B,qBAAuB;IACvB,6BAAgC;IAEhC,uBAA8C;IAE9C,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,oCAmBC;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;IAED;;;OAGG;IACH,WAFa,OAAO,CAAC,IAAI,CAAC,CAuFzB;IAED;;OAEG;IACH,mBAUC;CACF;2BAtoB0B,kBAAkB"}
@@ -1561,6 +1561,7 @@ var HMR = (() => {
1561
1561
  constructor(httpUrl) {
1562
1562
  this.httpUrl = httpUrl;
1563
1563
  this.loadQueue = /* @__PURE__ */ new Map();
1564
+ this.versions = /* @__PURE__ */ new Map();
1564
1565
  }
1565
1566
  async loadFile(path) {
1566
1567
  const isCSS = path.endsWith(".css");
@@ -1591,22 +1592,10 @@ var HMR = (() => {
1591
1592
  document.head.appendChild(link);
1592
1593
  });
1593
1594
  }
1594
- // Replace the existing module script with a cache busted URL.
1595
- // Changing the URL makes the browser treat it as a new module
1596
- // and execute it from scratch.
1597
1595
  async loadModule(path) {
1598
1596
  const url = this.makeUrl(path);
1599
- const existing = document.querySelector(`script[data-file="${path}"]`);
1600
- if (existing) existing.remove();
1601
- const script = document.createElement("script");
1602
- script.type = "module";
1603
- script.src = url;
1604
- script.setAttribute("data-file", path);
1605
- return new Promise((resolve, reject) => {
1606
- script.onload = () => resolve(true);
1607
- script.onerror = () => reject(new Error(`Failed to execute module: ${path}`));
1608
- document.head.appendChild(script);
1609
- });
1597
+ await import(url);
1598
+ return true;
1610
1599
  }
1611
1600
  async loadScript(path) {
1612
1601
  const url = this.makeUrl(path);
@@ -1662,11 +1651,13 @@ var HMR = (() => {
1662
1651
  el.remove();
1663
1652
  await new Promise((r) => setTimeout(r, 0));
1664
1653
  }
1654
+ this.versions.delete(path);
1665
1655
  }
1666
- // Cache bust with timestamp + random to avoid duplicate URLs on rapid reloads.
1656
+ // Increment the version counter for individual files and return a versioned URL
1667
1657
  makeUrl(path) {
1668
- const cb = `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
1669
- return `${this.httpUrl}${path}?cb=${cb}`;
1658
+ const v = (this.versions.get(path) ?? 0) + 1;
1659
+ this.versions.set(path, v);
1660
+ return `${this.httpUrl}${path}?v=${v}`;
1670
1661
  }
1671
1662
  };
1672
1663
 
@@ -1764,6 +1755,7 @@ var HMR = (() => {
1764
1755
  * @param {boolean} [options.autoReconnect=true] - Reconnect on disconnect with exponential backoff.
1765
1756
  * @param {number} [options.reconnectDelay=2000] - Base reconnect delay in ms
1766
1757
  * @param {number} [options.maxReconnectDelay=30000] - Maximum reconnect delay cap in ms
1758
+ * @param {boolean} [options.skipOnReconnect=true] - Skip files already present in the page on reconnect, preventing them from being loaded again.
1767
1759
  * @param {string[]} [options.skip] - Glob patterns for files that should never be loaded (e.g. `['_*\/**']`)
1768
1760
  * @param {function(string, string[]): boolean} [options.filterSkip] - Custom skip logic. Receives `(filePath, allFiles)`. Combined with `skip` via OR.
1769
1761
  * @param {string[]} [options.cold] - Glob patterns for files that require a full page reload. Merged with the server's `cold` config on connect. A `cold` event is emitted instead of hot reloading.
@@ -1783,6 +1775,7 @@ var HMR = (() => {
1783
1775
  this.autoReconnect = this._autoReconnectDefault;
1784
1776
  this.reconnectDelay = opts.reconnectDelay || 2e3;
1785
1777
  this.maxReconnectDelay = opts.maxReconnectDelay || 3e4;
1778
+ this.skipOnReconnect = opts.skipOnReconnect !== false;
1786
1779
  this._coldPatterns = opts.cold || null;
1787
1780
  this._filterCold = opts.filterCold || null;
1788
1781
  this.shouldSkipFile = this.makeFilter(opts.skip || null, opts.filterSkip || null);
@@ -1930,7 +1923,19 @@ var HMR = (() => {
1930
1923
  skipped.forEach((f) => console.log(` \u2514\u2500 ${getFileName(f)}`));
1931
1924
  console.groupEnd();
1932
1925
  }
1933
- const withOverrides = this.buildOverrideMap(filtered);
1926
+ let toLoad = filtered;
1927
+ if (this.skipOnReconnect) {
1928
+ const alreadyLoaded = [];
1929
+ toLoad = [];
1930
+ for (const f of filtered) {
1931
+ const isLoaded = this.fileLoader.versions.has(f) || document.querySelector(`[data-file="${f}"]`);
1932
+ (isLoaded ? alreadyLoaded : toLoad).push(f);
1933
+ }
1934
+ if (alreadyLoaded.length > 0) {
1935
+ this.log("info", `Server reconnected - skipping ${alreadyLoaded.length} existing file${alreadyLoaded.length !== 1 ? "s" : ""}`);
1936
+ }
1937
+ }
1938
+ const withOverrides = this.buildOverrideMap(toLoad);
1934
1939
  const sorted2 = this.sortFiles(withOverrides);
1935
1940
  this.logInitFileGroup(sorted2, this.overrideMap, this.isColdFile.bind(this));
1936
1941
  for (const file of sorted2) {