@turntrout/subfont 1.7.0 → 1.8.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/CLAUDE.md +39 -13
- package/README.md +24 -14
- package/lib/FontTracerPool.d.ts +38 -0
- package/lib/FontTracerPool.d.ts.map +1 -0
- package/lib/FontTracerPool.js +230 -217
- package/lib/FontTracerPool.js.map +1 -0
- package/lib/HeadlessBrowser.d.ts +18 -0
- package/lib/HeadlessBrowser.d.ts.map +1 -0
- package/lib/HeadlessBrowser.js +216 -210
- package/lib/HeadlessBrowser.js.map +1 -0
- package/lib/cli.d.ts +3 -0
- package/lib/cli.d.ts.map +1 -0
- package/lib/cli.js +15 -12
- package/lib/cli.js.map +1 -0
- package/lib/codepointMaps.d.ts +4 -0
- package/lib/codepointMaps.d.ts.map +1 -0
- package/lib/codepointMaps.js +99 -0
- package/lib/codepointMaps.js.map +1 -0
- package/lib/collectFeatureGlyphIds.d.ts +3 -0
- package/lib/collectFeatureGlyphIds.d.ts.map +1 -0
- package/lib/collectFeatureGlyphIds.js +124 -138
- package/lib/collectFeatureGlyphIds.js.map +1 -0
- package/lib/collectTextsByPage.d.ts +41 -0
- package/lib/collectTextsByPage.d.ts.map +1 -0
- package/lib/collectTextsByPage.js +726 -965
- package/lib/collectTextsByPage.js.map +1 -0
- package/lib/concurrencyLimit.d.ts +3 -0
- package/lib/concurrencyLimit.d.ts.map +1 -0
- package/lib/concurrencyLimit.js +12 -11
- package/lib/concurrencyLimit.js.map +1 -0
- package/lib/escapeJsStringLiteral.d.ts +3 -0
- package/lib/escapeJsStringLiteral.d.ts.map +1 -0
- package/lib/escapeJsStringLiteral.js +7 -6
- package/lib/escapeJsStringLiteral.js.map +1 -0
- package/lib/extractReferencedCustomPropertyNames.d.ts +3 -0
- package/lib/extractReferencedCustomPropertyNames.d.ts.map +1 -0
- package/lib/extractReferencedCustomPropertyNames.js +15 -16
- package/lib/extractReferencedCustomPropertyNames.js.map +1 -0
- package/lib/extractVisibleText.d.ts +7 -0
- package/lib/extractVisibleText.d.ts.map +1 -0
- package/lib/extractVisibleText.js +110 -119
- package/lib/extractVisibleText.js.map +1 -0
- package/lib/findCustomPropertyDefinitions.d.ts +8 -0
- package/lib/findCustomPropertyDefinitions.d.ts.map +1 -0
- package/lib/findCustomPropertyDefinitions.js +41 -48
- package/lib/findCustomPropertyDefinitions.js.map +1 -0
- package/lib/fontConverter.d.ts +2 -0
- package/lib/fontConverter.d.ts.map +1 -0
- package/lib/fontConverter.js +40 -21
- package/lib/fontConverter.js.map +1 -0
- package/lib/fontConverterWorker.d.ts +2 -0
- package/lib/fontConverterWorker.d.ts.map +1 -0
- package/lib/fontConverterWorker.js +52 -15
- package/lib/fontConverterWorker.js.map +1 -0
- package/lib/fontFaceHelpers.d.ts +64 -0
- package/lib/fontFaceHelpers.d.ts.map +1 -0
- package/lib/fontFaceHelpers.js +237 -249
- package/lib/fontFaceHelpers.js.map +1 -0
- package/lib/fontFeatureHelpers.d.ts +30 -0
- package/lib/fontFeatureHelpers.d.ts.map +1 -0
- package/lib/fontFeatureHelpers.js +277 -212
- package/lib/fontFeatureHelpers.js.map +1 -0
- package/lib/fontTracerWorker.d.ts +11 -0
- package/lib/fontTracerWorker.d.ts.map +1 -0
- package/lib/fontTracerWorker.js +94 -60
- package/lib/fontTracerWorker.js.map +1 -0
- package/lib/gatherStylesheetsWithPredicates.d.ts +26 -0
- package/lib/gatherStylesheetsWithPredicates.d.ts.map +1 -0
- package/lib/gatherStylesheetsWithPredicates.js +75 -84
- package/lib/gatherStylesheetsWithPredicates.js.map +1 -0
- package/lib/getCssRulesByProperty.d.ts +29 -0
- package/lib/getCssRulesByProperty.d.ts.map +1 -0
- package/lib/getCssRulesByProperty.js +316 -316
- package/lib/getCssRulesByProperty.js.map +1 -0
- package/lib/getFontInfo.d.ts +11 -0
- package/lib/getFontInfo.d.ts.map +1 -0
- package/lib/getFontInfo.js +31 -33
- package/lib/getFontInfo.js.map +1 -0
- package/lib/initialValueByProp.d.ts +3 -0
- package/lib/initialValueByProp.d.ts.map +1 -0
- package/lib/initialValueByProp.js +20 -17
- package/lib/initialValueByProp.js.map +1 -0
- package/lib/injectSubsetDefinitions.d.ts +3 -0
- package/lib/injectSubsetDefinitions.d.ts.map +1 -0
- package/lib/injectSubsetDefinitions.js +55 -59
- package/lib/injectSubsetDefinitions.js.map +1 -0
- package/lib/normalizeFontPropertyValue.d.ts +3 -0
- package/lib/normalizeFontPropertyValue.d.ts.map +1 -0
- package/lib/normalizeFontPropertyValue.js +59 -54
- package/lib/normalizeFontPropertyValue.js.map +1 -0
- package/lib/parseCommandLineOptions.d.ts +9 -0
- package/lib/parseCommandLineOptions.d.ts.map +1 -0
- package/lib/parseCommandLineOptions.js +145 -149
- package/lib/parseCommandLineOptions.js.map +1 -0
- package/lib/parseFontVariationSettings.d.ts +3 -0
- package/lib/parseFontVariationSettings.d.ts.map +1 -0
- package/lib/parseFontVariationSettings.js +38 -36
- package/lib/parseFontVariationSettings.js.map +1 -0
- package/lib/progress.d.ts +27 -0
- package/lib/progress.d.ts.map +1 -0
- package/lib/progress.js +51 -54
- package/lib/progress.js.map +1 -0
- package/lib/sfntCache.d.ts +4 -0
- package/lib/sfntCache.d.ts.map +1 -0
- package/lib/sfntCache.js +67 -25
- package/lib/sfntCache.js.map +1 -0
- package/lib/stripLocalTokens.d.ts +3 -0
- package/lib/stripLocalTokens.d.ts.map +1 -0
- package/lib/stripLocalTokens.js +23 -21
- package/lib/stripLocalTokens.js.map +1 -0
- package/lib/subfont.d.ts +54 -0
- package/lib/subfont.d.ts.map +1 -0
- package/lib/subfont.js +531 -629
- package/lib/subfont.js.map +1 -0
- package/lib/subsetFontWithGlyphs.d.ts +21 -0
- package/lib/subsetFontWithGlyphs.d.ts.map +1 -0
- package/lib/subsetFontWithGlyphs.js +285 -259
- package/lib/subsetFontWithGlyphs.js.map +1 -0
- package/lib/subsetFonts.d.ts +55 -0
- package/lib/subsetFonts.d.ts.map +1 -0
- package/lib/subsetFonts.js +899 -1200
- package/lib/subsetFonts.js.map +1 -0
- package/lib/subsetGeneration.d.ts +36 -0
- package/lib/subsetGeneration.d.ts.map +1 -0
- package/lib/subsetGeneration.js +328 -325
- package/lib/subsetGeneration.js.map +1 -0
- package/lib/types/shared.d.ts +11 -0
- package/lib/types/shared.d.ts.map +1 -0
- package/lib/types/shared.js +3 -0
- package/lib/types/shared.js.map +1 -0
- package/lib/unicodeRange.d.ts +3 -0
- package/lib/unicodeRange.d.ts.map +1 -0
- package/lib/unicodeRange.js +17 -30
- package/lib/unicodeRange.js.map +1 -0
- package/lib/unquote.d.ts +3 -0
- package/lib/unquote.d.ts.map +1 -0
- package/lib/unquote.js +18 -25
- package/lib/unquote.js.map +1 -0
- package/lib/variationAxes.d.ts +33 -0
- package/lib/variationAxes.d.ts.map +1 -0
- package/lib/variationAxes.js +127 -157
- package/lib/variationAxes.js.map +1 -0
- package/lib/warnAboutMissingGlyphs.d.ts +43 -0
- package/lib/warnAboutMissingGlyphs.d.ts.map +1 -0
- package/lib/warnAboutMissingGlyphs.js +139 -147
- package/lib/warnAboutMissingGlyphs.js.map +1 -0
- package/lib/wasmQueue.d.ts +3 -0
- package/lib/wasmQueue.d.ts.map +1 -0
- package/lib/wasmQueue.js +13 -10
- package/lib/wasmQueue.js.map +1 -0
- package/package.json +12 -2
package/lib/subsetFonts.js
CHANGED
|
@@ -1,95 +1,101 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
const urltools = __importStar(require("urltools"));
|
|
36
|
+
const fontverter = __importStar(require("fontverter"));
|
|
37
|
+
const compileQuery = require("assetgraph/lib/compileQuery");
|
|
38
|
+
const findCustomPropertyDefinitions = require("./findCustomPropertyDefinitions");
|
|
39
|
+
const extractReferencedCustomPropertyNames = require("./extractReferencedCustomPropertyNames");
|
|
40
|
+
const injectSubsetDefinitions = require("./injectSubsetDefinitions");
|
|
41
|
+
const progress_1 = require("./progress");
|
|
42
|
+
const cssFontParser = __importStar(require("css-font-parser"));
|
|
43
|
+
const cssListHelpers = __importStar(require("css-list-helpers"));
|
|
44
|
+
const unquote = require("./unquote");
|
|
45
|
+
const normalizeFontPropertyValue = require("./normalizeFontPropertyValue");
|
|
46
|
+
const unicodeRange = require("./unicodeRange");
|
|
47
|
+
const getFontInfo = require("./getFontInfo");
|
|
48
|
+
const collectTextsByPage = require("./collectTextsByPage");
|
|
49
|
+
const escapeJsStringLiteral = require("./escapeJsStringLiteral");
|
|
50
|
+
const fontFaceHelpers_1 = require("./fontFaceHelpers");
|
|
51
|
+
const variationAxes_1 = require("./variationAxes");
|
|
52
|
+
const subsetGeneration_1 = require("./subsetGeneration");
|
|
53
|
+
const subsetFontWithGlyphs = require("./subsetFontWithGlyphs");
|
|
54
|
+
const warnAboutMissingGlyphs = require("./warnAboutMissingGlyphs");
|
|
35
55
|
const googleFontsCssUrlRegex = /^(?:https?:)?\/\/fonts\.googleapis\.com\/css/;
|
|
36
|
-
|
|
37
56
|
function getParents(asset, assetQuery) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return parents;
|
|
57
|
+
const assetMatcher = compileQuery(assetQuery);
|
|
58
|
+
const seenAssets = new Set();
|
|
59
|
+
const parents = [];
|
|
60
|
+
(function visit(asset) {
|
|
61
|
+
if (seenAssets.has(asset)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
seenAssets.add(asset);
|
|
65
|
+
for (const incomingRelation of asset.incomingRelations) {
|
|
66
|
+
if (assetMatcher(incomingRelation.from)) {
|
|
67
|
+
parents.push(incomingRelation.from);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
visit(incomingRelation.from);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
})(asset);
|
|
74
|
+
return parents;
|
|
57
75
|
}
|
|
58
|
-
|
|
59
76
|
function countUniqueFontUrls(htmlOrSvgAssetTextsWithProps) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
77
|
+
const urls = new Set();
|
|
78
|
+
for (const item of htmlOrSvgAssetTextsWithProps) {
|
|
79
|
+
for (const fu of item.fontUsages) {
|
|
80
|
+
if (fu.fontUrl)
|
|
81
|
+
urls.add(fu.fontUrl);
|
|
82
|
+
}
|
|
64
83
|
}
|
|
65
|
-
|
|
66
|
-
return urls.size;
|
|
84
|
+
return urls.size;
|
|
67
85
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
)
|
|
82
|
-
);
|
|
83
|
-
const mediaAssignment = originalRelation.media
|
|
84
|
-
? `el.media = '${escapeJsStringLiteral(originalRelation.media)}';`
|
|
85
|
-
: '';
|
|
86
|
-
const asyncCssLoadingRelation = htmlOrSvgAsset.addRelation(
|
|
87
|
-
{
|
|
88
|
-
type: 'HtmlScript',
|
|
89
|
-
hrefType: 'inline',
|
|
90
|
-
to: {
|
|
91
|
-
type: 'JavaScript',
|
|
92
|
-
text: `
|
|
86
|
+
function asyncLoadStyleRelationWithFallback(htmlOrSvgAsset, originalRelation, hrefType) {
|
|
87
|
+
// Async load google font stylesheet
|
|
88
|
+
// Insert async CSS loading <script>
|
|
89
|
+
const href = escapeJsStringLiteral(htmlOrSvgAsset.assetGraph.buildHref(originalRelation.to.url, htmlOrSvgAsset.url, { hrefType }));
|
|
90
|
+
const mediaAssignment = originalRelation.media
|
|
91
|
+
? `el.media = '${escapeJsStringLiteral(originalRelation.media)}';`
|
|
92
|
+
: '';
|
|
93
|
+
const asyncCssLoadingRelation = htmlOrSvgAsset.addRelation({
|
|
94
|
+
type: 'HtmlScript',
|
|
95
|
+
hrefType: 'inline',
|
|
96
|
+
to: {
|
|
97
|
+
type: 'JavaScript',
|
|
98
|
+
text: `
|
|
93
99
|
(function () {
|
|
94
100
|
var el = document.createElement('link');
|
|
95
101
|
el.href = '${href}'.toString('url');
|
|
@@ -98,1195 +104,888 @@ function asyncLoadStyleRelationWithFallback(
|
|
|
98
104
|
document.body.appendChild(el);
|
|
99
105
|
}())
|
|
100
106
|
`,
|
|
101
|
-
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
type: 'HtmlStyle',
|
|
121
|
-
media: originalRelation.media,
|
|
122
|
-
to: originalRelation.to,
|
|
123
|
-
hrefType,
|
|
124
|
-
},
|
|
125
|
-
'last'
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
noScriptFallbackRelation.inline();
|
|
129
|
-
asyncCssLoadingRelation.to.minify();
|
|
130
|
-
htmlOrSvgAsset.markDirty();
|
|
107
|
+
},
|
|
108
|
+
}, 'lastInBody');
|
|
109
|
+
// Insert <noscript> fallback sync CSS loading
|
|
110
|
+
const noScriptFallbackRelation = htmlOrSvgAsset.addRelation({
|
|
111
|
+
type: 'HtmlNoscript',
|
|
112
|
+
to: {
|
|
113
|
+
type: 'Html',
|
|
114
|
+
text: '',
|
|
115
|
+
},
|
|
116
|
+
}, 'lastInBody');
|
|
117
|
+
noScriptFallbackRelation.to.addRelation({
|
|
118
|
+
type: 'HtmlStyle',
|
|
119
|
+
media: originalRelation.media,
|
|
120
|
+
to: originalRelation.to,
|
|
121
|
+
hrefType,
|
|
122
|
+
}, 'last');
|
|
123
|
+
noScriptFallbackRelation.inline();
|
|
124
|
+
asyncCssLoadingRelation.to.minify();
|
|
125
|
+
htmlOrSvgAsset.markDirty();
|
|
131
126
|
}
|
|
132
|
-
|
|
133
127
|
const extensionByFormat = {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
128
|
+
truetype: '.ttf',
|
|
129
|
+
woff: '.woff',
|
|
130
|
+
woff2: '.woff2',
|
|
137
131
|
};
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
`${cssFontFaceSrc.to.baseName}-${hashHexPrefix(rawSrc)}${
|
|
172
|
-
extensionByFormat[format]
|
|
173
|
-
}`
|
|
174
|
-
);
|
|
175
|
-
const fontAsset =
|
|
176
|
-
assetGraph.findAssets({ url })[0] ||
|
|
177
|
-
(await assetGraph.addAsset({
|
|
178
|
-
url,
|
|
179
|
-
rawSrc,
|
|
180
|
-
}));
|
|
181
|
-
srcFragments.push(
|
|
182
|
-
`url(${assetGraph.buildHref(fontAsset.url, subsetUrl, {
|
|
183
|
-
hrefType,
|
|
184
|
-
})}) format('${format}')`
|
|
185
|
-
);
|
|
132
|
+
async function createSelfHostedGoogleFontsCssAsset(assetGraph, googleFontsCssAsset, formats, hrefType, subsetUrl) {
|
|
133
|
+
const lines = [];
|
|
134
|
+
for (const cssFontFaceSrc of assetGraph.findRelations({
|
|
135
|
+
from: googleFontsCssAsset,
|
|
136
|
+
type: 'CssFontFaceSrc',
|
|
137
|
+
})) {
|
|
138
|
+
lines.push(`@font-face {`);
|
|
139
|
+
const fontFaceDeclaration = cssFontFaceSrc.node;
|
|
140
|
+
fontFaceDeclaration.walkDecls((declaration) => {
|
|
141
|
+
const propName = declaration.prop.toLowerCase();
|
|
142
|
+
if (propName !== 'src') {
|
|
143
|
+
lines.push(` ${propName}: ${declaration.value};`);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
// Convert to all formats in parallel
|
|
147
|
+
const convertedFonts = await Promise.all(formats.map((format) => fontverter.convert(cssFontFaceSrc.to.rawSrc, format)));
|
|
148
|
+
const srcFragments = [];
|
|
149
|
+
for (let fi = 0; fi < formats.length; fi++) {
|
|
150
|
+
const format = formats[fi];
|
|
151
|
+
const rawSrc = convertedFonts[fi];
|
|
152
|
+
const url = assetGraph.resolveUrl(subsetUrl, `${cssFontFaceSrc.to.baseName}-${(0, fontFaceHelpers_1.hashHexPrefix)(rawSrc)}${extensionByFormat[format]}`);
|
|
153
|
+
const fontAsset = assetGraph.findAssets({ url })[0] ||
|
|
154
|
+
(await assetGraph.addAsset({
|
|
155
|
+
url,
|
|
156
|
+
rawSrc,
|
|
157
|
+
}));
|
|
158
|
+
srcFragments.push(`url(${assetGraph.buildHref(fontAsset.url, subsetUrl, {
|
|
159
|
+
hrefType,
|
|
160
|
+
})}) format('${format}')`);
|
|
161
|
+
}
|
|
162
|
+
lines.push(` src: ${srcFragments.join(', ')};`);
|
|
163
|
+
lines.push(` unicode-range: ${unicodeRange((await getFontInfo(cssFontFaceSrc.to.rawSrc)).characterSet)};`);
|
|
164
|
+
lines.push('}');
|
|
186
165
|
}
|
|
187
|
-
lines.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
(
|
|
191
|
-
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
const text = lines.join('\n');
|
|
196
|
-
const fallbackAsset = assetGraph.addAsset({
|
|
197
|
-
type: 'Css',
|
|
198
|
-
url: assetGraph.resolveUrl(
|
|
199
|
-
subsetUrl,
|
|
200
|
-
`fallback-${hashHexPrefix(text)}.css`
|
|
201
|
-
),
|
|
202
|
-
text,
|
|
203
|
-
});
|
|
204
|
-
return fallbackAsset;
|
|
166
|
+
const text = lines.join('\n');
|
|
167
|
+
const fallbackAsset = assetGraph.addAsset({
|
|
168
|
+
type: 'Css',
|
|
169
|
+
url: assetGraph.resolveUrl(subsetUrl, `fallback-${(0, fontFaceHelpers_1.hashHexPrefix)(text)}.css`),
|
|
170
|
+
text,
|
|
171
|
+
});
|
|
172
|
+
return fallbackAsset;
|
|
205
173
|
}
|
|
206
|
-
|
|
207
174
|
const validFontDisplayValues = [
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
175
|
+
'auto',
|
|
176
|
+
'block',
|
|
177
|
+
'swap',
|
|
178
|
+
'fallback',
|
|
179
|
+
'optional',
|
|
213
180
|
];
|
|
214
|
-
|
|
215
|
-
const warnAboutMissingGlyphs = require('./warnAboutMissingGlyphs');
|
|
216
|
-
|
|
217
181
|
// Create (or retrieve from disk cache) the subset CSS asset for a set of
|
|
218
182
|
// fontUsages, relocating the font binary to its hashed URL under subsetUrl.
|
|
219
|
-
async function getOrCreateSubsetCssAsset({
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
183
|
+
async function getOrCreateSubsetCssAsset({ assetGraph, subsetCssText, subsetFontUsages, formats, subsetUrl, hrefType, inlineCss, fontUrlsUsedOnEveryPage, numPages, subsetCssAssetCache, }) {
|
|
184
|
+
let cssAsset = subsetCssAssetCache.get(subsetCssText);
|
|
185
|
+
if (cssAsset)
|
|
186
|
+
return cssAsset;
|
|
187
|
+
cssAsset = assetGraph.addAsset({
|
|
188
|
+
type: 'Css',
|
|
189
|
+
url: `${subsetUrl}subfontTemp.css`,
|
|
190
|
+
text: subsetCssText,
|
|
191
|
+
});
|
|
192
|
+
await cssAsset.minify();
|
|
193
|
+
for (const [i, fontRelation] of cssAsset.outgoingRelations.entries()) {
|
|
194
|
+
const fontAsset = fontRelation.to;
|
|
195
|
+
if (!fontAsset.isLoaded) {
|
|
196
|
+
// An unused variant that does not exist, don't try to hash
|
|
197
|
+
fontRelation.hrefType = hrefType;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const fontUsage = subsetFontUsages[i];
|
|
201
|
+
if (formats.length === 1 &&
|
|
202
|
+
fontUsage &&
|
|
203
|
+
fontUsage.fontUrl &&
|
|
204
|
+
(!inlineCss || numPages === 1) &&
|
|
205
|
+
fontUrlsUsedOnEveryPage.has(fontUsage.fontUrl)) {
|
|
206
|
+
// We're only outputting one font format, we're not inlining the subfont CSS (or there's only one page), and this font is used on every page -- keep it inline in the subfont CSS
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const extension = (fontAsset.contentType ?? '').split('/').pop();
|
|
210
|
+
const nameProps = ['font-family', 'font-weight', 'font-style']
|
|
211
|
+
.map((prop) => fontRelation.node.nodes?.find((decl) => decl.prop === prop))
|
|
212
|
+
.map((decl) => decl?.value);
|
|
213
|
+
const fontWeightRangeStr = nameProps[1]
|
|
214
|
+
.split(/\s+/)
|
|
215
|
+
.map((token) => normalizeFontPropertyValue('font-weight', token))
|
|
216
|
+
.join('_');
|
|
217
|
+
const fileNamePrefix = `${unquote(cssFontParser.parseFontFamily(nameProps[0])[0])
|
|
218
|
+
.replace(/__subset$/, '')
|
|
219
|
+
.replace(/[^a-z0-9_-]/gi, '_')}-${fontWeightRangeStr}${nameProps[2] === 'italic' ? 'i' : ''}`;
|
|
220
|
+
const fontFileName = `${fileNamePrefix}-${fontAsset.md5Hex.slice(0, 10)}.${extension}`;
|
|
221
|
+
// If it's not inline, it's one of the unused variants that gets a mirrored declaration added
|
|
222
|
+
// for the __subset @font-face. Do not move it to /subfont/
|
|
223
|
+
if (fontAsset.isInline) {
|
|
224
|
+
const fontAssetUrl = subsetUrl + fontFileName;
|
|
225
|
+
const existingFontAsset = assetGraph.findAssets({
|
|
226
|
+
url: fontAssetUrl,
|
|
227
|
+
})[0];
|
|
228
|
+
if (existingFontAsset) {
|
|
229
|
+
fontRelation.to = existingFontAsset;
|
|
230
|
+
assetGraph.removeAsset(fontAsset);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
fontAsset.url = subsetUrl + fontFileName;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
fontRelation.hrefType = hrefType;
|
|
248
237
|
}
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
if (
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
(!inlineCss || numPages === 1) &&
|
|
255
|
-
fontUrlsUsedOnEveryPage.has(fontUsage.fontUrl)
|
|
256
|
-
) {
|
|
257
|
-
// We're only outputting one font format, we're not inlining the subfont CSS (or there's only one page), and this font is used on every page -- keep it inline in the subfont CSS
|
|
258
|
-
continue;
|
|
238
|
+
const cssAssetUrl = `${subsetUrl}fonts-${cssAsset.md5Hex.slice(0, 10)}.css`;
|
|
239
|
+
const existingCssAsset = assetGraph.findAssets({ url: cssAssetUrl })[0];
|
|
240
|
+
if (existingCssAsset) {
|
|
241
|
+
assetGraph.removeAsset(cssAsset);
|
|
242
|
+
cssAsset = existingCssAsset;
|
|
259
243
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const nameProps = ['font-family', 'font-weight', 'font-style']
|
|
264
|
-
.map((prop) => fontRelation.node.nodes.find((decl) => decl.prop === prop))
|
|
265
|
-
.map((decl) => decl.value);
|
|
266
|
-
|
|
267
|
-
const fontWeightRangeStr = nameProps[1]
|
|
268
|
-
.split(/\s+/)
|
|
269
|
-
.map((token) => normalizeFontPropertyValue('font-weight', token))
|
|
270
|
-
.join('_');
|
|
271
|
-
const fileNamePrefix = `${unquote(
|
|
272
|
-
cssFontParser.parseFontFamily(nameProps[0])[0]
|
|
273
|
-
)
|
|
274
|
-
.replace(/__subset$/, '')
|
|
275
|
-
.replace(/[^a-z0-9_-]/gi, '_')}-${fontWeightRangeStr}${
|
|
276
|
-
nameProps[2] === 'italic' ? 'i' : ''
|
|
277
|
-
}`;
|
|
278
|
-
|
|
279
|
-
const fontFileName = `${fileNamePrefix}-${fontAsset.md5Hex.slice(
|
|
280
|
-
0,
|
|
281
|
-
10
|
|
282
|
-
)}.${extension}`;
|
|
283
|
-
|
|
284
|
-
// If it's not inline, it's one of the unused variants that gets a mirrored declaration added
|
|
285
|
-
// for the __subset @font-face. Do not move it to /subfont/
|
|
286
|
-
if (fontAsset.isInline) {
|
|
287
|
-
const fontAssetUrl = subsetUrl + fontFileName;
|
|
288
|
-
const existingFontAsset = assetGraph.findAssets({
|
|
289
|
-
url: fontAssetUrl,
|
|
290
|
-
})[0];
|
|
291
|
-
if (existingFontAsset) {
|
|
292
|
-
fontRelation.to = existingFontAsset;
|
|
293
|
-
assetGraph.removeAsset(fontAsset);
|
|
294
|
-
} else {
|
|
295
|
-
fontAsset.url = subsetUrl + fontFileName;
|
|
296
|
-
}
|
|
244
|
+
else {
|
|
245
|
+
cssAsset.url = cssAssetUrl;
|
|
297
246
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const cssAssetUrl = `${subsetUrl}fonts-${cssAsset.md5Hex.slice(0, 10)}.css`;
|
|
303
|
-
const existingCssAsset = assetGraph.findAssets({ url: cssAssetUrl })[0];
|
|
304
|
-
if (existingCssAsset) {
|
|
305
|
-
assetGraph.removeAsset(cssAsset);
|
|
306
|
-
cssAsset = existingCssAsset;
|
|
307
|
-
} else {
|
|
308
|
-
cssAsset.url = cssAssetUrl;
|
|
309
|
-
}
|
|
310
|
-
subsetCssAssetCache.set(subsetCssText, cssAsset);
|
|
311
|
-
return cssAsset;
|
|
247
|
+
subsetCssAssetCache.set(subsetCssText, cssAsset);
|
|
248
|
+
return cssAsset;
|
|
312
249
|
}
|
|
313
|
-
|
|
314
250
|
// Insert <link rel="preload"> hints for every woff2 subset flagged as
|
|
315
251
|
// preload-worthy, so the browser starts fetching them during HTML parse.
|
|
316
|
-
function addSubsetFontPreloads({
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
).replace(/__subset$/, '');
|
|
342
|
-
if (
|
|
343
|
-
!fontUsages.some(
|
|
344
|
-
(fontUsage) =>
|
|
345
|
-
fontUsage.fontFamilies.has(originalFontFamily) && fontUsage.preload
|
|
346
|
-
)
|
|
347
|
-
) {
|
|
348
|
-
continue;
|
|
252
|
+
function addSubsetFontPreloads({ cssAsset, fontUsages, htmlOrSvgAsset, subsetUrl, hrefType, insertionPoint, }) {
|
|
253
|
+
if (htmlOrSvgAsset.type !== 'Html')
|
|
254
|
+
return insertionPoint;
|
|
255
|
+
// Only <link rel="preload"> for woff2 subset files whose original
|
|
256
|
+
// font-family is marked for preloading.
|
|
257
|
+
for (const fontRelation of cssAsset.outgoingRelations) {
|
|
258
|
+
if (fontRelation.hrefType === 'inline')
|
|
259
|
+
continue;
|
|
260
|
+
const fontAsset = fontRelation.to;
|
|
261
|
+
if (fontAsset.contentType !== 'font/woff2' ||
|
|
262
|
+
!fontRelation.to.url.startsWith(subsetUrl)) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const familyDecl = fontRelation.node.nodes?.find((node) => node.prop === 'font-family');
|
|
266
|
+
const originalFontFamily = unquote(familyDecl?.value ?? '').replace(/__subset$/, '');
|
|
267
|
+
if (!fontUsages.some((fontUsage) => fontUsage.fontFamilies.has(originalFontFamily) && fontUsage.preload)) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const htmlPreloadLink = htmlOrSvgAsset.addRelation({
|
|
271
|
+
type: 'HtmlPreloadLink',
|
|
272
|
+
hrefType,
|
|
273
|
+
to: fontAsset,
|
|
274
|
+
as: 'font',
|
|
275
|
+
}, insertionPoint ? 'before' : 'firstInHead', insertionPoint);
|
|
276
|
+
insertionPoint = insertionPoint || htmlPreloadLink;
|
|
349
277
|
}
|
|
350
|
-
|
|
351
|
-
const htmlPreloadLink = htmlOrSvgAsset.addRelation(
|
|
352
|
-
{
|
|
353
|
-
type: 'HtmlPreloadLink',
|
|
354
|
-
hrefType,
|
|
355
|
-
to: fontAsset,
|
|
356
|
-
as: 'font',
|
|
357
|
-
},
|
|
358
|
-
insertionPoint ? 'before' : 'firstInHead',
|
|
359
|
-
insertionPoint
|
|
360
|
-
);
|
|
361
|
-
insertionPoint = insertionPoint || htmlPreloadLink;
|
|
362
|
-
}
|
|
363
|
-
return insertionPoint;
|
|
278
|
+
return insertionPoint;
|
|
364
279
|
}
|
|
365
|
-
|
|
366
280
|
// Skip Google Fonts populate when no Google Fonts references exist —
|
|
367
281
|
// otherwise assetgraph spends ~30s network-walking for nothing on sites
|
|
368
282
|
// that only self-host. Returns whether the populate ran so callers can
|
|
369
283
|
// annotate their phase timing.
|
|
370
284
|
async function populateGoogleFontsIfPresent(assetGraph) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
to: { url: { $regex: googleFontsCssUrlRegex } },
|
|
285
|
+
const hasGoogleFonts = assetGraph.findRelations({
|
|
286
|
+
to: { url: { $regex: googleFontsCssUrlRegex } },
|
|
374
287
|
}).length > 0;
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
288
|
+
if (!hasGoogleFonts)
|
|
289
|
+
return false;
|
|
290
|
+
await assetGraph.populate({
|
|
291
|
+
followRelations: {
|
|
292
|
+
$or: [
|
|
293
|
+
{ to: { url: { $regex: googleFontsCssUrlRegex } } },
|
|
294
|
+
{
|
|
295
|
+
type: 'CssFontFaceSrc',
|
|
296
|
+
from: { url: { $regex: googleFontsCssUrlRegex } },
|
|
297
|
+
},
|
|
298
|
+
],
|
|
384
299
|
},
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
});
|
|
388
|
-
return true;
|
|
300
|
+
});
|
|
301
|
+
return true;
|
|
389
302
|
}
|
|
390
|
-
|
|
391
303
|
// Strip every original @font-face rule when --no-fallbacks is set. The
|
|
392
304
|
// severed assets are returned via the `potentiallyOrphanedAssets` set so
|
|
393
305
|
// the final orphan sweep can remove anything left dangling.
|
|
394
|
-
function removeOriginalFontFaceRules(
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
306
|
+
function removeOriginalFontFaceRules(htmlOrSvgAssets, fontFaceDeclarationsByHtmlOrSvgAsset, potentiallyOrphanedAssets) {
|
|
307
|
+
for (const htmlOrSvgAsset of htmlOrSvgAssets) {
|
|
308
|
+
const accumulatedFontFaceDeclarations = fontFaceDeclarationsByHtmlOrSvgAsset.get(htmlOrSvgAsset);
|
|
309
|
+
if (!accumulatedFontFaceDeclarations)
|
|
310
|
+
continue;
|
|
311
|
+
for (const { relations } of accumulatedFontFaceDeclarations) {
|
|
312
|
+
for (const relation of relations) {
|
|
313
|
+
potentiallyOrphanedAssets.add(relation.to);
|
|
314
|
+
if (relation.node.parentNode) {
|
|
315
|
+
relation.node.parentNode.removeChild(relation.node);
|
|
316
|
+
}
|
|
317
|
+
relation.remove();
|
|
318
|
+
}
|
|
407
319
|
}
|
|
408
|
-
|
|
409
|
-
}
|
|
320
|
+
htmlOrSvgAsset.markDirty();
|
|
410
321
|
}
|
|
411
|
-
htmlOrSvgAsset.markDirty();
|
|
412
|
-
}
|
|
413
322
|
}
|
|
414
|
-
|
|
415
323
|
// Rewrite CSS source-map relations to the caller's chosen hrefType so they
|
|
416
324
|
// align with the rest of the emitted assets. Only invoked when sourceMaps is
|
|
417
325
|
// enabled — subsetFonts normally skips source-map serialization for speed.
|
|
418
326
|
async function rewriteCssSourceMaps(assetGraph, hrefType) {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
327
|
+
await assetGraph.serializeSourceMaps(undefined, {
|
|
328
|
+
type: 'Css',
|
|
329
|
+
outgoingRelations: {
|
|
330
|
+
$where: (relations) => relations.some((relation) => relation.type === 'CssSourceMappingUrl'),
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
for (const relation of assetGraph.findRelations({
|
|
334
|
+
type: 'SourceMapSource',
|
|
335
|
+
})) {
|
|
336
|
+
relation.hrefType = hrefType;
|
|
337
|
+
}
|
|
338
|
+
for (const relation of assetGraph.findRelations({
|
|
339
|
+
type: 'CssSourceMappingUrl',
|
|
340
|
+
hrefType: { $in: ['relative', 'inline'] },
|
|
341
|
+
})) {
|
|
342
|
+
relation.hrefType = hrefType;
|
|
343
|
+
}
|
|
437
344
|
}
|
|
438
|
-
|
|
439
345
|
// Remove assets whose last incoming relation was severed during subset
|
|
440
346
|
// injection (original @font-face rules, merged Google Fonts CSS, etc.) so
|
|
441
347
|
// the emitted site doesn't ship with dangling files.
|
|
442
348
|
function removeOrphanedAssets(assetGraph, potentiallyOrphanedAssets) {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
349
|
+
for (const asset of potentiallyOrphanedAssets) {
|
|
350
|
+
if (asset.incomingRelations.length === 0) {
|
|
351
|
+
assetGraph.removeAsset(asset);
|
|
352
|
+
}
|
|
446
353
|
}
|
|
447
|
-
}
|
|
448
354
|
}
|
|
449
|
-
|
|
450
355
|
// Shape the per-page fontUsages into the external fontInfo report: strip
|
|
451
356
|
// internal bookkeeping (subsets buffer, feature-tag scratch) and flatten
|
|
452
357
|
// each page to { assetFileName, fontUsages }.
|
|
453
358
|
function buildFontInfoReport(htmlOrSvgAssetTextsWithProps) {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
fontUsage
|
|
459
|
-
)
|
|
460
|
-
),
|
|
461
|
-
}));
|
|
359
|
+
return htmlOrSvgAssetTextsWithProps.map(({ fontUsages, htmlOrSvgAsset }) => ({
|
|
360
|
+
assetFileName: htmlOrSvgAsset.nonInlineAncestor.urlOrDescription,
|
|
361
|
+
fontUsages: fontUsages.map((fontUsage) => (({ subsets, hasFontFeatureSettings, fontFeatureTags, ...rest }) => rest)(fontUsage)),
|
|
362
|
+
}));
|
|
462
363
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
onlyInfo,
|
|
474
|
-
dynamic,
|
|
475
|
-
console = global.console,
|
|
476
|
-
text,
|
|
477
|
-
sourceMaps = false,
|
|
478
|
-
debug = false,
|
|
479
|
-
concurrency,
|
|
480
|
-
chromeArgs = [],
|
|
481
|
-
cacheDir = null,
|
|
482
|
-
} = {}
|
|
483
|
-
) {
|
|
484
|
-
if (!validFontDisplayValues.includes(fontDisplay)) {
|
|
485
|
-
fontDisplay = undefined;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// Pre-warm the WASM pool: start compiling harfbuzz WASM while
|
|
489
|
-
// collectTextsByPage traces fonts. Compilation (~50-200ms) overlaps
|
|
490
|
-
// with tracing work rather than appearing on the critical path.
|
|
491
|
-
// Catch silently — the error will surface when subsetFontWithGlyphs
|
|
492
|
-
// is actually called, where it's properly handled.
|
|
493
|
-
subsetFontWithGlyphs.warmup().catch(() => {});
|
|
494
|
-
|
|
495
|
-
const subsetUrl = urltools.ensureTrailingSlash(assetGraph.root + subsetPath);
|
|
496
|
-
|
|
497
|
-
const timings = {};
|
|
498
|
-
const trackPhase = makePhaseTracker(console, debug);
|
|
499
|
-
|
|
500
|
-
const applySourceMapsPhase = trackPhase('applySourceMaps');
|
|
501
|
-
if (sourceMaps) {
|
|
502
|
-
await assetGraph.applySourceMaps({ type: 'Css' });
|
|
503
|
-
}
|
|
504
|
-
timings.applySourceMaps = applySourceMapsPhase.end();
|
|
505
|
-
|
|
506
|
-
const googlePopulatePhase = trackPhase('populate (google fonts)');
|
|
507
|
-
const hasGoogleFonts = await populateGoogleFontsIfPresent(assetGraph);
|
|
508
|
-
timings['populate (google fonts)'] = googlePopulatePhase.end(
|
|
509
|
-
hasGoogleFonts ? null : 'skipped, no Google Fonts found'
|
|
510
|
-
);
|
|
511
|
-
|
|
512
|
-
const htmlOrSvgAssets = assetGraph.findAssets({
|
|
513
|
-
$or: [
|
|
514
|
-
{
|
|
515
|
-
type: 'Html',
|
|
516
|
-
isInline: false,
|
|
517
|
-
},
|
|
518
|
-
{
|
|
519
|
-
type: 'Svg',
|
|
520
|
-
},
|
|
521
|
-
],
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
const collectPhase = trackPhase(
|
|
525
|
-
`collectTextsByPage (${htmlOrSvgAssets.length} pages)`
|
|
526
|
-
);
|
|
527
|
-
const {
|
|
528
|
-
htmlOrSvgAssetTextsWithProps,
|
|
529
|
-
fontFaceDeclarationsByHtmlOrSvgAsset,
|
|
530
|
-
subTimings,
|
|
531
|
-
} = await collectTextsByPage(assetGraph, htmlOrSvgAssets, {
|
|
532
|
-
text,
|
|
533
|
-
console,
|
|
534
|
-
dynamic,
|
|
535
|
-
debug,
|
|
536
|
-
concurrency,
|
|
537
|
-
chromeArgs,
|
|
538
|
-
});
|
|
539
|
-
timings.collectTextsByPage = collectPhase.end();
|
|
540
|
-
timings.collectTextsByPageDetails = subTimings;
|
|
541
|
-
|
|
542
|
-
const omitFallbacksPhase = trackPhase('omitFallbacks processing');
|
|
543
|
-
const potentiallyOrphanedAssets = new Set();
|
|
544
|
-
if (omitFallbacks) {
|
|
545
|
-
removeOriginalFontFaceRules(
|
|
546
|
-
htmlOrSvgAssets,
|
|
547
|
-
fontFaceDeclarationsByHtmlOrSvgAsset,
|
|
548
|
-
potentiallyOrphanedAssets
|
|
549
|
-
);
|
|
550
|
-
}
|
|
551
|
-
timings['omitFallbacks processing'] = omitFallbacksPhase.end();
|
|
552
|
-
|
|
553
|
-
const codepointPhase = trackPhase('codepoint generation');
|
|
554
|
-
|
|
555
|
-
if (fontDisplay) {
|
|
556
|
-
for (const htmlOrSvgAssetTextWithProps of htmlOrSvgAssetTextsWithProps) {
|
|
557
|
-
for (const fontUsage of htmlOrSvgAssetTextWithProps.fontUsages) {
|
|
558
|
-
fontUsage.props['font-display'] = fontDisplay;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Pre-compute the global codepoints (original, used, unused) once per fontUrl
|
|
564
|
-
// since fontUsage.text is the same global union on every page.
|
|
565
|
-
// Pre-index all loaded assets by URL for O(1) lookups instead of O(n) scans.
|
|
566
|
-
const loadedAssetsByUrl = new Map();
|
|
567
|
-
for (const asset of assetGraph.findAssets({ isLoaded: true })) {
|
|
568
|
-
if (asset.url) loadedAssetsByUrl.set(asset.url, asset);
|
|
569
|
-
}
|
|
570
|
-
const codepointFontAssetByUrl = new Map();
|
|
571
|
-
for (const htmlOrSvgAssetTextWithProps of htmlOrSvgAssetTextsWithProps) {
|
|
572
|
-
for (const fontUsage of htmlOrSvgAssetTextWithProps.fontUsages) {
|
|
573
|
-
if (
|
|
574
|
-
fontUsage.fontUrl &&
|
|
575
|
-
!codepointFontAssetByUrl.has(fontUsage.fontUrl)
|
|
576
|
-
) {
|
|
577
|
-
const originalFont = loadedAssetsByUrl.get(fontUsage.fontUrl);
|
|
578
|
-
if (originalFont) {
|
|
579
|
-
codepointFontAssetByUrl.set(fontUsage.fontUrl, originalFont);
|
|
364
|
+
async function subsetFonts(assetGraph, { formats = ['woff2'], subsetPath = 'subfont/', omitFallbacks = false, inlineCss = false, fontDisplay, hrefType = 'rootRelative', onlyInfo, dynamic, console = global.console, text, sourceMaps = false, debug = false, concurrency, chromeArgs = [], cacheDir = null, } = {}) {
|
|
365
|
+
if (fontDisplay && !validFontDisplayValues.includes(fontDisplay)) {
|
|
366
|
+
fontDisplay = undefined;
|
|
367
|
+
}
|
|
368
|
+
// Pre-warm the WASM pool: start compiling harfbuzz WASM while
|
|
369
|
+
// collectTextsByPage traces fonts. Compilation (~50-200ms) overlaps
|
|
370
|
+
// with tracing work rather than appearing on the critical path.
|
|
371
|
+
subsetFontWithGlyphs.warmup().catch((err) => {
|
|
372
|
+
if (debug) {
|
|
373
|
+
console.warn('WASM warmup failed (will retry on first subset call):', err);
|
|
580
374
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
)
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
if (cached.originalCodepoints) {
|
|
628
|
-
// Cache getCodepoints result by pageText string to avoid
|
|
629
|
-
// recomputing for pages with identical text per font
|
|
630
|
-
let pageCodepoints = codepointsCache.get(fontUsage.pageText);
|
|
631
|
-
if (!pageCodepoints) {
|
|
632
|
-
pageCodepoints = getCodepoints(fontUsage.pageText);
|
|
633
|
-
codepointsCache.set(fontUsage.pageText, pageCodepoints);
|
|
375
|
+
});
|
|
376
|
+
const subsetUrl = urltools.ensureTrailingSlash(assetGraph.root + subsetPath);
|
|
377
|
+
const timings = {};
|
|
378
|
+
const trackPhase = (0, progress_1.makePhaseTracker)(console, debug);
|
|
379
|
+
const applySourceMapsPhase = trackPhase('applySourceMaps');
|
|
380
|
+
if (sourceMaps) {
|
|
381
|
+
await assetGraph.applySourceMaps({ type: 'Css' });
|
|
382
|
+
}
|
|
383
|
+
timings.applySourceMaps = applySourceMapsPhase.end();
|
|
384
|
+
const googlePopulatePhase = trackPhase('populate (google fonts)');
|
|
385
|
+
const hasGoogleFonts = await populateGoogleFontsIfPresent(assetGraph);
|
|
386
|
+
timings['populate (google fonts)'] = googlePopulatePhase.end(hasGoogleFonts ? null : 'skipped, no Google Fonts found');
|
|
387
|
+
const htmlOrSvgAssets = assetGraph.findAssets({
|
|
388
|
+
$or: [
|
|
389
|
+
{
|
|
390
|
+
type: 'Html',
|
|
391
|
+
isInline: false,
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
type: 'Svg',
|
|
395
|
+
},
|
|
396
|
+
],
|
|
397
|
+
});
|
|
398
|
+
const collectPhase = trackPhase(`collectTextsByPage (${htmlOrSvgAssets.length} pages)`);
|
|
399
|
+
const { htmlOrSvgAssetTextsWithProps, fontFaceDeclarationsByHtmlOrSvgAsset, subTimings, } = await collectTextsByPage(assetGraph, htmlOrSvgAssets, {
|
|
400
|
+
text,
|
|
401
|
+
console,
|
|
402
|
+
dynamic,
|
|
403
|
+
debug,
|
|
404
|
+
concurrency,
|
|
405
|
+
chromeArgs,
|
|
406
|
+
});
|
|
407
|
+
timings.collectTextsByPage = collectPhase.end();
|
|
408
|
+
timings.collectTextsByPageDetails = subTimings;
|
|
409
|
+
const omitFallbacksPhase = trackPhase('omitFallbacks processing');
|
|
410
|
+
const potentiallyOrphanedAssets = new Set();
|
|
411
|
+
if (omitFallbacks) {
|
|
412
|
+
removeOriginalFontFaceRules(htmlOrSvgAssets, fontFaceDeclarationsByHtmlOrSvgAsset, potentiallyOrphanedAssets);
|
|
413
|
+
}
|
|
414
|
+
timings['omitFallbacks processing'] = omitFallbacksPhase.end();
|
|
415
|
+
const codepointPhase = trackPhase('codepoint generation');
|
|
416
|
+
if (fontDisplay) {
|
|
417
|
+
for (const htmlOrSvgAssetTextWithProps of htmlOrSvgAssetTextsWithProps) {
|
|
418
|
+
for (const fontUsage of htmlOrSvgAssetTextWithProps.fontUsages) {
|
|
419
|
+
fontUsage.props['font-display'] = fontDisplay;
|
|
420
|
+
}
|
|
634
421
|
}
|
|
635
|
-
fontUsage.codepoints = {
|
|
636
|
-
original: cached.originalCodepoints,
|
|
637
|
-
used: cached.usedCodepoints,
|
|
638
|
-
unused: cached.unusedCodepoints,
|
|
639
|
-
page: pageCodepoints,
|
|
640
|
-
};
|
|
641
|
-
} else {
|
|
642
|
-
fontUsage.codepoints = {
|
|
643
|
-
original: [],
|
|
644
|
-
used: [],
|
|
645
|
-
unused: [],
|
|
646
|
-
page: [],
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
timings['codepoint generation'] = codepointPhase.end();
|
|
653
|
-
|
|
654
|
-
if (onlyInfo) {
|
|
655
|
-
return {
|
|
656
|
-
fontInfo: htmlOrSvgAssetTextsWithProps.map(
|
|
657
|
-
({ fontUsages, htmlOrSvgAsset }) => ({
|
|
658
|
-
assetFileName: htmlOrSvgAsset.nonInlineAncestor.urlOrDescription,
|
|
659
|
-
fontUsages: fontUsages.map((fontUsage) =>
|
|
660
|
-
(({ hasFontFeatureSettings, fontFeatureTags, ...rest }) => rest)(
|
|
661
|
-
fontUsage
|
|
662
|
-
)
|
|
663
|
-
),
|
|
664
|
-
})
|
|
665
|
-
),
|
|
666
|
-
timings,
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
const variationPhase = trackPhase('variation axis usage');
|
|
671
|
-
const { seenAxisValuesByFontUrlAndAxisName } = getVariationAxisUsage(
|
|
672
|
-
htmlOrSvgAssetTextsWithProps,
|
|
673
|
-
parseFontWeightRange,
|
|
674
|
-
parseFontStretchRange
|
|
675
|
-
);
|
|
676
|
-
timings['variation axis usage'] = variationPhase.end();
|
|
677
|
-
|
|
678
|
-
// Generate subsets:
|
|
679
|
-
if (console) {
|
|
680
|
-
const uniqueFontUrls = countUniqueFontUrls(htmlOrSvgAssetTextsWithProps);
|
|
681
|
-
if (uniqueFontUrls > 0) {
|
|
682
|
-
console.log(
|
|
683
|
-
` Subsetting ${uniqueFontUrls} unique font file${uniqueFontUrls === 1 ? '' : 's'}...`
|
|
684
|
-
);
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
const subsetPhase = trackPhase('getSubsetsForFontUsage');
|
|
688
|
-
await getSubsetsForFontUsage(
|
|
689
|
-
assetGraph,
|
|
690
|
-
htmlOrSvgAssetTextsWithProps,
|
|
691
|
-
formats,
|
|
692
|
-
seenAxisValuesByFontUrlAndAxisName,
|
|
693
|
-
cacheDir,
|
|
694
|
-
console,
|
|
695
|
-
debug
|
|
696
|
-
);
|
|
697
|
-
timings.getSubsetsForFontUsage = subsetPhase.end();
|
|
698
|
-
|
|
699
|
-
const warnGlyphsPhase = trackPhase('warnAboutMissingGlyphs');
|
|
700
|
-
await warnAboutMissingGlyphs(htmlOrSvgAssetTextsWithProps, assetGraph);
|
|
701
|
-
timings.warnAboutMissingGlyphs = warnGlyphsPhase.end();
|
|
702
|
-
|
|
703
|
-
// Insert subsets:
|
|
704
|
-
|
|
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.
|
|
707
|
-
const fontUrlsUsedOnEveryPage = new Set();
|
|
708
|
-
if (htmlOrSvgAssetTextsWithProps.length > 0) {
|
|
709
|
-
const firstPageFontUrls = new Set();
|
|
710
|
-
for (const fu of htmlOrSvgAssetTextsWithProps[0].fontUsages) {
|
|
711
|
-
if (fu.pageText) firstPageFontUrls.add(fu.fontUrl);
|
|
712
422
|
}
|
|
713
|
-
|
|
714
|
-
|
|
423
|
+
// Pre-compute the global codepoints (original, used, unused) once per fontUrl
|
|
424
|
+
// since fontUsage.text is the same global union on every page.
|
|
425
|
+
// Pre-index all loaded assets by URL for O(1) lookups instead of O(n) scans.
|
|
426
|
+
const loadedAssetsByUrl = new Map();
|
|
427
|
+
for (const asset of assetGraph.findAssets({ isLoaded: true })) {
|
|
428
|
+
if (asset.url)
|
|
429
|
+
loadedAssetsByUrl.set(asset.url, asset);
|
|
715
430
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
431
|
+
const codepointFontAssetByUrl = new Map();
|
|
432
|
+
for (const htmlOrSvgAssetTextWithProps of htmlOrSvgAssetTextsWithProps) {
|
|
433
|
+
for (const fontUsage of htmlOrSvgAssetTextWithProps.fontUsages) {
|
|
434
|
+
if (fontUsage.fontUrl &&
|
|
435
|
+
!codepointFontAssetByUrl.has(fontUsage.fontUrl)) {
|
|
436
|
+
const originalFont = loadedAssetsByUrl.get(fontUsage.fontUrl);
|
|
437
|
+
if (originalFont) {
|
|
438
|
+
codepointFontAssetByUrl.set(fontUsage.fontUrl, originalFont);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
724
441
|
}
|
|
725
|
-
}
|
|
726
442
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
// O(1) lookups per page instead of repeated assetGraph.findRelations.
|
|
742
|
-
const styleRelsByAsset = new Map();
|
|
743
|
-
const noscriptRelsByAsset = new Map();
|
|
744
|
-
const preloadRelsByAsset = new Map();
|
|
745
|
-
const relTypeToIndex = {
|
|
746
|
-
HtmlStyle: styleRelsByAsset,
|
|
747
|
-
SvgStyle: styleRelsByAsset,
|
|
748
|
-
HtmlNoscript: noscriptRelsByAsset,
|
|
749
|
-
HtmlPrefetchLink: preloadRelsByAsset,
|
|
750
|
-
HtmlPreloadLink: preloadRelsByAsset,
|
|
751
|
-
};
|
|
752
|
-
for (const relation of assetGraph.findRelations({
|
|
753
|
-
type: { $in: Object.keys(relTypeToIndex) },
|
|
754
|
-
})) {
|
|
755
|
-
const index = relTypeToIndex[relation.type];
|
|
756
|
-
const from = relation.from;
|
|
757
|
-
if (!index.has(from)) index.set(from, []);
|
|
758
|
-
index.get(from).push(relation);
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
const insertPhase = trackPhase(
|
|
762
|
-
`insert subsets loop (${htmlOrSvgAssetTextsWithProps.length} pages)`
|
|
763
|
-
);
|
|
764
|
-
let numFontUsagesWithSubset = 0;
|
|
765
|
-
for (const {
|
|
766
|
-
htmlOrSvgAsset,
|
|
767
|
-
fontUsages,
|
|
768
|
-
accumulatedFontFaceDeclarations,
|
|
769
|
-
} of htmlOrSvgAssetTextsWithProps) {
|
|
770
|
-
const styleRels = styleRelsByAsset.get(htmlOrSvgAsset) || [];
|
|
771
|
-
let insertionPoint = styleRels[0];
|
|
772
|
-
|
|
773
|
-
// Fall back to inserting before a <noscript> that contains a stylesheet
|
|
774
|
-
// when no direct stylesheet relation exists (assetgraph#1251)
|
|
775
|
-
if (!insertionPoint && htmlOrSvgAsset.type === 'Html') {
|
|
776
|
-
for (const htmlNoScript of noscriptRelsByAsset.get(htmlOrSvgAsset) ||
|
|
777
|
-
[]) {
|
|
778
|
-
const noscriptStyleRels = styleRelsByAsset.get(htmlNoScript.to) || [];
|
|
779
|
-
if (noscriptStyleRels.length > 0) {
|
|
780
|
-
insertionPoint = htmlNoScript;
|
|
781
|
-
break;
|
|
443
|
+
const fontInfoPromises = new Map();
|
|
444
|
+
for (const [fontUrl, fontAsset] of codepointFontAssetByUrl) {
|
|
445
|
+
if (fontAsset.isLoaded) {
|
|
446
|
+
fontInfoPromises.set(fontUrl,
|
|
447
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
448
|
+
getFontInfo(fontAsset.rawSrc).catch((rawErr) => {
|
|
449
|
+
const err = rawErr instanceof Error
|
|
450
|
+
? rawErr
|
|
451
|
+
: new Error(String(rawErr));
|
|
452
|
+
err.asset =
|
|
453
|
+
err.asset || fontAsset;
|
|
454
|
+
assetGraph.warn(err);
|
|
455
|
+
return null;
|
|
456
|
+
}));
|
|
782
457
|
}
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
const subsetFontUsages = fontUsages.filter(
|
|
786
|
-
(fontUsage) => fontUsage.subsets
|
|
787
|
-
);
|
|
788
|
-
const subsetFontUsagesSet = new Set(subsetFontUsages);
|
|
789
|
-
const unsubsettedFontUsages = fontUsages.filter(
|
|
790
|
-
(fontUsage) => !subsetFontUsagesSet.has(fontUsage)
|
|
791
|
-
);
|
|
792
|
-
|
|
793
|
-
// Remove all existing preload hints to fonts that might have new subsets
|
|
794
|
-
const fontUrls = new Set(fontUsages.map((fu) => fu.fontUrl));
|
|
795
|
-
for (const relation of preloadRelsByAsset.get(htmlOrSvgAsset) || []) {
|
|
796
|
-
if (!relation.to || !fontUrls.has(relation.to.url)) continue;
|
|
797
|
-
|
|
798
|
-
if (relation.type === 'HtmlPrefetchLink') {
|
|
799
|
-
const err = new Error(
|
|
800
|
-
`Detached ${relation.node.outerHTML}. Will be replaced with preload with JS fallback.\nIf you feel this is wrong, open an issue at https://github.com/alexander-turner/subfont/issues`
|
|
801
|
-
);
|
|
802
|
-
err.asset = relation.from;
|
|
803
|
-
err.relation = relation;
|
|
804
|
-
assetGraph.info(err);
|
|
805
|
-
}
|
|
806
|
-
relation.detach();
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
const unsubsettedFontUsagesToPreload = unsubsettedFontUsages.filter(
|
|
810
|
-
(fontUsage) => fontUsage.preload
|
|
811
|
-
);
|
|
812
|
-
|
|
813
|
-
if (unsubsettedFontUsagesToPreload.length > 0) {
|
|
814
|
-
// Insert <link rel="preload">
|
|
815
|
-
for (const fontUsage of unsubsettedFontUsagesToPreload) {
|
|
816
|
-
// Always preload unsubsetted font files, they might be any format, so can't be clever here
|
|
817
|
-
const preloadRelation = htmlOrSvgAsset.addRelation(
|
|
818
|
-
{
|
|
819
|
-
type: 'HtmlPreloadLink',
|
|
820
|
-
hrefType,
|
|
821
|
-
to: fontUsage.fontUrl,
|
|
822
|
-
as: 'font',
|
|
823
|
-
},
|
|
824
|
-
insertionPoint ? 'before' : 'firstInHead',
|
|
825
|
-
insertionPoint
|
|
826
|
-
);
|
|
827
|
-
insertionPoint = insertionPoint || preloadRelation;
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
if (subsetFontUsages.length === 0) {
|
|
832
|
-
continue;
|
|
833
458
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
to: cssAsset,
|
|
881
|
-
},
|
|
882
|
-
insertionPoint ? 'before' : 'firstInHead',
|
|
883
|
-
insertionPoint
|
|
884
|
-
);
|
|
885
|
-
insertionPoint = insertionPoint || cssRelation;
|
|
886
|
-
|
|
887
|
-
if (!omitFallbacks && inlineCss && unusedVariantsCss) {
|
|
888
|
-
// The fallback CSS for unused variants needs to go into its own stylesheet after the crude version of the JS-based preload "polyfill"
|
|
889
|
-
const cssAsset = htmlOrSvgAsset.addRelation(
|
|
890
|
-
{
|
|
891
|
-
type: 'HtmlStyle',
|
|
892
|
-
to: {
|
|
893
|
-
type: 'Css',
|
|
894
|
-
text: unusedVariantsCss,
|
|
895
|
-
},
|
|
896
|
-
},
|
|
897
|
-
'after',
|
|
898
|
-
cssRelation
|
|
899
|
-
).to;
|
|
900
|
-
for (const relation of cssAsset.outgoingRelations) {
|
|
901
|
-
relation.hrefType = hrefType;
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
timings['insert subsets loop'] = insertPhase.end();
|
|
907
|
-
|
|
908
|
-
if (numFontUsagesWithSubset === 0) {
|
|
909
|
-
return { fontInfo: [], timings };
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
const lazyFallbackPhase = trackPhase('lazy load fallback CSS');
|
|
913
|
-
const relationsToRemove = new Set();
|
|
914
|
-
|
|
915
|
-
// Lazy load the original @font-face declarations of self-hosted fonts (unless omitFallbacks)
|
|
916
|
-
const originalRelations = new Set();
|
|
917
|
-
const fallbackCssAssetCache = new Map();
|
|
918
|
-
for (const htmlOrSvgAsset of htmlOrSvgAssets) {
|
|
919
|
-
const accumulatedFontFaceDeclarations =
|
|
920
|
-
fontFaceDeclarationsByHtmlOrSvgAsset.get(htmlOrSvgAsset);
|
|
921
|
-
const containedRelationsByFontFaceRule = new Map();
|
|
922
|
-
for (const { relations } of accumulatedFontFaceDeclarations) {
|
|
923
|
-
for (const relation of relations) {
|
|
924
|
-
if (
|
|
925
|
-
relation.from.hostname === 'fonts.googleapis.com' || // Google Web Fonts handled separately below
|
|
926
|
-
containedRelationsByFontFaceRule.has(relation.node)
|
|
927
|
-
) {
|
|
928
|
-
continue;
|
|
459
|
+
const fontInfoResults = new Map(await Promise.all([...fontInfoPromises].map(async ([key, promise]) => [key, await promise])));
|
|
460
|
+
// Build global codepoints synchronously from pre-fetched results
|
|
461
|
+
const globalCodepointsByFontUrl = new Map();
|
|
462
|
+
const codepointsCache = new Map();
|
|
463
|
+
for (const htmlOrSvgAssetTextWithProps of htmlOrSvgAssetTextsWithProps) {
|
|
464
|
+
for (const fontUsage of htmlOrSvgAssetTextWithProps.fontUsages) {
|
|
465
|
+
let cached = globalCodepointsByFontUrl.get(fontUsage.fontUrl);
|
|
466
|
+
if (!cached) {
|
|
467
|
+
cached = { originalCodepoints: null };
|
|
468
|
+
const fontInfo = fontUsage.fontUrl
|
|
469
|
+
? fontInfoResults.get(fontUsage.fontUrl)
|
|
470
|
+
: undefined;
|
|
471
|
+
if (fontInfo) {
|
|
472
|
+
const originalCodepoints = fontInfo.characterSet;
|
|
473
|
+
const usedCodepoints = (0, fontFaceHelpers_1.getCodepoints)(fontUsage.text);
|
|
474
|
+
const usedCodepointsSet = new Set(usedCodepoints);
|
|
475
|
+
cached.originalCodepoints = originalCodepoints;
|
|
476
|
+
cached.usedCodepoints = usedCodepoints;
|
|
477
|
+
cached.unusedCodepoints = originalCodepoints.filter((n) => !usedCodepointsSet.has(n));
|
|
478
|
+
}
|
|
479
|
+
globalCodepointsByFontUrl.set(fontUsage.fontUrl, cached);
|
|
480
|
+
}
|
|
481
|
+
if (cached.originalCodepoints) {
|
|
482
|
+
// Cache getCodepoints result by pageText string to avoid
|
|
483
|
+
// recomputing for pages with identical text per font
|
|
484
|
+
const pageText = fontUsage.pageText ?? '';
|
|
485
|
+
let pageCodepoints = codepointsCache.get(pageText);
|
|
486
|
+
if (!pageCodepoints) {
|
|
487
|
+
pageCodepoints = (0, fontFaceHelpers_1.getCodepoints)(pageText);
|
|
488
|
+
codepointsCache.set(pageText, pageCodepoints);
|
|
489
|
+
}
|
|
490
|
+
fontUsage.codepoints = {
|
|
491
|
+
original: cached.originalCodepoints,
|
|
492
|
+
used: cached.usedCodepoints ?? [],
|
|
493
|
+
unused: cached.unusedCodepoints ?? [],
|
|
494
|
+
page: pageCodepoints,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
fontUsage.codepoints = {
|
|
499
|
+
original: [],
|
|
500
|
+
used: [],
|
|
501
|
+
unused: [],
|
|
502
|
+
page: [],
|
|
503
|
+
};
|
|
504
|
+
}
|
|
929
505
|
}
|
|
930
|
-
originalRelations.add(relation);
|
|
931
|
-
containedRelationsByFontFaceRule.set(
|
|
932
|
-
relation.node,
|
|
933
|
-
relation.from.outgoingRelations.filter(
|
|
934
|
-
(otherRelation) => otherRelation.node === relation.node
|
|
935
|
-
)
|
|
936
|
-
);
|
|
937
|
-
}
|
|
938
506
|
}
|
|
939
|
-
|
|
940
|
-
if (
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
507
|
+
timings['codepoint generation'] = codepointPhase.end();
|
|
508
|
+
if (onlyInfo) {
|
|
509
|
+
return {
|
|
510
|
+
fontInfo: htmlOrSvgAssetTextsWithProps.map(({ fontUsages, htmlOrSvgAsset }) => ({
|
|
511
|
+
assetFileName: htmlOrSvgAsset.nonInlineAncestor.urlOrDescription,
|
|
512
|
+
fontUsages: fontUsages.map((fontUsage) => (({ hasFontFeatureSettings, fontFeatureTags, ...rest }) => rest)(fontUsage)),
|
|
513
|
+
})),
|
|
514
|
+
timings,
|
|
515
|
+
};
|
|
946
516
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
//
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
let ancestor = rule.parent;
|
|
956
|
-
while (ancestor) {
|
|
957
|
-
if (
|
|
958
|
-
ancestor.type === 'atrule' &&
|
|
959
|
-
ancestor.name.toLowerCase() === 'media'
|
|
960
|
-
) {
|
|
961
|
-
mediaKey = ancestor.params;
|
|
962
|
-
break;
|
|
517
|
+
const variationPhase = trackPhase('variation axis usage');
|
|
518
|
+
const { seenAxisValuesByFontUrlAndAxisName } = (0, variationAxes_1.getVariationAxisUsage)(htmlOrSvgAssetTextsWithProps, fontFaceHelpers_1.parseFontWeightRange, fontFaceHelpers_1.parseFontStretchRange);
|
|
519
|
+
timings['variation axis usage'] = variationPhase.end();
|
|
520
|
+
// Generate subsets:
|
|
521
|
+
if (console) {
|
|
522
|
+
const uniqueFontUrls = countUniqueFontUrls(htmlOrSvgAssetTextsWithProps);
|
|
523
|
+
if (uniqueFontUrls > 0) {
|
|
524
|
+
console.log(` Subsetting ${uniqueFontUrls} unique font file${uniqueFontUrls === 1 ? '' : 's'}...`);
|
|
963
525
|
}
|
|
964
|
-
ancestor = ancestor.parent;
|
|
965
|
-
}
|
|
966
|
-
if (!rulesByMedia.has(mediaKey)) rulesByMedia.set(mediaKey, []);
|
|
967
|
-
rulesByMedia
|
|
968
|
-
.get(mediaKey)
|
|
969
|
-
.push(
|
|
970
|
-
getFontFaceDeclarationText(
|
|
971
|
-
rule,
|
|
972
|
-
containedRelationsByFontFaceRule.get(rule)
|
|
973
|
-
)
|
|
974
|
-
);
|
|
975
|
-
}
|
|
976
|
-
let fallbackCssText = '';
|
|
977
|
-
for (const [media, texts] of rulesByMedia) {
|
|
978
|
-
if (media) {
|
|
979
|
-
fallbackCssText += `@media ${media}{${texts.join('')}}`;
|
|
980
|
-
} else {
|
|
981
|
-
fallbackCssText += texts.join('');
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
let cssAsset = fallbackCssAssetCache.get(fallbackCssText);
|
|
986
|
-
if (!cssAsset) {
|
|
987
|
-
cssAsset = assetGraph.addAsset({
|
|
988
|
-
type: 'Css',
|
|
989
|
-
text: fallbackCssText,
|
|
990
|
-
});
|
|
991
|
-
for (const relation of cssAsset.outgoingRelations) {
|
|
992
|
-
relation.hrefType = hrefType;
|
|
993
|
-
}
|
|
994
|
-
await cssAsset.minify();
|
|
995
|
-
cssAsset.url = `${subsetUrl}fallback-${cssAsset.md5Hex.slice(0, 10)}.css`;
|
|
996
|
-
fallbackCssAssetCache.set(fallbackCssText, cssAsset);
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
// Create a <link rel="stylesheet"> that asyncLoadStyleRelationWithFallback can convert to async with noscript fallback:
|
|
1000
|
-
const fallbackHtmlStyle = htmlOrSvgAsset.addRelation({
|
|
1001
|
-
type: 'HtmlStyle',
|
|
1002
|
-
to: cssAsset,
|
|
1003
|
-
});
|
|
1004
|
-
|
|
1005
|
-
asyncLoadStyleRelationWithFallback(
|
|
1006
|
-
htmlOrSvgAsset,
|
|
1007
|
-
fallbackHtmlStyle,
|
|
1008
|
-
hrefType
|
|
1009
|
-
);
|
|
1010
|
-
relationsToRemove.add(fallbackHtmlStyle);
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
timings['lazy load fallback CSS'] = lazyFallbackPhase.end();
|
|
1014
|
-
|
|
1015
|
-
const removeFontFacePhase = trackPhase('remove original @font-face');
|
|
1016
|
-
|
|
1017
|
-
// Remove the original @font-face blocks, and don't leave behind empty stylesheets:
|
|
1018
|
-
const maybeEmptyCssAssets = new Set();
|
|
1019
|
-
for (const relation of originalRelations) {
|
|
1020
|
-
const cssAsset = relation.from;
|
|
1021
|
-
if (relation.node.parent) {
|
|
1022
|
-
relation.node.parent.removeChild(relation.node);
|
|
1023
526
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
527
|
+
const subsetPhase = trackPhase('getSubsetsForFontUsage');
|
|
528
|
+
await (0, subsetGeneration_1.getSubsetsForFontUsage)(assetGraph, htmlOrSvgAssetTextsWithProps, formats, seenAxisValuesByFontUrlAndAxisName, cacheDir, console, debug);
|
|
529
|
+
timings.getSubsetsForFontUsage = subsetPhase.end();
|
|
530
|
+
const warnGlyphsPhase = trackPhase('warnAboutMissingGlyphs');
|
|
531
|
+
await warnAboutMissingGlyphs(htmlOrSvgAssetTextsWithProps, assetGraph);
|
|
532
|
+
timings.warnAboutMissingGlyphs = warnGlyphsPhase.end();
|
|
533
|
+
// Insert subsets:
|
|
534
|
+
// Pre-compute which fontUrls are used (with text) on every page.
|
|
535
|
+
// Set intersection: O(pages × fonts_per_page) vs the old every+some approach.
|
|
536
|
+
const fontUrlsUsedOnEveryPage = new Set();
|
|
537
|
+
const pages = htmlOrSvgAssetTextsWithProps;
|
|
538
|
+
if (pages.length > 0) {
|
|
539
|
+
for (const fu of pages[0].fontUsages) {
|
|
540
|
+
if (fu.pageText && fu.fontUrl)
|
|
541
|
+
fontUrlsUsedOnEveryPage.add(fu.fontUrl);
|
|
542
|
+
}
|
|
543
|
+
for (let i = 1; i < pages.length; i++) {
|
|
544
|
+
if (fontUrlsUsedOnEveryPage.size === 0)
|
|
545
|
+
break;
|
|
546
|
+
const pageFontUrls = new Set();
|
|
547
|
+
for (const fu of pages[i].fontUsages) {
|
|
548
|
+
if (fu.pageText && fu.fontUrl)
|
|
549
|
+
pageFontUrls.add(fu.fontUrl);
|
|
550
|
+
}
|
|
551
|
+
for (const fontUrl of fontUrlsUsedOnEveryPage) {
|
|
552
|
+
if (!pageFontUrls.has(fontUrl)) {
|
|
553
|
+
fontUrlsUsedOnEveryPage.delete(fontUrl);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
1035
557
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
if (
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
if (!selfHostedGoogleFontsCssAsset) {
|
|
1083
|
-
selfHostedGoogleFontsCssAsset =
|
|
1084
|
-
await createSelfHostedGoogleFontsCssAsset(
|
|
1085
|
-
assetGraph,
|
|
1086
|
-
googleFontStylesheetRelation.to,
|
|
1087
|
-
formats,
|
|
1088
|
-
hrefType,
|
|
1089
|
-
subsetUrl
|
|
1090
|
-
);
|
|
1091
|
-
await selfHostedGoogleFontsCssAsset.minify();
|
|
1092
|
-
selfHostedGoogleCssByUrl.set(
|
|
1093
|
-
googleFontStylesheetRelation.to.url,
|
|
1094
|
-
selfHostedGoogleFontsCssAsset
|
|
1095
|
-
);
|
|
558
|
+
// Cache subset CSS assets by their source text to avoid redundant
|
|
559
|
+
// addAsset/minify/removeAsset cycles for pages sharing identical CSS.
|
|
560
|
+
const subsetCssAssetCache = new Map();
|
|
561
|
+
// Cache the heavy CSS-text assembly (including base64-encoded font data)
|
|
562
|
+
// keyed by the shared accumulatedFontFaceDeclarations array. Pages grouped
|
|
563
|
+
// under the same stylesheet config produce byte-identical output, so this
|
|
564
|
+
// collapses the per-page string build from O(pages) to O(unique configs).
|
|
565
|
+
const subsetCssTextCache = new WeakMap();
|
|
566
|
+
// Pre-index relations by source asset to avoid O(allRelations) scans
|
|
567
|
+
// in the per-page injection loop below. Build indices once, then use
|
|
568
|
+
// O(1) lookups per page instead of repeated assetGraph.findRelations.
|
|
569
|
+
const styleRelsByAsset = new Map();
|
|
570
|
+
const noscriptRelsByAsset = new Map();
|
|
571
|
+
const preloadRelsByAsset = new Map();
|
|
572
|
+
const relTypeToIndex = {
|
|
573
|
+
HtmlStyle: styleRelsByAsset,
|
|
574
|
+
SvgStyle: styleRelsByAsset,
|
|
575
|
+
HtmlNoscript: noscriptRelsByAsset,
|
|
576
|
+
HtmlPrefetchLink: preloadRelsByAsset,
|
|
577
|
+
HtmlPreloadLink: preloadRelsByAsset,
|
|
578
|
+
};
|
|
579
|
+
for (const relation of assetGraph.findRelations({
|
|
580
|
+
type: { $in: Object.keys(relTypeToIndex) },
|
|
581
|
+
})) {
|
|
582
|
+
const index = relTypeToIndex[relation.type];
|
|
583
|
+
const from = relation.from;
|
|
584
|
+
if (!index.has(from))
|
|
585
|
+
index.set(from, []);
|
|
586
|
+
index.get(from).push(relation);
|
|
587
|
+
}
|
|
588
|
+
const insertPhase = trackPhase(`insert subsets loop (${htmlOrSvgAssetTextsWithProps.length} pages)`);
|
|
589
|
+
let numFontUsagesWithSubset = 0;
|
|
590
|
+
for (const { htmlOrSvgAsset, fontUsages, accumulatedFontFaceDeclarations, } of htmlOrSvgAssetTextsWithProps) {
|
|
591
|
+
const styleRels = styleRelsByAsset.get(htmlOrSvgAsset) || [];
|
|
592
|
+
let insertionPoint = styleRels[0];
|
|
593
|
+
// Fall back to inserting before a <noscript> that contains a stylesheet
|
|
594
|
+
// when no direct stylesheet relation exists (assetgraph#1251)
|
|
595
|
+
if (!insertionPoint && htmlOrSvgAsset.type === 'Html') {
|
|
596
|
+
for (const htmlNoScript of noscriptRelsByAsset.get(htmlOrSvgAsset) ||
|
|
597
|
+
[]) {
|
|
598
|
+
const noscriptStyleRels = styleRelsByAsset.get(htmlNoScript.to) || [];
|
|
599
|
+
if (noscriptStyleRels.length > 0) {
|
|
600
|
+
insertionPoint = htmlNoScript;
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
1096
604
|
}
|
|
1097
|
-
const
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
605
|
+
const subsetFontUsages = fontUsages.filter((fontUsage) => fontUsage.subsets);
|
|
606
|
+
const subsetFontUsagesSet = new Set(subsetFontUsages);
|
|
607
|
+
const unsubsettedFontUsages = fontUsages.filter((fontUsage) => !subsetFontUsagesSet.has(fontUsage));
|
|
608
|
+
// Remove all existing preload hints to fonts that might have new subsets
|
|
609
|
+
const fontUrls = new Set(fontUsages.map((fu) => fu.fontUrl));
|
|
610
|
+
for (const relation of preloadRelsByAsset.get(htmlOrSvgAsset) || []) {
|
|
611
|
+
if (!relation.to || !fontUrls.has(relation.to.url))
|
|
612
|
+
continue;
|
|
613
|
+
if (relation.type === 'HtmlPrefetchLink') {
|
|
614
|
+
const err = new Error(`Detached ${relation.node.outerHTML}. Will be replaced with preload with JS fallback.\nIf you feel this is wrong, open an issue at https://github.com/alexander-turner/subfont/issues`);
|
|
615
|
+
err.asset = relation.from;
|
|
616
|
+
err.relation = relation;
|
|
617
|
+
assetGraph.info(err);
|
|
618
|
+
}
|
|
619
|
+
relation.detach();
|
|
620
|
+
}
|
|
621
|
+
const unsubsettedFontUsagesToPreload = unsubsettedFontUsages.filter((fontUsage) => fontUsage.preload);
|
|
622
|
+
if (unsubsettedFontUsagesToPreload.length > 0) {
|
|
623
|
+
// Insert <link rel="preload">
|
|
624
|
+
for (const fontUsage of unsubsettedFontUsagesToPreload) {
|
|
625
|
+
// Always preload unsubsetted font files, they might be any format, so can't be clever here
|
|
626
|
+
const preloadRelation = htmlOrSvgAsset.addRelation({
|
|
627
|
+
type: 'HtmlPreloadLink',
|
|
628
|
+
hrefType,
|
|
629
|
+
to: fontUsage.fontUrl,
|
|
630
|
+
as: 'font',
|
|
631
|
+
}, insertionPoint ? 'before' : 'firstInHead', insertionPoint);
|
|
632
|
+
insertionPoint = insertionPoint || preloadRelation;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (subsetFontUsages.length === 0) {
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
numFontUsagesWithSubset += subsetFontUsages.length;
|
|
639
|
+
let cssTextParts = subsetCssTextCache.get(accumulatedFontFaceDeclarations);
|
|
640
|
+
if (!cssTextParts) {
|
|
641
|
+
cssTextParts = {
|
|
642
|
+
subset: (0, fontFaceHelpers_1.getFontUsageStylesheet)(subsetFontUsages),
|
|
643
|
+
unused: (0, fontFaceHelpers_1.getUnusedVariantsStylesheet)(fontUsages, accumulatedFontFaceDeclarations),
|
|
644
|
+
};
|
|
645
|
+
subsetCssTextCache.set(accumulatedFontFaceDeclarations, cssTextParts);
|
|
646
|
+
}
|
|
647
|
+
let subsetCssText = cssTextParts.subset;
|
|
648
|
+
const unusedVariantsCss = cssTextParts.unused;
|
|
649
|
+
if (!inlineCss && !omitFallbacks) {
|
|
650
|
+
// This can go into the same stylesheet because we won't reload all __subset suffixed families in the JS preload fallback
|
|
651
|
+
subsetCssText += unusedVariantsCss;
|
|
652
|
+
}
|
|
653
|
+
const cssAsset = await getOrCreateSubsetCssAsset({
|
|
654
|
+
assetGraph,
|
|
655
|
+
subsetCssText,
|
|
656
|
+
subsetFontUsages,
|
|
657
|
+
formats,
|
|
658
|
+
subsetUrl,
|
|
659
|
+
hrefType,
|
|
660
|
+
inlineCss,
|
|
661
|
+
fontUrlsUsedOnEveryPage,
|
|
662
|
+
numPages: htmlOrSvgAssetTextsWithProps.length,
|
|
663
|
+
subsetCssAssetCache,
|
|
664
|
+
});
|
|
665
|
+
insertionPoint = addSubsetFontPreloads({
|
|
666
|
+
cssAsset,
|
|
667
|
+
fontUsages,
|
|
668
|
+
htmlOrSvgAsset,
|
|
669
|
+
subsetUrl,
|
|
1101
670
|
hrefType,
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
671
|
+
insertionPoint,
|
|
672
|
+
});
|
|
673
|
+
const cssRelation = htmlOrSvgAsset.addRelation({
|
|
674
|
+
type: `${htmlOrSvgAsset.type}Style`,
|
|
675
|
+
hrefType: inlineCss || htmlOrSvgAsset.type === 'Svg' ? 'inline' : hrefType,
|
|
676
|
+
to: cssAsset,
|
|
677
|
+
}, insertionPoint ? 'before' : 'firstInHead', insertionPoint);
|
|
678
|
+
insertionPoint = insertionPoint || cssRelation;
|
|
679
|
+
if (!omitFallbacks && inlineCss && unusedVariantsCss) {
|
|
680
|
+
// The fallback CSS for unused variants needs to go into its own stylesheet after the crude version of the JS-based preload "polyfill"
|
|
681
|
+
const cssAsset = htmlOrSvgAsset.addRelation({
|
|
682
|
+
type: 'HtmlStyle',
|
|
683
|
+
to: {
|
|
684
|
+
type: 'Css',
|
|
685
|
+
text: unusedVariantsCss,
|
|
686
|
+
},
|
|
687
|
+
}, 'after', cssRelation).to;
|
|
688
|
+
for (const relation of cssAsset.outgoingRelations) {
|
|
689
|
+
relation.hrefType = hrefType;
|
|
690
|
+
}
|
|
1112
691
|
}
|
|
1113
|
-
}
|
|
1114
692
|
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
693
|
+
timings['insert subsets loop'] = insertPhase.end();
|
|
694
|
+
if (numFontUsagesWithSubset === 0) {
|
|
695
|
+
return { fontInfo: [], timings };
|
|
696
|
+
}
|
|
697
|
+
const lazyFallbackPhase = trackPhase('lazy load fallback CSS');
|
|
698
|
+
const relationsToRemove = new Set();
|
|
699
|
+
// Lazy load the original @font-face declarations of self-hosted fonts (unless omitFallbacks)
|
|
700
|
+
const originalRelations = new Set();
|
|
701
|
+
const fallbackCssAssetCache = new Map();
|
|
702
|
+
for (const htmlOrSvgAsset of htmlOrSvgAssets) {
|
|
703
|
+
const accumulatedFontFaceDeclarations = fontFaceDeclarationsByHtmlOrSvgAsset.get(htmlOrSvgAsset);
|
|
704
|
+
if (!accumulatedFontFaceDeclarations)
|
|
705
|
+
continue;
|
|
706
|
+
const containedRelationsByFontFaceRule = new Map();
|
|
707
|
+
for (const { relations } of accumulatedFontFaceDeclarations) {
|
|
708
|
+
for (const relation of relations) {
|
|
709
|
+
if (relation.from.hostname ===
|
|
710
|
+
'fonts.googleapis.com' || // Google Web Fonts handled separately below
|
|
711
|
+
containedRelationsByFontFaceRule.has(relation.node)) {
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
originalRelations.add(relation);
|
|
715
|
+
containedRelationsByFontFaceRule.set(relation.node, relation.from.outgoingRelations.filter((otherRelation) => otherRelation.node === relation.node));
|
|
716
|
+
}
|
|
1137
717
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
718
|
+
if (containedRelationsByFontFaceRule.size === 0 ||
|
|
719
|
+
omitFallbacks ||
|
|
720
|
+
htmlOrSvgAsset.type !== 'Html') {
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
// Group @font-face rules by their enclosing @media context so the
|
|
724
|
+
// fallback CSS preserves the original media-conditional loading.
|
|
725
|
+
// Walk up the ancestor chain in case the rule is nested (e.g.
|
|
726
|
+
// inside @supports inside @media).
|
|
727
|
+
const rulesByMedia = new Map();
|
|
728
|
+
for (const rule of containedRelationsByFontFaceRule.keys()) {
|
|
729
|
+
let mediaKey = '';
|
|
730
|
+
let ancestor = rule.parent;
|
|
731
|
+
while (ancestor) {
|
|
732
|
+
if (ancestor.type === 'atrule' &&
|
|
733
|
+
ancestor.name?.toLowerCase() === 'media') {
|
|
734
|
+
mediaKey = ancestor.params ?? '';
|
|
735
|
+
break;
|
|
736
|
+
}
|
|
737
|
+
ancestor = ancestor.parent;
|
|
738
|
+
}
|
|
739
|
+
if (!rulesByMedia.has(mediaKey))
|
|
740
|
+
rulesByMedia.set(mediaKey, []);
|
|
741
|
+
rulesByMedia
|
|
742
|
+
.get(mediaKey)
|
|
743
|
+
.push((0, fontFaceHelpers_1.getFontFaceDeclarationText)(rule, containedRelationsByFontFaceRule.get(rule) ?? []));
|
|
744
|
+
}
|
|
745
|
+
let fallbackCssText = '';
|
|
746
|
+
for (const [media, texts] of rulesByMedia) {
|
|
747
|
+
if (media) {
|
|
748
|
+
fallbackCssText += `@media ${media}{${texts.join('')}}`;
|
|
749
|
+
}
|
|
750
|
+
else {
|
|
751
|
+
fallbackCssText += texts.join('');
|
|
752
|
+
}
|
|
1169
753
|
}
|
|
1170
|
-
|
|
754
|
+
let cssAsset = fallbackCssAssetCache.get(fallbackCssText);
|
|
755
|
+
if (!cssAsset) {
|
|
756
|
+
cssAsset = assetGraph.addAsset({
|
|
757
|
+
type: 'Css',
|
|
758
|
+
text: fallbackCssText,
|
|
759
|
+
});
|
|
760
|
+
for (const relation of cssAsset.outgoingRelations) {
|
|
761
|
+
relation.hrefType = hrefType;
|
|
762
|
+
}
|
|
763
|
+
await cssAsset.minify();
|
|
764
|
+
cssAsset.url = `${subsetUrl}fallback-${cssAsset.md5Hex.slice(0, 10)}.css`;
|
|
765
|
+
fallbackCssAssetCache.set(fallbackCssText, cssAsset);
|
|
766
|
+
}
|
|
767
|
+
// Create a <link rel="stylesheet"> that asyncLoadStyleRelationWithFallback can convert to async with noscript fallback:
|
|
768
|
+
const fallbackHtmlStyle = htmlOrSvgAsset.addRelation({
|
|
769
|
+
type: 'HtmlStyle',
|
|
770
|
+
to: cssAsset,
|
|
771
|
+
});
|
|
772
|
+
asyncLoadStyleRelationWithFallback(htmlOrSvgAsset, fallbackHtmlStyle, hrefType);
|
|
773
|
+
relationsToRemove.add(fallbackHtmlStyle);
|
|
774
|
+
}
|
|
775
|
+
timings['lazy load fallback CSS'] = lazyFallbackPhase.end();
|
|
776
|
+
const removeFontFacePhase = trackPhase('remove original @font-face');
|
|
777
|
+
// Remove the original @font-face blocks, and don't leave behind empty stylesheets:
|
|
778
|
+
const maybeEmptyCssAssets = new Set();
|
|
779
|
+
for (const relation of originalRelations) {
|
|
780
|
+
const cssAsset = relation.from;
|
|
781
|
+
if (relation.node.parent) {
|
|
782
|
+
relation.node.parent.removeChild(relation.node);
|
|
783
|
+
}
|
|
784
|
+
relation.remove();
|
|
785
|
+
cssAsset.markDirty();
|
|
786
|
+
maybeEmptyCssAssets.add(cssAsset);
|
|
1171
787
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
788
|
+
for (const cssAsset of maybeEmptyCssAssets) {
|
|
789
|
+
if ((0, fontFaceHelpers_1.cssAssetIsEmpty)(cssAsset)) {
|
|
790
|
+
for (const incomingRelation of cssAsset.incomingRelations) {
|
|
791
|
+
incomingRelation.detach();
|
|
792
|
+
}
|
|
793
|
+
assetGraph.removeAsset(cssAsset);
|
|
794
|
+
}
|
|
1174
795
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
796
|
+
timings['remove original @font-face'] = removeFontFacePhase.end();
|
|
797
|
+
const googleCleanupPhase = trackPhase('Google Fonts + cleanup');
|
|
798
|
+
// Async load Google Web Fonts CSS. Skip the regex findAssets scan and
|
|
799
|
+
// the surrounding loop entirely when no Google Fonts were detected up
|
|
800
|
+
// front — the final detach loop below still runs because other phases
|
|
801
|
+
// (lazy fallback CSS) populate relationsToRemove.
|
|
802
|
+
const googleFontStylesheets = hasGoogleFonts
|
|
803
|
+
? assetGraph.findAssets({
|
|
804
|
+
type: 'Css',
|
|
805
|
+
url: { $regex: googleFontsCssUrlRegex },
|
|
806
|
+
})
|
|
807
|
+
: [];
|
|
808
|
+
const selfHostedGoogleCssByUrl = new Map();
|
|
809
|
+
for (const googleFontStylesheet of googleFontStylesheets) {
|
|
810
|
+
const seenPages = new Set(); // Only do the work once for each font on each page
|
|
811
|
+
for (const googleFontStylesheetRelation of googleFontStylesheet.incomingRelations) {
|
|
812
|
+
let htmlParents;
|
|
813
|
+
if (googleFontStylesheetRelation.type === 'CssImport') {
|
|
814
|
+
// Gather Html parents. Relevant if we are dealing with CSS @import relations
|
|
815
|
+
htmlParents = getParents(googleFontStylesheetRelation.to, {
|
|
816
|
+
type: { $in: ['Html', 'Svg'] },
|
|
817
|
+
isInline: false,
|
|
818
|
+
isLoaded: true,
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
else if (['Html', 'Svg'].includes(googleFontStylesheetRelation.from.type ?? '')) {
|
|
822
|
+
htmlParents = [googleFontStylesheetRelation.from];
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
htmlParents = [];
|
|
826
|
+
}
|
|
827
|
+
for (const htmlParent of htmlParents) {
|
|
828
|
+
if (seenPages.has(htmlParent))
|
|
829
|
+
continue;
|
|
830
|
+
seenPages.add(htmlParent);
|
|
831
|
+
relationsToRemove.add(googleFontStylesheetRelation);
|
|
832
|
+
if (omitFallbacks)
|
|
833
|
+
continue;
|
|
834
|
+
let selfHostedGoogleFontsCssAsset = selfHostedGoogleCssByUrl.get(googleFontStylesheetRelation.to.url);
|
|
835
|
+
if (!selfHostedGoogleFontsCssAsset) {
|
|
836
|
+
selfHostedGoogleFontsCssAsset =
|
|
837
|
+
await createSelfHostedGoogleFontsCssAsset(assetGraph, googleFontStylesheetRelation.to, formats, hrefType, subsetUrl);
|
|
838
|
+
await selfHostedGoogleFontsCssAsset.minify();
|
|
839
|
+
selfHostedGoogleCssByUrl.set(googleFontStylesheetRelation.to.url, selfHostedGoogleFontsCssAsset);
|
|
840
|
+
}
|
|
841
|
+
const selfHostedFallbackRelation = htmlParent.addRelation({
|
|
842
|
+
type: `${htmlParent.type}Style`,
|
|
843
|
+
to: selfHostedGoogleFontsCssAsset,
|
|
844
|
+
hrefType,
|
|
845
|
+
}, 'lastInBody');
|
|
846
|
+
relationsToRemove.add(selfHostedFallbackRelation);
|
|
847
|
+
if (htmlParent.type === 'Html') {
|
|
848
|
+
asyncLoadStyleRelationWithFallback(htmlParent, selfHostedFallbackRelation, hrefType);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
1198
851
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
if (
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
}
|
|
852
|
+
googleFontStylesheet.unload();
|
|
853
|
+
}
|
|
854
|
+
// Clean up, making sure not to detach the same relation twice, eg. when multiple pages use the same stylesheet that imports a font
|
|
855
|
+
for (const relation of relationsToRemove) {
|
|
856
|
+
relation.detach();
|
|
857
|
+
}
|
|
858
|
+
timings['Google Fonts + cleanup'] = googleCleanupPhase.end();
|
|
859
|
+
const injectPhase = trackPhase('inject subset font-family into CSS/SVG');
|
|
860
|
+
// Use subsets in font-family:
|
|
861
|
+
const webfontNameMap = Object.create(null);
|
|
862
|
+
for (const { fontUsages } of htmlOrSvgAssetTextsWithProps) {
|
|
863
|
+
for (const { subsets, fontFamilies, props } of fontUsages) {
|
|
864
|
+
if (subsets) {
|
|
865
|
+
for (const fontFamily of fontFamilies) {
|
|
866
|
+
webfontNameMap[fontFamily.toLowerCase()] =
|
|
867
|
+
`${props['font-family']}__subset`;
|
|
868
|
+
}
|
|
1217
869
|
}
|
|
1218
|
-
}
|
|
1219
870
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
871
|
+
}
|
|
872
|
+
let customPropertyDefinitions;
|
|
873
|
+
const cssAssetsDirtiedByCustomProps = new Set();
|
|
874
|
+
// Inject subset font name before original webfont in SVG font-family attributes
|
|
875
|
+
const svgAssets = assetGraph.findAssets({ type: 'Svg' });
|
|
876
|
+
for (const svgAsset of svgAssets) {
|
|
877
|
+
if (!svgAsset.isLoaded)
|
|
878
|
+
continue;
|
|
879
|
+
let changesMade = false;
|
|
880
|
+
for (const element of Array.from(svgAsset.parseTree.querySelectorAll('[font-family]'))) {
|
|
881
|
+
const fontFamilies = cssListHelpers.splitByCommas(element.getAttribute('font-family'));
|
|
882
|
+
for (let i = 0; i < fontFamilies.length; i += 1) {
|
|
883
|
+
const subsetFontFamily = webfontNameMap[cssFontParser.parseFontFamily(fontFamilies[i])[0].toLowerCase()];
|
|
884
|
+
if (subsetFontFamily && !fontFamilies.includes(subsetFontFamily)) {
|
|
885
|
+
fontFamilies.splice(i, omitFallbacks ? 1 : 0, (0, fontFaceHelpers_1.maybeCssQuote)(subsetFontFamily));
|
|
886
|
+
i += 1;
|
|
887
|
+
element.setAttribute('font-family', fontFamilies.join(', '));
|
|
888
|
+
changesMade = true;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
1237
891
|
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
const fontFamilies =
|
|
1241
|
-
fontProperties && fontProperties['font-family'].map(unquote);
|
|
1242
|
-
if (!fontFamilies) return;
|
|
1243
|
-
|
|
1244
|
-
const subsetFontFamily = webfontNameMap[fontFamilies[0].toLowerCase()];
|
|
1245
|
-
if (!subsetFontFamily || fontFamilies.includes(subsetFontFamily))
|
|
1246
|
-
return;
|
|
1247
|
-
|
|
1248
|
-
// Rebuild the font shorthand with the subset family prepended
|
|
1249
|
-
if (omitFallbacks) {
|
|
1250
|
-
fontFamilies.shift();
|
|
892
|
+
if (changesMade) {
|
|
893
|
+
svgAsset.markDirty();
|
|
1251
894
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
? `${fontProperties['font-weight']} `
|
|
1258
|
-
: '';
|
|
1259
|
-
const lineHeightSuffix = fontProperties['line-height']
|
|
1260
|
-
? `/${fontProperties['line-height']}`
|
|
1261
|
-
: '';
|
|
1262
|
-
cssRule.value = `${stylePrefix}${weightPrefix}${
|
|
1263
|
-
fontProperties['font-size']
|
|
1264
|
-
}${lineHeightSuffix} ${fontFamilies.map(maybeCssQuote).join(', ')}`;
|
|
1265
|
-
changesMade = true;
|
|
1266
|
-
}
|
|
895
|
+
}
|
|
896
|
+
// Inject subset font name before original webfont in CSS
|
|
897
|
+
const cssAssets = assetGraph.findAssets({
|
|
898
|
+
type: 'Css',
|
|
899
|
+
isLoaded: true,
|
|
1267
900
|
});
|
|
1268
|
-
|
|
1269
|
-
|
|
901
|
+
const parseTreeToAsset = new Map();
|
|
902
|
+
for (const cssAsset of cssAssets) {
|
|
903
|
+
parseTreeToAsset.set(cssAsset.parseTree, cssAsset);
|
|
904
|
+
}
|
|
905
|
+
for (const cssAsset of cssAssets) {
|
|
906
|
+
let changesMade = false;
|
|
907
|
+
cssAsset.eachRuleInParseTree((cssRule) => {
|
|
908
|
+
if (cssRule.parent.type !== 'rule' || cssRule.type !== 'decl')
|
|
909
|
+
return;
|
|
910
|
+
const propName = cssRule.prop.toLowerCase();
|
|
911
|
+
if ((propName === 'font' || propName === 'font-family') &&
|
|
912
|
+
cssRule.value.includes('var(')) {
|
|
913
|
+
if (!customPropertyDefinitions) {
|
|
914
|
+
customPropertyDefinitions = findCustomPropertyDefinitions(cssAssets);
|
|
915
|
+
}
|
|
916
|
+
for (const customPropertyName of extractReferencedCustomPropertyNames(cssRule.value)) {
|
|
917
|
+
for (const relatedCssRule of [
|
|
918
|
+
cssRule,
|
|
919
|
+
...(customPropertyDefinitions[customPropertyName] || []),
|
|
920
|
+
]) {
|
|
921
|
+
const modifiedValue = injectSubsetDefinitions(relatedCssRule.value, webfontNameMap, omitFallbacks // replaceOriginal
|
|
922
|
+
);
|
|
923
|
+
if (modifiedValue !== relatedCssRule.value) {
|
|
924
|
+
relatedCssRule.value = modifiedValue;
|
|
925
|
+
const ownerAsset = parseTreeToAsset.get(relatedCssRule.root());
|
|
926
|
+
if (ownerAsset) {
|
|
927
|
+
cssAssetsDirtiedByCustomProps.add(ownerAsset);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
else if (propName === 'font-family') {
|
|
934
|
+
const fontFamilies = cssListHelpers.splitByCommas(cssRule.value);
|
|
935
|
+
for (let i = 0; i < fontFamilies.length; i += 1) {
|
|
936
|
+
const subsetFontFamily = webfontNameMap[cssFontParser.parseFontFamily(fontFamilies[i])[0].toLowerCase()];
|
|
937
|
+
if (subsetFontFamily && !fontFamilies.includes(subsetFontFamily)) {
|
|
938
|
+
fontFamilies.splice(i, omitFallbacks ? 1 : 0, (0, fontFaceHelpers_1.maybeCssQuote)(subsetFontFamily));
|
|
939
|
+
i += 1;
|
|
940
|
+
cssRule.value = fontFamilies.join(', ');
|
|
941
|
+
changesMade = true;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
else if (propName === 'font') {
|
|
946
|
+
const fontProperties = cssFontParser.parseFont(cssRule.value);
|
|
947
|
+
const fontFamilies = fontProperties && fontProperties['font-family'].map(unquote);
|
|
948
|
+
if (!fontFamilies)
|
|
949
|
+
return;
|
|
950
|
+
const subsetFontFamily = webfontNameMap[fontFamilies[0].toLowerCase()];
|
|
951
|
+
if (!subsetFontFamily || fontFamilies.includes(subsetFontFamily))
|
|
952
|
+
return;
|
|
953
|
+
// Rebuild the font shorthand with the subset family prepended
|
|
954
|
+
if (omitFallbacks) {
|
|
955
|
+
fontFamilies.shift();
|
|
956
|
+
}
|
|
957
|
+
fontFamilies.unshift(subsetFontFamily);
|
|
958
|
+
const stylePrefix = fontProperties['font-style']
|
|
959
|
+
? `${fontProperties['font-style']} `
|
|
960
|
+
: '';
|
|
961
|
+
const weightPrefix = fontProperties['font-weight']
|
|
962
|
+
? `${fontProperties['font-weight']} `
|
|
963
|
+
: '';
|
|
964
|
+
const lineHeightSuffix = fontProperties['line-height']
|
|
965
|
+
? `/${fontProperties['line-height']}`
|
|
966
|
+
: '';
|
|
967
|
+
cssRule.value = `${stylePrefix}${weightPrefix}${fontProperties['font-size']}${lineHeightSuffix} ${fontFamilies.map(fontFaceHelpers_1.maybeCssQuote).join(', ')}`;
|
|
968
|
+
changesMade = true;
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
if (changesMade) {
|
|
972
|
+
cssAsset.markDirty();
|
|
973
|
+
}
|
|
1270
974
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
return {
|
|
1287
|
-
fontInfo: buildFontInfoReport(htmlOrSvgAssetTextsWithProps),
|
|
1288
|
-
timings,
|
|
1289
|
-
};
|
|
975
|
+
for (const dirtiedAsset of cssAssetsDirtiedByCustomProps) {
|
|
976
|
+
dirtiedAsset.markDirty();
|
|
977
|
+
}
|
|
978
|
+
timings['inject subset font-family'] = injectPhase.end();
|
|
979
|
+
const orphanCleanupPhase = trackPhase('source maps + orphan cleanup');
|
|
980
|
+
if (sourceMaps) {
|
|
981
|
+
await rewriteCssSourceMaps(assetGraph, hrefType);
|
|
982
|
+
}
|
|
983
|
+
removeOrphanedAssets(assetGraph, potentiallyOrphanedAssets);
|
|
984
|
+
timings['source maps + orphan cleanup'] = orphanCleanupPhase.end();
|
|
985
|
+
return {
|
|
986
|
+
fontInfo: buildFontInfoReport(htmlOrSvgAssetTextsWithProps),
|
|
987
|
+
timings,
|
|
988
|
+
};
|
|
1290
989
|
}
|
|
1291
|
-
|
|
1292
990
|
module.exports = subsetFonts;
|
|
991
|
+
//# sourceMappingURL=subsetFonts.js.map
|