Главная / Статьи

Почему визуальные редакторы сталкиваются с трудностями при работе с компонентами от shadcn UI

Визуальные редакторы сталкиваются с трудностями при работе с shadcn/ui, поскольку карты CVA хранятся в вашем репозитории, а Tailwind требует процесса сборки. Сбор классов DOM помогает объединить режим проектирования в React.

Издатель: REACTAPP.TOP9 мин чтения2063 слов

Источники: shadcn/ui says the code is yours. So why can’t anything edit it?

shadcn/ui позиционирует себя как решение, позволяющее полностью контролировать код за счёт копирования и вставки: запустите CLI, поместите файл components/ui/button.tsx в репозиторий, и считайте его частью собственного кода проекта. Нет никаких механизмов принудительного обновления, конфликтов с другими пакетами или необходимости бороться со стилями с использованием чужих файлов.

Это утверждение вызывает практический вопрос: если компонент уже находится в проекте, почему для его изменения всё равно приходится вручную открывать файл?

Ответ на этот вопрос технический, и это не первое предположение, которое приходит в голову большинству команд.

Инструменты визуального создания ожидают каталога свойств. Карты, находящиеся в собственности проекта, отличаются.

Инструменты для визуального создания компонентов React обычно предполагают, что компоненты поступают из пакетов с стабильным и задокументированным набором свойств. Material UI — классический пример такого подхода. Кнопка предоставляет энумерации вроде variant, size и color. Инструмент может считывать типы этих свойств, отображать выпадающие списки и возвращать текстовые значения. Сам компонент остается «черным ящиком» с маркированной панелью управления снаружи.

Вместо этого откройте кнопку из shadcn.

const buttonVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium ...", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground ...", outline: "border border-input bg-background hover:bg-accent ...", }, size: { default: "h-10 px-4 py-2", sm: "h-9 px-3", lg: "h-11 px-8" }, }, defaultVariants: { variant: "default", size: "default" }, } )

Здесь нет внешнего каталога свойств. Есть лишь карта вариантов — обычный файл на TypeScript, принадлежащий проекту.

Обычно считается, что это ухудшает работу инструментов: нет ничего для анализа внутри папки node_modules, нет сайтов с документацией, собранных автоматически, и каждая копия проекта постепенно отклоняется от исходного варианта. Редакторы, ориентированные на структуру пакетов, действительно не могут соответствовать таким требованиям.

Более обоснованный вариант трактовки противоположен. Карта CVA содержит больше информации, чем таблица свойств, и эта информация уже хранится на диске.

В таблице свойств указано, что variant принимает значение "contained". Однако там не говорится, что именно отображает "contained". Такая связь генерируется внутри библиотеки. Чтобы её понять, необходимо читать документацию, делать предположения или пробовать напрямую.

В карте CVA указано, что variant: "destructive" это bg-destructive text-destructive-foreground. Эта связь представляет собой данные из исходного кода, находящиеся под локальным контролем. Ничего не скрыто, поскольку ничего не было упаковано отдельно.

Следовательно, утверждение имеет ограниченный объем:

Для структурированной визуальной редактирования необходимо знать, что делает тот или иной элемент управления, а не просто то, что он существует. Компоненты в форматах пакетов скрывают эту информацию в компилированных внутренних структурах. shadcn сохраняет её в виде прямого объекта в репозитории. По этим критериям shadcn должен быть самой простой библиотекой для визуальной редактирования — а не самой сложной.

Что до сих пор отсутствует, так это структурированное редактирование вариантов, основанное на вашей карте CVA внутри вашего дерева. Существуют визуализаторы Tailwind и редакторы тем shadcn, но инструменты для тем переписывают CSS-переменные. Это меняет значение переменной bg-primary, но не влияет на то, какой вариант используется для кнопки или какие утилиты содержится в этом варианте. Препятствие для выполнения второй задачи находится на уровень ниже самых вариантов.

Настоящая проблема: классы утилит становятся CSS только после анализа

MUI применяет стили во время выполнения. Объект sx={{ p: 2 }} преобразуется в CSS в момент отрисовки компонента — нет необходимости ничего предварительно вычислять или сканировать. Запуск MUI в браузере без локальной установки по-прежнему затруднён, но причина заключается в решении проблем с модулями, единственными экземплярами тем и эмоций, а также в согласованности версий пакетов, а не в генерации стилей.

Tailwind меняет эту модель. Он сканирует исходный текст в поисках имён классов-утилит и генерирует только соответствующие правила. Отсутствие сканирования означает отсутствие CSS. Сканирование исходного кода является этапом сборки.

У компонентов, использующих классы-утилиты, которые запускают структуру приложения без установки npm или локального монитора, возникают проблемы, с которыми никогда не сталкивался MUI. Файл button.tsx может быть корректно транспилирован, но при этом создавать элемент без стилей. Токены классов появляются в DOM, но не имеют значения, так как никакой пайплайн не генерировал соответствующих правил.

