@turntrout/subfont 1.5.0 → 1.6.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/CHANGELOG.md +7 -0
- package/README.md +31 -0
- package/lib/collectTextsByPage.js +109 -337
- package/lib/concurrencyLimit.js +5 -1
- package/lib/fontFeatureHelpers.js +249 -0
- package/lib/fontTracerWorker.js +0 -10
- package/lib/parseCommandLineOptions.js +2 -2
- package/lib/progress.js +101 -0
- package/lib/subfont.js +25 -38
- package/lib/subsetFontWithGlyphs.js +8 -0
- package/lib/subsetFonts.js +60 -91
- package/lib/subsetGeneration.js +14 -1
- package/lib/variationAxes.js +3 -32
- package/lib/wasmQueue.js +4 -1
- package/package.json +2 -1
package/lib/subsetFonts.js
CHANGED
|
@@ -7,6 +7,7 @@ const compileQuery = require('assetgraph/lib/compileQuery');
|
|
|
7
7
|
const findCustomPropertyDefinitions = require('./findCustomPropertyDefinitions');
|
|
8
8
|
const extractReferencedCustomPropertyNames = require('./extractReferencedCustomPropertyNames');
|
|
9
9
|
const injectSubsetDefinitions = require('./injectSubsetDefinitions');
|
|
10
|
+
const { makePhaseTracker } = require('./progress');
|
|
10
11
|
const cssFontParser = require('css-font-parser');
|
|
11
12
|
const cssListHelpers = require('css-list-helpers');
|
|
12
13
|
const unquote = require('./unquote');
|
|
@@ -66,6 +67,16 @@ function escapeJsStringLiteral(str) {
|
|
|
66
67
|
.replace(/</g, '\\x3c');
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
function countUniqueFontUrls(htmlOrSvgAssetTextsWithProps) {
|
|
71
|
+
const urls = new Set();
|
|
72
|
+
for (const item of htmlOrSvgAssetTextsWithProps) {
|
|
73
|
+
for (const fu of item.fontUsages) {
|
|
74
|
+
if (fu.fontUrl) urls.add(fu.fontUrl);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return urls.size;
|
|
78
|
+
}
|
|
79
|
+
|
|
69
80
|
function asyncLoadStyleRelationWithFallback(
|
|
70
81
|
htmlOrSvgAsset,
|
|
71
82
|
originalRelation,
|
|
@@ -396,18 +407,14 @@ async function subsetFonts(
|
|
|
396
407
|
const subsetUrl = urltools.ensureTrailingSlash(assetGraph.root + subsetPath);
|
|
397
408
|
|
|
398
409
|
const timings = {};
|
|
410
|
+
const trackPhase = makePhaseTracker(console, debug);
|
|
399
411
|
|
|
400
|
-
|
|
412
|
+
const applySourceMapsPhase = trackPhase('applySourceMaps');
|
|
401
413
|
if (sourceMaps) {
|
|
402
414
|
await assetGraph.applySourceMaps({ type: 'Css' });
|
|
403
415
|
}
|
|
404
|
-
timings.applySourceMaps =
|
|
405
|
-
if (debug && console)
|
|
406
|
-
console.log(
|
|
407
|
-
`[subfont timing] applySourceMaps: ${timings.applySourceMaps}ms`
|
|
408
|
-
);
|
|
416
|
+
timings.applySourceMaps = applySourceMapsPhase.end();
|
|
409
417
|
|
|
410
|
-
phaseStart = Date.now();
|
|
411
418
|
// Only run Google Fonts populate if there are actually Google Fonts
|
|
412
419
|
// references in the graph. This avoids ~30s of wasted work on sites
|
|
413
420
|
// that use only self-hosted fonts.
|
|
@@ -416,6 +423,7 @@ async function subsetFonts(
|
|
|
416
423
|
to: { url: { $regex: googleFontsCssUrlRegex } },
|
|
417
424
|
}).length > 0;
|
|
418
425
|
|
|
426
|
+
const googlePopulatePhase = trackPhase('populate (google fonts)');
|
|
419
427
|
if (hasGoogleFonts) {
|
|
420
428
|
await assetGraph.populate({
|
|
421
429
|
followRelations: {
|
|
@@ -435,11 +443,9 @@ async function subsetFonts(
|
|
|
435
443
|
},
|
|
436
444
|
});
|
|
437
445
|
}
|
|
438
|
-
timings['populate (google fonts)'] =
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
`[subfont timing] populate (google fonts): ${timings['populate (google fonts)']}ms${hasGoogleFonts ? '' : ' (skipped, no Google Fonts found)'}`
|
|
442
|
-
);
|
|
446
|
+
timings['populate (google fonts)'] = googlePopulatePhase.end(
|
|
447
|
+
hasGoogleFonts ? null : 'skipped, no Google Fonts found'
|
|
448
|
+
);
|
|
443
449
|
|
|
444
450
|
const htmlOrSvgAssets = assetGraph.findAssets({
|
|
445
451
|
$or: [
|
|
@@ -453,11 +459,9 @@ async function subsetFonts(
|
|
|
453
459
|
],
|
|
454
460
|
});
|
|
455
461
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
);
|
|
460
|
-
const collectStart = Date.now();
|
|
462
|
+
const collectPhase = trackPhase(
|
|
463
|
+
`collectTextsByPage (${htmlOrSvgAssets.length} pages)`
|
|
464
|
+
);
|
|
461
465
|
const {
|
|
462
466
|
htmlOrSvgAssetTextsWithProps,
|
|
463
467
|
fontFaceDeclarationsByHtmlOrSvgAsset,
|
|
@@ -470,14 +474,10 @@ async function subsetFonts(
|
|
|
470
474
|
concurrency,
|
|
471
475
|
chromeArgs,
|
|
472
476
|
});
|
|
473
|
-
timings.collectTextsByPage =
|
|
477
|
+
timings.collectTextsByPage = collectPhase.end();
|
|
474
478
|
timings.collectTextsByPageDetails = subTimings;
|
|
475
|
-
if (debug && console)
|
|
476
|
-
console.log(
|
|
477
|
-
`[subfont timing] collectTextsByPage finished in ${timings.collectTextsByPage}ms`
|
|
478
|
-
);
|
|
479
479
|
|
|
480
|
-
|
|
480
|
+
const omitFallbacksPhase = trackPhase('omitFallbacks processing');
|
|
481
481
|
|
|
482
482
|
const potentiallyOrphanedAssets = new Set();
|
|
483
483
|
if (omitFallbacks) {
|
|
@@ -498,12 +498,9 @@ async function subsetFonts(
|
|
|
498
498
|
}
|
|
499
499
|
}
|
|
500
500
|
|
|
501
|
-
timings['omitFallbacks processing'] =
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
`[subfont timing] omitFallbacks processing: ${timings['omitFallbacks processing']}ms`
|
|
505
|
-
);
|
|
506
|
-
phaseStart = Date.now();
|
|
501
|
+
timings['omitFallbacks processing'] = omitFallbacksPhase.end();
|
|
502
|
+
|
|
503
|
+
const codepointPhase = trackPhase('codepoint generation');
|
|
507
504
|
|
|
508
505
|
if (fontDisplay) {
|
|
509
506
|
for (const htmlOrSvgAssetTextWithProps of htmlOrSvgAssetTextsWithProps) {
|
|
@@ -602,12 +599,7 @@ async function subsetFonts(
|
|
|
602
599
|
}
|
|
603
600
|
}
|
|
604
601
|
|
|
605
|
-
timings['codepoint generation'] =
|
|
606
|
-
if (debug && console)
|
|
607
|
-
console.log(
|
|
608
|
-
`[subfont timing] codepoint generation: ${timings['codepoint generation']}ms`
|
|
609
|
-
);
|
|
610
|
-
phaseStart = Date.now();
|
|
602
|
+
timings['codepoint generation'] = codepointPhase.end();
|
|
611
603
|
|
|
612
604
|
if (onlyInfo) {
|
|
613
605
|
return {
|
|
@@ -625,43 +617,38 @@ async function subsetFonts(
|
|
|
625
617
|
};
|
|
626
618
|
}
|
|
627
619
|
|
|
620
|
+
const variationPhase = trackPhase('variation axis usage');
|
|
628
621
|
const { seenAxisValuesByFontUrlAndAxisName } = getVariationAxisUsage(
|
|
629
622
|
htmlOrSvgAssetTextsWithProps,
|
|
630
623
|
parseFontWeightRange,
|
|
631
624
|
parseFontStretchRange
|
|
632
625
|
);
|
|
633
|
-
|
|
634
|
-
timings['variation axis usage'] = Date.now() - phaseStart;
|
|
635
|
-
if (debug && console)
|
|
636
|
-
console.log(
|
|
637
|
-
`[subfont timing] variation axis usage: ${timings['variation axis usage']}ms`
|
|
638
|
-
);
|
|
639
|
-
phaseStart = Date.now();
|
|
626
|
+
timings['variation axis usage'] = variationPhase.end();
|
|
640
627
|
|
|
641
628
|
// Generate subsets:
|
|
629
|
+
if (console) {
|
|
630
|
+
const uniqueFontUrls = countUniqueFontUrls(htmlOrSvgAssetTextsWithProps);
|
|
631
|
+
if (uniqueFontUrls > 0) {
|
|
632
|
+
console.log(
|
|
633
|
+
` Subsetting ${uniqueFontUrls} unique font file${uniqueFontUrls === 1 ? '' : 's'}...`
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const subsetPhase = trackPhase('getSubsetsForFontUsage');
|
|
642
638
|
await getSubsetsForFontUsage(
|
|
643
639
|
assetGraph,
|
|
644
640
|
htmlOrSvgAssetTextsWithProps,
|
|
645
641
|
formats,
|
|
646
642
|
seenAxisValuesByFontUrlAndAxisName,
|
|
647
643
|
cacheDir,
|
|
648
|
-
console
|
|
644
|
+
console,
|
|
645
|
+
debug
|
|
649
646
|
);
|
|
647
|
+
timings.getSubsetsForFontUsage = subsetPhase.end();
|
|
650
648
|
|
|
651
|
-
|
|
652
|
-
if (debug && console)
|
|
653
|
-
console.log(
|
|
654
|
-
`[subfont timing] getSubsetsForFontUsage: ${timings.getSubsetsForFontUsage}ms`
|
|
655
|
-
);
|
|
656
|
-
phaseStart = Date.now();
|
|
657
|
-
|
|
649
|
+
const warnGlyphsPhase = trackPhase('warnAboutMissingGlyphs');
|
|
658
650
|
await warnAboutMissingGlyphs(htmlOrSvgAssetTextsWithProps, assetGraph);
|
|
659
|
-
timings.warnAboutMissingGlyphs =
|
|
660
|
-
if (debug && console)
|
|
661
|
-
console.log(
|
|
662
|
-
`[subfont timing] warnAboutMissingGlyphs: ${timings.warnAboutMissingGlyphs}ms`
|
|
663
|
-
);
|
|
664
|
-
phaseStart = Date.now();
|
|
651
|
+
timings.warnAboutMissingGlyphs = warnGlyphsPhase.end();
|
|
665
652
|
|
|
666
653
|
// Insert subsets:
|
|
667
654
|
|
|
@@ -711,6 +698,9 @@ async function subsetFonts(
|
|
|
711
698
|
index.get(from).push(relation);
|
|
712
699
|
}
|
|
713
700
|
|
|
701
|
+
const insertPhase = trackPhase(
|
|
702
|
+
`insert subsets loop (${htmlOrSvgAssetTextsWithProps.length} pages)`
|
|
703
|
+
);
|
|
714
704
|
let numFontUsagesWithSubset = 0;
|
|
715
705
|
for (const {
|
|
716
706
|
htmlOrSvgAsset,
|
|
@@ -845,17 +835,13 @@ async function subsetFonts(
|
|
|
845
835
|
}
|
|
846
836
|
}
|
|
847
837
|
|
|
848
|
-
timings['insert subsets loop'] =
|
|
849
|
-
if (debug && console)
|
|
850
|
-
console.log(
|
|
851
|
-
`[subfont timing] insert subsets loop: ${timings['insert subsets loop']}ms`
|
|
852
|
-
);
|
|
853
|
-
phaseStart = Date.now();
|
|
838
|
+
timings['insert subsets loop'] = insertPhase.end();
|
|
854
839
|
|
|
855
840
|
if (numFontUsagesWithSubset === 0) {
|
|
856
841
|
return { fontInfo: [], timings };
|
|
857
842
|
}
|
|
858
843
|
|
|
844
|
+
const lazyFallbackPhase = trackPhase('lazy load fallback CSS');
|
|
859
845
|
const relationsToRemove = new Set();
|
|
860
846
|
|
|
861
847
|
// Lazy load the original @font-face declarations of self-hosted fonts (unless omitFallbacks)
|
|
@@ -956,12 +942,9 @@ async function subsetFonts(
|
|
|
956
942
|
relationsToRemove.add(fallbackHtmlStyle);
|
|
957
943
|
}
|
|
958
944
|
|
|
959
|
-
timings['lazy load fallback CSS'] =
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
`[subfont timing] lazy load fallback CSS: ${timings['lazy load fallback CSS']}ms`
|
|
963
|
-
);
|
|
964
|
-
phaseStart = Date.now();
|
|
945
|
+
timings['lazy load fallback CSS'] = lazyFallbackPhase.end();
|
|
946
|
+
|
|
947
|
+
const removeFontFacePhase = trackPhase('remove original @font-face');
|
|
965
948
|
|
|
966
949
|
// Remove the original @font-face blocks, and don't leave behind empty stylesheets:
|
|
967
950
|
const maybeEmptyCssAssets = new Set();
|
|
@@ -984,12 +967,9 @@ async function subsetFonts(
|
|
|
984
967
|
}
|
|
985
968
|
}
|
|
986
969
|
|
|
987
|
-
timings['remove original @font-face'] =
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
`[subfont timing] remove original @font-face: ${timings['remove original @font-face']}ms`
|
|
991
|
-
);
|
|
992
|
-
phaseStart = Date.now();
|
|
970
|
+
timings['remove original @font-face'] = removeFontFacePhase.end();
|
|
971
|
+
|
|
972
|
+
const googleCleanupPhase = trackPhase('Google Fonts + cleanup');
|
|
993
973
|
|
|
994
974
|
// Async load Google Web Fonts CSS
|
|
995
975
|
const googleFontStylesheets = assetGraph.findAssets({
|
|
@@ -1067,12 +1047,9 @@ async function subsetFonts(
|
|
|
1067
1047
|
relation.detach();
|
|
1068
1048
|
}
|
|
1069
1049
|
|
|
1070
|
-
timings['Google Fonts + cleanup'] =
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
`[subfont timing] Google Fonts + cleanup: ${timings['Google Fonts + cleanup']}ms`
|
|
1074
|
-
);
|
|
1075
|
-
phaseStart = Date.now();
|
|
1050
|
+
timings['Google Fonts + cleanup'] = googleCleanupPhase.end();
|
|
1051
|
+
|
|
1052
|
+
const injectPhase = trackPhase('inject subset font-family into CSS/SVG');
|
|
1076
1053
|
|
|
1077
1054
|
// Use subsets in font-family:
|
|
1078
1055
|
|
|
@@ -1220,13 +1197,9 @@ async function subsetFonts(
|
|
|
1220
1197
|
}
|
|
1221
1198
|
}
|
|
1222
1199
|
|
|
1223
|
-
timings['inject subset font-family'] =
|
|
1224
|
-
if (debug && console)
|
|
1225
|
-
console.log(
|
|
1226
|
-
`[subfont timing] inject subset font-family into CSS/SVG: ${timings['inject subset font-family']}ms`
|
|
1227
|
-
);
|
|
1228
|
-
phaseStart = Date.now();
|
|
1200
|
+
timings['inject subset font-family'] = injectPhase.end();
|
|
1229
1201
|
|
|
1202
|
+
const orphanCleanupPhase = trackPhase('source maps + orphan cleanup');
|
|
1230
1203
|
if (sourceMaps) {
|
|
1231
1204
|
await assetGraph.serializeSourceMaps(undefined, {
|
|
1232
1205
|
type: 'Css',
|
|
@@ -1254,11 +1227,7 @@ async function subsetFonts(
|
|
|
1254
1227
|
}
|
|
1255
1228
|
}
|
|
1256
1229
|
|
|
1257
|
-
timings['source maps + orphan cleanup'] =
|
|
1258
|
-
if (debug && console)
|
|
1259
|
-
console.log(
|
|
1260
|
-
`[subfont timing] source maps + orphan cleanup: ${timings['source maps + orphan cleanup']}ms`
|
|
1261
|
-
);
|
|
1230
|
+
timings['source maps + orphan cleanup'] = orphanCleanupPhase.end();
|
|
1262
1231
|
|
|
1263
1232
|
// Hand out some useful info about the detected subsets:
|
|
1264
1233
|
return {
|
package/lib/subsetGeneration.js
CHANGED
|
@@ -118,9 +118,11 @@ async function getSubsetsForFontUsage(
|
|
|
118
118
|
formats,
|
|
119
119
|
seenAxisValuesByFontUrlAndAxisName,
|
|
120
120
|
cacheDir = null,
|
|
121
|
-
console = null
|
|
121
|
+
console = null,
|
|
122
|
+
debug = false
|
|
122
123
|
) {
|
|
123
124
|
const diskCache = cacheDir ? new SubsetDiskCache(cacheDir, console) : null;
|
|
125
|
+
const cacheStats = diskCache ? { hits: 0, misses: 0 } : null;
|
|
124
126
|
|
|
125
127
|
// Collect one canonical fontUsage per font URL
|
|
126
128
|
const canonicalFontUsageByUrl = new Map();
|
|
@@ -240,8 +242,10 @@ async function getSubsetsForFontUsage(
|
|
|
240
242
|
const cachedResult = diskCache ? await diskCache.get(cacheKey) : null;
|
|
241
243
|
|
|
242
244
|
if (cachedResult) {
|
|
245
|
+
if (cacheStats) cacheStats.hits++;
|
|
243
246
|
subsetPromiseMap.set(promiseId, Promise.resolve(cachedResult));
|
|
244
247
|
} else {
|
|
248
|
+
if (cacheStats) cacheStats.misses++;
|
|
245
249
|
const subsetCall = subsetFontWithGlyphs(fontBuffer, text, {
|
|
246
250
|
targetFormat,
|
|
247
251
|
glyphIds: featureGlyphIds,
|
|
@@ -263,6 +267,7 @@ async function getSubsetsForFontUsage(
|
|
|
263
267
|
.catch((err) => {
|
|
264
268
|
err.asset = err.asset || fontAssetsByUrl.get(fontUrl);
|
|
265
269
|
assetGraph.warn(err);
|
|
270
|
+
return null;
|
|
266
271
|
})
|
|
267
272
|
);
|
|
268
273
|
}
|
|
@@ -278,6 +283,14 @@ async function getSubsetsForFontUsage(
|
|
|
278
283
|
)
|
|
279
284
|
);
|
|
280
285
|
|
|
286
|
+
if (cacheStats && debug && console) {
|
|
287
|
+
const total = cacheStats.hits + cacheStats.misses;
|
|
288
|
+
const pct = total > 0 ? Math.round((cacheStats.hits * 100) / total) : 0;
|
|
289
|
+
console.log(
|
|
290
|
+
`[subfont timing] subset disk cache: ${cacheStats.hits} hit${cacheStats.hits === 1 ? '' : 's'}, ${cacheStats.misses} miss${cacheStats.misses === 1 ? '' : 'es'} (${pct}% hit rate)`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
281
294
|
// Assign subset results to canonical font usages
|
|
282
295
|
for (const [, fontUsage] of canonicalFontUsageByUrl) {
|
|
283
296
|
const info = subsetInfoByFontUrl.get(fontUsage.fontUrl);
|
package/lib/variationAxes.js
CHANGED
|
@@ -8,26 +8,11 @@ const standardVariationAxes = new Set(['wght', 'wdth', 'ital', 'slnt', 'opsz']);
|
|
|
8
8
|
// CSS maps oblique to slnt -14.
|
|
9
9
|
const DEFAULT_OBLIQUE_SLNT = -14;
|
|
10
10
|
|
|
11
|
-
// When no opsz values are determined from font-
|
|
12
|
-
//
|
|
13
|
-
//
|
|
11
|
+
// When no opsz values are determined from font-variation-settings, the axis is
|
|
12
|
+
// pinned to its default value rather than preserving the full range, which can
|
|
13
|
+
// significantly bloat variable font subsets.
|
|
14
14
|
const ignoredVariationAxes = new Set();
|
|
15
15
|
|
|
16
|
-
// Parse a CSS font-size value to a numeric px value.
|
|
17
|
-
// Returns the number if the value is in absolute units (px, pt), NaN otherwise.
|
|
18
|
-
// Relative units (em, rem, %, vw, etc.) cannot be resolved without DOM context.
|
|
19
|
-
const PX_PER_PT = 4 / 3;
|
|
20
|
-
function parseFontSizePx(value) {
|
|
21
|
-
if (typeof value === 'number') return value;
|
|
22
|
-
if (typeof value !== 'string') return NaN;
|
|
23
|
-
const match = value.match(/^([\d.]+)(px|pt)?$/i);
|
|
24
|
-
if (!match) return NaN;
|
|
25
|
-
const num = parseFloat(match[1]);
|
|
26
|
-
if (Number.isNaN(num) || num <= 0) return NaN;
|
|
27
|
-
const unit = (match[2] || 'px').toLowerCase();
|
|
28
|
-
return unit === 'pt' ? num * PX_PER_PT : num;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
16
|
function clamp(value, min, max) {
|
|
32
17
|
return Math.min(Math.max(value, min), max);
|
|
33
18
|
}
|
|
@@ -71,7 +56,6 @@ function getVariationAxisUsage(
|
|
|
71
56
|
fontWeights,
|
|
72
57
|
fontStretches,
|
|
73
58
|
fontVariationSettings,
|
|
74
|
-
fontSizes,
|
|
75
59
|
props,
|
|
76
60
|
} of fontUsages) {
|
|
77
61
|
if (seenFontUrls.has(fontUrl)) continue;
|
|
@@ -113,18 +97,6 @@ function getVariationAxisUsage(
|
|
|
113
97
|
);
|
|
114
98
|
}
|
|
115
99
|
|
|
116
|
-
// Map font-size to the opsz axis. With font-optical-sizing: auto
|
|
117
|
-
// (the CSS default), browsers set opsz = font-size in px.
|
|
118
|
-
// Only absolute units (px, pt) can be resolved without DOM context.
|
|
119
|
-
if (fontSizes) {
|
|
120
|
-
for (const fontSize of fontSizes) {
|
|
121
|
-
const px = parseFontSizePx(fontSize);
|
|
122
|
-
if (!Number.isNaN(px)) {
|
|
123
|
-
noteUsedValue(fontUrl, 'opsz', px);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
100
|
for (const fontVariationSettingsValue of fontVariationSettings) {
|
|
129
101
|
for (const [axisName, axisValue] of parseFontVariationSettings(
|
|
130
102
|
fontVariationSettingsValue
|
|
@@ -195,7 +167,6 @@ async function getVariationAxisBounds(
|
|
|
195
167
|
module.exports = {
|
|
196
168
|
standardVariationAxes,
|
|
197
169
|
ignoredVariationAxes,
|
|
198
|
-
parseFontSizePx,
|
|
199
170
|
renderNumberRange,
|
|
200
171
|
getVariationAxisUsage,
|
|
201
172
|
getVariationAxisBounds,
|
package/lib/wasmQueue.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@turntrout/subfont",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Automatically subset web fonts to only the characters used on your pages. Fork of Munter/subfont with modern defaults.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18.0.0"
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"postcss-value-parser": "^4.0.2",
|
|
64
64
|
"pretty-bytes": "^5.1.0",
|
|
65
65
|
"puppeteer-core": "^24.39.1",
|
|
66
|
+
"sanitize-filename": "^1.6.4",
|
|
66
67
|
"specificity": "^0.4.1",
|
|
67
68
|
"urltools": "^0.4.1",
|
|
68
69
|
"yargs": "^17.7.2"
|