Сборка шрифтов в Webpack относится к обработке статических ресурсов.
Начиная с Webpack 5 для этого чаще всего используются Asset Modules,
позволяющие отказаться от file-loader и
url-loader.
Наиболее распространённые форматы шрифтов:
| Формат | Назначение |
|---|---|
| WOFF | Современный формат с хорошим сжатием |
| WOFF2 | Основной формат для современных браузеров |
| TTF | TrueType-шрифты, более тяжёлые |
| OTF | OpenType-шрифты |
| EOT | Устаревший формат для старых Internet Explorer |
В современных проектах основной акцент делается на woff2
и иногда woff.
Структура проекта:
src/
├── fonts/
│ ├── Inter-Regular.woff2
│ ├── Inter-Regular.woff
│ └── Inter-Bold.woff2
│
├── styles/
│ └── main.css
│
└── index.js
Базовая конфигурация Webpack:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(woff2?|ttf|otf|eot)$/i,
type: 'asset/resource',
},
],
},
};
asset/resourceТип asset/resource:
После сборки:
dist/
├── fonts/
│ └── a1b2c3d4.woff2
По умолчанию Webpack размещает файлы в корне output-директории. Обычно шрифты выносятся отдельно.
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(woff2?|ttf|otf|eot)$/i,
type: 'asset/resource',
generator: {
filename: 'fonts/[name][ext]',
},
},
],
},
};
Результат:
dist/
└── fonts/
├── Inter-Regular.woff2
└── Inter-Bold.woff2
После настройки обработки файлов используется
@font-face.
@font-face {
font-family: 'Inter';
src:
url('../fonts/Inter-Regular.woff2') format('woff2'),
url('../fonts/Inter-Regular.woff') format('woff');
font-weight: 400;
font-style: normal;
}
Использование:
body {
font-family: 'Inter', sans-serif;
}
Webpack автоматически обработает пути внутри url().
Каждое начертание подключается отдельно.
@font-face {
font-family: 'Inter';
src: url('../fonts/Inter-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Inter';
src: url('../fonts/Inter-Bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
}
Применение:
.title {
font-weight: 700;
}
.text {
font-weight: 400;
}
font-displayСвойство font-display управляет отображением текста во
время загрузки шрифта.
Пример:
@font-face {
font-family: 'Inter';
src: url('../fonts/Inter-Regular.woff2') format('woff2');
font-display: swap;
}
font-display| Значение | Поведение |
|---|---|
| auto | Поведение браузера по умолчанию |
| block | Текст скрывается до загрузки |
| swap | Сначала системный шрифт, затем замена |
| fallback | Короткий период ожидания |
| optional | Браузер может не загружать шрифт |
Наиболее популярный вариант:
font-display: swap;
Он предотвращает появление пустого текста.
Современная практика:
@font-face {
font-family: 'Inter';
src:
url('../fonts/Inter-Regular.woff2') format('woff2'),
url('../fonts/Inter-Regular.woff') format('woff');
font-display: swap;
}
Причины:
woff2 обеспечивает лучшее сжатие;woff нужен для более старых браузеров;Для долгосрочного кеширования используется content hash.
generator: {
filename: 'fonts/[name].[contenthash][ext]',
}
Результат:
fonts/Inter-Regular.8f3d1a2b.woff2
При размещении проекта в подкаталоге важно корректно настроить пути.
module.exports = {
output: {
publicPath: '/',
},
};
Для CDN:
output: {
publicPath: 'https://cdn.example.com/',
}
Тогда шрифты будут загружаться с CDN.
Webpack позволяет встраивать маленькие шрифты в bundle через Base64.
{
test: /\.(woff2?)$/i,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10 * 1024,
},
},
}
Маленькие файлы:
url(data:font/woff2;base64,...)
Большие файлы:
dist/fonts/font.woff2
Для шрифтов inline-режим используется редко.
Для ускорения отображения текста используется preload.
<link
rel="preload"
href="/fonts/Inter-Regular.woff2"
as="font"
type="font/woff2"
crossorigin
>
Браузер:
Flash of Unstyled Text.
Сначала отображается системный шрифт, затем происходит замена.
Причина:
font-display: swap;
Flash of Invisible Text.
Текст временно скрывается до загрузки шрифта.
Причина:
font-display: block;
Современные проекты обычно предпочитают FOUT вместо FOIT.
local()Можно использовать уже установленные шрифты системы.
@font-face {
font-family: 'Inter';
src:
local('Inter'),
local('Inter Regular'),
url('../fonts/Inter-Regular.woff2') format('woff2');
}
Преимущества:
Недостаток — различия версий шрифтов у пользователей.
SCSS-файл:
@font-face {
font-family: 'Inter';
src:
url('../fonts/Inter-Regular.woff2') format('woff2');
font-weight: 400;
}
Webpack-конфигурация:
module.exports = {
module: {
rules: [
{
test: /\.scss$/i,
use: [
'style-loader',
'css-loader',
'sass-loader',
],
},
],
},
};
Webpack часто используется вместе с CSS minimizer.
npm install css-minimizer-webpack-plugin --save-dev
Конфигурация:
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
optimization: {
minimizer: [
`...`,
new CssMinimizerPlugin(),
],
},
};
После минификации:
@font-face{font-family:Inter;src:url(fonts/inter.woff2) format("woff2")}
Во время разработки полезны source maps.
module.exports = {
devtool: 'source-map',
};
Это облегчает отладку CSS и путей к шрифтам.
Вместо CDN шрифты можно хранить локально.
Установка:
npm install @fontsource/inter
Подключение:
import '@fontsource/inter';
Или конкретного веса:
import '@fontsource/inter/400.css';
import '@fontsource/inter/700.css';
Каждый дополнительный файл:
Рекомендуется:
Subset — уменьшенная версия шрифта с ограниченным набором символов.
Пример:
Можно разделять шрифты по диапазонам символов.
@font-face {
font-family: 'Inter Latin';
src: url('../fonts/inter-latin.woff2') format('woff2');
unicode-range:
U+0000-00FF;
}
Для кириллицы:
@font-face {
font-family: 'Inter Cyrillic';
src: url('../fonts/inter-cyrillic.woff2') format('woff2');
unicode-range:
U+0400-04FF;
}
Браузер загружает только нужный subset.
Сервер обычно настраивается так:
Cache-Control: public, max-age=31536000, immutable
При использовании content hash это безопасно.
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
],
},
{
test: /\.(woff2?|ttf|otf|eot)$/i,
type: 'asset/resource',
generator: {
filename: 'fonts/[name].[contenthash][ext]',
},
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash].css',
}),
],
};
Ошибка:
src: url('/fonts/font.woff2');
При использовании Webpack чаще используются относительные пути:
src: url('../fonts/font.woff2');
crossoriginПри preload возможны ошибки CORS.
Правильно:
<link
rel="preload"
href="/fonts/inter.woff2"
as="font"
type="font/woff2"
crossorigin
>
Плохая практика:
TTF + OTF без WOFF2
Современная практика:
WOFF2 + WOFF
Проблемный набор:
100
200
300
400
500
600
700
800
900
Оптимальный вариант:
400
700
Во вкладке Network DevTools можно проверить:
Корректный MIME:
font/woff2
Сервер должен отдавать правильные типы.
| Формат | MIME |
|---|---|
| WOFF | font/woff |
| WOFF2 | font/woff2 |
| TTF | font/ttf |
| OTF | font/otf |
Неверный MIME может приводить к отказу загрузки.
Иногда второстепенные шрифты загружаются позже.
Пример:
body {
font-family: system-ui;
}
.fonts-loaded body {
font-family: 'Inter';
}
После загрузки:
document.documentElement.classList.add('fonts-loaded');
Подход уменьшает блокировку рендера.
Variable Fonts содержат несколько weight внутри одного файла.
Пример:
@font-face {
font-family: 'Inter Variable';
src:
url('../fonts/Inter.var.woff2')
format('woff2-variations');
font-weight: 100 900;
}
Использование:
.title {
font-weight: 750;
}
Шрифты напрямую влияют на:
| Метрика | Влияние |
|---|---|
| FCP | Скорость первого отображения |
| LCP | Скорость отображения крупного контента |
| CLS | Скачки макета |
| INP | Косвенное влияние через рендер |
Для уменьшения CLS используются:
font-display: swap;Наиболее распространённая схема:
woff2.woff для совместимости.font-display: swap.