@turntrout/subfont 1.10.7 → 1.11.0

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.
Files changed (74) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/README.md +21 -21
  3. package/lib/FontTracerPool.d.ts +22 -27
  4. package/lib/FontTracerPool.d.ts.map +1 -1
  5. package/lib/FontTracerPool.js +51 -277
  6. package/lib/FontTracerPool.js.map +1 -1
  7. package/lib/HeadlessBrowser.d.ts.map +1 -1
  8. package/lib/HeadlessBrowser.js +44 -28
  9. package/lib/HeadlessBrowser.js.map +1 -1
  10. package/lib/cli.js +14 -10
  11. package/lib/cli.js.map +1 -1
  12. package/lib/codepointMaps.d.ts.map +1 -1
  13. package/lib/codepointMaps.js +0 -3
  14. package/lib/codepointMaps.js.map +1 -1
  15. package/lib/collectTextsByPage.d.ts +2 -1
  16. package/lib/collectTextsByPage.d.ts.map +1 -1
  17. package/lib/collectTextsByPage.js +15 -4
  18. package/lib/collectTextsByPage.js.map +1 -1
  19. package/lib/escapeJsStringLiteral.d.ts.map +1 -1
  20. package/lib/escapeJsStringLiteral.js +12 -5
  21. package/lib/escapeJsStringLiteral.js.map +1 -1
  22. package/lib/extractVisibleText.d.ts.map +1 -1
  23. package/lib/extractVisibleText.js +68 -147
  24. package/lib/extractVisibleText.js.map +1 -1
  25. package/lib/fontConverter.d.ts +5 -1
  26. package/lib/fontConverter.d.ts.map +1 -1
  27. package/lib/fontConverter.js +47 -122
  28. package/lib/fontConverter.js.map +1 -1
  29. package/lib/fontConverterWorker.d.ts +14 -1
  30. package/lib/fontConverterWorker.d.ts.map +1 -1
  31. package/lib/fontConverterWorker.js +16 -16
  32. package/lib/fontConverterWorker.js.map +1 -1
  33. package/lib/fontFaceHelpers.d.ts +3 -2
  34. package/lib/fontFaceHelpers.d.ts.map +1 -1
  35. package/lib/fontFaceHelpers.js +18 -17
  36. package/lib/fontFaceHelpers.js.map +1 -1
  37. package/lib/fontFeatureHelpers.d.ts.map +1 -1
  38. package/lib/fontFeatureHelpers.js +5 -1
  39. package/lib/fontFeatureHelpers.js.map +1 -1
  40. package/lib/fontTracerWorker.d.ts +17 -4
  41. package/lib/fontTracerWorker.d.ts.map +1 -1
  42. package/lib/fontTracerWorker.js +31 -60
  43. package/lib/fontTracerWorker.js.map +1 -1
  44. package/lib/getCssRulesByProperty.d.ts.map +1 -1
  45. package/lib/getCssRulesByProperty.js +11 -2
  46. package/lib/getCssRulesByProperty.js.map +1 -1
  47. package/lib/parseCommandLineOptions.d.ts.map +1 -1
  48. package/lib/parseCommandLineOptions.js +12 -7
  49. package/lib/parseCommandLineOptions.js.map +1 -1
  50. package/lib/piscinaRunWithTimeout.d.ts +18 -0
  51. package/lib/piscinaRunWithTimeout.d.ts.map +1 -0
  52. package/lib/piscinaRunWithTimeout.js +78 -0
  53. package/lib/piscinaRunWithTimeout.js.map +1 -0
  54. package/lib/subfont.js.map +1 -1
  55. package/lib/subsetFontWithGlyphs.d.ts +1 -0
  56. package/lib/subsetFontWithGlyphs.d.ts.map +1 -1
  57. package/lib/subsetFontWithGlyphs.js +4 -2
  58. package/lib/subsetFontWithGlyphs.js.map +1 -1
  59. package/lib/subsetFonts.d.ts +4 -3
  60. package/lib/subsetFonts.d.ts.map +1 -1
  61. package/lib/subsetFonts.js +19 -11
  62. package/lib/subsetFonts.js.map +1 -1
  63. package/lib/subsetGeneration.d.ts +1 -1
  64. package/lib/subsetGeneration.d.ts.map +1 -1
  65. package/lib/subsetGeneration.js +7 -7
  66. package/lib/subsetGeneration.js.map +1 -1
  67. package/lib/types/shared.d.ts +1 -0
  68. package/lib/types/shared.d.ts.map +1 -1
  69. package/lib/types/shared.js.map +1 -1
  70. package/lib/unquote.d.ts +17 -0
  71. package/lib/unquote.d.ts.map +1 -1
  72. package/lib/unquote.js +63 -9
  73. package/lib/unquote.js.map +1 -1
  74. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -22,7 +22,7 @@ On [TurnTrout.com](https://github.com/alexander-turner/TurnTrout.com) (382 pages
22
22
  ### New features
23
23
 
24
24
  - **`--cache [dir]`** -- Cache subset results to disk. Speeds up repeat builds.
25
- - **`--chrome-flags`** -- Custom flags for headless Chrome with `--dynamic`.
25
+ - **`--chrome-flags`** -- Custom flag for headless Chrome with `--dynamic`. Use the `=` form and repeat for multiple flags (`--chrome-flags=--no-sandbox --chrome-flags=--disable-features=Foo,Bar`); the prior comma-separated form was dropped because real flag values like `--disable-features=Foo,Bar` contain commas.
26
26
  - **`--concurrency N`** -- Control worker thread count for parallel font tracing.
27
27
  - **Parallel font tracing** -- Worker pool (up to 8 threads). Pages sharing identical CSS are traced once.
28
28
  - **`--root` validation** -- Fails early with a clear error.
package/README.md CHANGED
@@ -65,27 +65,27 @@ subfont path/to/index.html -i --cache
65
65
 
66
66
  ## Options
67
67
 
68
- | Flag | Default | Description |
69
- | -----------------: | :-----: | :---------------------------------------------------------------------------------------------------------------- |
70
- | `-i, --in-place` | off | Modify files in-place |
71
- | `-o, --output` | | Output directory |
72
- | `--root` | | Path to web root (deduced from input files if not specified) |
73
- | `--canonical-root` | | URI root where the site will be deployed |
74
- | `-r, --recursive` | off | Crawl linked pages |
75
- | `--dynamic` | off | Trace with headless browser |
76
- | `--dry-run` | off | Preview without writing |
77
- | `--fallbacks` | on | Async-load the full original font as a fallback for dynamic content |
78
- | `--font-display` | `swap` | `auto`/`block`/`swap`/`fallback`/`optional` |
79
- | `--text` | | Extra characters for every subset |
80
- | `--cache [dir]` | off | Cache subset results to disk between runs |
81
- | `--concurrency N` | auto | Max worker threads (defaults to CPU count, max 8). Warns when exceeding memory-based estimate (~50 MB per worker) |
82
- | `--chrome-flags` | | Custom Chrome flags for `--dynamic` (comma-separated) |
83
- | `--source-maps` | off | Preserve CSS source maps (slower) |
84
- | `--strict` | off | Exit non-zero if any warnings are emitted |
85
- | `-s, --silent` | off | Suppress all console output |
86
- | `-d, --debug` | off | Verbose timing and font glyph detection info |
87
- | `--relative-urls` | off | Emit relative URLs instead of root-relative |
88
- | `--inline-css` | off | Inline the subset @font-face CSS into HTML |
68
+ | Flag | Default | Description |
69
+ | -----------------: | :-----: | :---------------------------------------------------------------------------------------------------------------------------------------------------------- |
70
+ | `-i, --in-place` | off | Modify files in-place |
71
+ | `-o, --output` | | Output directory |
72
+ | `--root` | | Path to web root (deduced from input files if not specified) |
73
+ | `--canonical-root` | | URI root where the site will be deployed |
74
+ | `-r, --recursive` | off | Crawl linked pages |
75
+ | `--dynamic` | off | Trace with headless browser |
76
+ | `--dry-run` | off | Preview without writing |
77
+ | `--fallbacks` | on | Async-load the full original font as a fallback for dynamic content |
78
+ | `--font-display` | `swap` | `auto`/`block`/`swap`/`fallback`/`optional` |
79
+ | `--text` | | Extra characters for every subset |
80
+ | `--cache [dir]` | off | Cache subset results to disk between runs |
81
+ | `--concurrency N` | auto | Max worker threads (defaults to CPU count, max 8). Warns when exceeding memory-based estimate (~50 MB per worker) |
82
+ | `--chrome-flags` | | Custom Chrome flag for `--dynamic`. Use the `=` form and repeat for multiple flags: `--chrome-flags=--no-sandbox --chrome-flags=--disable-features=Foo,Bar` |
83
+ | `--source-maps` | off | Preserve CSS source maps (slower) |
84
+ | `--strict` | off | Exit non-zero if any warnings are emitted |
85
+ | `-s, --silent` | off | Suppress all console output |
86
+ | `-d, --debug` | off | Verbose timing and font glyph detection info |
87
+ | `--relative-urls` | off | Emit relative URLs instead of root-relative |
88
+ | `--inline-css` | off | Inline the subset @font-face CSS into HTML |
89
89
 
90
90
  Run `subfont --help` for the full list.
91
91
 
@@ -1,3 +1,6 @@
1
+ interface FontTracerPoolOptions {
2
+ taskTimeoutMs?: number;
3
+ }
1
4
  interface StylesheetWithPredicates {
2
5
  text?: string;
3
6
  asset?: {
@@ -5,39 +8,31 @@ interface StylesheetWithPredicates {
5
8
  };
6
9
  predicates?: Record<string, unknown>;
7
10
  }
8
- interface FontTracerPoolOptions {
9
- taskTimeoutMs?: number;
10
- respawnOnExit?: boolean;
11
+ interface TextByPropsEntry {
12
+ text: string;
13
+ props: Record<string, unknown>;
14
+ }
15
+ interface TraceOptions {
16
+ signal?: AbortSignal;
11
17
  }
12
18
  declare class FontTracerPool {
13
- private _workerPath;
14
- private _numWorkers;
19
+ private _pool;
15
20
  private _taskTimeoutMs;
16
- private _respawnOnExit;
17
- private _workers;
18
- private _idle;
19
- private _pendingTasks;
20
- private _taskCallbacks;
21
- private _taskTimers;
22
- private _taskByWorker;
23
- private _nextTaskId;
24
- private _destroying;
25
- private _spawning;
26
- constructor(numWorkers: number, { taskTimeoutMs, respawnOnExit, }?: FontTracerPoolOptions);
27
- private _spawnOne;
21
+ constructor(numWorkers: number, { taskTimeoutMs }?: FontTracerPoolOptions);
28
22
  init(): Promise<void>;
29
- private _onWorkerError;
30
- private _clearTaskTimer;
31
- private _onWorkerMessage;
32
- private _onWorkerExit;
33
- private _drainPendingWith;
34
- private _startTaskTimer;
35
- private _dispatchPending;
23
+ trace(htmlText: string, stylesheetsWithPredicates: StylesheetWithPredicates[], { signal }?: TraceOptions): Promise<TextByPropsEntry[]>;
24
+ /**
25
+ * Number of worker threads in the underlying piscina pool. Piscina
26
+ * spawns up to minThreads === maxThreads at construction time, though
27
+ * individual workers may still be loading their module when this is
28
+ * read. Exposed primarily for tests.
29
+ */
30
+ get threadCount(): number;
36
31
  /**
37
- * Run fontTracer on the given HTML text + stylesheets in a worker.
38
- * Returns a promise that resolves to textByProps.
32
+ * Number of trace tasks waiting in the piscina queue (not yet dispatched
33
+ * to a worker).
39
34
  */
40
- trace(htmlText: string, stylesheetsWithPredicates: StylesheetWithPredicates[]): Promise<unknown>;
35
+ get queueSize(): number;
41
36
  destroy(): Promise<void>;
42
37
  }
43
38
  export = FontTracerPool;
@@ -1 +1 @@
1
- {"version":3,"file":"FontTracerPool.d.ts","sourceRoot":"","sources":["../src/FontTracerPool.ts"],"names":[],"mappings":"AAsBA,UAAU,wBAAwB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAI1B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAqCD,UAAU,qBAAqB;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,cAAM,cAAc;IAClB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,cAAc,CAAU;IAChC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,aAAa,CAAmC;IACxD,OAAO,CAAC,cAAc,CAA6B;IACnD,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAU;IAC7B,OAAO,CAAC,SAAS,CAAS;gBAGxB,UAAU,EAAE,MAAM,EAClB,EACE,aAAuC,EACvC,aAAoB,GACrB,GAAE,qBAA0B;IAiB/B,OAAO,CAAC,SAAS;IAyDX,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3B,OAAO,CAAC,cAAc;IAgBtB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,gBAAgB;IAmBxB,OAAO,CAAC,aAAa;IA+CrB,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,eAAe;IA2BvB,OAAO,CAAC,gBAAgB;IAsBxB;;;OAGG;IAGH,KAAK,CACH,QAAQ,EAAE,MAAM,EAChB,yBAAyB,EAAE,wBAAwB,EAAE,GAEpD,OAAO,CAAC,OAAO,CAAC;IAsBb,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CA6C/B;AAED,SAAS,cAAc,CAAC"}
1
+ {"version":3,"file":"FontTracerPool.d.ts","sourceRoot":"","sources":["../src/FontTracerPool.ts"],"names":[],"mappings":"AAmBA,UAAU,qBAAqB;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,wBAAwB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAI1B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAaD,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IAGb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,UAAU,YAAY;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,cAAM,cAAc;IAClB,OAAO,CAAC,KAAK,CAAU;IACvB,OAAO,CAAC,cAAc,CAAS;gBAG7B,UAAU,EAAE,MAAM,EAClB,EAAE,aAAuC,EAAE,GAAE,qBAA0B;IAgBnE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB3B,KAAK,CACH,QAAQ,EAAE,MAAM,EAChB,yBAAyB,EAAE,wBAAwB,EAAE,EACrD,EAAE,MAAM,EAAE,GAAE,YAAiB,GAC5B,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAuB9B;;;;;OAKG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;;OAGG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;IAEK,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B;AAED,SAAS,cAAc,CAAC"}
@@ -1,9 +1,14 @@
1
1
  "use strict";
2
2
  const pathModule = require("path");
3
- const worker_threads_1 = require("worker_threads");
3
+ const piscina_1 = require("piscina");
4
+ const piscinaRunWithTimeout_1 = require("./piscinaRunWithTimeout");
4
5
  /**
5
6
  * Worker pool for running fontTracer in parallel across pages.
6
7
  * Each worker re-parses HTML with jsdom and runs fontTracer independently.
8
+ *
9
+ * Wraps `piscina` to preserve the existing FontTracerPool surface (init,
10
+ * trace, destroy) while delegating worker lifecycle, queueing, and
11
+ * structured-clone handling to a battle-tested dependency.
7
12
  */
8
13
  // Heavy pages (large blog posts with elaborate CSS) under CPU contention on
9
14
  // CI runners can comfortably exceed a minute in jsdom + font-tracer. The
@@ -11,298 +16,67 @@ const worker_threads_1 = require("worker_threads");
11
16
  // errs generous.
12
17
  const DEFAULT_TASK_TIMEOUT_MS = 600_000;
13
18
  class FontTracerPool {
14
- _workerPath;
15
- _numWorkers;
19
+ _pool;
16
20
  _taskTimeoutMs;
17
- _respawnOnExit;
18
- _workers;
19
- _idle;
20
- _pendingTasks;
21
- _taskCallbacks;
22
- _taskTimers;
23
- _taskByWorker;
24
- _nextTaskId;
25
- _destroying;
26
- _spawning;
27
- constructor(numWorkers, { taskTimeoutMs = DEFAULT_TASK_TIMEOUT_MS, respawnOnExit = true, } = {}) {
28
- this._workerPath = pathModule.join(__dirname, 'fontTracerWorker.js');
29
- this._numWorkers = numWorkers;
21
+ constructor(numWorkers, { taskTimeoutMs = DEFAULT_TASK_TIMEOUT_MS } = {}) {
30
22
  this._taskTimeoutMs = taskTimeoutMs;
31
- this._respawnOnExit = respawnOnExit;
32
- this._workers = [];
33
- this._idle = [];
34
- this._pendingTasks = [];
35
- this._taskCallbacks = new Map();
36
- this._taskTimers = new Map();
37
- this._taskByWorker = new Map();
38
- this._nextTaskId = 0;
39
- this._destroying = false;
40
- this._spawning = 0;
41
- }
42
- _spawnOne() {
43
- this._spawning++;
44
- return new Promise((resolve, reject) => {
45
- let worker;
46
- try {
47
- worker = new worker_threads_1.Worker(this._workerPath);
48
- }
49
- catch (err) {
50
- this._spawning--;
51
- reject(err instanceof Error ? err : new Error(String(err)));
52
- return;
53
- }
54
- this._workers.push(worker);
55
- const detachInitHandlers = () => {
56
- worker.off('message', onMessage);
57
- worker.off('error', failOnInit);
58
- worker.off('exit', exitDuringInit);
59
- };
60
- const failOnInit = (err) => {
61
- detachInitHandlers();
62
- const idx = this._workers.indexOf(worker);
63
- if (idx !== -1)
64
- this._workers.splice(idx, 1);
65
- this._spawning--;
66
- worker.terminate();
67
- reject(err);
68
- };
69
- const exitDuringInit = (code) => {
70
- // Worker exited before sending 'ready' (e.g. terminated by destroy()
71
- // mid-spawn, or crashed during init without emitting 'error'). Settle
72
- // the spawn promise so the .catch() upstream can run.
73
- detachInitHandlers();
74
- const idx = this._workers.indexOf(worker);
75
- if (idx !== -1)
76
- this._workers.splice(idx, 1);
77
- this._spawning--;
78
- reject(new Error(`Worker exited during init with code ${code}`));
79
- };
80
- const onMessage = (msg) => {
81
- if (msg.type === 'ready') {
82
- detachInitHandlers();
83
- worker.on('message', (m) => this._onWorkerMessage(worker, m));
84
- worker.on('error', (e) => this._onWorkerError(worker, e));
85
- worker.on('exit', (code) => this._onWorkerExit(worker, code));
86
- this._idle.push(worker);
87
- this._spawning--;
88
- resolve();
89
- this._dispatchPending();
90
- }
91
- };
92
- worker.on('message', onMessage);
93
- worker.on('error', failOnInit);
94
- worker.on('exit', exitDuringInit);
95
- worker.postMessage({ type: 'init' });
23
+ this._pool = new piscina_1.Piscina({
24
+ filename: pathModule.join(__dirname, 'fontTracerWorker.js'),
25
+ // Pin pool size so concurrency is bounded by the caller-supplied
26
+ // worker count (memory and CPU contention both scale with this).
27
+ minThreads: numWorkers,
28
+ maxThreads: numWorkers,
29
+ concurrentTasksPerWorker: 1,
30
+ // Workers stay warm for the lifetime of the pool — pages on the
31
+ // same site share stylesheet parses through per-worker memoization.
32
+ idleTimeout: Infinity,
96
33
  });
97
34
  }
98
35
  async init() {
99
- const initPromises = [];
100
- for (let i = 0; i < this._numWorkers; i++) {
101
- initPromises.push(this._spawnOne());
102
- }
103
- await Promise.all(initPromises);
104
- }
105
- _onWorkerError(worker, err) {
106
- // A post-init worker emitted an error. Reject the in-flight task so
107
- // callers don't hang. The worker is about to exit; _onWorkerExit will
108
- // remove it from _workers/_idle.
109
- const taskId = this._taskByWorker.get(worker);
110
- if (taskId !== undefined) {
111
- this._taskByWorker.delete(worker);
112
- this._clearTaskTimer(taskId);
113
- const cb = this._taskCallbacks.get(taskId);
114
- if (cb) {
115
- this._taskCallbacks.delete(taskId);
116
- cb.reject(err);
117
- }
118
- }
119
- }
120
- _clearTaskTimer(taskId) {
121
- const timer = this._taskTimers.get(taskId);
122
- if (timer) {
123
- clearTimeout(timer);
124
- this._taskTimers.delete(taskId);
125
- }
126
- }
127
- _onWorkerMessage(worker, msg) {
128
- if (msg.type === 'ready')
129
- return;
130
- this._taskByWorker.delete(worker);
131
- this._clearTaskTimer(msg.taskId);
132
- const cb = this._taskCallbacks.get(msg.taskId);
133
- if (cb) {
134
- this._taskCallbacks.delete(msg.taskId);
135
- if (msg.type === 'result') {
136
- cb.resolve(msg.textByProps);
137
- }
138
- else if (msg.type === 'error') {
139
- const detail = msg.stack ? `${msg.error}\n${msg.stack}` : msg.error;
140
- cb.reject(new Error(`Worker error: ${detail}`));
141
- }
142
- }
143
- // Worker is now idle, check for pending tasks
144
- this._idle.push(worker);
145
- this._dispatchPending();
146
- }
147
- _onWorkerExit(worker, code) {
148
- const workerIdx = this._workers.indexOf(worker);
149
- if (workerIdx !== -1) {
150
- this._workers.splice(workerIdx, 1);
151
- }
152
- const idleIdx = this._idle.indexOf(worker);
153
- if (idleIdx !== -1) {
154
- this._idle.splice(idleIdx, 1);
155
- }
156
- // Reject any task that was in-flight on this worker. A graceful exit
157
- // (code 0) mid-task still leaves the caller waiting, so don't gate on
158
- // code !== 0.
159
- const taskId = this._taskByWorker.get(worker);
160
- this._taskByWorker.delete(worker);
161
- if (taskId !== undefined) {
162
- this._clearTaskTimer(taskId);
163
- const cb = this._taskCallbacks.get(taskId);
164
- if (cb) {
165
- this._taskCallbacks.delete(taskId);
166
- cb.reject(new Error(`Worker exited with code ${code}`));
167
- }
168
- }
169
- if (this._destroying)
170
- return;
171
- if (this._respawnOnExit) {
172
- this._spawnOne().catch((err) => {
173
- // Respawn failed — fall back to draining if the pool is now empty.
174
- if (this._workers.length === 0 &&
175
- this._spawning === 0 &&
176
- this._pendingTasks.length > 0) {
177
- this._drainPendingWith(`All workers have crashed and respawn failed: ${err.message}`);
178
- }
179
- });
180
- return;
181
- }
182
- if (code !== 0 && this._workers.length === 0) {
183
- this._drainPendingWith('All workers have crashed, no workers available');
184
- }
185
- }
186
- _drainPendingWith(message) {
187
- for (const task of this._pendingTasks) {
188
- const cb = this._taskCallbacks.get(task.message.taskId);
189
- if (cb) {
190
- this._taskCallbacks.delete(task.message.taskId);
191
- cb.reject(new Error(message));
192
- }
36
+ // Piscina spawns workers eagerly when minThreads === maxThreads, but
37
+ // each worker still pays a one-time `require('jsdom')` + `postcss` cost
38
+ // on its first message — hundreds of ms cold. Fire one warmup trace
39
+ // per worker so that cost lands in init() (where the phase tracker
40
+ // expects it) instead of in the first real `trace()` batch.
41
+ //
42
+ // Empty HTML produces an empty result quickly and exercises the full
43
+ // require chain. Tasks load-balance across all idle workers.
44
+ const minThreads = this._pool.options.minThreads;
45
+ if (minThreads > 0) {
46
+ await Promise.all(Array.from({ length: minThreads }, () => this._pool.run({ htmlText: '', stylesheetsWithPredicates: [] })));
193
47
  }
194
- this._pendingTasks = [];
195
48
  }
196
- _startTaskTimer(taskId) {
197
- if (this._taskTimeoutMs <= 0)
198
- return;
199
- const timer = setTimeout(() => {
200
- this._taskTimers.delete(taskId);
201
- const cb = this._taskCallbacks.get(taskId);
202
- if (cb) {
203
- this._taskCallbacks.delete(taskId);
204
- cb.reject(new Error(`Font tracing task ${taskId} timed out after ${this._taskTimeoutMs}ms`));
205
- }
206
- // Terminate the hung worker so it doesn't permanently consume a pool
207
- // slot. _onWorkerExit will remove it from _workers and _idle.
208
- for (const [worker, tid] of this._taskByWorker) {
209
- if (tid === taskId) {
210
- this._taskByWorker.delete(worker);
211
- worker.terminate();
212
- break;
213
- }
214
- }
215
- }, this._taskTimeoutMs);
216
- timer.unref();
217
- this._taskTimers.set(taskId, timer);
218
- }
219
- _dispatchPending() {
220
- while (this._idle.length > 0 && this._pendingTasks.length > 0) {
221
- const worker = this._idle.pop();
222
- const task = this._pendingTasks.shift();
223
- this._taskByWorker.set(worker, task.message.taskId);
224
- try {
225
- worker.postMessage(task.message);
226
- this._startTaskTimer(task.message.taskId);
227
- }
228
- catch (err) {
229
- // postMessage can fail synchronously (e.g. structured clone error).
230
- // Return the worker to the idle pool and reject the task.
231
- this._taskByWorker.delete(worker);
232
- this._idle.push(worker);
233
- const cb = this._taskCallbacks.get(task.message.taskId);
234
- if (cb) {
235
- this._taskCallbacks.delete(task.message.taskId);
236
- cb.reject(err);
237
- }
238
- }
239
- }
240
- }
241
- /**
242
- * Run fontTracer on the given HTML text + stylesheets in a worker.
243
- * Returns a promise that resolves to textByProps.
244
- */
245
- // The pool is payload-agnostic; callers (subsetFonts.ts) interpret the
246
- // returned textByProps according to font-tracer's contract.
247
- trace(htmlText, stylesheetsWithPredicates
248
- // eslint-disable-next-line no-restricted-syntax
249
- ) {
250
- const taskId = this._nextTaskId++;
49
+ trace(htmlText, stylesheetsWithPredicates, { signal } = {}) {
251
50
  // Serialize stylesheets to plain data — asset objects contain DOM/PostCSS
252
51
  // trees that cannot be transferred via structured clone.
253
- const serializedStylesheets = stylesheetsWithPredicates.map((entry) => ({
52
+ const serialized = stylesheetsWithPredicates.map((entry) => ({
254
53
  text: entry.text || (entry.asset && entry.asset.text) || '',
255
54
  predicates: entry.predicates || {},
256
55
  }));
257
- const message = {
258
- type: 'trace',
259
- taskId,
56
+ const task = {
260
57
  htmlText,
261
- stylesheetsWithPredicates: serializedStylesheets,
58
+ stylesheetsWithPredicates: serialized,
262
59
  };
263
- return new Promise((resolve, reject) => {
264
- this._taskCallbacks.set(taskId, { resolve, reject });
265
- this._pendingTasks.push({ message });
266
- this._dispatchPending();
267
- });
60
+ return (0, piscinaRunWithTimeout_1.runWithTimeoutAndSignal)(this._pool, task, signal, this._taskTimeoutMs, (ms) => `Font tracing task timed out after ${ms}ms`);
61
+ }
62
+ /**
63
+ * Number of worker threads in the underlying piscina pool. Piscina
64
+ * spawns up to minThreads === maxThreads at construction time, though
65
+ * individual workers may still be loading their module when this is
66
+ * read. Exposed primarily for tests.
67
+ */
68
+ get threadCount() {
69
+ return this._pool.threads.length;
70
+ }
71
+ /**
72
+ * Number of trace tasks waiting in the piscina queue (not yet dispatched
73
+ * to a worker).
74
+ */
75
+ get queueSize() {
76
+ return this._pool.queueSize;
268
77
  }
269
78
  async destroy() {
270
- this._destroying = true;
271
- // Clear all task timers
272
- for (const timer of this._taskTimers.values()) {
273
- clearTimeout(timer);
274
- }
275
- this._taskTimers.clear();
276
- // Reject any tasks still waiting in the queue
277
- for (const task of this._pendingTasks) {
278
- const cb = this._taskCallbacks.get(task.message.taskId);
279
- if (cb) {
280
- this._taskCallbacks.delete(task.message.taskId);
281
- cb.reject(new Error('Worker pool destroyed'));
282
- }
283
- }
284
- this._pendingTasks = [];
285
- // Reject any in-flight tasks still assigned to workers.
286
- // Clear _taskByWorker before terminate() so _onWorkerExit won't double-reject.
287
- for (const [, taskId] of this._taskByWorker) {
288
- const cb = this._taskCallbacks.get(taskId);
289
- if (cb) {
290
- this._taskCallbacks.delete(taskId);
291
- cb.reject(new Error('Worker pool destroyed'));
292
- }
293
- }
294
- this._taskByWorker.clear();
295
- // Terminate workers with a 5-second timeout to prevent hanging
296
- const TERMINATE_TIMEOUT_MS = 5000;
297
- await Promise.all(this._workers.map((w) => Promise.race([
298
- w.terminate(),
299
- new Promise((resolve) => {
300
- const timer = setTimeout(resolve, TERMINATE_TIMEOUT_MS);
301
- timer.unref();
302
- }),
303
- ])));
304
- this._workers = [];
305
- this._idle = [];
79
+ await this._pool.destroy();
306
80
  }
307
81
  }
308
82
  module.exports = FontTracerPool;
@@ -1 +1 @@
1
- {"version":3,"file":"FontTracerPool.js","sourceRoot":"","sources":["../src/FontTracerPool.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"FontTracerPool.js","sourceRoot":"","sources":["../src/FontTracerPool.ts"],"names":[],"mappings":";AAAA,mCAAoC;AACpC,qCAAkC;AAClC,mEAAkE;AAElE;;;;;;;GAOG;AAEH,4EAA4E;AAC5E,yEAAyE;AACzE,2EAA2E;AAC3E,iBAAiB;AACjB,MAAM,uBAAuB,GAAG,OAAO,CAAC;AAqCxC,MAAM,cAAc;IACV,KAAK,CAAU;IACf,cAAc,CAAS;IAE/B,YACE,UAAkB,EAClB,EAAE,aAAa,GAAG,uBAAuB,KAA4B,EAAE;QAEvE,IAAI,CAAC,cAAc,GAAG,aAAa,CAAC;QACpC,IAAI,CAAC,KAAK,GAAG,IAAI,iBAAO,CAAC;YACvB,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,qBAAqB,CAAC;YAC3D,iEAAiE;YACjE,iEAAiE;YACjE,UAAU,EAAE,UAAU;YACtB,UAAU,EAAE,UAAU;YACtB,wBAAwB,EAAE,CAAC;YAC3B,gEAAgE;YAChE,oEAAoE;YACpE,WAAW,EAAE,QAAQ;SACtB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAI;QACR,qEAAqE;QACrE,wEAAwE;QACxE,oEAAoE;QACpE,mEAAmE;QACnE,4DAA4D;QAC5D,EAAE;QACF,qEAAqE;QACrE,6DAA6D;QAC7D,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC;QACjD,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;YACnB,MAAM,OAAO,CAAC,GAAG,CACf,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,GAAG,EAAE,CACtC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,yBAAyB,EAAE,EAAE,EAAE,CAAC,CAChE,CACF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,CACH,QAAgB,EAChB,yBAAqD,EACrD,EAAE,MAAM,KAAmB,EAAE;QAE7B,0EAA0E;QAC1E,yDAAyD;QACzD,MAAM,UAAU,GAA2B,yBAAyB,CAAC,GAAG,CACtE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACV,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE;YAC3D,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,EAAE;SACnC,CAAC,CACH,CAAC;QACF,MAAM,IAAI,GAAc;YACtB,QAAQ;YACR,yBAAyB,EAAE,UAAU;SACtC,CAAC;QAEF,OAAO,IAAA,+CAAuB,EAC5B,IAAI,CAAC,KAAK,EACV,IAAI,EACJ,MAAM,EACN,IAAI,CAAC,cAAc,EACnB,CAAC,EAAE,EAAE,EAAE,CAAC,qCAAqC,EAAE,IAAI,CACpD,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACH,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;IACnC,CAAC;IAED;;;OAGG;IACH,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;IAC7B,CAAC;CACF;AAED,iBAAS,cAAc,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"HeadlessBrowser.d.ts","sourceRoot":"","sources":["../src/HeadlessBrowser.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAc,KAAK,EAAE,MAAM,YAAY,CAAC;AAKpD,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AA2E3C,UAAU,sBAAsB;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,cAAM,eAAe;IACnB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,WAAW,CAAW;IAC9B,OAAO,CAAC,cAAc,CAAC,CAA4B;IACnD,OAAO,CAAC,OAAO,CAAkB;gBAErB,EAAE,OAAO,EAAE,UAAe,EAAE,EAAE,sBAAsB;IAKhE,OAAO,CAAC,sBAAsB;IAgB9B,OAAO,CAAC,yBAAyB;IAwCjC,OAAO,CAAC,qBAAqB;IA2BvB,SAAS,CAAC,SAAS,EAAE,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAwDnD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAkB7B;AAED,SAAS,eAAe,CAAC"}
1
+ {"version":3,"file":"HeadlessBrowser.d.ts","sourceRoot":"","sources":["../src/HeadlessBrowser.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAc,KAAK,EAAE,MAAM,YAAY,CAAC;AAKpD,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AA2E3C,UAAU,sBAAsB;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,cAAM,eAAe;IACnB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,WAAW,CAAW;IAC9B,OAAO,CAAC,cAAc,CAAC,CAA4B;IACnD,OAAO,CAAC,OAAO,CAAkB;gBAErB,EAAE,OAAO,EAAE,UAAe,EAAE,EAAE,sBAAsB;IAKhE,OAAO,CAAC,sBAAsB;IAgB9B,OAAO,CAAC,yBAAyB;IA0CjC,OAAO,CAAC,qBAAqB;IA2BvB,SAAS,CAAC,SAAS,EAAE,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAwEnD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAkB7B;AAED,SAAS,eAAe,CAAC"}
@@ -114,40 +114,41 @@ class HeadlessBrowser {
114
114
  return this._launchPromise;
115
115
  }
116
116
  _attachRequestInterceptor(page, assetGraph, baseUrl) {
117
+ // Puppeteer request methods return promises, but page.on callbacks
118
+ // are synchronous. Attach .catch(noop) to each call so rejections
119
+ // (e.g. request already handled, page closing) don't become
120
+ // unhandled-rejection crashes.
121
+ const noop = () => { };
117
122
  page.on('request', (request) => {
118
123
  const url = request.url();
119
- try {
120
- if (url.startsWith(baseUrl)) {
121
- let agUrl = url.replace(baseUrl, assetGraph.root);
122
- if (/\/$/.test(agUrl)) {
123
- agUrl += 'index.html';
124
- }
125
- const asset = assetGraph.findAssets({
126
- isLoaded: true,
127
- url: agUrl,
128
- })[0];
129
- if (asset) {
130
- request.respond({
131
- status: 200,
132
- contentType: asset.contentType,
133
- body: asset.rawSrc,
134
- });
135
- }
136
- else {
137
- request.respond({ status: 404, body: '' });
138
- }
139
- return;
124
+ if (url.startsWith(baseUrl)) {
125
+ let agUrl = url.replace(baseUrl, assetGraph.root);
126
+ if (/\/$/.test(agUrl)) {
127
+ agUrl += 'index.html';
128
+ }
129
+ const asset = assetGraph.findAssets({
130
+ isLoaded: true,
131
+ url: agUrl,
132
+ })[0];
133
+ if (asset) {
134
+ void request
135
+ .respond({
136
+ status: 200,
137
+ contentType: asset.contentType,
138
+ body: asset.rawSrc,
139
+ })
140
+ .catch(noop);
140
141
  }
141
- if (url.startsWith('file:')) {
142
- request.continue();
143
- return;
142
+ else {
143
+ void request.respond({ status: 404, body: '' }).catch(noop);
144
144
  }
145
- // External request — abort to avoid hanging on DNS/network.
146
- request.abort('failed');
145
+ return;
147
146
  }
148
- catch {
149
- // Request may already be handled or page may be closing — ignore.
147
+ if (url.startsWith('file:')) {
148
+ void request.continue().catch(noop);
149
+ return;
150
150
  }
151
+ void request.abort('failed').catch(noop);
151
152
  });
152
153
  }
153
154
  _attachErrorListeners(page) {
@@ -211,6 +212,21 @@ class HeadlessBrowser {
211
212
  }
212
213
  }
213
214
  finally {
215
+ // Detach request interception before closing so puppeteer's CDP
216
+ // bookkeeping has a chance to flush pending intercepted requests;
217
+ // skipping this can leave puppeteer's internal queue waiting on a
218
+ // continue/abort for a request whose page just went away. Failures
219
+ // here are noisy but non-fatal — log and continue so they can't
220
+ // mask a real page.close() error that the caller does need to see.
221
+ try {
222
+ await page.setRequestInterception(false);
223
+ }
224
+ catch (err) {
225
+ this.console.error(`HeadlessBrowser: setRequestInterception(false) failed during cleanup: ${err.message}`);
226
+ }
227
+ // Intentionally do NOT swallow a page.close() rejection — if the page
228
+ // can't be closed cleanly that's a real problem the orchestrator
229
+ // (subsetFonts.ts) needs to surface.
214
230
  await page.close();
215
231
  }
216
232
  }