Вот почему визуальные инструменты застревают на этом этапе. Варианты не являются корневой причиной — проблема кроется в стилизации.

Существует более четкое объяснение той же проблемы. Сканер Tailwind читает исходные строки кода, но компоненты shadcn зачастую в месте вызова даже не записывают эти строки — вместо этого они вызывают функцию. На той строке не может появиться литерал "bg-destructive"; во время выполнения происходит объединение карты CVA, поступающих параметров и настроек от вызывающего кода.

Обычная процедура сборки хорошо справляется с этим: сканируется файл карты вариантов, там хранятся полные литералы, и инструмент извлечения их видит. Настоящее правило Tailwind гласит: «не создавайте строки классов вида text-${color}-500». Карты CVA содержат полные литералы. shadcn представляет собой рекомендуемый подход, а не временное решение, и в нормальной процедуре сборки ему не нужен список запрещенных элементов.

Без локальной системы обработки файлов ничто не сканирует их в поисках утилит, поэтому механизм композиции не имеет возможности зарегистрироваться. Именно по этой причине режим без установки строже, чем общее напоминание о необходимости сборки в Tailwind.

Решение: извлечение утилит из отрендеренного маркапа

Практическим решением является отказ от сканирования исходного кода.

Система обработки файлов обеспечивает настоящую работу Tailwind прямо в браузере — с использованием кода из исходников, а не самодельного парсера. Выбор движка зависит от объявленной версии: приложения v3 загружают сборку с Play CDN, расположенную внутри Studio; приложения v4+ загружают @tailwindcss/browser аналогичным образом. Загрузка из сети предназначена для версий без встроенного соответствующего движка — например, для гипотетического проекта v5. Файл tailwind.config проекта передается движку, чтобы расширения тем оставались авторитетными.

Почему бы просто не запустить инструмент извлечения Tailwind в браузере и всё равно не проанализировать исходный код? Измерения показали явную стоимость этого подхода: одна лишь отрисовка уже приводит к примерно 40–340 операциям чтения файлов хоста, а при работе с крупными шаблонами одна операция чтения может занимать от 5 до 8 мс без какой-либо выгоды от параллелизма. Анализ репозитория означает необходимость чтения каждого файла, а не только тех, которые были отрисованы. Несмотря на такие затраты, классные имена, сгенерированные во время выполнения, всё равно упускаются. Метод сбора данных DOM предполагает замену операции «чтение всей структуры» на операцию «чтение отрисованного результата один раз».

Запуск движка — это лишь половина проблемы. Затем появляются дополнительные ограничения.

Движок работает в рамках документа и наблюдает только за легким DOM — он вызывает observe(document.documentElement) и никогда не входит в тени корней. Режим проектирования отображает шаблон внутри тени корня для изоляции. Поэтому движок смотрит на внешний документ, не видит названий классов компонентов и ничего не генерирует. Правильный движок, правильная конфигурация, пустой стилевой лист.

