Fonts

Publish at:

Assets management in webpack concludes with fonts. Just like images, fonts are content assets with their own life cycle. The main difference is that fonts are referenced from CSS and used to alter the font face of the text displayed to the user.

Before we dive into font management, it is worth revisiting our folder structure of the final output to see how different types of assets fit together.

Destination folder structure #

Our webpack journey started with JavaScript assets. The configuration was set up to save all of them into the js folder under dist. The rest of the folders were relative to the js one. Now, with all the assets in place, the output structure looks like this:

project
└───dist
    │   favicon.ico
    │   index.html
    |   ...
    |
    └───css
    |   │   feature.css
    |   │   main.css
    |   │   ...
    |
    └───fonts
    |   │   open-sans-latin-300.woff
    |   │   open-sans-latin-700.woff
    |   │   ...
    |
    └───img
    |   |  logo1.png
    |
    └───js
        |   feature.bundle.js
        |   feature.bundle.js.map
        |   main.bundle.js
        |   main.bundle.js.map
        |   ...

This structure has pros and cons. It makes the dist directory look clean by having only necessary items and everything else in a separate subfolder. On the other hand, all the webpack configs are relative to the js folder. That might be a bit confusing, and there are unnecessary relative path references in the generated index.html file.

<link href="/js/../css/main.css" rel="stylesheet"></head>

Let's fix the configuration so that all the assets are put in the appropriate folders as above, but there are no relative path references in the final output. We are going to use a more standard webpack setup where JavaScript assets are placed under the root of the destination.

project
└───dist
    │   favicon.ico
    |   feature.bundle.js
    |   feature.bundle.js.map
    │   index.html
    |   main.bundle.js
    |   main.bundle.js.map
    |   ...
    |
    └───css
    |   │   feature.css
    |   │   main.css
    |   │   ...
    |
    └───fonts
    |   │   open-sans-latin-300.woff
    |   │   open-sans-latin-700.woff
    |   │   ...
    |
    └───img
    |   |  logo1.png
    |

Webpack configuration #

webpack/parts.ts #

Change webpack/parts.ts

output: {
    path: folders.dist(),
    filename: '[name].bundle.js',
    publicPath: '/'
} as Output

// ...

{
    test: /\.(png|jpg|gif|bmp)$/,
    exclude: /favicons/,
    use: [
    {
        loader: 'url-loader',
        options: {
            limit: 10000,
            name: './img/[name].[ext]',
        }
    }]
}

In the output section we changed the publicPath to be the root of the project and the path itself to point to the dist folder. We also changed output paths for favicons and images from relative to js to relative to /. A similar change applies to the index.html file path.

webpack/webpack.config.dev.ts #

Change webpack/webpack.config.dev.ts

plugins: parts.plugins.concat(
    new CopyPlugin([{
        from: '../assets/fonts/**/*',
        to: path.resolve(folders.dist(), 'assets')
    }])
)

In dev mode we are only making sure that font files are copied to the output folder from the assets folder.

webpack/webpack.config.ts #

Change webpack/webpack.config.ts

{
    test: /\.(woff|woff2)$/,
    use: [{
        loader: 'file-loader',
        options: {
            name: './fonts/[name].[ext]'
        }
    }]
}

// ...

new MiniCssExtractPlugin({
    filename: 'css/[name].css',
    chunkFilename: 'css/[name].css',
}),

// ...

In production mode we are handling font files via file-loader with specific font file extensions. We also changed the CSS files output path to be root‑relative.

Sources #

src/app/index.pcss #

Change src/app/index.pcss

body {
    background-color: azure;
    font-family: Open Sans;
    font-weight: 300;
}

@font-face {
    font-family: Open Sans;
    font-style: normal;
    font-display: swap;
    font-weight: 300;
    src: url(../../assets/fonts/open-sans/open-sans-latin-300.woff2) format("woff2"), url(../../assets/fonts/open-sans/open-sans-latin-300.woff) format("woff");
}

@font-face {
    font-family: Open Sans;
    font-style: normal;
    font-display: swap;
    font-weight: 700;
    src: url(../../assets/fonts/open-sans/open-sans-latin-700.woff2) format("woff2"), url(../../assets/fonts/open-sans/open-sans-latin-700.woff) format("woff");
}

We introduced two font faces. They both point to the assets/fonts folder for the respective font files in the available formats. The last step is to apply these font families in the appropriate styles (in this case, body styles).

@font-face references a physical font file from the assets folder using a relative path value for the src property. The path is relative to the file where styles are defined, in this case src/app/index.pcss.

Final version #

Reference implementation (opens in a new tab)