Browse Source

Detect non WOFF2 fonts and estimate gain

Gaël Métais 4 years ago
parent
commit
d93af3d6e4

+ 20 - 0
front/src/views/rule.html

@@ -483,6 +483,26 @@
         </div>
     </div>
 
+    <div ng-if="policyName === 'nonWoff2Fonts'">
+        <h3 ng-if="rule.value > 0">{{rule.value | bytes}} could be saved on <ng-pluralize count="rule.offendersObj.list.fonts.length" when="{'one': '1 file', 'other': '{} files'}"></ng-pluralize></h3>
+        <div class="table">
+            <div class="headers">
+                <div>File</div>
+                <div>Current weight</div>
+                <div>Woff 2 weight</div>
+                <div>Gain</div>
+            </div>
+            <div ng-repeat="file in rule.offendersObj.list.fonts | orderBy:'-gain'">
+                <div>
+                    <url-link url="file.url" max-length="70"></url-link>
+                </div>
+                <div>{{file.originalSize | bytes}}</div>
+                <div>{{file.woff2Size | bytes}}</div>
+                <div><b>-{{file.gain | bytes}}</b></div>
+            </div>
+        </div>
+    </div>
+
     <div ng-if="policyName === 'http2'">
         <h3>Protocols advertised by the server</h3>
         <div class="offendersTable">

+ 10 - 0
lib/metadata/policies.js

@@ -927,6 +927,16 @@ var policies = {
             return offenders;
         }
     },*/
+    "nonWoff2Fonts": {
+        "tool": "redownload",
+        "label": "Woff 2",
+        "message": "<p>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.</p>",
+        "isOkThreshold": 0,
+        "isBadThreshold": 61440,
+        "isAbnormalThreshold": 122880,
+        "hasOffenders": true,
+        "unit": 'bytes'
+    },
     "oldHttpProtocol": {
         "label": "HTTP/1 requests",
         "message": "<p>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!</p><p>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.</p>",

+ 3 - 1
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": {

+ 12 - 6
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: {

+ 81 - 4
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);
             });
 
+        }
+
+        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();
         

+ 40 - 0
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;

+ 3 - 1
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",