css-loader: импорт CSS в JavaScript

css-loader — один из базовых загрузчиков Webpack, предназначенный для обработки CSS-файлов внутри графа зависимостей JavaScript-приложения. Он позволяет импортировать CSS напрямую в JS-модули, преобразовывать директивы @import и url(), а также интегрировать CSS в систему модульной сборки Webpack.

Без css-loader Webpack воспринимает CSS-файл как неизвестный тип ресурса. Попытка импортировать стили приводит к ошибке:

import './styles.css';

Ошибка:

Module parse failed: Unexpected token

После подключения css-loader CSS становится полноценной частью dependency graph.


Установка

Минимальная установка включает два загрузчика:

npm install css-loader style-loader --save-dev
  • css-loader — анализирует CSS и превращает его в JavaScript-модуль
  • style-loader — внедряет стили в DOM через <style>

Базовая конфигурация

// webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  }
};

Как работает цепочка загрузчиков

Webpack применяет загрузчики справа налево.

Конструкция:

use: ['style-loader', 'css-loader']

эквивалентна:

style-loader(css-loader(styles.css))

Этапы обработки

1. css-loader

Преобразует CSS:

body {
  background: black;
}

в JS-модуль примерно такого вида:

export default [
  {
    css: 'body { background: black; }'
  }
];

Также анализируются:

  • @import
  • url()
  • CSS Modules
  • source maps

2. style-loader

Получает результат предыдущего loader и вставляет CSS в DOM:

<style>
body {
  background: black;
}
</style>

Импорт CSS в JavaScript

Глобальный CSS

import './global.css';

Файл:

body {
  margin: 0;
  font-family: sans-serif;
}

После сборки стили автоматически появляются на странице.


Обработка @import

Исходный CSS

@import './reset.css';

body {
  color: black;
}

css-loader преобразует импорт в зависимости Webpack.

Фактически:

styles.css
 └── reset.css

становится частью dependency graph.


Обработка url()

Одно из важнейших назначений css-loader — анализ ссылок на ресурсы.

Пример

.logo {
  background-image: url('./logo.png');
}

Webpack:

  1. обнаруживает url()
  2. создаёт зависимость
  3. передаёт файл в asset-модуль
  4. заменяет путь

Например:

background-image: url(/assets/logo.a1b2c3.png);

Совместная работа с asset modules

Конфигурация

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.(png|jpg|svg)$/i,
        type: 'asset/resource'
      }
    ]
  }
};

Импорт CSS из JavaScript-модулей

Структура

src/
 ├── index.js
 ├── app.js
 └── styles.css

index.js

import './styles.css';
import './app';

Использование CSS внутри компонентов

button.js

import './button.css';

export function createButton() {
  const button = document.createElement('button');

  button.className = 'button';
  button.textContent = 'Click';

  return button;
}

button.css

.button {
  background: royalblue;
  color: white;
}

Такой подход позволяет хранить стили рядом с компонентами.


CSS Modules

Проблема глобальных классов

Обычный CSS создаёт глобальное пространство имён.

.button {
  color: red;
}

При большом количестве компонентов возникают:

  • конфликты имён
  • перезапись стилей
  • проблемы масштабирования

Включение CSS Modules

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true
            }
          }
        ]
      }
    ]
  }
};

Использование CSS Modules

button.module.css

.button {
  background: green;
  color: white;
}

button.js

import styles from './button.module.css';

button.className = styles.button;

Результат преобразования

Исходный класс:

.button

превращается во что-то вроде:

.button_a6f12

или:

._button_1x8ab_3

Это исключает конфликты между компонентами.


Автоматическое включение CSS Modules

Частая практика:

{
  test: /\.module\.css$/,
  use: [
    'style-loader',
    {
      loader: 'css-loader',
      options: {
        modules: true
      }
    }
  ]
}

Тогда:

  • app.css → глобальный CSS
  • button.module.css → CSS Modules

Настройка modules

Локальные имена классов

localIdentName

options: {
  modules: {
    localIdentName: '[name]__[local]__[hash:base64:5]'
  }
}

Пример результата

Button__button__a1b2c

Шаблоны:

Шаблон Значение
[name] имя файла
[local] имя класса
[hash] уникальный hash

Production-конфигурация

В production обычно используют короткие имена:

localIdentName: '[hash:base64]'

Это уменьшает размер CSS.


Опция importLoaders

Назначение

Определяет количество loader’ов, применяемых к ресурсам из @import.


Пример с PostCSS

{
  test: /\.css$/,
  use: [
    'style-loader',
    'css-loader',
    'postcss-loader'
  ]
}

Без importLoaders:

@import './reset.css';

может не пройти через postcss-loader.


Правильная настройка

{
  loader: 'css-loader',
  options: {
    importLoaders: 1
  }
}

Что означает 1

Один loader после css-loader должен применяться к импортированным CSS-файлам.

