subfont 6.8.0 → 6.9.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 CHANGED
@@ -1,3 +1,11 @@
1
+ ### v6.9.0 (2022-08-07)
2
+
3
+ - [Update font-tracer to ^3.3.0 Adds support for tracing ::marker, fixes \#166](https://github.com/Munter/subfont/commit/e46c79bac9cb3e62c70deb357823b7963114863b) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
4
+ - [Move some code into a collectTextsByPage function](https://github.com/Munter/subfont/commit/1dff3819fb80793f23a3cc37f9ff58bafffa4efc) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
5
+ - [Move the missing glyph detection to a function](https://github.com/Munter/subfont/commit/38bf36eba4370ecdbc777cb2457696b9bc7c7d1c) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
6
+ - [Fix typo causing regexp to not be matched correctly](https://github.com/Munter/subfont/commit/05137708b5c48af305f3119deb836b1cd9fed683) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
7
+ - [Remove delayed minification of CSS, seems like it's no longer necessary](https://github.com/Munter/subfont/commit/b98976b24c3a859ffbdd2a43f9f05e5048175f5a) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
8
+
1
9
  ### v6.8.0 (2022-07-28)
2
10
 
3
11
  - [Update assetgraph to ^7.8.1](https://github.com/Munter/subfont/commit/888a97912f98bd937a53b7bec0f39d50ddc96023) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
@@ -574,62 +574,104 @@ function cssAssetIsEmpty(cssAsset) {
574
574
  );
575
575
  }
576
576
 
577
- async function subsetFonts(
578
- assetGraph,
579
- {
580
- formats = ['woff2', 'woff'],
581
- subsetPath = 'subfont/',
582
- omitFallbacks = false,
583
- inlineCss,
584
- fontDisplay,
585
- hrefType = 'rootRelative',
586
- onlyInfo,
587
- dynamic,
588
- console = global.console,
589
- text,
590
- } = {}
591
- ) {
592
- if (!validFontDisplayValues.includes(fontDisplay)) {
593
- fontDisplay = undefined;
577
+ function warnAboutMissingGlyphs(htmlOrSvgAssetTextsWithProps, assetGraph) {
578
+ const missingGlyphsErrors = [];
579
+
580
+ for (const {
581
+ htmlOrSvgAsset,
582
+ fontUsages,
583
+ accumulatedFontFaceDeclarations,
584
+ } of htmlOrSvgAssetTextsWithProps) {
585
+ for (const fontUsage of fontUsages) {
586
+ if (fontUsage.subsets) {
587
+ const characterSet = fontkit.create(
588
+ Object.values(fontUsage.subsets)[0]
589
+ ).characterSet;
590
+
591
+ let missedAny = false;
592
+ for (const char of [...fontUsage.pageText]) {
593
+ // Turns out that browsers don't mind that these are missing:
594
+ if (char === '\t' || char === '\n') {
595
+ continue;
596
+ }
597
+
598
+ const codePoint = char.codePointAt(0);
599
+
600
+ const isMissing = !characterSet.includes(codePoint);
601
+
602
+ if (isMissing) {
603
+ let location;
604
+ const charIdx = htmlOrSvgAsset.text.indexOf(char);
605
+
606
+ if (charIdx === -1) {
607
+ location = `${htmlOrSvgAsset.urlOrDescription} (generated content)`;
608
+ } else {
609
+ const position = new LinesAndColumns(
610
+ htmlOrSvgAsset.text
611
+ ).locationForIndex(charIdx);
612
+ location = `${htmlOrSvgAsset.urlOrDescription}:${
613
+ position.line + 1
614
+ }:${position.column + 1}`;
615
+ }
616
+
617
+ missingGlyphsErrors.push({
618
+ codePoint,
619
+ char,
620
+ htmlOrSvgAsset,
621
+ fontUsage,
622
+ location,
623
+ });
624
+ missedAny = true;
625
+ }
626
+ }
627
+ if (missedAny) {
628
+ const fontFaces = accumulatedFontFaceDeclarations.filter((fontFace) =>
629
+ fontUsage.fontFamilies.has(fontFace['font-family'])
630
+ );
631
+ for (const fontFace of fontFaces) {
632
+ const cssFontFaceSrc = fontFace.relations[0];
633
+ const fontFaceDeclaration = cssFontFaceSrc.node;
634
+ if (
635
+ !fontFaceDeclaration.some((node) => node.prop === 'unicode-range')
636
+ ) {
637
+ fontFaceDeclaration.append({
638
+ prop: 'unicode-range',
639
+ value: unicodeRange(fontUsage.codepoints.original),
640
+ });
641
+ cssFontFaceSrc.from.markDirty();
642
+ }
643
+ }
644
+ }
645
+ }
646
+ }
594
647
  }
595
648
 
596
- const htmlOrSvgAssetTextsWithProps = [];
597
- const subsetUrl = urltools.ensureTrailingSlash(assetGraph.root + subsetPath);
649
+ if (missingGlyphsErrors.length) {
650
+ const errorLog = missingGlyphsErrors.map(
651
+ ({ char, fontUsage, location }) =>
652
+ `- \\u{${char.codePointAt(0).toString(16)}} (${char}) in font-family '${
653
+ fontUsage.props['font-family']
654
+ }' (${fontUsage.props['font-weight']}/${
655
+ fontUsage.props['font-style']
656
+ }) at ${location}`
657
+ );
598
658
 
599
- await assetGraph.applySourceMaps({ type: 'Css' });
659
+ const message = `Missing glyph fallback detected.
660
+ When your primary webfont doesn't contain the glyphs you use, browsers that don't support unicode-range will load your fallback fonts, which will be a potential waste of bandwidth.
661
+ These glyphs are used on your site, but they don't exist in the font you applied to them:`;
600
662
 
601
- await assetGraph.populate({
602
- followRelations: {
603
- $or: [
604
- {
605
- to: {
606
- url: googleFontsCssUrlRegex,
607
- },
608
- },
609
- {
610
- type: 'CssFontFaceSrc',
611
- from: {
612
- url: googleFontsCssUrlRegex,
613
- },
614
- },
615
- ],
616
- },
617
- });
663
+ assetGraph.info(new Error(`${message}\n${errorLog.join('\n')}`));
664
+ }
665
+ }
618
666
 
619
- // Collect texts by page
667
+ async function collectTextsByPage(
668
+ assetGraph,
669
+ htmlOrSvgAssets,
670
+ { text, console, dynamic = false } = {}
671
+ ) {
672
+ const htmlOrSvgAssetTextsWithProps = [];
620
673
 
621
674
  const memoizedGetCssRulesByProperty = memoizeSync(getCssRulesByProperty);
622
- const htmlOrSvgAssets = assetGraph.findAssets({
623
- $or: [
624
- {
625
- type: 'Html',
626
- isInline: false,
627
- },
628
- {
629
- type: 'Svg',
630
- },
631
- ],
632
- });
633
675
  const traversalRelationQuery = {
634
676
  $or: [
635
677
  {
@@ -644,12 +686,7 @@ async function subsetFonts(
644
686
  ],
645
687
  };
646
688
 
647
- // Keep track of the injected CSS assets that should eventually be minified
648
- // Minifying them along the way currently doesn't work because some of the
649
- // manipulation is sensitive to the exact text contents. We should fix that.
650
- const subsetFontsToBeMinified = new Set();
651
689
  const fontFaceDeclarationsByHtmlOrSvgAsset = new Map();
652
- const potentiallyOrphanedAssets = new Set();
653
690
 
654
691
  const headlessBrowser = dynamic && new HeadlessBrowser({ console });
655
692
  const globalTextByProps = [];
@@ -774,7 +811,72 @@ async function subsetFonts(
774
811
  }
775
812
  }
776
813
  }
814
+ return { htmlOrSvgAssetTextsWithProps, fontFaceDeclarationsByHtmlOrSvgAsset };
815
+ }
777
816
 
817
+ async function subsetFonts(
818
+ assetGraph,
819
+ {
820
+ formats = ['woff2', 'woff'],
821
+ subsetPath = 'subfont/',
822
+ omitFallbacks = false,
823
+ inlineCss,
824
+ fontDisplay,
825
+ hrefType = 'rootRelative',
826
+ onlyInfo,
827
+ dynamic,
828
+ console = global.console,
829
+ text,
830
+ } = {}
831
+ ) {
832
+ if (!validFontDisplayValues.includes(fontDisplay)) {
833
+ fontDisplay = undefined;
834
+ }
835
+
836
+ const subsetUrl = urltools.ensureTrailingSlash(assetGraph.root + subsetPath);
837
+
838
+ await assetGraph.applySourceMaps({ type: 'Css' });
839
+
840
+ await assetGraph.populate({
841
+ followRelations: {
842
+ $or: [
843
+ {
844
+ to: {
845
+ url: { $regex: googleFontsCssUrlRegex },
846
+ },
847
+ },
848
+ {
849
+ type: 'CssFontFaceSrc',
850
+ from: {
851
+ url: { $regex: googleFontsCssUrlRegex },
852
+ },
853
+ },
854
+ ],
855
+ },
856
+ });
857
+
858
+ const htmlOrSvgAssets = assetGraph.findAssets({
859
+ $or: [
860
+ {
861
+ type: 'Html',
862
+ isInline: false,
863
+ },
864
+ {
865
+ type: 'Svg',
866
+ },
867
+ ],
868
+ });
869
+
870
+ // Collect texts by page
871
+
872
+ const { htmlOrSvgAssetTextsWithProps, fontFaceDeclarationsByHtmlOrSvgAsset } =
873
+ await collectTextsByPage(assetGraph, htmlOrSvgAssets, {
874
+ text,
875
+ console,
876
+ dynamic,
877
+ });
878
+
879
+ const potentiallyOrphanedAssets = new Set();
778
880
  if (omitFallbacks) {
779
881
  for (const htmlOrSvgAsset of htmlOrSvgAssets) {
780
882
  const accumulatedFontFaceDeclarations =
@@ -848,94 +950,7 @@ async function subsetFonts(
848
950
  formats
849
951
  );
850
952
 
851
- // Warn about missing glyphs
852
- const missingGlyphsErrors = [];
853
-
854
- for (const {
855
- htmlOrSvgAsset,
856
- fontUsages,
857
- accumulatedFontFaceDeclarations,
858
- } of htmlOrSvgAssetTextsWithProps) {
859
- for (const fontUsage of fontUsages) {
860
- if (fontUsage.subsets) {
861
- const characterSet = fontkit.create(
862
- Object.values(fontUsage.subsets)[0]
863
- ).characterSet;
864
-
865
- let missedAny = false;
866
- for (const char of [...fontUsage.pageText]) {
867
- // Turns out that browsers don't mind that these are missing:
868
- if (char === '\t' || char === '\n') {
869
- continue;
870
- }
871
-
872
- const codePoint = char.codePointAt(0);
873
-
874
- const isMissing = !characterSet.includes(codePoint);
875
-
876
- if (isMissing) {
877
- let location;
878
- const charIdx = htmlOrSvgAsset.text.indexOf(char);
879
-
880
- if (charIdx === -1) {
881
- location = `${htmlOrSvgAsset.urlOrDescription} (generated content)`;
882
- } else {
883
- const position = new LinesAndColumns(
884
- htmlOrSvgAsset.text
885
- ).locationForIndex(charIdx);
886
- location = `${htmlOrSvgAsset.urlOrDescription}:${
887
- position.line + 1
888
- }:${position.column + 1}`;
889
- }
890
-
891
- missingGlyphsErrors.push({
892
- codePoint,
893
- char,
894
- htmlOrSvgAsset,
895
- fontUsage,
896
- location,
897
- });
898
- missedAny = true;
899
- }
900
- }
901
- if (missedAny) {
902
- const fontFaces = accumulatedFontFaceDeclarations.filter((fontFace) =>
903
- fontUsage.fontFamilies.has(fontFace['font-family'])
904
- );
905
- for (const fontFace of fontFaces) {
906
- const cssFontFaceSrc = fontFace.relations[0];
907
- const fontFaceDeclaration = cssFontFaceSrc.node;
908
- if (
909
- !fontFaceDeclaration.some((node) => node.prop === 'unicode-range')
910
- ) {
911
- fontFaceDeclaration.append({
912
- prop: 'unicode-range',
913
- value: unicodeRange(fontUsage.codepoints.original),
914
- });
915
- cssFontFaceSrc.from.markDirty();
916
- }
917
- }
918
- }
919
- }
920
- }
921
- }
922
-
923
- if (missingGlyphsErrors.length) {
924
- const errorLog = missingGlyphsErrors.map(
925
- ({ char, fontUsage, location }) =>
926
- `- \\u{${char.codePointAt(0).toString(16)}} (${char}) in font-family '${
927
- fontUsage.props['font-family']
928
- }' (${fontUsage.props['font-weight']}/${
929
- fontUsage.props['font-style']
930
- }) at ${location}`
931
- );
932
-
933
- const message = `Missing glyph fallback detected.
934
- When your primary webfont doesn't contain the glyphs you use, browsers that don't support unicode-range will load your fallback fonts, which will be a potential waste of bandwidth.
935
- These glyphs are used on your site, but they don't exist in the font you applied to them:`;
936
-
937
- assetGraph.info(new Error(`${message}\n${errorLog.join('\n')}`));
938
- }
953
+ warnAboutMissingGlyphs(htmlOrSvgAssetTextsWithProps, assetGraph);
939
954
 
940
955
  // Insert subsets:
941
956
 
@@ -1021,7 +1036,7 @@ These glyphs are used on your site, but they don't exist in the font you applied
1021
1036
  text: subsetCssText,
1022
1037
  });
1023
1038
 
1024
- subsetFontsToBeMinified.add(cssAsset);
1039
+ await cssAsset.minify();
1025
1040
 
1026
1041
  for (const [i, fontRelation] of cssAsset.outgoingRelations.entries()) {
1027
1042
  const fontAsset = fontRelation.to;
@@ -1091,7 +1106,6 @@ These glyphs are used on your site, but they don't exist in the font you applied
1091
1106
  const existingCssAsset = assetGraph.findAssets({ url: cssAssetUrl })[0];
1092
1107
  if (existingCssAsset) {
1093
1108
  assetGraph.removeAsset(cssAsset);
1094
- subsetFontsToBeMinified.delete(cssAsset);
1095
1109
  cssAsset = existingCssAsset;
1096
1110
  } else {
1097
1111
  cssAsset.url = cssAssetUrl;
@@ -1226,7 +1240,7 @@ These glyphs are used on your site, but they don't exist in the font you applied
1226
1240
  assetGraph.removeAsset(cssAsset);
1227
1241
  cssAsset = existingCssAsset;
1228
1242
  } else {
1229
- subsetFontsToBeMinified.add(cssAsset);
1243
+ await cssAsset.minify();
1230
1244
  cssAsset.url = cssAssetUrl;
1231
1245
  }
1232
1246
 
@@ -1311,7 +1325,7 @@ These glyphs are used on your site, but they don't exist in the font you applied
1311
1325
  formats,
1312
1326
  hrefType
1313
1327
  );
1314
- subsetFontsToBeMinified.add(selfHostedGoogleFontsCssAsset);
1328
+ await selfHostedGoogleFontsCssAsset.minify();
1315
1329
  selfHostedGoogleCssByUrl.set(
1316
1330
  googleFontStylesheetRelation.to.url,
1317
1331
  selfHostedGoogleFontsCssAsset
@@ -1492,11 +1506,6 @@ These glyphs are used on your site, but they don't exist in the font you applied
1492
1506
  }
1493
1507
  }
1494
1508
 
1495
- // This is a bit awkward now, but if it's done sooner, it breaks the CSS source regexping:
1496
- for (const cssAsset of subsetFontsToBeMinified) {
1497
- await cssAsset.minify();
1498
- }
1499
-
1500
1509
  await assetGraph.serializeSourceMaps(undefined, {
1501
1510
  type: 'Css',
1502
1511
  outgoingRelations: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "subfont",
3
- "version": "6.8.0",
3
+ "version": "6.9.0",
4
4
  "description": "Speeds up your pages initial paint by automatically subsetting local or Google fonts and loading them optimally",
5
5
  "engines": {
6
6
  "node": ">=10.0.0"
@@ -54,7 +54,7 @@
54
54
  "css-list-helpers": "^2.0.0",
55
55
  "font-family-papandreou": "^0.2.0-patch2",
56
56
  "font-snapper": "^1.2.0",
57
- "font-tracer": "^3.2.0",
57
+ "font-tracer": "^3.3.0",
58
58
  "fontkit": "^1.8.0",
59
59
  "fontverter": "^2.0.0",
60
60
  "gettemporaryfilepath": "^1.0.1",