cindel 1.0.2 → 1.0.4

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
@@ -5,6 +5,7 @@
5
5
  [![npm version](https://img.shields.io/npm/v/cindel.svg)](https://www.npmjs.com/package/cindel)
6
6
  [![npm bundle size](https://img.shields.io/bundlephobia/minzip/cindel?style=round-square)](https://bundlephobia.com/package/cindel@latest)
7
7
  [![license](https://img.shields.io/npm/l/cindel.svg)](LICENSE)
8
+
8
9
  ---
9
10
 
10
11
  ## Features
@@ -387,25 +388,26 @@ process.on("SIGINT", () => server.stop().then(() => process.exit(0)));
387
388
  - **`string`** treated as a full WebSocket URL
388
389
  - **`object`** full config, see below
389
390
 
390
- | Option | Type | Default | Description |
391
- | ------------------- | ------------------------------------ | ------------------------- | ------------------------------------------------------------------------------------------------ |
392
- | `port` | `number` | | Port number |
393
- | `host` | `string` | `'localhost'` | Hostname |
394
- | `secure` | `boolean` | `false` | Use `wss://` and `https://` |
395
- | `wsUrl` | `string` | | Explicit WebSocket URL, overrides host/port |
396
- | `httpUrl` | `string` | | Explicit HTTP base URL for file fetching |
397
- | `wsPath` | `string` | `'/hmr'` | WebSocket path |
398
- | `autoReconnect` | `boolean` | `true` | Reconnect on disconnect with exponential backoff |
399
- | `reconnectDelay` | `number` | `2000` | Base reconnect delay in ms |
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
- | `skip` | `string[]` | | Glob patterns for files to never load |
403
- | `filterSkip` | `(file, allFiles) => boolean` | | Custom skip logic, OR'd with `skip` |
404
- | `cold` | `string[]` | | Glob patterns that trigger a full page reload. Merged with the server's `cold` config on connect |
405
- | `filterCold` | `(file) => boolean` | | Custom cold logic, OR'd with `cold` |
406
- | `getOverrideTarget` | `(file, allFiles) => string \| null` | | Map an override file to the original it replaces |
407
- | `onFileLoaded` | `(file) => void` | | Called after each file is loaded or reloaded |
408
- | `sortFiles` | `(files) => string[]` | CSS before JS, cold first | Custom sort for the initial load order |
391
+ | Option | Type | Default | Description |
392
+ | ------------------- | ------------------------------------ | ------------- | ----------------------------------------------------------------- |
393
+ | `port` | `number` | | Port number |
394
+ | `host` | `string` | `'localhost'` | Hostname |
395
+ | `secure` | `boolean` | `false` | Use `wss://` and `https://` |
396
+ | `wsUrl` | `string` | | Explicit WebSocket URL, overrides host/port |
397
+ | `httpUrl` | `string` | | Explicit HTTP base URL for file fetching |
398
+ | `wsPath` | `string` | `'/hmr'` | WebSocket path |
399
+ | `autoReconnect` | `boolean` | `true` | Reconnect on disconnect with exponential backoff |
400
+ | `reconnectDelay` | `number` | `2000` | Base reconnect delay in ms |
401
+ | `maxReconnectDelay` | `number` | `30000` | Maximum reconnect delay cap in ms |
402
+ | `skipOnReconnect` | `boolean` | `true` | Skip files already loaded in the page when the server reconnects |
403
+ | `skip` | `string[]` | | Glob patterns for files to never load |
404
+ | `filterSkip` | `(file, allFiles) => boolean` | | Custom skip logic, OR'd with `skip` |
405
+ | `cold` | `string[]` | | Glob patterns that emit a `cold` event instead of reloading |
406
+ | `filterCold` | `(file) => boolean` | | Custom cold logic, OR'd with `cold` |
407
+ | `getOverrideTarget` | `(file, allFiles) => string \| null` | | Map an override file to the original it replaces |
408
+ | `onFileLoaded` | `(file) => void` | | Called after each file is loaded or reloaded |
409
+ | `loadOrder` | `Stage[]` | | Extra stages prepended before the built-in sorting |
410
+ | `sortFiles` | `(files) => string[]` | | Fully replaces the default sort. When set, `loadOrder` is ignored |
409
411
 
410
412
  ### Methods
411
413
 
@@ -470,7 +472,7 @@ client
470
472
 
471
473
  ### Skip and Cold Filters
472
474
 
473
- `skip` prevents files from ever being loaded by the client. `cold` marks files that need a full page reload rather than a hot swap. Both options accept glob patterns, a custom filter function, or both combined via OR logic.
475
+ `skip` prevents files from ever being loaded by the client. `cold` marks files that emit a `cold` event instead of being hot-reloaded, what happens next is up to your `cold` event handler. Both options accept glob patterns, a custom filter function, or both combined via OR logic. Client and server `cold` patterns are merged on connect.
474
476
 
475
477
  > **Note:** Glob patterns are always relative to the project root, not the watched directory.
476
478
 
@@ -486,7 +488,7 @@ new HMRClient({
486
488
  return allFiles.includes(file.replace(".override.js", ".js"));
487
489
  },
488
490
 
489
- // These files can't be hot-swapped, they need a full reload
491
+ // These files emit a cold event instead of being hot-reloaded
490
492
  cold: ["**/*.cold.js", "src/bootstrap.js"],
491
493
 
492
494
  // Custom cold logic
@@ -496,6 +498,60 @@ new HMRClient({
496
498
 
497
499
  ---
498
500
 
501
+ ### Load Order
502
+
503
+ When the client receives the initial file list it sorts them before loading. The default order is:
504
+
505
+ 1. CSS before JS: stylesheets load first so scripts never run against an unstyled page
506
+ 2. Cold files first: files that require a full page reload are loaded before hot-swappable ones
507
+ 3. Alphabetical: stable tiebreaker within each group
508
+
509
+ `loadOrder` lets you prepend extra stages to the pipeline without giving up the built-in sorting. Each stage is detected by how many arguments it takes:
510
+
511
+ - **One argument** `f => boolean` return `true` to load that file earlier, `false` to leave it in its normal position
512
+ - **Two arguments** `(a, b) => number` works exactly like a standard `Array.sort` callback
513
+
514
+ The first stage to return a non-zero result wins; the built-in stages always follow as a fallback.
515
+
516
+ ```js
517
+ new HMRClient({
518
+ port: 1338,
519
+
520
+ loadOrder: [
521
+ // Load the bootstrap file before everything else
522
+ (f) => f === "src/bootstrap.js",
523
+
524
+ // Load files in the core/ directory before others
525
+ (f) => f.startsWith("core/"),
526
+
527
+ // Higher-level files first (fewer path segments)
528
+ (a, b) => a.split("/").length - b.split("/").length,
529
+ ],
530
+ });
531
+ ```
532
+
533
+ Each stage is tried in order. The first one that produces a difference between two files decides which loads first. If a stage sees no difference, the next one is tried. The built-in stages (CSS-first, cold-first, alphabetical) always follow as a final fallback.
534
+
535
+ When you need total control over the load order, pass `sortFiles` instead. It receives the full file list and must return a sorted copy. The built-in stages and any `loadOrder` are completely bypassed.
536
+
537
+ ```js
538
+ new HMRClient({
539
+ port: 1338,
540
+
541
+ sortFiles: (files) => {
542
+ const order = ["src/reset.css", "src/theme.css", "src/bootstrap.js"];
543
+ return [
544
+ // Pinned files in explicit order
545
+ ...order.filter((f) => files.includes(f)),
546
+ // Everything else, alphabetical
547
+ ...files.filter((f) => !order.includes(f)).sort(),
548
+ ];
549
+ },
550
+ });
551
+ ```
552
+
553
+ ---
554
+
499
555
  ### Override Detection
500
556
 
501
557
  Override detection lets you maintain a parallel directory of replacement files that shadow originals without modifying them. When an override changes, the client unloads the original before loading the override.
@@ -46,7 +46,8 @@ export class HMRClient {
46
46
  * @param {function(string): boolean} [options.filterCold] - Custom cold file logic. Receives `(filePath)`. Combined with `cold` via OR.
47
47
  * @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.
48
48
  * @param {function(string): void} [options.onFileLoaded] - Called after each file loads or reloads. Receives `(filePath)`.
49
- * @param {function(string[]): string[]} [options.sortFiles] - Custom sort for the initial file load order. Default sorts CSS before JS, cold files first.
49
+ * @param {function(string[]): string[]} [options.sortFiles] - Custom sort for the initial file load order. When provided, replaces `defaultSortFiles` entirely and `loadOrder` is ignored.
50
+ * @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.
50
51
  */
51
52
  constructor(options: {
52
53
  wsUrl?: string;
@@ -66,6 +67,7 @@ export class HMRClient {
66
67
  getOverrideTarget?: (arg0: string, arg1: string[]) => string | null;
67
68
  onFileLoaded?: (arg0: string) => void;
68
69
  sortFiles?: (arg0: string[]) => string[];
70
+ loadOrder?: Array<Function>;
69
71
  });
70
72
  wsUrl: any;
71
73
  httpUrl: any;
@@ -82,6 +84,7 @@ export class HMRClient {
82
84
  allFiles: any[];
83
85
  getOverrideTarget: (arg0: string, arg1: string[]) => string | null;
84
86
  onFileLoaded: (arg0: string) => void;
87
+ loadOrder: Function[];
85
88
  sortFiles: any;
86
89
  socket: WebSocket;
87
90
  reconnectAttempts: number;
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"hmr-client.d.ts","sourceRoot":"","sources":["../../src/client/hmr-client.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH;IACE;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,qBAnBG;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;QACF,SAAS,GAAnC,KAAK,UAAU;KACzB,EAgEA;IAzDC,WAAkB;IAClB,aAAsB;IACtB,oBAAsB;IAEtB,+BAAyD;IACzD,uBAA+C;IAC/C,uBAAiD;IACjD,0BAAwD;IACxD,yBAAqD;IAGrD,wBAAsC;IACtC,oBAxBkB,MAAM,KAAG,OAAO,CAwBQ;IAG1C,oBAAiF;IACjF,gBAAuE;IAGvE,gBAAkB;IAElB,0BAhCkB,MAAM,QAAE,MAAM,EAAE,KAAG,MAAM,GAAC,IAAI,CAgCO;IACvD,qBAhCkB,MAAM,KAAG,IAAI,CAgCc;IAC7C,sBAAqC;IACrC,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,oCAkBC;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;2BAvoB0B,kBAAkB"}
@@ -1762,7 +1762,8 @@ var HMR = (() => {
1762
1762
  * @param {function(string): boolean} [options.filterCold] - Custom cold file logic. Receives `(filePath)`. Combined with `cold` via OR.
1763
1763
  * @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
1764
  * @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.
1765
+ * @param {function(string[]): string[]} [options.sortFiles] - Custom sort for the initial file load order. When provided, replaces `defaultSortFiles` entirely and `loadOrder` is ignored.
1766
+ * @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.
1766
1767
  */
1767
1768
  constructor(options) {
1768
1769
  const opts = typeof options === "object" && !Array.isArray(options) ? options : {};
@@ -1783,6 +1784,7 @@ var HMR = (() => {
1783
1784
  this.allFiles = [];
1784
1785
  this.getOverrideTarget = opts.getOverrideTarget || null;
1785
1786
  this.onFileLoaded = opts.onFileLoaded || null;
1787
+ this.loadOrder = opts.loadOrder || [];
1786
1788
  this.sortFiles = opts.sortFiles || this.defaultSortFiles.bind(this);
1787
1789
  this.socket = null;
1788
1790
  this.reconnectAttempts = 0;
@@ -1810,16 +1812,18 @@ var HMR = (() => {
1810
1812
  }
1811
1813
  defaultSortFiles(files) {
1812
1814
  const coldSet = new Set(files.filter((f) => this.isColdFile(f)));
1815
+ const builtinStages = [
1816
+ (f) => f.endsWith(".css"),
1817
+ (f) => coldSet.has(f),
1818
+ (a, b) => a.localeCompare(b)
1819
+ ];
1820
+ const stages = [...this.loadOrder, ...builtinStages];
1813
1821
  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);
1822
+ for (const stage of stages) {
1823
+ const result = stage.length === 2 ? stage(a, b) : stage(b) - stage(a);
1824
+ if (result !== 0) return result;
1825
+ }
1826
+ return 0;
1823
1827
  });
1824
1828
  }
1825
1829
  makeFilter(patterns, callback) {