javascript

Первая в мире библиотека Web Components в духе shadcn. Серьёзно, я проверял

  • вторник, 2 декабря 2025 г. в 00:00:04
https://habr.com/ru/articles/972038/

Окей, заголовок звучит максимально самоуверенно, я понимаю. Но насколько мне удалось нагуглить — это действительно первая попытка сделать что-то подобное. Если я не прав — напишите в комментах, я с удовольствием посмотрю на альтернативы. А пока давайте я расскажу, что это за зверь такой и зачем он вообще нужен.

Предыстория, или как я дошёл до жизни такой

Всё началось с микрофронтендов. Знаете, это когда у вас один проект, но внутри него живёт Vue, React, и ещё какой-нибудь легаси на jQuery, который никто не хочет трогать, потому что "оно работает, не трогай".

И вот сидишь ты такой, дизайнер приносит макет новой кнопки. Красивая кнопка, с градиентом, с hover-эффектом, всё как надо. И ты понимаешь, что сейчас тебе предстоит:

  1. Написать эту кнопку для Vue

  2. Написать эту же кнопку для React

  3. Написать её ещё раз для того самого легаси

  4. Молиться, чтобы они выглядели одинаково

И так каждый раз. Каждый. Раз.

В какой-то момент я подумал: "А что, если написать компонент один раз и использовать везде?" Революционная мысль, да? На самом деле нет, Web Components существуют уже давно. Но почему-то никто не сделал для них нормальный DX, как у того же shadcn/ui.

Что такое shadcn и почему это важно

Для тех, кто не в курсе: shadcn/ui — это не совсем библиотека в привычном понимании. Это скорее набор компонентов, которые ты копируешь себе в проект. Не устанавливаешь как зависимость, а именно копируешь.

Почему это круто:

  • Полный контроль над кодом

  • Никаких проблем с версиями

  • Кастомизируй как хочешь

  • Удаляй лишнее, добавляй нужное

Проблема в том, что shadcn написан для React. А мне нужно было что-то, что работает везде. Буквально везде.

Встречайте CapsuleUI

Короче, я взял и сделал то же самое, но для 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 же...

Да, я знаю что вы хотите сказать. "Web Components — это сложно", "Shadow DOM — это боль", "А как стилизовать?", "А SSR?".

Давайте по порядку.

Сложность

Компоненты написаны на Lit. Если кто не в курсе — это легковесная библиотека от Google для создания Web Components. Она делает всю грязную работу за тебя: реактивность, шаблонизация, lifecycle. Код получается чистый и понятный.

Shadow DOM

Да, используем. Но это не проблема, а фича. Стили компонента изолированы — они не сломают твой сайт, и твой сайт не сломает их. Для кастомизации есть CSS Custom Properties и ::part().

Стилизация

Все компоненты используют CSS переменные. Хочешь поменять цвет кнопки? Пожалуйста:

:root {
  --capsule-color-primary: #8b5cf6;
}

Хочешь полностью переписать стили? Файлы у тебя в проекте, открывай и редактируй.

SSR — здесь всё интересно

Окей, тут есть нюанс. 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, но это уже отдельная история.

Фишка с VSCode, которой я горжусь

Знаете что меня всегда бесило в Web Components? Отсутствие автодополнения. Пишешь <my-component и IDE такая: "Понятия не имею что это, удачи тебе братан".

В CapsuleUI это работает. Когда ты добавляешь компонент, CLI автоматически обновляет настройки VSCode. И твоя IDE начинает понимать кастомные элементы!

Пишешь <capsule-button — получаешь автодополнение для variant, size, disabled. Наводишь курсор — видишь документацию. Как будто это встроенный HTML-тег, только лучше.

Это работает через html.customData в настройках VSCode. CLI генерирует JSON с описанием всех компонентов и их атрибутов, а IDE это подхватывает. Магия? Нет, просто правильный DX.

Генератор VSCode data — для продвинутых

Но что если ты обновил компонент, добавил новые атрибуты, и хочешь обновить автодополнение? Или работаешь со своей папкой компонентов?

Для этого есть команда 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

Многие 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 проект — это всегда командная работа.

Спасибо, что дочитали до конца. Вы восхитительны! ✨