@turntrout/subfont 1.6.0 → 1.7.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.
@@ -16,6 +16,7 @@ const unicodeRange = require('./unicodeRange');
16
16
  const getFontInfo = require('./getFontInfo');
17
17
  const collectTextsByPage = require('./collectTextsByPage');
18
18
 
19
+ const escapeJsStringLiteral = require('./escapeJsStringLiteral');
19
20
  const {
20
21
  maybeCssQuote,
21
22
  getFontFaceDeclarationText,
@@ -55,18 +56,6 @@ function getParents(asset, assetQuery) {
55
56
  return parents;
56
57
  }
57
58
 
58
- // Escape a value for safe inclusion in any JS string context (single-quoted,
59
- // double-quoted, or template literal). Uses JSON.stringify for robust escaping
60
- // of backslashes, quotes, newlines, U+2028, U+2029, etc.
61
- // The < escape prevents </script> from closing an inline script tag.
62
- function escapeJsStringLiteral(str) {
63
- return JSON.stringify(str)
64
- .slice(1, -1)
65
- .replace(/'/g, "\\'")
66
- .replace(/`/g, '\\x60')
67
- .replace(/</g, '\\x3c');
68
- }
69
-
70
59
  function countUniqueFontUrls(htmlOrSvgAssetTextsWithProps) {
71
60
  const urls = new Set();
72
61
  for (const item of htmlOrSvgAssetTextsWithProps) {
@@ -225,8 +214,8 @@ const validFontDisplayValues = [
225
214
 
226
215
  const warnAboutMissingGlyphs = require('./warnAboutMissingGlyphs');
227
216
 
228
- // Extract subset CSS asset creation/caching into a standalone function
229
- // to reduce cyclomatic complexity of the main injection loop.
217
+ // Create (or retrieve from disk cache) the subset CSS asset for a set of
218
+ // fontUsages, relocating the font binary to its hashed URL under subsetUrl.
230
219
  async function getOrCreateSubsetCssAsset({
231
220
  assetGraph,
232
221
  subsetCssText,
@@ -322,7 +311,8 @@ async function getOrCreateSubsetCssAsset({
322
311
  return cssAsset;
323
312
  }
324
313
 
325
- // Extract subset font preload insertion to reduce injection loop complexity.
314
+ // Insert <link rel="preload"> hints for every woff2 subset flagged as
315
+ // preload-worthy, so the browser starts fetching them during HTML parse.
326
316
  function addSubsetFontPreloads({
327
317
  cssAsset,
328
318
  fontUsages,
@@ -373,6 +363,104 @@ function addSubsetFontPreloads({
373
363
  return insertionPoint;
374
364
  }
375
365
 
366
+ // Skip Google Fonts populate when no Google Fonts references exist —
367
+ // otherwise assetgraph spends ~30s network-walking for nothing on sites
368
+ // that only self-host. Returns whether the populate ran so callers can
369
+ // annotate their phase timing.
370
+ async function populateGoogleFontsIfPresent(assetGraph) {
371
+ const hasGoogleFonts =
372
+ assetGraph.findRelations({
373
+ to: { url: { $regex: googleFontsCssUrlRegex } },
374
+ }).length > 0;
375
+ if (!hasGoogleFonts) return false;
376
+
377
+ await assetGraph.populate({
378
+ followRelations: {
379
+ $or: [
380
+ { to: { url: { $regex: googleFontsCssUrlRegex } } },
381
+ {
382
+ type: 'CssFontFaceSrc',
383
+ from: { url: { $regex: googleFontsCssUrlRegex } },
384
+ },
385
+ ],
386
+ },
387
+ });
388
+ return true;
389
+ }
390
+
391
+ // Strip every original @font-face rule when --no-fallbacks is set. The
392
+ // severed assets are returned via the `potentiallyOrphanedAssets` set so
393
+ // the final orphan sweep can remove anything left dangling.
394
+ function removeOriginalFontFaceRules(
395
+ htmlOrSvgAssets,
396
+ fontFaceDeclarationsByHtmlOrSvgAsset,
397
+ potentiallyOrphanedAssets
398
+ ) {
399
+ for (const htmlOrSvgAsset of htmlOrSvgAssets) {
400
+ const accumulatedFontFaceDeclarations =
401
+ fontFaceDeclarationsByHtmlOrSvgAsset.get(htmlOrSvgAsset);
402
+ for (const { relations } of accumulatedFontFaceDeclarations) {
403
+ for (const relation of relations) {
404
+ potentiallyOrphanedAssets.add(relation.to);
405
+ if (relation.node.parentNode) {
406
+ relation.node.parentNode.removeChild(relation.node);
407
+ }
408
+ relation.remove();
409
+ }
410
+ }
411
+ htmlOrSvgAsset.markDirty();
412
+ }
413
+ }
414
+
415
+ // Rewrite CSS source-map relations to the caller's chosen hrefType so they
416
+ // align with the rest of the emitted assets. Only invoked when sourceMaps is
417
+ // enabled — subsetFonts normally skips source-map serialization for speed.
418
+ async function rewriteCssSourceMaps(assetGraph, hrefType) {
419
+ await assetGraph.serializeSourceMaps(undefined, {
420
+ type: 'Css',
421
+ outgoingRelations: {
422
+ $where: (relations) =>
423
+ relations.some((relation) => relation.type === 'CssSourceMappingUrl'),
424
+ },
425
+ });
426
+ for (const relation of assetGraph.findRelations({
427
+ type: 'SourceMapSource',
428
+ })) {
429
+ relation.hrefType = hrefType;
430
+ }
431
+ for (const relation of assetGraph.findRelations({
432
+ type: 'CssSourceMappingUrl',
433
+ hrefType: { $in: ['relative', 'inline'] },
434
+ })) {
435
+ relation.hrefType = hrefType;
436
+ }
437
+ }
438
+
439
+ // Remove assets whose last incoming relation was severed during subset
440
+ // injection (original @font-face rules, merged Google Fonts CSS, etc.) so
441
+ // the emitted site doesn't ship with dangling files.
442
+ function removeOrphanedAssets(assetGraph, potentiallyOrphanedAssets) {
443
+ for (const asset of potentiallyOrphanedAssets) {
444
+ if (asset.incomingRelations.length === 0) {
445
+ assetGraph.removeAsset(asset);
446
+ }
447
+ }
448
+ }
449
+
450
+ // Shape the per-page fontUsages into the external fontInfo report: strip
451
+ // internal bookkeeping (subsets buffer, feature-tag scratch) and flatten
452
+ // each page to { assetFileName, fontUsages }.
453
+ function buildFontInfoReport(htmlOrSvgAssetTextsWithProps) {
454
+ return htmlOrSvgAssetTextsWithProps.map(({ fontUsages, htmlOrSvgAsset }) => ({
455
+ assetFileName: htmlOrSvgAsset.nonInlineAncestor.urlOrDescription,
456
+ fontUsages: fontUsages.map((fontUsage) =>
457
+ (({ subsets, hasFontFeatureSettings, fontFeatureTags, ...rest }) => rest)(
458
+ fontUsage
459
+ )
460
+ ),
461
+ }));
462
+ }
463
+
376
464
  async function subsetFonts(
377
465
  assetGraph,
378
466
  {
@@ -415,34 +503,8 @@ async function subsetFonts(
415
503
  }
416
504
  timings.applySourceMaps = applySourceMapsPhase.end();
417
505
 
418
- // Only run Google Fonts populate if there are actually Google Fonts
419
- // references in the graph. This avoids ~30s of wasted work on sites
420
- // that use only self-hosted fonts.
421
- const hasGoogleFonts =
422
- assetGraph.findRelations({
423
- to: { url: { $regex: googleFontsCssUrlRegex } },
424
- }).length > 0;
425
-
426
506
  const googlePopulatePhase = trackPhase('populate (google fonts)');
427
- if (hasGoogleFonts) {
428
- await assetGraph.populate({
429
- followRelations: {
430
- $or: [
431
- {
432
- to: {
433
- url: { $regex: googleFontsCssUrlRegex },
434
- },
435
- },
436
- {
437
- type: 'CssFontFaceSrc',
438
- from: {
439
- url: { $regex: googleFontsCssUrlRegex },
440
- },
441
- },
442
- ],
443
- },
444
- });
445
- }
507
+ const hasGoogleFonts = await populateGoogleFontsIfPresent(assetGraph);
446
508
  timings['populate (google fonts)'] = googlePopulatePhase.end(
447
509
  hasGoogleFonts ? null : 'skipped, no Google Fonts found'
448
510
  );
@@ -478,26 +540,14 @@ async function subsetFonts(
478
540
  timings.collectTextsByPageDetails = subTimings;
479
541
 
480
542
  const omitFallbacksPhase = trackPhase('omitFallbacks processing');
481
-
482
543
  const potentiallyOrphanedAssets = new Set();
483
544
  if (omitFallbacks) {
484
- for (const htmlOrSvgAsset of htmlOrSvgAssets) {
485
- const accumulatedFontFaceDeclarations =
486
- fontFaceDeclarationsByHtmlOrSvgAsset.get(htmlOrSvgAsset);
487
- // Remove the original @font-face rules:
488
- for (const { relations } of accumulatedFontFaceDeclarations) {
489
- for (const relation of relations) {
490
- potentiallyOrphanedAssets.add(relation.to);
491
- if (relation.node.parentNode) {
492
- relation.node.parentNode.removeChild(relation.node);
493
- }
494
- relation.remove();
495
- }
496
- }
497
- htmlOrSvgAsset.markDirty();
498
- }
545
+ removeOriginalFontFaceRules(
546
+ htmlOrSvgAssets,
547
+ fontFaceDeclarationsByHtmlOrSvgAsset,
548
+ potentiallyOrphanedAssets
549
+ );
499
550
  }
500
-
501
551
  timings['omitFallbacks processing'] = omitFallbacksPhase.end();
502
552
 
503
553
  const codepointPhase = trackPhase('codepoint generation');
@@ -652,22 +702,26 @@ async function subsetFonts(
652
702
 
653
703
  // Insert subsets:
654
704
 
655
- // Pre-compute which fontUrls are used (with text) on every page,
656
- // so we can avoid O(pages × fontUsages) checks inside the font loop.
705
+ // Pre-compute which fontUrls are used (with text) on every page.
706
+ // Set intersection: O(pages × fonts_per_page) vs the old every+some approach.
657
707
  const fontUrlsUsedOnEveryPage = new Set();
658
708
  if (htmlOrSvgAssetTextsWithProps.length > 0) {
659
- // Start with all fontUrls from the first page
660
709
  const firstPageFontUrls = new Set();
661
710
  for (const fu of htmlOrSvgAssetTextsWithProps[0].fontUsages) {
662
711
  if (fu.pageText) firstPageFontUrls.add(fu.fontUrl);
663
712
  }
664
713
  for (const fontUrl of firstPageFontUrls) {
665
- if (
666
- htmlOrSvgAssetTextsWithProps.every(({ fontUsages }) =>
667
- fontUsages.some((fu) => fu.pageText && fu.fontUrl === fontUrl)
668
- )
669
- ) {
670
- fontUrlsUsedOnEveryPage.add(fontUrl);
714
+ fontUrlsUsedOnEveryPage.add(fontUrl);
715
+ }
716
+ for (let i = 1; i < htmlOrSvgAssetTextsWithProps.length; i++) {
717
+ const pageFontUrls = new Set();
718
+ for (const fu of htmlOrSvgAssetTextsWithProps[i].fontUsages) {
719
+ if (fu.pageText) pageFontUrls.add(fu.fontUrl);
720
+ }
721
+ for (const fontUrl of fontUrlsUsedOnEveryPage) {
722
+ if (!pageFontUrls.has(fontUrl)) {
723
+ fontUrlsUsedOnEveryPage.delete(fontUrl);
724
+ }
671
725
  }
672
726
  }
673
727
  }
@@ -676,6 +730,12 @@ async function subsetFonts(
676
730
  // addAsset/minify/removeAsset cycles for pages sharing identical CSS.
677
731
  const subsetCssAssetCache = new Map();
678
732
 
733
+ // Cache the heavy CSS-text assembly (including base64-encoded font data)
734
+ // keyed by the shared accumulatedFontFaceDeclarations array. Pages grouped
735
+ // under the same stylesheet config produce byte-identical output, so this
736
+ // collapses the per-page string build from O(pages) to O(unique configs).
737
+ const subsetCssTextCache = new WeakMap();
738
+
679
739
  // Pre-index relations by source asset to avoid O(allRelations) scans
680
740
  // in the per-page injection loop below. Build indices once, then use
681
741
  // O(1) lookups per page instead of repeated assetGraph.findRelations.
@@ -773,11 +833,19 @@ async function subsetFonts(
773
833
  }
774
834
  numFontUsagesWithSubset += subsetFontUsages.length;
775
835
 
776
- let subsetCssText = getFontUsageStylesheet(subsetFontUsages);
777
- const unusedVariantsCss = getUnusedVariantsStylesheet(
778
- fontUsages,
779
- accumulatedFontFaceDeclarations
780
- );
836
+ let cssTextParts = subsetCssTextCache.get(accumulatedFontFaceDeclarations);
837
+ if (!cssTextParts) {
838
+ cssTextParts = {
839
+ subset: getFontUsageStylesheet(subsetFontUsages),
840
+ unused: getUnusedVariantsStylesheet(
841
+ fontUsages,
842
+ accumulatedFontFaceDeclarations
843
+ ),
844
+ };
845
+ subsetCssTextCache.set(accumulatedFontFaceDeclarations, cssTextParts);
846
+ }
847
+ let subsetCssText = cssTextParts.subset;
848
+ const unusedVariantsCss = cssTextParts.unused;
781
849
  if (!inlineCss && !omitFallbacks) {
782
850
  // This can go into the same stylesheet because we won't reload all __subset suffixed families in the JS preload fallback
783
851
  subsetCssText += unusedVariantsCss;
@@ -971,11 +1039,16 @@ async function subsetFonts(
971
1039
 
972
1040
  const googleCleanupPhase = trackPhase('Google Fonts + cleanup');
973
1041
 
974
- // Async load Google Web Fonts CSS
975
- const googleFontStylesheets = assetGraph.findAssets({
976
- type: 'Css',
977
- url: { $regex: googleFontsCssUrlRegex },
978
- });
1042
+ // Async load Google Web Fonts CSS. Skip the regex findAssets scan and
1043
+ // the surrounding loop entirely when no Google Fonts were detected up
1044
+ // front — the final detach loop below still runs because other phases
1045
+ // (lazy fallback CSS) populate relationsToRemove.
1046
+ const googleFontStylesheets = hasGoogleFonts
1047
+ ? assetGraph.findAssets({
1048
+ type: 'Css',
1049
+ url: { $regex: googleFontsCssUrlRegex },
1050
+ })
1051
+ : [];
979
1052
  const selfHostedGoogleCssByUrl = new Map();
980
1053
  for (const googleFontStylesheet of googleFontStylesheets) {
981
1054
  const seenPages = new Set(); // Only do the work once for each font on each page
@@ -1053,7 +1126,7 @@ async function subsetFonts(
1053
1126
 
1054
1127
  // Use subsets in font-family:
1055
1128
 
1056
- const webfontNameMap = {};
1129
+ const webfontNameMap = Object.create(null);
1057
1130
 
1058
1131
  for (const { fontUsages } of htmlOrSvgAssetTextsWithProps) {
1059
1132
  for (const { subsets, fontFamilies, props } of fontUsages) {
@@ -1067,6 +1140,7 @@ async function subsetFonts(
1067
1140
  }
1068
1141
 
1069
1142
  let customPropertyDefinitions; // Avoid computing this unless necessary
1143
+ const cssAssetsDirtiedByCustomProps = new Set();
1070
1144
  // Inject subset font name before original webfont in SVG font-family attributes
1071
1145
  const svgAssets = assetGraph.findAssets({ type: 'Svg' });
1072
1146
  for (const svgAsset of svgAssets) {
@@ -1105,7 +1179,10 @@ async function subsetFonts(
1105
1179
  type: 'Css',
1106
1180
  isLoaded: true,
1107
1181
  });
1108
- let changesMadeToCustomPropertyDefinitions = false;
1182
+ const parseTreeToAsset = new Map();
1183
+ for (const cssAsset of cssAssets) {
1184
+ parseTreeToAsset.set(cssAsset.parseTree, cssAsset);
1185
+ }
1109
1186
  for (const cssAsset of cssAssets) {
1110
1187
  let changesMade = false;
1111
1188
  cssAsset.eachRuleInParseTree((cssRule) => {
@@ -1133,7 +1210,10 @@ async function subsetFonts(
1133
1210
  );
1134
1211
  if (modifiedValue !== relatedCssRule.value) {
1135
1212
  relatedCssRule.value = modifiedValue;
1136
- changesMadeToCustomPropertyDefinitions = true;
1213
+ const ownerAsset = parseTreeToAsset.get(relatedCssRule.root());
1214
+ if (ownerAsset) {
1215
+ cssAssetsDirtiedByCustomProps.add(ownerAsset);
1216
+ }
1137
1217
  }
1138
1218
  }
1139
1219
  }
@@ -1190,60 +1270,23 @@ async function subsetFonts(
1190
1270
  }
1191
1271
  }
1192
1272
 
1193
- // This is a bit crude, could be more efficient if we tracked the containing asset in findCustomPropertyDefinitions
1194
- if (changesMadeToCustomPropertyDefinitions) {
1195
- for (const cssAsset of cssAssets) {
1196
- cssAsset.markDirty();
1197
- }
1273
+ for (const dirtiedAsset of cssAssetsDirtiedByCustomProps) {
1274
+ dirtiedAsset.markDirty();
1198
1275
  }
1199
1276
 
1200
1277
  timings['inject subset font-family'] = injectPhase.end();
1201
1278
 
1202
1279
  const orphanCleanupPhase = trackPhase('source maps + orphan cleanup');
1203
1280
  if (sourceMaps) {
1204
- await assetGraph.serializeSourceMaps(undefined, {
1205
- type: 'Css',
1206
- outgoingRelations: {
1207
- $where: (relations) =>
1208
- relations.some((relation) => relation.type === 'CssSourceMappingUrl'),
1209
- },
1210
- });
1211
- for (const relation of assetGraph.findRelations({
1212
- type: 'SourceMapSource',
1213
- })) {
1214
- relation.hrefType = hrefType;
1215
- }
1216
- for (const relation of assetGraph.findRelations({
1217
- type: 'CssSourceMappingUrl',
1218
- hrefType: { $in: ['relative', 'inline'] },
1219
- })) {
1220
- relation.hrefType = hrefType;
1221
- }
1222
- }
1223
-
1224
- for (const asset of potentiallyOrphanedAssets) {
1225
- if (asset.incomingRelations.length === 0) {
1226
- assetGraph.removeAsset(asset);
1227
- }
1281
+ await rewriteCssSourceMaps(assetGraph, hrefType);
1228
1282
  }
1229
-
1283
+ removeOrphanedAssets(assetGraph, potentiallyOrphanedAssets);
1230
1284
  timings['source maps + orphan cleanup'] = orphanCleanupPhase.end();
1231
1285
 
1232
- // Hand out some useful info about the detected subsets:
1233
1286
  return {
1234
- fontInfo: htmlOrSvgAssetTextsWithProps.map(
1235
- ({ fontUsages, htmlOrSvgAsset }) => ({
1236
- assetFileName: htmlOrSvgAsset.nonInlineAncestor.urlOrDescription,
1237
- fontUsages: fontUsages.map((fontUsage) =>
1238
- (({ subsets, hasFontFeatureSettings, fontFeatureTags, ...rest }) =>
1239
- rest)(fontUsage)
1240
- ),
1241
- })
1242
- ),
1287
+ fontInfo: buildFontInfoReport(htmlOrSvgAssetTextsWithProps),
1243
1288
  timings,
1244
1289
  };
1245
1290
  }
1246
1291
 
1247
1292
  module.exports = subsetFonts;
1248
- // Exported for testing
1249
- module.exports._escapeJsStringLiteral = escapeJsStringLiteral;
package/lib/unquote.js CHANGED
@@ -1,10 +1,15 @@
1
1
  function unescapeCssString(str) {
2
2
  return str.replace(
3
3
  /\\([0-9a-f]{1,6})(\s?)/gi,
4
- ($0, hexChars, followingWhitespace) =>
5
- `${String.fromCodePoint(parseInt(hexChars, 16))}${
6
- hexChars.length === 6 ? followingWhitespace : ''
7
- }`
4
+ ($0, hexChars, followingWhitespace) => {
5
+ try {
6
+ return `${String.fromCodePoint(parseInt(hexChars, 16))}${
7
+ hexChars.length === 6 ? followingWhitespace : ''
8
+ }`;
9
+ } catch {
10
+ return $0;
11
+ }
12
+ }
8
13
  );
9
14
  }
10
15
 
@@ -42,6 +42,41 @@ async function warnAboutMissingGlyphs(
42
42
  accumulatedFontFaceDeclarations,
43
43
  } of htmlOrSvgAssetTextsWithProps) {
44
44
  let linesAndColumns;
45
+ // Dedupe scans for the same missing char across different fontUsages on
46
+ // this page. On KaTeX-heavy pages the same symbol is often missing in
47
+ // several font-families, and each scan is an O(N) walk of the HTML text.
48
+ const charLookupCache = new Map();
49
+ const lookupChar = (char) => {
50
+ let cached = charLookupCache.get(char);
51
+ if (cached) return cached;
52
+ let firstLocation;
53
+ let occurrences = 0;
54
+ if (char.length > 0) {
55
+ const sourceText = htmlOrSvgAsset.text;
56
+ let searchIdx = 0;
57
+ while (true) {
58
+ const charIdx = sourceText.indexOf(char, searchIdx);
59
+ if (charIdx === -1) break;
60
+ occurrences++;
61
+ if (occurrences === 1) {
62
+ if (!linesAndColumns) {
63
+ linesAndColumns = new LinesAndColumns(sourceText);
64
+ }
65
+ const position = linesAndColumns.locationForIndex(charIdx);
66
+ firstLocation = `${htmlOrSvgAsset.urlOrDescription}:${
67
+ position.line + 1
68
+ }:${position.column + 1}`;
69
+ }
70
+ searchIdx = charIdx + char.length;
71
+ }
72
+ }
73
+ if (!firstLocation) {
74
+ firstLocation = `${htmlOrSvgAsset.urlOrDescription} (generated content)`;
75
+ }
76
+ cached = { firstLocation, occurrences };
77
+ charLookupCache.set(char, cached);
78
+ return cached;
79
+ };
45
80
  for (const fontUsage of fontUsages) {
46
81
  if (!fontUsage.subsets) continue;
47
82
  const subsetBuffer = Object.values(fontUsage.subsets)[0];
@@ -63,31 +98,7 @@ async function warnAboutMissingGlyphs(
63
98
  // Report only the first location plus a count of remaining
64
99
  // occurrences. A character like U+200B can appear thousands of
65
100
  // times on a page and per-occurrence lines drown the log.
66
- let firstLocation;
67
- let occurrences = 0;
68
- if (char.length > 0) {
69
- const sourceText = htmlOrSvgAsset.text;
70
- let searchIdx = 0;
71
- while (true) {
72
- const charIdx = sourceText.indexOf(char, searchIdx);
73
- if (charIdx === -1) break;
74
- occurrences++;
75
- if (occurrences === 1) {
76
- if (!linesAndColumns) {
77
- linesAndColumns = new LinesAndColumns(sourceText);
78
- }
79
- const position = linesAndColumns.locationForIndex(charIdx);
80
- firstLocation = `${htmlOrSvgAsset.urlOrDescription}:${
81
- position.line + 1
82
- }:${position.column + 1}`;
83
- }
84
- searchIdx = charIdx + char.length;
85
- }
86
- }
87
-
88
- if (!firstLocation) {
89
- firstLocation = `${htmlOrSvgAsset.urlOrDescription} (generated content)`;
90
- }
101
+ const { firstLocation, occurrences } = lookupChar(char);
91
102
 
92
103
  missingGlyphsErrors.push({
93
104
  codePoint,
package/lib/wasmQueue.js CHANGED
@@ -5,10 +5,14 @@
5
5
  let queue = Promise.resolve();
6
6
 
7
7
  function enqueue(fn) {
8
- return (queue = queue.then(
8
+ // Chain fn after the previous task settles. Both handlers wrap fn() in
9
+ // an arrow to avoid leaking the previous result/error as an argument.
10
+ // The error handler ensures a prior rejection doesn't block the queue.
11
+ queue = queue.then(
9
12
  () => fn(),
10
13
  () => fn()
11
- ));
14
+ );
15
+ return queue;
12
16
  }
13
17
 
14
18
  module.exports = enqueue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turntrout/subfont",
3
- "version": "1.6.0",
3
+ "version": "1.7.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"
@@ -57,7 +57,6 @@
57
57
  "jsdom": "^25.0.0",
58
58
  "lines-and-columns": "^1.1.6",
59
59
  "memoizesync": "^1.1.1",
60
- "p-limit": "^3.0.0",
61
60
  "parse5": "^7.0.0",
62
61
  "postcss": "^8.3.11",
63
62
  "postcss-value-parser": "^4.0.2",