Импорт CSS из JavaScript

Современные фронтенд-приложения часто рассматривают стили как часть модульной системы проекта. Вместо подключения CSS-файлов через теги <link> стили импортируются непосредственно из JavaScript-модулей:

import './styles.css'

Esbuild поддерживает подобный подход на уровне сборщика. Во время обработки модулей он анализирует зависимости, находит CSS-файлы и включает их в итоговую сборку.

Такой механизм позволяет:

  • хранить стили рядом с компонентами;
  • автоматически отслеживать зависимости;
  • объединять множество CSS-файлов в один результат;
  • использовать CSS Modules;
  • минимизировать стили при продакшн-сборке;
  • исключать неиспользуемые файлы из цепочки зависимостей.

Базовый импорт CSS

Пусть имеется следующая структура проекта:

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

Файл стилей:

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

h1 {
    color: steelblue;
}

Файл Jav * aScript:

import './styles.css'

document.body.innerHTML = '<h1>Esbuild</h1>'

Сборка:

esbuild src/index.js --bundle --outdir=dist

После выполнения команды Esbuild:

  1. обнаружит импорт CSS;
  2. обработает файл как зависимость;
  3. создаст итоговый CSS-файл;
  4. свяжет его с результатом сборки.

В каталоге dist появятся файлы:

dist/
├── index.js
└── index.css

Как Esbuild обрабатывает CSS

При обнаружении конструкции:

import './styles.css'

происходит несколько этапов.

Анализ графа зависимостей

Esbuild строит дерево модулей:

index.js
│
└── styles.css

Если внутри JavaScript подключаются дополнительные модули:

import './styles.css'
import './layout.css'
import './theme.css'

то все стили попадают в единый граф зависимостей.

Объединение файлов

Исходные файлы:

import './reset.css'
import './layout.css'
import './theme.css'

Могут быть объединены в один результирующий CSS:

/* reset.css */
/* layout.css */
/* theme.css */

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


Использование API вместо CLI

Импорт CSS работает одинаково как через командную строку, так и через JavaScript API.

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

import * as esbuild from 'esbuild'

await esbuild.build({
    entryPoints: ['src/index.js'],
    bundle: true,
    outdir: 'dist'
})

Если в цепочке зависимостей присутствует CSS:

import './styles.css'

Esbuild автоматически создаст итоговый файл стилей.


Генерация отдельных CSS-файлов

При сборке JavaScript и CSS разделяются.

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

import './styles.css'

Результат:

dist/
├── app.js
└── app.css

JavaScript не содержит содержимого CSS внутри себя.

Это отличается от некоторых других сборщиков, которые могут внедрять стили в JavaScript через специальные загрузчики.


Минификация CSS

Esbuild умеет минимизировать стили.

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

body {
    margin: 0;
    padding: 0;
}

.container {
    width: 100%;
}

Сборка:

esbuild src/index.js \
  --bundle \
  --minify \
  --outdir=dist

Результат:

body{margin:0;padding:0}.container{width:100%}

Минификация применяется автоматически ко всем импортированным CSS-файлам.


Импорт CSS внутри модулей

Часто стили располагаются рядом с компонентами.

Структура:

src/
├── components/
│   ├── Button.js
│   └── Button.css
└── index.js

Компонент:

import './Button.css'

export function Button() {
    return '<button class="btn">Нажать</button>'
}

Главный модуль:

import { Button } from './components/Button.js'

document.body.innerHTML = Button()

Esbuild автоматически найдет все CSS-зависимости независимо от глубины вложенности.

Граф будет выглядеть следующим образом:

index.js
│
└── Button.js
     │
     └── Button.css

Множественные импорты стилей

Разрешается импортировать несколько CSS-файлов.

import './reset.css'
import './typography.css'
import './theme.css'

Итоговый CSS будет содержать правила в указанном порядке.

Это важно учитывать при переопределении селекторов.

Например:

/* reset.css */
button {
    border: none;
}
/* theme.css */
button {
    border: 1px solid black;
}

В результирующем CSS второе правило окажется ниже и переопределит первое.


Импорт CSS из npm-пакетов

Esbuild умеет работать со стилями сторонних библиотек.

Пример:

import 'bootstrap/dist/css/bootstrap.min.css'

После установки пакета:

npm install bootstrap

Esbuild включит CSS-файл в общую сборку.

Подобный подход широко используется для подключения:

  • Bootstrap;
  • Bulma;
  • Foundation;
  • Material UI стилей;
  • различных UI-библиотек.

CSS как точка входа

Esbuild позволяет собирать CSS отдельно от JavaScript.

Структура:

src/
└── styles.css

Команда:

esbuild src/styles.css \
  --bundle \
  --outfile=dist/styles.css

В этом случае CSS выступает самостоятельной точкой входа.


Импорт через директиву @import

Esbuild умеет обрабатывать зависимости внутри CSS.

Файл:

@import './reset.css';
@import './layout.css';

body {
    background: white;
}

