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 +8 -0
- package/lib/subsetFonts.js +160 -151
- package/package.json +2 -2
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))
|
package/lib/subsetFonts.js
CHANGED
|
@@ -574,62 +574,104 @@ function cssAssetIsEmpty(cssAsset) {
|
|
|
574
574
|
);
|
|
575
575
|
}
|
|
576
576
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
-
|
|
597
|
-
|
|
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
|
-
|
|
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
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
57
|
+
"font-tracer": "^3.3.0",
|
|
58
58
|
"fontkit": "^1.8.0",
|
|
59
59
|
"fontverter": "^2.0.0",
|
|
60
60
|
"gettemporaryfilepath": "^1.0.1",
|