В CSS существуют две важные конструкции, связанные с подключением внешних ресурсов:
@import — импортирует другие CSS-файлы;
url() — подключает файлы внутри CSS:
Webpack не умеет анализировать CSS самостоятельно. Для понимания
таких конструкций используются специальные загрузчики, главным образом
css-loader.
При подключении CSS-файла:
import './styles/main.css';
Webpack передаёт файл цепочке loaders.
Типичная схема:
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
Процесс выглядит так:
css-loader
@import;url();style-loader
<style> внутри
страницы.@importФайл:
/* main.css */
@import './reset.css';
body {
margin: 0;
}
Webpack воспринимает импорт как зависимость.
Файл reset.css будет:
css-loader преобразует:
@import './reset.css';
примерно в:
import './reset.css';
То есть CSS начинает работать как модульная система.
Если существует цепочка:
/* main.css */
@import './base.css';
/* base.css */
@import './theme.css';
Webpack рекурсивно проходит все зависимости.
В результате формируется единый граф модулей.
Можно импортировать стили библиотек:
@import 'normalize.css';
Webpack ищет пакет в:
node_modules/
Это работает аналогично JavaScript-import.
~В старых версиях Webpack и css-loader применялся синтаксис:
@import '~bootstrap/dist/css/bootstrap.css';
Символ ~ означал:
искать внутри node_modules
В современных версиях:
~ больше не обязателен;@import 'bootstrap/dist/css/bootstrap.css';
@importCSS сохраняет последовательность импортов.
Например:
@import './reset.css';
@import './theme.css';
body {
background: white;
}
Webpack объединяет стили в указанном порядке.
Это критически важно для:
@importКорректно:
@import './reset.css';
body {
margin: 0;
}
Некорректно:
body {
margin: 0;
}
@import './reset.css';
CSS-спецификация требует размещать @import до обычных
правил.
CSS поддерживает media queries внутри import:
@import './mobile.css' screen and (max-width: 768px);
Webpack сохраняет media condition.
url()Webpack анализирует:
background: url('./image.png');
и превращает файл в зависимость проекта.
src/
├── css/
│ └── style.css
├── images/
│ └── logo.png
CSS:
.logo {
background-image: url('../images/logo.png');
}
Webpack:
Исходный CSS:
background-image: url('../images/logo.png');
После сборки:
background-image: url(/assets/logo.a1b2c3.png);
В Webpack 5 обработка файлов встроена в ядро.
Пример настройки:
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif|svg)$/i,
type: 'asset/resource'
}
]
}
};
Копирует файл отдельно.
type: 'asset/resource'
Результат:
dist/
└── images/
└── logo.8d91fa.png
Встраивает файл в Base64.
type: 'asset/inline'
Результат:
background-image: url(data:image/png;base64,...);
Импортирует содержимое файла как строку.
Автоматически выбирает:
Обычно решение принимается по размеру файла.
Пример:
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg)$/i,
type: 'asset/resource',
generator: {
filename: 'images/[name].[contenthash][ext]'
}
}
]
}
};
contenthash создаёт уникальное имя файла:
logo.a4f92d.png
Хеш меняется только при изменении содержимого.
Это важно для:
@font-face {
font-family: 'Roboto';
src: url('../fonts/Roboto-Regular.woff2') format('woff2');
}
Webpack:
SVG может подключаться через:
background-image: url('./icon.svg');
или:
mask-image: url('./mask.svg');
SVG также проходит через asset modules.
Путь внутри CSS вычисляется относительно самого CSS-файла.
Структура:
css/style.css
images/logo.png
CSS:
background: url('../images/logo.png');
Webpack может работать с абсолютными путями:
background: url('/images/logo.png');
Но здесь есть важный нюанс.
Такой URL:
Файл должен существовать физически на сервере.
Webpack умеет автоматически добавлять базовый URL.
Пример:
output: {
publicPath: '/static/'
}
Результат:
background-image: url(/static/images/logo.png);
{
loader: 'css-loader',
options: {
url: false
}
}
Теперь Webpack не анализирует:
url(...)
Пути останутся как есть.
Это используется:
Можно обрабатывать не все URL.
Пример:
{
loader: 'css-loader',
options: {
url: {
filter: (url) => {
return !url.includes('external');
}
}
}
}
{
loader: 'css-loader',
options: {
import: false
}
}
Теперь:
@import './style.css';
не будет анализироваться Webpack.
Очень важный параметр.
Пример:
{
loader: 'css-loader',
options: {
importLoaders: 1
}
}
Предположим:
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
И существует:
@import './theme.css';
Без importLoaders:
theme.css не пройдёт через
postcss-loader.С:
importLoaders: 1
импортированные файлы тоже обрабатываются PostCSS.
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
importLoaders: 1
Будет применён:
postcss-loader
use: [
'style-loader',
'css-loader',
'postcss-loader',
'sass-loader'
]
importLoaders: 2
Импортированные файлы пройдут через:
postcss-loader;sass-loader.SCSS:
@import './variables';
Webpack передаёт импорт в sass-loader.
После компиляции:
css-loader;url() и оставшиеся
@import..hero {
background: url('../img/bg.jpg');
}
После компиляции Sass:
css-loader;При работе с CSS полезны source maps.
Пример:
{
loader: 'css-loader',
options: {
sourceMap: true
}
}
Это позволяет:
url();В production часто используется:
MiniCssExtractPlugin.loader
вместо:
style-loader
Пример:
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
Когда CSS выносится в отдельный файл:
dist/css/main.css
пути к ресурсам пересчитываются относительно нового расположения.
Иногда требуется:
publicPath
или:
MiniCssExtractPlugin.loader
с дополнительными настройками.
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 1
}
},
'postcss-loader'
]
},
{
test: /\.(png|jpg|svg|woff2?)$/i,
type: 'asset/resource',
generator: {
filename: 'assets/[name].[contenthash][ext]'
}
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash].css'
})
]
};
Ошибка:
Module not found: Error: Can't resolve './image.png'
Причины:
Частая проблема:
url('/img/logo.png')
вместо:
url('../img/logo.png')
Абсолютный URL не проходит через Webpack.
Иногда один файл импортируется:
Webpack может создать несколько копий при неправильной конфигурации.
asset/inline не подходит для крупных изображений.
Минусы:
Webpack поддерживает aliases.
Настройка:
resolve: {
alias: {
'@images': path.resolve(__dirname, 'src/images')
}
}
CSS:
background-image: url('~@images/logo.png');
Webpack корректно обрабатывает:
background-image: url('./icon.svg?v=1');
и:
background-image: url('./icon.svg#hash');
css-loader поддерживает:
background-image: url('data:image/png;base64,...');
Такие URL не обрабатываются Webpack как файлы.
При динамическом импорте:
import('./theme.css');
Webpack создаёт отдельный chunk.
Все ресурсы внутри CSS:
также становятся частью соответствующего чанка.
Webpack рассматривает CSS как полноценный модуль.
Это означает:
Условно:
background: url('./logo.png');
превращается в:
import logo from './logo.png';
element.style.background = `url(${logo})`;
Именно поэтому Webpack способен: