Современные фронтенд-приложения часто рассматривают стили как часть
модульной системы проекта. Вместо подключения CSS-файлов через теги
<link> стили импортируются непосредственно из
JavaScript-модулей:
import './styles.css'
Esbuild поддерживает подобный подход на уровне сборщика. Во время обработки модулей он анализирует зависимости, находит 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:
В каталоге dist появятся файлы:
dist/
├── index.js
└── index.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 */
Порядок подключения сохраняется в соответствии с порядком импортов.
Импорт CSS работает одинаково как через командную строку, так и через JavaScript API.
Конфигурация:
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outdir: 'dist'
})
Если в цепочке зависимостей присутствует CSS:
import './styles.css'
Esbuild автоматически создаст итоговый файл стилей.
При сборке JavaScript и CSS разделяются.
Исходный код:
import './styles.css'
Результат:
dist/
├── app.js
└── app.css
JavaScript не содержит содержимого CSS внутри себя.
Это отличается от некоторых других сборщиков, которые могут внедрять стили в JavaScript через специальные загрузчики.
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-файлам.
Часто стили располагаются рядом с компонентами.
Структура:
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 второе правило окажется ниже и переопределит первое.
Esbuild умеет работать со стилями сторонних библиотек.
Пример:
import 'bootstrap/dist/css/bootstrap.min.css'
После установки пакета:
npm install bootstrap
Esbuild включит CSS-файл в общую сборку.
Подобный подход широко используется для подключения:
Esbuild позволяет собирать CSS отдельно от JavaScript.
Структура:
src/
└── styles.css
Команда:
esbuild src/styles.css \
--bundle \
--outfile=dist/styles.css
В этом случае CSS выступает самостоятельной точкой входа.
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
Типичная структура приложения:
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-файлы попадут в общий результирующий файл.
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"
}
Это позволяет безопасно использовать стили без риска пересечения имен.
При включении разделения кода:
await esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
splitting: true,
format: 'esm',
outdir: 'dist'
})
Esbuild анализирует зависимости каждого чанка.
Если различные части приложения используют разные стили, сборщик формирует соответствующие 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
Хеширование удобно для долгосрочного кэширования браузером.
Конфигурация:
await esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
metafile: true,
outfile: 'dist/app.js'
})
Результат содержит сведения о зависимостях:
result.metafile
Из объекта можно получить информацию:
Это полезно при оптимизации крупных проектов.
Порядок импортов напрямую влияет на итоговый CSS.
Пример:
import './base.css'
import './theme.css'
Результат:
/* base.css */
/* theme.css */
Если поменять порядок:
import './theme.css'
import './base.css'
изменится и порядок правил в итоговом CSS.
Поэтому переопределяющие стили обычно подключаются позже базовых.
Несмотря на широкую поддержку CSS, следует учитывать несколько особенностей:
<link>.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-модулей.