Цепочка:

css-loader
postcss-loader

Пример с Sass

use: [
  'style-loader',
  {
    loader: 'css-loader',
    options: {
      importLoaders: 2
    }
  },
  'postcss-loader',
  'sass-loader'
]

Опция url

Отключение обработки url()

По умолчанию:

url: true

Полное отключение

options: {
  url: false
}

Теперь:

background: url('./image.png');

не будет преобразовываться Webpack.


Когда это полезно

Например:

  • CDN-ресурсы
  • внешние URL
  • серверная обработка путей
  • legacy-проекты

Опция import

Позволяет отключить обработку @import.

options: {
  import: false
}

Source Maps

Подключение

{
  loader: 'css-loader',
  options: {
    sourceMap: true
  }
}

Назначение

Source maps позволяют:

  • видеть исходный CSS в DevTools
  • определять исходный файл
  • упрощать отладку

Extract CSS вместо style-loader

Проблема style-loader

style-loader вставляет CSS через Jav * aScript:

<style>...</style>

Это подходит для development, но не для production.

Недостатки:

  • CSS загружается вместе с JS
  • нет отдельного кеширования
  • FOUC
  • ухудшение производительности

MiniCssExtractPlugin

Установка

npm install mini-css-extract-plugin --save-dev

Конфигурация

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader'
        ]
      }
    ]
  },

  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css'
    })
  ]
};

Результат

Webpack создаёт отдельный CSS-файл:

main.a1b2c3.css

Комбинация с PostCSS

Конфигурация

{
  test: /\.css$/,
  use: [
    'style-loader',
    'css-loader',
    'postcss-loader'
  ]
}

Возможности PostCSS

Через postcss-loader можно подключать:

  • Autoprefixer
  • CSSNano
  • PostCSS Preset Env
  • nesting
  • custom properties

Комбинация с Sass

Установка

npm install sass sass-loader --save-dev

Конфигурация

{
  test: /\.scss$/,
  use: [
    'style-loader',
    'css-loader',
    'sass-loader'
  ]
}

Порядок обработки

SCSS
 ↓
sass-loader
 ↓
css-loader
 ↓
style-loader

ES Modules и CommonJS

ES Modules

По умолчанию css-loader использует ES Modules:

import styles from './style.css';

Отключение

options: {
  esModule: false
}

CommonJS

const styles = require('./style.css');

Экспорт только классов

exportOnlyLocals

Полезно для SSR.

options: {
  modules: {
    exportOnlyLocals: true
  }
}

Поведение

CSS не внедряется в DOM.

Экспортируются только имена классов:

{
  button: 'button_a1b2c'
}

Named Exports

Включение

modules: {
  namedExport: true
}

Использование

.title {
  color: red;
}
import { title } from './style.module.css';

Lazy Style Injection

Использование style-loader в lazy-режиме

use: [
  {
    loader: 'style-loader',
    options: {
      injectType: 'lazyStyleTag'
    }
  },
  'css-loader'
]

Импорт

import styles from './style.css';

styles.use();
styles.unuse();

Это позволяет динамически подключать и отключать стили.


HMR и css-loader

css-loader поддерживает Hot Module Replacement.

При изменении CSS:

  • страница не перезагружается
  • обновляются только стили
  • сохраняется состояние приложения

Типичные ошибки

Перепутан порядок loader’ов

Неправильно:

use: ['css-loader', 'style-loader']

Правильно:

use: ['style-loader', 'css-loader']

Отсутствует style-loader

use: ['css-loader']

CSS обработается, но не попадёт в DOM.


Не работает url()

Причина часто связана с отсутствием asset modules:

{
  test: /\.(png|jpg|svg)$/i,
  type: 'asset/resource'
}

CSS Modules не активированы

Ошибка:

import styles from './style.css';

console.log(styles.button);

styles.button будет undefined, если modules: true отключён.


Производительность

Кеширование

В production обычно используют:

filename: '[name].[contenthash].css'

Это позволяет браузеру эффективно кешировать стили.


Минификация

Обычно подключается через:

  • css-minimizer-webpack-plugin
  • optimization.minimize

Архитектурные подходы

Глобальный CSS

Подходит для:

  • reset
  • typography
  • variables
  • layout

CSS Modules

Подходит для:

  • компонентной архитектуры
  • React
  • Vue
  • изолированных UI-модулей

Комбинированная схема

Часто используется:

src/
 ├── styles/
 │    ├── reset.css
 │    └── globals.css
 │
 └── components/
      └── Button/
           ├── Button.js
           └── Button.module.css

Пример production-конфигурации

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /\.module\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              modules: {
                localIdentName: '[hash:base64]'
              },
              importLoaders: 1
            }
          },
          'postcss-loader'
        ]
      },

      {
        test: /\.css$/,
        exclude: /\.module\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader'
        ]
      }
    ]
  },

  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css'
    })
  ]
};