Thursday, December 14, 2017

Using webfonts-generator with POI

The problem

This might come as a surprise to you (it did to me at least) that POI is not handling generation of webfonts by default. It does so many wonderful things but webfonts generation is not one of them, unfortunately. You might ask what are webfonts and why do we need to generate one in a frontend project? Aren't there enough fonts to choose from already? To answer that we need to understand how browsers work when downloading multiple resources. First of all there is a limit to how many connections can be made at the same time to one server. Secondly there is a limit to how many connections can there be in total at any given time. So if, for example, you'd like to load 30 small black and white images (for use as icons) then the browser would have a lot of work to do and the page would load very, very slowly. To mitigate that problem you could use a custom font that will contain all the images, compressed, and it will take a fraction of the time to load. This is exactly what webfonts-generator does.

The solution

So how do we use it in a POI project if it is not available by default? It's actually not that simple. We need to extend the already impressive webpack configuration to teach it what to do with the font descriptors. Font descriptors are just JavaScript modules ending with .font.js exporting definition of the font, like the one below:

module.exports = {
  fontName: 'icons',
  files: [
    'phone.svg',
  ]
}

Having that in place we can start extending our configuration. As you already know POI contains webpack rules for all the common types of files. Those rules differ from production to test to development configuration. The rule we need to add for it to work properly needs to be just like the one for CSSes, just with the webfonts-loader added at the end of the use list. So first we need to find the css rule:

const isCssRule = rule => rule.test.toString().indexOf('\.css$') >= 0
const findCssRule = rules => rules.find(isCssRule)

Having that rule in hand let's create another rule for webfonts that contains the definition of a loader:

const constructWebfontRuleFromCssRule = rule => ({
  test: /\.font\.js$/,
  use: [
    ...rule.use, 
    {
      loader: 'webfonts-loader', options: {
        fileName: 'assets/fonts/[fontname].[hash:8].[ext]'
      }
    },
  ]
})

Then we can add the rule to webpack configuration in poi.config.js:

module.exports = {
  webpack (config) {
    const rule = findCssRule(config.module.rules)
    config.module.rules.push(constructWebfontRuleFromCssRule(rule))

    return config
  }
}

And finally we need to install the additional loader like so:

npm install --save-dev webfonts-loader

webfonts-generator is a transitive dependency so it will be installed automatically too.

Final thoughts

I know that doesn't seem like much but it took me way too long to figure it out so I figured I'll post it for posterity. In the meanwhile I have created a ticket in the POI project for it to be implemented as part of the default configuration so that we won't have to deal with it ourselves. Will see if that will catch on or not.

Happy building!

No comments: