Первая в мире библиотека Web Components в духе shadcn. Серьёзно, я проверял
- вторник, 2 декабря 2025 г. в 00:00:04
Окей, заголовок звучит максимально самоуверенно, я понимаю. Но насколько мне удалось нагуглить — это действительно первая попытка сделать что-то подобное. Если я не прав — напишите в комментах, я с удовольствием посмотрю на альтернативы. А пока давайте я расскажу, что это за зверь такой и зачем он вообще нужен.
Всё началось с микрофронтендов. Знаете, это когда у вас один проект, но внутри него живёт Vue, React, и ещё какой-нибудь легаси на jQuery, который никто не хочет трогать, потому что "оно работает, не трогай".
И вот сидишь ты такой, дизайнер приносит макет новой кнопки. Красивая кнопка, с градиентом, с hover-эффектом, всё как надо. И ты понимаешь, что сейчас тебе предстоит:
Написать эту кнопку для Vue
Написать эту же кнопку для React
Написать её ещё раз для того самого легаси
Молиться, чтобы они выглядели одинаково
И так каждый раз. Каждый. Раз.
В какой-то момент я подумал: "А что, если написать компонент один раз и использовать везде?" Революционная мысль, да? На самом деле нет, Web Components существуют уже давно. Но почему-то никто не сделал для них нормальный DX, как у того же shadcn/ui.
Для тех, кто не в курсе: shadcn/ui — это не совсем библиотека в привычном понимании. Это скорее набор компонентов, которые ты копируешь себе в проект. Не устанавливаешь как зависимость, а именно копируешь.
Почему это круто:
Полный контроль над кодом
Никаких проблем с версиями
Кастомизируй как хочешь
Удаляй лишнее, добавляй нужное
Проблема в том, что shadcn написан для React. А мне нужно было что-то, что работает везде. Буквально везде.
Короче, я взял и сделал то же самое, но для Web Components. Называется CapsuleUI, и работает это примерно так:
# Инициализируем проект
npx @zizigy/capsule init
# Добавляем компонент
npx @zizigy/capsule add Button
Всё. После этого у тебя в проекте появляется папка @capsule, а внутри — готовый компонент кнопки. Не ссылка на node_modules, не какая-то м��гия — просто файлы с кодом, которые ты можешь открыть, почитать и поменять.
<capsule-button variant="primary" size="lg">
Нажми меня
</capsule-button>
Да, вот так просто. Это работает в React:
function App() {
return (
<div>
<capsule-button variant="primary">
Привет из React
</capsule-button>
</div>
)
}
Это работает в Vue:
<template>
<capsule-button variant="primary">
Привет из Vue
</capsule-button>
</template>
Это работает в чистом HTML:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="@capsule/global.css">
<script type="module" src="@capsule/index.js"></script>
</head>
<body>
<capsule-button variant="primary">
Привет из 2005 года
</capsule-button>
</body>
</html>
И самое прикольное — это один и тот же компонент. Не три разных реализации, а один. Единственный. Неповторимый.
Да, я знаю что вы хотите сказать. "Web Components — это сложно", "Shadow DOM — это боль", "А как стилизовать?", "А SSR?".
Давайте по порядку.
Компоненты написаны на Lit. Если кто не в курсе — это легковесная библиотека от Google для создания Web Components. Она делает всю грязную работу за тебя: реактивность, шаблонизация, lifecycle. Код получается чистый и понятный.
Да, используем. Но это не проблема, а фича. Стили компонента изолированы — они не сломают твой сайт, и твой сайт не сломает их. Для кастомизации есть CSS Custom Properties и ::part().
Все компоненты используют CSS переменные. Хочешь поменять цвет кнопки? Пожалуйста:
:root {
--capsule-color-primary: #8b5cf6;
}
Хочешь полностью переписать стили? Файлы у тебя в проекте, открывай и редактируй.
Окей, тут есть нюанс. Web Components работают с SSR. Сервер отрендерит сам тег компонента — он будет в HTML:
<capsule-button variant="primary">Нажми меня</capsule-button>
Но если компонент сам генерирует HTML внутри Shadow DOM (например, сложная структура со вложенными элементами), то этот HTML не будет в SSR. Тег компонента будет, содержимое Shadow DOM — нет. Это нормальное поведение для Web Components, и обычно это не проблема, потому что контент всё равно рендерится на клиенте.
Для большинства случаев это работает отлично. Если тебе критично нужен полный SSR с содержимым Shadow DOM — есть Declarative Shadow DOM, но это уже отдельная история.
Знаете что меня всегда бесило в Web Components? Отсутствие автодополнения. Пишешь <my-component и IDE такая: "Понятия не имею что это, удачи тебе братан".
В CapsuleUI это работает. Когда ты добавляешь компонент, CLI автоматически обновляет настройки VSCode. И твоя IDE начинает понимать кастомные элементы!
Пишешь <capsule-button — получаешь автодополнение для variant, size, disabled. Наводишь курсор — видишь документацию. Как будто это встроенный HTML-тег, только лучше.
Это работает через html.customData в настройках VSCode. CLI генерирует JSON с описанием всех компонентов и их атрибутов, а IDE это подхватывает. Магия? Нет, просто правильный DX.
Но что если ты обновил компонент, добавил новые атрибуты, и хочешь обновить автодополнение? Или работаешь со своей папкой компонентов?
Для этого есть команда generate:
# Генерирует vscode.data.json для конкретной папки
npx @zizigy/capsule generate --dir ./my-components
# Или для отдельной папки компонента
npx @zizigy/capsule generate --dir @capsule/components/capsule-button
# Можно указать выходную директорию
npx @zizigy/capsule generate --dir ./src/components --out ./vscode-config
Эта команда парсит JavaScript и CSS файлы компонентов, извлекает все атрибуты, их типы и возможные значения, и генерирует vscode.data.json для автодополнения. Умно? Думаю, да.
Окей, давайте немного технических деталей для тех, кому интересно.
@capsule/
├── components/
│ └── capsule-button/
│ ├── button.js
│ ├── button.style.css
│ └── register.js
├── global.css
└── index.js
Всё логично и понятно. Хочешь найти стили кнопки? Открываешь button.style.css. Хочешь поменять логику? Открываешь button.js. Никакой магии, никаких скрытых файлов в node_modules.
Компоненты полностью реактивные благодаря Lit. Меняешь атрибут — компонент перерисовывается:
const button = document.querySelector('capsule-button');
button.variant = 'secondary'; // Автоматически обновится
button.disabled = true; // И это тоже
Это работает и с JavaScript, и с фреймворками. Vue автоматически биндит атрибуты, React тоже (с небольшими нюансами, но решаемо).
Не нравится capsule-? Можно использовать свой:
npx @zizigy/capsule add Button --prefix ui
И получишь <ui-button> вместо <capsule-button>. Удобно, если у тебя уже есть свой неймспейс.
Помимо компонентов есть ещё модули. Типо, дополнительный функционал который не является компонентом, но часто нужен.
Например, модуль валидации форм:
npx @zizigy/capsule module add form
После этого у тебя появляется валидатор с кучей готовых правил:
import { CapsuleValidator, CapsuleRules } from '@capsule/modules/form';
const validator = new CapsuleValidator({
email: [CapsuleRules.required(), CapsuleRules.email()],
password: [CapsuleRules.required(), CapsuleRules.minLength(8)]
});
const result = validator.validate({
email: 'test@example.com',
password: '123'
});
// result.isValid === false
// result.errors.password === ['Минимальная длина: 8 символов']
Опять же — это твой код. Хочешь добавить своё правило? Добавляй. Хочешь убрать лишние? Убирай.
CapsuleUI — это не готовая дизайн-система. Это скорее конструктор для создания своей.
Компоненты намеренно минималистичные по стилям. Базовые состояния есть, но они легко переопределяются. Потому что я понятия не имею, какой у тебя дизайн. Может у тебя Material Design, может Tailwind-стиль, может вообще что-то своё уникальное.
Идея в том, чтобы дать тебе рабочий фундамент, который ты докрутишь под себя. А не заставлять "сражаться" с чужими стилями, пытаясь их переопределить.
Давайте я приведу несколько примеров, где это действительно пригодилось.
У тебя есть проект, где часть на Vue 3, часть на React 18, а где-то ещё легаси на jQuery. И тебе нужно, чтобы все кнопки выглядели одинаково. С CapsuleUI это решается добавлением одного компонента — он работает везде.
Ты работаешь в большой компании, и у тебя куча разных проектов на разных технологиях. Но все должны использовать единый дизайн. Вместо того чтобы поддерживать отдельные библиотеки для каждого фреймворка, можно сделать один набор Web Components.
Нужно быстро забабахать прототип, но не хочется тащить целый фреймворк? Web Components работают в чистом HTML. Добавил компоненты — получил рабочую разметку.
Многие CMS (WordPress, Drupal и т.д.) позволяют использовать кастомные HTML. Но они не понимают React или Vue. А Web Components? Запросто. Твой редактор контента может использовать твои компоненты.
Если честно, CapsuleUI — это нишевый инструмент. Он не для всех проектов.
Он идеально подходит, если:
У тебя микрофронтенды с разными фреймворками
Ты хочешь создать свою дизайн-систему на Web Components
Тебе нужны компоненты, которые работают везде
Ты хочешь полный контроль над кодом
Он скорее всего не подходит, если:
У тебя монолит на одном фреймворке (проще взять shadcn/ui для React или аналог для Vue)
Тебе нужна готовая дизайн-система из коробки (бери Vuetify, MUI, Ant Design)
Ты не хочешь заморачиваться с кастомизацией
Сейчас в библиотеке есть базовый набор компонентов, который покрывает основные потребности. Но это только начало.
В планах:
Больше компонентов (модалки, дропдауны, датапикеры)
Улучшенная документация
Примеры интеграции с разными фреймворками
Возможно, CLI для генерации своих компонентов
Если есть идеи или пожелания — пишите в issues на GitHub. Я реально читаю и отвечаю.
# Создаём новый проект (или используем существующий)
mkdir my-project && cd my-project
# Инициализируем
npx @zizigy/capsule init
# Добавляем компоненты
npx @zizigy/capsule add Button
npx @zizigy/capsule add Alert
npx @zizigy/capsule add Tabs
# Смотрим список доступных компонентов
npx @zizigy/capsule list
# Если обновил компонент, генерируем VSCode data заново
npx @zizigy/capsule generate --dir @capsule/components/capsule-button
GitHub: github.com/ZiZIGY/CapsuleUI
npm: @zizigy/capsule
Документация: CapsuleUI
Знаете, когда я начинал этот проект, я просто хотел решить конкретную проблему на работе. А в итоге получилось что-то, чем я реально горжусь.
Web Components долгое время были "технологией будущего, которое никогда не наступит". Но сейчас они работают во всех браузерах, есть отличные инструменты вроде Lit, и, как мне кажется, самое время дать им нормальный developer experience.
CapsuleUI — это моя попытка это сделать. Возможно, не идеальная. Возможно, с багами (если найдёте — пишите, исправлю). Но это работает, и это решает реальную проблему.
Буду рад фидбеку, звёздам на GitHub и, конечно, контрибьюторам. Потому что один в поле не воин, а хороший open-source проект — это всегда командная работа.
Спасибо, что дочитали до конца. Вы восхитительны! ✨
