@turntrout/subfont 1.0.4 → 1.1.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.
- package/README.md +20 -14
- package/lib/FontTracerPool.js +11 -0
- package/lib/HeadlessBrowser.js +9 -2
- package/lib/concurrencyLimit.js +10 -0
- package/lib/extractVisibleText.js +2 -0
- package/lib/fontTracerWorker.js +6 -6
- package/lib/parseCommandLineOptions.js +19 -2
- package/lib/subfont.js +13 -10
- package/lib/unicodeRange.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,20 +36,26 @@ subfont path/to/index.html -i --cache
|
|
|
36
36
|
|
|
37
37
|
## Options
|
|
38
38
|
|
|
39
|
-
|
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
| `--
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
39
|
+
| Flag | Default | Description |
|
|
40
|
+
| -----------------: | :-----: | :----------------------------------------------------------- |
|
|
41
|
+
| `-i, --in-place` | off | Modify files in-place |
|
|
42
|
+
| `-o, --output` | | Output directory |
|
|
43
|
+
| `--root` | | Path to web root (deduced from input files if not specified) |
|
|
44
|
+
| `--canonical-root` | | URI root where the site will be deployed |
|
|
45
|
+
| `-r, --recursive` | off | Crawl linked pages |
|
|
46
|
+
| `--dynamic` | off | Trace with headless browser |
|
|
47
|
+
| `--dry-run` | off | Preview without writing |
|
|
48
|
+
| `--fallbacks` | on | Load the full original font for characters not in the subset |
|
|
49
|
+
| `--font-display` | `swap` | `auto`/`block`/`swap`/`fallback`/`optional` |
|
|
50
|
+
| `--text` | | Extra characters for every subset |
|
|
51
|
+
| `--cache [dir]` | off | Cache subset results to disk between runs |
|
|
52
|
+
| `--concurrency N` | | Max worker threads (capped by available memory, ~50 MB each) |
|
|
53
|
+
| `--chrome-flags` | | Custom Chrome flags for `--dynamic` |
|
|
54
|
+
| `--source-maps` | off | Preserve CSS source maps (slower) |
|
|
55
|
+
| `-s, --silent` | off | Suppress all console output |
|
|
56
|
+
| `-d, --debug` | off | Verbose timing and font glyph detection info |
|
|
57
|
+
| `--relative-urls` | off | Emit relative URLs instead of root-relative |
|
|
58
|
+
| `--inline-css` | off | Inline the subset @font-face CSS into HTML |
|
|
53
59
|
|
|
54
60
|
Run `subfont --help` for the full list.
|
|
55
61
|
|
package/lib/FontTracerPool.js
CHANGED
|
@@ -159,6 +159,17 @@ class FontTracerPool {
|
|
|
159
159
|
}
|
|
160
160
|
this._pendingTasks = [];
|
|
161
161
|
|
|
162
|
+
// Reject any in-flight tasks still assigned to workers.
|
|
163
|
+
// Clear _taskByWorker before terminate() so _onWorkerExit won't double-reject.
|
|
164
|
+
for (const [, taskId] of this._taskByWorker) {
|
|
165
|
+
const cb = this._taskCallbacks.get(taskId);
|
|
166
|
+
if (cb) {
|
|
167
|
+
this._taskCallbacks.delete(taskId);
|
|
168
|
+
cb.reject(new Error('Worker pool destroyed'));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
this._taskByWorker.clear();
|
|
172
|
+
|
|
162
173
|
// Terminate workers with a 5-second timeout to prevent hanging
|
|
163
174
|
const TERMINATE_TIMEOUT_MS = 5000;
|
|
164
175
|
await Promise.all(
|
package/lib/HeadlessBrowser.js
CHANGED
|
@@ -180,7 +180,8 @@ class HeadlessBrowser {
|
|
|
180
180
|
urlTools.resolveUrl(
|
|
181
181
|
baseUrl,
|
|
182
182
|
urlTools.buildRelativeUrl(assetGraph.root, htmlAsset.url)
|
|
183
|
-
)
|
|
183
|
+
),
|
|
184
|
+
{ timeout: 30000 }
|
|
184
185
|
);
|
|
185
186
|
|
|
186
187
|
await page.addScriptTag({
|
|
@@ -202,7 +203,13 @@ class HeadlessBrowser {
|
|
|
202
203
|
const launchPromise = this._launchPromise;
|
|
203
204
|
if (launchPromise) {
|
|
204
205
|
this._launchPromise = undefined;
|
|
205
|
-
|
|
206
|
+
let browser;
|
|
207
|
+
try {
|
|
208
|
+
browser = await launchPromise;
|
|
209
|
+
} catch {
|
|
210
|
+
// Launch failed — nothing to close
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
206
213
|
await browser.close();
|
|
207
214
|
}
|
|
208
215
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
|
|
3
|
+
// Approximate memory footprint of each worker thread (jsdom + font-tracer).
|
|
4
|
+
const WORKER_MEMORY_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
5
|
+
|
|
6
|
+
function getMaxConcurrency() {
|
|
7
|
+
return Math.max(1, Math.floor(os.totalmem() / WORKER_MEMORY_BYTES));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = { WORKER_MEMORY_BYTES, getMaxConcurrency };
|
|
@@ -10,6 +10,7 @@ const INVISIBLE_ELEMENTS = new Set([
|
|
|
10
10
|
'iframe',
|
|
11
11
|
'object',
|
|
12
12
|
'embed',
|
|
13
|
+
'datalist',
|
|
13
14
|
]);
|
|
14
15
|
const TEXT_ATTRIBUTES = new Set([
|
|
15
16
|
'alt',
|
|
@@ -72,3 +73,4 @@ function extractVisibleText(html) {
|
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
module.exports = extractVisibleText;
|
|
76
|
+
module.exports.INVISIBLE_ELEMENTS = INVISIBLE_ELEMENTS;
|
package/lib/fontTracerWorker.js
CHANGED
|
@@ -28,11 +28,11 @@ parentPort.on('message', (msg) => {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
if (msg.type === 'trace') {
|
|
31
|
+
const { taskId, htmlText, stylesheetsWithPredicates: serialized } = msg;
|
|
32
|
+
let dom;
|
|
31
33
|
try {
|
|
32
|
-
const { taskId, htmlText, stylesheetsWithPredicates: serialized } = msg;
|
|
33
|
-
|
|
34
34
|
// Re-parse HTML with jsdom to get a DOM document
|
|
35
|
-
|
|
35
|
+
dom = new JSDOM(htmlText);
|
|
36
36
|
const document = dom.window.document;
|
|
37
37
|
|
|
38
38
|
// Re-parse CSS from serialized text — asset objects with PostCSS
|
|
@@ -50,9 +50,6 @@ parentPort.on('message', (msg) => {
|
|
|
50
50
|
getCssRulesByProperty: memoizedGetCssRulesByProperty,
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
// Clean up jsdom to free memory
|
|
54
|
-
dom.window.close();
|
|
55
|
-
|
|
56
53
|
// Strip any non-serializable data from results
|
|
57
54
|
const serializableResults = textByProps.map((entry) => ({
|
|
58
55
|
text: entry.text,
|
|
@@ -71,6 +68,9 @@ parentPort.on('message', (msg) => {
|
|
|
71
68
|
error: err.message,
|
|
72
69
|
stack: err.stack,
|
|
73
70
|
});
|
|
71
|
+
} finally {
|
|
72
|
+
// Clean up jsdom to free memory — must run even if fontTracer throws
|
|
73
|
+
if (dom) dom.window.close();
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
});
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
module.exports = function parseCommandLineOptions(argv) {
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const { getMaxConcurrency } = require('./concurrencyLimit');
|
|
2
4
|
let yargs = require('yargs');
|
|
3
5
|
if (argv) {
|
|
4
6
|
yargs = yargs(argv);
|
|
5
7
|
}
|
|
8
|
+
|
|
9
|
+
const maxConcurrency = getMaxConcurrency();
|
|
10
|
+
|
|
6
11
|
yargs
|
|
7
12
|
.usage(
|
|
8
13
|
'Create optimal font subsets from your actual font usage.\n$0 [options] <htmlFile(s) | url(s)>'
|
|
@@ -106,10 +111,22 @@ module.exports = function parseCommandLineOptions(argv) {
|
|
|
106
111
|
},
|
|
107
112
|
})
|
|
108
113
|
.options('concurrency', {
|
|
109
|
-
describe:
|
|
110
|
-
'Maximum number of worker threads for parallel font tracing. Defaults to the number of CPU cores (max 8)',
|
|
114
|
+
describe: `Maximum number of worker threads for parallel font tracing. Defaults to the number of CPU cores (max 8). Upper bound: ${maxConcurrency} (based on available memory)`,
|
|
111
115
|
type: 'number',
|
|
112
116
|
})
|
|
117
|
+
.check((argv) => {
|
|
118
|
+
if (argv.concurrency !== undefined) {
|
|
119
|
+
if (!Number.isInteger(argv.concurrency) || argv.concurrency < 1) {
|
|
120
|
+
throw new Error('--concurrency must be a positive integer');
|
|
121
|
+
}
|
|
122
|
+
if (argv.concurrency > maxConcurrency) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`--concurrency must not exceed ${maxConcurrency} (each worker uses ~50 MB; system has ${Math.round(os.totalmem() / (1024 * 1024 * 1024))} GB total)`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
})
|
|
113
130
|
.options('source-maps', {
|
|
114
131
|
describe: 'Preserve CSS source maps through subfont processing',
|
|
115
132
|
type: 'boolean',
|
package/lib/subfont.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const fsPromises = require('fs/promises');
|
|
2
|
+
const os = require('os');
|
|
2
3
|
const pathModule = require('path');
|
|
4
|
+
const { getMaxConcurrency } = require('./concurrencyLimit');
|
|
3
5
|
const AssetGraph = require('assetgraph');
|
|
4
6
|
const prettyBytes = require('pretty-bytes');
|
|
5
7
|
const urlTools = require('urltools');
|
|
@@ -43,6 +45,12 @@ module.exports = async function subfont(
|
|
|
43
45
|
) {
|
|
44
46
|
throw new UsageError('--concurrency must be a positive integer');
|
|
45
47
|
}
|
|
48
|
+
const maxConcurrency = getMaxConcurrency();
|
|
49
|
+
if (concurrency !== undefined && concurrency > maxConcurrency) {
|
|
50
|
+
throw new UsageError(
|
|
51
|
+
`--concurrency must not exceed ${maxConcurrency} (each worker uses ~50 MB; system has ${Math.round(os.totalmem() / (1024 * 1024 * 1024))} GB total)`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
46
54
|
|
|
47
55
|
const formats = ['woff2'];
|
|
48
56
|
|
|
@@ -216,14 +224,15 @@ module.exports = async function subfont(
|
|
|
216
224
|
}
|
|
217
225
|
}
|
|
218
226
|
|
|
219
|
-
|
|
220
|
-
for (const asset of assetGraph.findAssets({
|
|
227
|
+
const sizeableAssetQuery = {
|
|
221
228
|
isInline: false,
|
|
222
229
|
isLoaded: true,
|
|
223
230
|
type: {
|
|
224
231
|
$in: ['Html', 'Svg', 'Css', 'JavaScript'],
|
|
225
232
|
},
|
|
226
|
-
}
|
|
233
|
+
};
|
|
234
|
+
let sumSizesBefore = 0;
|
|
235
|
+
for (const asset of assetGraph.findAssets(sizeableAssetQuery)) {
|
|
227
236
|
sumSizesBefore += asset.rawSrc.length;
|
|
228
237
|
}
|
|
229
238
|
|
|
@@ -269,13 +278,7 @@ module.exports = async function subfont(
|
|
|
269
278
|
|
|
270
279
|
phaseStart = Date.now();
|
|
271
280
|
let sumSizesAfter = 0;
|
|
272
|
-
for (const asset of assetGraph.findAssets({
|
|
273
|
-
isInline: false,
|
|
274
|
-
isLoaded: true,
|
|
275
|
-
type: {
|
|
276
|
-
$in: ['Html', 'Svg', 'Css', 'JavaScript'],
|
|
277
|
-
},
|
|
278
|
-
})) {
|
|
281
|
+
for (const asset of assetGraph.findAssets(sizeableAssetQuery)) {
|
|
279
282
|
sumSizesAfter += asset.rawSrc.length;
|
|
280
283
|
}
|
|
281
284
|
|
package/lib/unicodeRange.js
CHANGED
package/package.json
CHANGED