From d93af3d6e4406398f2db2f72b90f7fa77a8f1ae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 21 Dec 2020 01:18:56 +0100 Subject: [PATCH] Detect non WOFF2 fonts and estimate gain --- front/src/views/rule.html | 20 +++++ lib/metadata/policies.js | 10 +++ lib/metadata/scoreProfileGeneric.json | 4 +- lib/tools/redownload/contentTypeChecker.js | 18 +++-- lib/tools/redownload/fontAnalyzer.js | 87 ++++++++++++++++++++-- lib/tools/redownload/redownload.js | 40 ++++++++++ package.json | 4 +- 7 files changed, 170 insertions(+), 13 deletions(-) diff --git a/front/src/views/rule.html b/front/src/views/rule.html index ee74fbd..9b063a6 100644 --- a/front/src/views/rule.html +++ b/front/src/views/rule.html @@ -483,6 +483,26 @@ +
+

{{rule.value | bytes}} could be saved on

+
+
+
File
+
Current weight
+
Woff 2 weight
+
Gain
+
+
+
+ +
+
{{file.originalSize | bytes}}
+
{{file.woff2Size | bytes}}
+
-{{file.gain | bytes}}
+
+
+
+

Protocols advertised by the server

diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index befe3df..b6d7e3d 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -927,6 +927,16 @@ var policies = { return offenders; } },*/ + "nonWoff2Fonts": { + "tool": "redownload", + "label": "Woff 2", + "message": "

The fonts listed here could be lighter if they were served with the latest woff 2 font file format. Some online tools can help you easily convert older formats to woff 2.

", + "isOkThreshold": 0, + "isBadThreshold": 61440, + "isAbnormalThreshold": 122880, + "hasOffenders": true, + "unit": 'bytes' + }, "oldHttpProtocol": { "label": "HTTP/1 requests", "message": "

HTTP/2 is the latest version of the HTTP protocol. It is designed to optimize load speed. HTTP/3 will come soon and should be even faster!

When a domain sends more than 4 requests over HTTP/1, this metric counts one point for each new request. Below 5 requests, the benefits of HTTP/2 are generally less significant.

", diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json index ab4abae..2ebd278 100644 --- a/lib/metadata/scoreProfileGeneric.json +++ b/lib/metadata/scoreProfileGeneric.json @@ -87,7 +87,9 @@ "label": "Web fonts", "policies": { "fontsCount": 1, - "heavyFonts": 1 + "heavyFonts": 0.5, + //"unusedUnicodeRanges": 0.5, + "nonWoff2Fonts": 0.5 } }, "serverConfig": { diff --git a/lib/tools/redownload/contentTypeChecker.js b/lib/tools/redownload/contentTypeChecker.js index 4d7d631..e83924f 100644 --- a/lib/tools/redownload/contentTypeChecker.js +++ b/lib/tools/redownload/contentTypeChecker.js @@ -15,9 +15,6 @@ var ContentTypeChecker = function() { function checkContentType(entry) { var deferred = Q.defer(); - - debug('Entering contentTypeChecker'); - debug(entry); // Setting isSomething values: switch(entry.type) { @@ -59,9 +56,15 @@ var ContentTypeChecker = function() { try { foundType = findContentType(entry.weightCheck.bodyBuffer); - if (foundType !== null && foundType.type !== entry.type) { - debug('Content type %s is wrong for %s. It should be %s.', entry.type, entry.ulr, foundType.type); - rewriteContentType(entry, foundType); + if (foundType !== null) { + if (foundType.type === 'webfont') { + // Always rewrite fonts for woff2 checking + rewriteContentType(entry, foundType); + } else if (foundType.type !== entry.type) { + // For other kind of files, just rewrite if needed + debug('Content type %s is wrong for %s. It should be %s.', entry.type, entry.ulr, foundType.type); + rewriteContentType(entry, foundType); + } } } catch(err) { @@ -188,6 +191,7 @@ var ContentTypeChecker = function() { updateFn: function(entry) { entry.type = 'webfont'; entry.isWebFont = true; + entry.isWoff = true; } }, woff2: { @@ -196,6 +200,7 @@ var ContentTypeChecker = function() { updateFn: function(entry) { entry.type = 'webfont'; entry.isWebFont = true; + entry.isWoff2 = true; } }, otf: { @@ -212,6 +217,7 @@ var ContentTypeChecker = function() { updateFn: function(entry) { entry.type = 'webfont'; entry.isWebFont = true; + entry.isTTF = true; } }, eot: { diff --git a/lib/tools/redownload/fontAnalyzer.js b/lib/tools/redownload/fontAnalyzer.js index 0fab2f5..3cddcfc 100644 --- a/lib/tools/redownload/fontAnalyzer.js +++ b/lib/tools/redownload/fontAnalyzer.js @@ -2,6 +2,8 @@ var debug = require('debug')('ylt:fontAnalyzer'); var Q = require('q'); var fontkit = require('fontkit'); +var woffTools = require('woff-tools'); +var ttf2woff2 = require('ttf2woff2'); var FontAnalyzer = function() { @@ -19,25 +21,100 @@ var FontAnalyzer = function() { if (entry.isWebFont) { debug('File %s is a font. Let\'s have a look inside!', entry.url); - getMetricsFromFont(entry, charsListOnPage) + convertToWoff2(entry) + + .then(function(entry) { + return getMetricsFromFont(entry, charsListOnPage); + }) .then(function(fontMetrics) { entry.fontMetrics = fontMetrics; - deferred.resolve(entry); }) .fail(function(error) { debug('Could not open the font: %s', error); - deferred.resolve(entry); }); - } else { - deferred.resolve(entry); } + + deferred.resolve(entry); return deferred.promise; } + function convertToWoff2(entry) { + var deferred = Q.defer(); + + debug('Entering font format converter...'); + + if (entry.isWoff2) { + + debug('File is already a woff2.'); + deferred.resolve(entry); + + } else if (entry.isWoff) { + + debug('File is a woff. Let\'s convert to woff2'); + + try { + + var fileSize = entry.weightCheck.bodySize; + debug('Current file size is %d', fileSize); + + var ttf = woffTools.toSfnt(entry.weightCheck.bodyBuffer); + var woff2 = ttf2woff2(ttf); + + var newFileSize = woff2.length; + + debug('New image size is %d', newFileSize); + debug('Filesize is %d bytes smaller (-%d%)', fileSize - newFileSize, Math.round((fileSize - newFileSize) * 100 / fileSize)); + entry.weightCheck.sizeAsWoff2 = newFileSize; + + deferred.resolve(entry); + + } catch(error) { + deferred.reject(error); + } + + } else if (entry.isTtf) { + + debug('File is a TTF. Let\'s convert to woff2'); + + try { + + var fileSize = entry.weightCheck.bodySize; + debug('Current file size is %d', fileSize); + + var woff2 = ttf2woff2(entry.weightCheck.bodyBuffer); + + var newFileSize = woff2.length; + + debug('New image size is %d', newFileSize); + debug('Filesize is %d bytes smaller (-%d%)', fileSize - newFileSize, Math.round((fileSize - newFileSize) * 100 / fileSize)); + entry.weightCheck.sizeAsWoff2 = newFileSize; + + deferred.resolve(entry); + + } catch(error) { + deferred.reject(error); + } + + } else { + // Other font formats are not handled + deferred.resolve(entry); + } + + return deferred.promise; + } + + // The gain is estimated of enough value if it's over 1KB or over 20%, + // but it's ignored if is below 100 bytes + function gainIsEnough(oldWeight, newWeight) { + var gain = oldWeight - newWeight; + var ratio = gain / oldWeight; + return (gain > 2048 || (ratio > 0.2 && gain > 100)); + } + function getMetricsFromFont(entry, charsListOnPage) { var deferred = Q.defer(); diff --git a/lib/tools/redownload/redownload.js b/lib/tools/redownload/redownload.js index d770df7..86927c4 100644 --- a/lib/tools/redownload/redownload.js +++ b/lib/tools/redownload/redownload.js @@ -167,6 +167,10 @@ var Redownload = function() { offenders.fontsCount = listFonts(results); metrics.fontsCount = offenders.fontsCount.count; + // Conversion to woff2 + offenders.nonWoff2Fonts = listNonWoff2Fonts(results); + metrics.nonWoff2Fonts = offenders.nonWoff2Fonts.totalGain; + // Heavy fonts offenders.heavyFonts = listHeavyFonts(results); metrics.heavyFonts = offenders.heavyFonts.totalGain; @@ -551,6 +555,42 @@ var Redownload = function() { }; } + function listNonWoff2Fonts(requests) { + var results = { + totalGain: 0, + fonts: [] + }; + + requests.forEach(function(req) { + if (!req.isWoff2 && req.weightCheck.sizeAsWoff2) { + var before = req.weightCheck.bodySize; + var after = req.weightCheck.sizeAsWoff2; + var gain = before - after; + + var type = null; + if (req.isWoff) { + type = 'woff'; + } else if (req.isTtf) { + type = 'ttf'; + } + + if (gain > 200) { + results.totalGain += gain; + + results.fonts.push({ + url: req.url, + originalSize: before, + type: type, + woff2Size: after, + gain: gain + }); + } + } + }); + + return results; + } + function listHeavyFonts(requests) { var list = []; var totalGain = 0; diff --git a/package.json b/package.json index 92ae9fb..1e31782 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,9 @@ "request": "2.88.0", "rimraf": "2.6.3", "temporary": "0.0.8", - "uglify-js": "3.4.9" + "ttf2woff2": "3.0.0", + "uglify-js": "3.4.9", + "woff-tools": "0.1.0" }, "devDependencies": { "chai": "~4.2.0",