Skip to main content

Fonts

Assets management story in webpack completes 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 font faces of the text displayed to the user.

Before we dive deep into fonts management, it is worth revisiting out 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 sub-folder. On the other hand, all the webpack configs are relative to js folder. That might be a bit confusing and also 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 in a way that all the assets are put in the appropriate folders as above but there is no relative path references involved in the final output. We are going to go with more standard webpack setup where JavaScript assets are placed under the root of the destination location.

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 /. Similar change happened 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 a 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 piping up font files via the file-loader with the specific font file extensions. We also changed 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 assets/fonts folder for the respective font files for all the available formats. The last step is to introduce font families in the appropriate styles (in this case body styles).

font-face references physical font file from the assets folder using 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