Сборка:

esbuild src/styles.css \
  --bundle \
  --outfile=dist/styles.css

Esbuild обнаружит директивы @import и объединит все файлы в один результат.

Граф зависимостей:

styles.css
│
├── reset.css
└── layout.css

Совместное использование JavaScript- и CSS-зависимостей

Типичная структура приложения:

src/
├── pages/
│   ├── Home.js
│   └── Home.css
├── pages/
│   ├── About.js
│   └── About.css
└── main.js

Модуль страницы:

import './Home.css'

export function Home() {
    return '<div class="home">Главная</div>'
}

Главный модуль:

import { Home } from './pages/Home.js'

Все найденные CSS-файлы попадут в общий результирующий файл.


CSS Modules

Esbuild поддерживает CSS Modules через специальный загрузчик.

Файл:

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

Сборка:

esbuild src/index.js \
  --bundle \
  --loader:.css=local-css \
  --outdir=dist

Импорт:

import styles from './Button.css'

console.log(styles.button)

После сборки класс получает уникальное имя:

{
    button: "button_abc123"
}

HTML:

button.className = styles.button

Результат:

<button class="button_abc123">

Такой механизм предотвращает конфликты между компонентами.


Глобальные и локальные классы

В режиме CSS Modules классы становятся локальными по умолчанию.

.title {
    color: red;
}

Будет преобразовано в уникальный идентификатор.

Для глобального селектора используется:

:global(.title) {
    color: red;
}

В этом случае имя класса останется неизменным.


Получение объекта классов

При использовании CSS Modules импорт возвращает объект соответствий.

CSS:

.card {
    padding: 20px;
}

Jav * aScript:

import styles from './Card.css'

console.log(styles)

Результат:

{
    card: "card_8f3a1"
}

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


Работа с code splitting

При включении разделения кода:

await esbuild.build({
    entryPoints: ['src/index.js'],
    bundle: true,
    splitting: true,
    format: 'esm',
    outdir: 'dist'
})

Esbuild анализирует зависимости каждого чанка.

Если различные части приложения используют разные стили, сборщик формирует соответствующие CSS-зависимости для этих частей.

Это уменьшает объем загружаемых ресурсов.


Импорт CSS в динамических модулях

Динамический импорт:

import('./admin.js')

Модуль:

import './admin.css'

При использовании code splitting Esbuild учитывает CSS данного модуля как зависимость соответствующего чанка.

Это позволяет загружать стили только при необходимости.


Настройка имен выходных файлов

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

await esbuild.build({
    entryPoints: ['src/index.js'],
    bundle: true,
    outdir: 'dist',
    entryNames: '[name]-[hash]'
})

Результат:

dist/
├── index-A1B2C3.js
└── index-A1B2C3.css

Хеширование удобно для долгосрочного кэширования браузером.


Использование metafile для анализа CSS

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

await esbuild.build({
    entryPoints: ['src/index.js'],
    bundle: true,
    metafile: true,
    outfile: 'dist/app.js'
})

Результат содержит сведения о зависимостях:

result.metafile

Из объекта можно получить информацию:

  • какие CSS-файлы были подключены;
  • размер каждого файла;
  • вклад файла в итоговую сборку.

Это полезно при оптимизации крупных проектов.


Особенности порядка подключения стилей

Порядок импортов напрямую влияет на итоговый CSS.

Пример:

import './base.css'
import './theme.css'

Результат:

/* base.css */
/* theme.css */

Если поменять порядок:

import './theme.css'
import './base.css'

изменится и порядок правил в итоговом CSS.

Поэтому переопределяющие стили обычно подключаются позже базовых.


Ограничения импорта CSS из JavaScript

Несмотря на широкую поддержку CSS, следует учитывать несколько особенностей:

  • Esbuild не внедряет CSS в DOM автоматически.
  • Для подключения итогового CSS-файла требуется ссылка через <link>.
  • Некоторые сложные преобразования PostCSS отсутствуют без дополнительных плагинов.
  • Автоматическая обработка CSS-препроцессоров требует соответствующих расширений или предварительной компиляции.
  • Порядок импортов остается критически важным для корректного каскадирования стилей.

Практическая структура проекта

src/
├── components/
│   ├── Button.js
│   ├── Button.css
│   ├── Modal.js
│   └── Modal.css
├── pages/
│   ├── Home.js
│   ├── Home.css
│   ├── About.js
│   └── About.css
├── app.js
└── index.js

Компонент:

import './Button.css'

export class Button {
    render() {
        return '<button class="button">Кнопка</button>'
    }
}

Страница:

import './Home.css'
import { Button } from '../components/Button.js'

Точка входа:

import './app.css'
import { Home } from './pages/Home.js'

Во время сборки Esbuild построит единый граф зависимостей, соберет все обнаруженные CSS-файлы, оптимизирует их, сохранит порядок подключения и сформирует итоговый CSS-ресурс, соответствующий структуре импортов JavaScript-модулей.