Search code examples
htmlcssfont-facecdnwebfonts

How to correctly preload a font whose filename is hashed, and whose style is in a CDN?


I would like to preload Material Icons font thanks to:

<link rel="preload" href="https://fonts.gstatic.com/s/materialicons/v125/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2" as="font" type="font/woff2" crossorigin="anonymous">

In fact, it works!

However, in the filename, we have a hash/UUID: v125/flUhRq6tzZclQEJ-Vdg-IuiaDsNc; consequently, if Google publishes a new release, for example v126/sjboabchdiamblq-Abf-abvichef, then my preload won't work!

For more details, I use their CDN like this:

<link
  href="https://fonts.googleapis.com/icon?family=Material+Icons"
  rel="stylesheet"
/>

Which returns this CSS:

@font-face {
  font-family: 'Material Icons';
  font-style: normal;
  font-weight: 400;
  src: url(https://fonts.gstatic.com/s/materialicons/v125/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2');
}

.material-icons {
  font-family: 'Material Icons';
  [...]
}

Notice: I'm working with Angular, and I want the loading of the font start immediately, and not when the application is loaded, i.e. when a Material Icon is displayed.Moreover, even if the CSS file loads immediately, the font won't start loading until a Material Icon is displayed.


Solution

  • I found a solution, thanks to https://www.npmjs.com/package/@angular-builders/custom-webpack, and using a library for mMaterial Icons (https://www.npmjs.com/package/material-icons)

    This builder allows to transform the index.html after the build process.

    1. Add classic preloading in index.html (works in dev)
    <link rel="preload" href="/material-icons.woff2" as="font" type="font/woff2" crossorigin="anonymous">
    <link rel="preload" href="/material-icons-outlined.woff2" as="font" type="font/woff2" crossorigin="anonymous">
    
    1. Add "indexTransform": "index-html-transform.js" in angular.json like this:
    "configurations": {
      "production": {
        [...],
        "indexTransform": "index-html-transform.js"
      }
    }
    
    1. Create index-html-transform.js:
    const fs = require("fs");
    
    /**
     * @param {string} indexHtmlSource
     * @return {string}
     */
    const setMaterialIconsFontsPreloading = (indexHtmlSource) => {
      const allOutputFilenames = fs.readdirSync("./dist/essai-preload-mat-icons");
    
      const requiredMatIconsFontsMatches = indexHtmlSource.matchAll(/(material-icons.*)\.woff2/g);
    
      /**
       * @exemple `['material-icons', 'material-icons-outlined']`
       */
      const requiredIconTypes = [...requiredMatIconsFontsMatches].map((match) => match[1]);
    
      return requiredIconTypes.reduce((previousIndexHtml, requiredIconType) => {
        /**
         * @exemple `'material-icons-outlined.woff2'`
         */
        const inputFilename = `${requiredIconType}.woff2`;
    
        /**
         * @exemple `'material-icons-outlined.125af8545b6.woff2'`
         */
    
        const outputFilename = allOutputFilenames.find((outputFilenameItem) => outputFilenameItem.match(`^${requiredIconType}\\.\\w+\\.woff2$`));
    
        return previousIndexHtml.replace(inputFilename, outputFilename);
      }, indexHtmlSource);
    }
    
    /**
     *
     * @param {{ configuration?: string; project: string; target: string;}} targetOptions
     * @param {string} indexHtmlSource
     * @returns {string} The final index.html to serve.
     */
    module.exports = (targetOptions, indexHtmlSource) => {
        return setMaterialIconsFontsPreloading(indexHtmlSource);
    };