Механизм, соединяющий их:

  1. После отрисовки просматривают тень DOM и собирают все токены классов, присутствующие на реальных элементах.
  2. Копируют собранные токены в скрытый узел легкого DOM, который доступен наблюдателю.
  3. Позволяют движку сгенерировать правила именно для используемых утилит.
  4. Возвращают полученный стилевой лист обратно в тень корня.
  • Привяжите MutationObserver к теневому корню — поддереву, с параметром attributeFilter: ['class'] — чтобы последующие изменения маршрутов, отложенное отображение контента и условная интерфейсная схема продолжали обновлять этот элемент.
  • Один важный момент: элемент должен быть вставлен с использованием встроенного метода appendChild. Библиотека canvas модифицирует метод document.body.appendChild, чтобы перенаправлять элементы в теневой корень; использование этой модифицированной версии метода приводит к тому, что элемент оказывается внутри теневого дерева, где движок его не может увидеть. В результате мост связи работает бесшумно.

    Этот элемент остается настоящим узлом light-DOM и продолжает накапливать токены по мере навигации — новые маршруты добавляют необходимые функции без перезагрузки canvas.

    Вторая проблема связана с тем же типом ошибки, хотя она не является специфичной для shadcn. Многие шаблоны Tailwind v3 Next устанавливают important: '#__next', что ограничивает действие всех стилей этим корневым идентификатором. У элемента canvas нет #__next, поэтому стили больше не совпадают. Селектор конфигурации должен быть перенаправлен на корневой узел рендеринга, а замена должна оставаться селектором по идентификатору, поскольку уровень специфичности (1,0,0) играет ключевую роль против собственного CSS шаблона.

    Преимущества извлечения данных из DOM:

    Может использоваться любое имя класса, сгенерированное во время выполнения. cn(), clsx, twMerge, CVA, тернарные выражения, таблицы поиска или конкатенация из атрибутов — движок никогда не должен парсить эти форматы. К моменту сборки коллекции процесс композиции уже завершился, и результат хранится в атрибуте class. Шаблоны вроде shadcn-admin отображаются корректно не из-за особенностей shadcn, а потому что этот подход не зависит от способа формирования строки.

    Честный лимит: CSS применяется только к тем классам, которые действительно отрисовываются. Обычная сборка Tailwind сканирует исходный код и генерирует обе ветви выражения cond ? 'bg-red-500' : 'bg-blue-500' даже в том случае, когда может отобразиться только одна из них. В таком случае неиспользуемая ветвь остается без стилей до момента отрисовки. Объект MutationObserver применяет стили к ней при изменении состояния — но первый кадр после этого изменения может на мгновение остаться без стилей. На практике это проявляется в кратковременном пустом пространстве при первом переключении на ранее невидимый вариант.

    Этот компромисс влияет с обеих сторон. Рассмотрим statusColors[res.status], где ключ поступает из API. В исходном коде не может существовать полного литерала-утилиты, поэтому сканирование кода ничего не выдаёт — для решения этой проблемы существуют списки разрешённых значений. Сборка DOM не обращает на это внимания: как только класс присутствует на элементе, он собирается. Сканирование исходного кода охватывает ветки, которые ещё не выполнялись. Сборка DOM охватывает имена классов, которые никогда не записывались в виде литералов. В браузере без процесса сборки второй вариант практически является единственным возможным.

    Что позволяет эта основа — и чего не позволяет

    Благодаря этому решению шаблон в стиле shadcn-admin — с именами классов, вариантами CVA, функцией cn() для комбинирования — может отображаться без установки, без сервера разработки, с использованием настоящего движка Tailwind и реальной конфигурации проекта.

    Преимущество заключается не в том, что «визуальная обработка завершена». Оно заключается в устранении другого вида ограничений.

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

    Именно эта стоимость в виде установки и ожидания исчезает. Проект shadcn может использовать свои реальные классы в течение секунд, после чего функции Studio, ориентированные на код, будут работать независимо от системы стилей: двунаправленный выбор между кодом и интерфейсом, возможность детального просмотра компонента, определенного в нескольких файлах, и проверка того, что сломается при удалении определенного файла. Эти функции работают с структурой исходного кода, а не с тем, как был создан CSS. Для их работы сначала требуется успешная отрисовка.

    Отсутствует инспектор стилей: className по-прежнему редактируется как простая строка, ничто не считывает карту CVA для создания элементов управления вариантами, а токены из tailwind.config доходят до движка, но не до панели. Инструменты MUI уже поддерживают редактирование тем и структурированных значений sx; Tailwind пока этого не делает. Лучше ясно указать на этот разрыв, чем позволять демо-версии создавать ложное впечатление.

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

    Поскольку shadcn использует утилиты вместо свойств стиля для формирования визуала, добавление строк классов, сгенерированных во время выполнения, на страницу с использованием настоящего CSS должно происходить до того, как любой структурированный инспектор сможет их изменить. Это затруднительно делать прямо в браузере: официальный движок отслеживает легковесный DOM, в то время как режим дизайна работает с контентом внутри «тени» корня DOM. Преодоление этой проблемы достигается путем извлечения классов непосредственно из активного DOM, а не из файлов. Такая основа наконец стала доступна.

    То, что строится поверх нее, меньше по объему, чем сама основа, но это не так уж просто. Карта CVA представляет собой обычный объект в локальном файле; её чтение и создание селектора вариантов — это обычные задачи интерфейса. Однако возвращение изменений обратно — это уже не так просто. Имя класса может поступать одновременно из нескольких источников:

    className={cn("border-b", isActive && "bg-red-500", buttonVariants({ variant }))}
    

    Если панель устанавливает отступ в 6, кто-то должен решить, какой именно элемент необходимо изменить — основную строку, условный вариант или общую конфигурацию, используемую всеми остальными кнопками. Иногда единственно правильного ответа нет, есть лишь определённые правила. Проблема возврата изменений — это следующий сложный аспект, и он сложен по-другому, чем проблема стилизации.

    До начала работы было бы легко угадать, какая часть сложнее — и ошибиться в прогнозе.

    Команды, работающие с shadcn и заботящиеся о том, как должна вести себя эта панель управления — выпадающие списки вариантов, управляющие отступами, которые заменяют вспомогательные функции на стили внутри элемента, правила определения того, какой именно элемент должен быть изменён при возврате данных — это та аудитория, которая способна сформировать этот слой до выпуска продукта, а не после.