Skip to content

Commit

Permalink
Merge pull request #111 from Munter/test/issue-80
Browse files Browse the repository at this point in the history
Implement an "ssr" mode where all subsets are made available on every page
  • Loading branch information
papandreou authored Jul 26, 2020
2 parents 7159e05 + 33d44e9 commit 231383e
Show file tree
Hide file tree
Showing 15 changed files with 357 additions and 87 deletions.
202 changes: 119 additions & 83 deletions lib/subsetFonts.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ const initialValueByProp = _.pick(require('./initialValueByProp'), [
'font-stretch',
]);

function uniqueChars(text) {
return [...new Set([...text])].sort().join('');
}

function cssQuoteIfNecessary(value) {
if (/^\w+$/.test(value)) {
return value;
Expand Down Expand Up @@ -99,10 +103,13 @@ function getFontFaceDeclarationText(node, relations) {

// Takes the output of fontTracer
function groupTextsByFontFamilyProps(
htmlAsset,
textByPropsArray,
availableFontFaceDeclarations
availableFontFaceDeclarations,
isOnPageFn
) {
const snappedTexts = _.flatMapDeep(textByPropsArray, (textAndProps) => {
const isOnPage = isOnPageFn(textAndProps);
const family = textAndProps.props['font-family'];
if (family === undefined) {
return [];
Expand Down Expand Up @@ -135,10 +142,12 @@ function groupTextsByFontFamilyProps(
const fontUrl = getPreferredFontUrl(relations);

return {
htmlAsset: textAndProps.htmlAsset,
text: textAndProps.text,
props,
fontRelations: relations,
fontUrl,
preload: isOnPage,
};
});
}).filter((textByProps) => textByProps && textByProps.fontUrl);
Expand All @@ -147,12 +156,11 @@ function groupTextsByFontFamilyProps(

return _.map(textsByFontUrl, (textsPropsArray, fontUrl) => {
const texts = textsPropsArray.map((obj) => obj.text);
const preload = textsPropsArray.some((obj) => obj.preload);
const fontFamilies = new Set(
textsPropsArray.map((obj) => obj.props['font-family'])
);

const pageText = [...new Set([...texts.join('')])].sort().join('');

let smallestOriginalSize;
let smallestOriginalFormat;
for (const relation of textsPropsArray[0].fontRelations) {
Expand All @@ -169,11 +177,17 @@ function groupTextsByFontFamilyProps(
smallestOriginalSize,
smallestOriginalFormat,
texts,
pageText,
text: pageText,
pageText: uniqueChars(
textsPropsArray
.filter((textsProps) => textsProps.htmlAsset === htmlAsset)
.map((obj) => obj.text)
.join('')
),
text: uniqueChars(texts.join('')),
props: { ...textsPropsArray[0].props },
fontUrl,
fontFamilies,
preload,
};
});
}
Expand Down Expand Up @@ -694,7 +708,7 @@ async function subsetFonts(
const potentiallyOrphanedAssets = new Set();

const headlessBrowser = dynamic && new HeadlessBrowser({ console });

const globalTextByProps = [];
try {
for (const htmlAsset of htmlAssets) {
const accumulatedFontFaceDeclarations = [];
Expand Down Expand Up @@ -768,12 +782,13 @@ async function subsetFonts(
if (headlessBrowser) {
textByProps.push(...(await headlessBrowser.tracePage(htmlAsset)));
}
for (const textByPropsEntry of textByProps) {
textByPropsEntry.htmlAsset = htmlAsset;
}
globalTextByProps.push(...textByProps);
htmlAssetTextsWithProps.push({
htmlAsset,
fontUsages: groupTextsByFontFamilyProps(
textByProps,
accumulatedFontFaceDeclarations
),
textByProps,
accumulatedFontFaceDeclarations,
});
}
Expand All @@ -784,6 +799,20 @@ async function subsetFonts(
}
}

for (const htmlAssetTextsWithPropsEntry of htmlAssetTextsWithProps) {
const {
htmlAsset,
textByProps,
accumulatedFontFaceDeclarations,
} = htmlAssetTextsWithPropsEntry;
htmlAssetTextsWithPropsEntry.fontUsages = groupTextsByFontFamilyProps(
htmlAsset,
subsetPerPage ? textByProps : globalTextByProps,
accumulatedFontFaceDeclarations,
(t) => (subsetPerPage ? true : textByProps.includes(t))
);
}

if (omitFallbacks) {
for (const htmlAsset of htmlAssets) {
const accumulatedFontFaceDeclarations = fontFaceDeclarationsByHtmlAsset.get(
Expand All @@ -803,37 +832,6 @@ async function subsetFonts(
}
}

if (htmlAssetTextsWithProps.length <= 1) {
subsetPerPage = false;
}

if (!subsetPerPage) {
const globalFontUsage = {};

// Gather all texts
for (const htmlAssetTextWithProps of htmlAssetTextsWithProps) {
for (const fontUsage of htmlAssetTextWithProps.fontUsages) {
if (!globalFontUsage[fontUsage.fontUrl]) {
globalFontUsage[fontUsage.fontUrl] = [];
}

globalFontUsage[fontUsage.fontUrl].push(fontUsage.text);
}
}

// Merge subset values, unique glyphs, sort
for (const htmlAssetTextWithProps of htmlAssetTextsWithProps) {
for (const fontUsage of htmlAssetTextWithProps.fontUsages) {
fontUsage.pageText = fontUsage.text;
fontUsage.text = [
...new Set([...globalFontUsage[fontUsage.fontUrl].join('')]),
]
.sort()
.join('');
}
}
}

if (fontDisplay) {
for (const htmlAssetTextWithProps of htmlAssetTextsWithProps) {
for (const fontUsage of htmlAssetTextWithProps.fontUsages) {
Expand Down Expand Up @@ -1008,7 +1006,7 @@ These glyphs are used on your site, but they don't exist in the font you applied
})) {
if (relation.type === 'HtmlPrefetchLink') {
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/assetgraph/assetgraph/issues`
`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/Munter/subfont/issues`
);
err.asset = relation.from;
err.relation = relation;
Expand All @@ -1020,52 +1018,63 @@ These glyphs are used on your site, but they don't exist in the font you applied
}
}

if (unsubsettedFontUsages.length > 0) {
const unsubsettedFontUsagesToPreload = unsubsettedFontUsages.filter(
(fontUsage) => fontUsage.preload
);

if (unsubsettedFontUsagesToPreload.length > 0) {
// Insert <link rel="preload">
const preloadRelations = unsubsettedFontUsages.map((fontUsage) => {
// Always preload unsubsetted font files, they might be any format, so can't be clever here
return htmlAsset.addRelation(
{
type: 'HtmlPreloadLink',
hrefType: 'rootRelative',
to: fontUsage.fontUrl,
as: 'font',
},
'before',
insertionPoint
);
});
const preloadRelations = unsubsettedFontUsagesToPreload.map(
(fontUsage) => {
// Always preload unsubsetted font files, they might be any format, so can't be clever here
return htmlAsset.addRelation(
{
type: 'HtmlPreloadLink',
hrefType: 'rootRelative',
to: fontUsage.fontUrl,
as: 'font',
},
'before',
insertionPoint
);
}
);

// Generate JS fallback for browser that don't support <link rel="preload">
const preloadData = unsubsettedFontUsages.map((fontUsage, idx) => {
const preloadRelation = preloadRelations[idx];

const formatMap = {
'.woff': 'woff',
'.woff2': 'woff2',
'.ttf': 'truetype',
'.svg': 'svg',
'.eot': 'embedded-opentype',
};
const name = fontUsage.props['font-family'];
const props = Object.keys(initialValueByProp).reduce((result, prop) => {
if (
fontUsage.props[prop] !==
normalizeFontPropertyValue(prop, initialValueByProp[prop])
) {
result[prop] = fontUsage.props[prop];
}
return result;
}, {});
const preloadData = unsubsettedFontUsagesToPreload.map(
(fontUsage, idx) => {
const preloadRelation = preloadRelations[idx];

const formatMap = {
'.woff': 'woff',
'.woff2': 'woff2',
'.ttf': 'truetype',
'.svg': 'svg',
'.eot': 'embedded-opentype',
};
const name = fontUsage.props['font-family'];
const props = Object.keys(initialValueByProp).reduce(
(result, prop) => {
if (
fontUsage.props[prop] !==
normalizeFontPropertyValue(prop, initialValueByProp[prop])
) {
result[prop] = fontUsage.props[prop];
}
return result;
},
{}
);

return `new FontFace(
return `new FontFace(
"${name}",
"url('" + "${preloadRelation.href}".toString('url') + "') format('${
formatMap[preloadRelation.to.extension]
}')",
formatMap[preloadRelation.to.extension]
}')",
${JSON.stringify(props)}
).load().then(void 0, function () {});`;
});
}
);

const originalFontJsPreloadAsset = htmlAsset.addRelation(
{
Expand Down Expand Up @@ -1183,6 +1192,22 @@ These glyphs are used on your site, but they don't exist in the font you applied
fontAsset.contentType === 'font/woff2' &&
fontRelation.href.startsWith('/subfont/')
) {
const fontFaceDeclaration = fontRelation.node;
const originalFontFamily = unquote(
fontFaceDeclaration.nodes.find(
(node) => node.prop === 'font-family'
).value
).replace(/__subset$/, '');
if (
!fontUsages.some(
(fontUsage) =>
fontUsage.fontFamilies.has(originalFontFamily) &&
fontUsage.preload
)
) {
continue;
}

// Only <link rel="preload"> for woff2 files
// Preload support is a subset of woff2 support:
// - https://caniuse.com/#search=woff2
Expand Down Expand Up @@ -1264,9 +1289,20 @@ These glyphs are used on your site, but they don't exist in the font you applied
});

if (url) {
fontFaceContructorCalls.push(
`new FontFace("${name}", ${url}, ${JSON.stringify(props)}).load();`
);
const originalFontFamily = name.replace(/__subset$/, '');
if (
fontUsages.some(
(fontUsage) =>
fontUsage.fontFamilies.has(originalFontFamily) &&
fontUsage.preload
)
) {
fontFaceContructorCalls.push(
`new FontFace("${name}", ${url}, ${JSON.stringify(
props
)}).load();`
);
}
}
});

Expand Down
Loading

0 comments on commit 231383e

Please sign in to comment.