Strona główna / Artykuły

Dlaczego edytorzy wizualni mają trudności z komponentami należącymi do interfejsu shadcn UI

Edytorzy wizualni mają trudności z shadcn ui, ponieważ mapy CVA znajdują się w repozytorium, a Tailwind wymaga procesu budowania. Zbieranie klas DOM umożliwia przechodzenie w tryb projektowania w React.

Wydawca: REACTAPP.TOP9 min czytania2063 słów

Źródła: shadcn/ui says the code is yours. So why can’t anything edit it?

shadcn/ui prezentuje się jako rozwiązanie typu „kopiuj-wklej” – uruchom interfejs CLI, umieść plik components/ui/button.tsx w repozytorium i traktuj go jako kod własny projektu. Nie ma tu konieczności aktualizacji w ciągłym cyklu, walk z wrapperami ani konfrontacji ze stylówkami innego pakietu.

To podejście rodzi praktyczne pytanie: skoro komponent już znajduje się w projekcie, dlaczego jego zmiana nadal wymaga ręcznego otwierania pliku?

Odpowiedź jest techniczna i nie jest to pierwsza hipoteza, do której dochodzą większość zespołów.

Budowniczowie interfejsów wizualnych oczekują katalogu właściwości. Mapy własne CVA są inne.

Budowniczowie interfejsów wizualnych w React zazwyczaj zakładają, że komponenty pochodzą z pakietu o stabilnej, udokumentowanej strukturze właściwości. Material UI jest klasycznym przykładem. Przycisk udostępnia takie typy jak variant, size i color. Narzędzie może odczytać te typy, wyświetlić listy rozwijane i zapisać tekst z powrotem. Komponent pozostaje „czarną skrzynką” z oznakowanym panelami sterowania z przodu.

Zamiast tego otwórz przycisk z 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" }, } )

Nie ma żadnego zewnętrznego katalogu właściwości. Istnieje mapa wariantów — zwykły TypeScript w pliku należącym do projektu.

Powszechnie uważa się, że to utrudnia pracę z narzędziami: nie ma nic do analizy w folderze node_modules, brakuje strony z dokumentacją zebraną automatycznie, a każda modyfikacja projektu powoduje odchylenia. Edytory w formie pakietów naprawdę nie mogą mieć takiej struktury.

Bardziej prawdopodobna interpretacja jest inna. Mapa CVA zawiera więcej informacji niż tabela propów, a te informacje już znajdują się na dysku.

Tabela propów podaje, że variant przyjmuje wartość "contained". Nie mówi jednak, co oznacza "contained". To powiązanie jest kompilowane wewnątrz biblioteki. Aby je poznać, trzeba przeczytać dokumentację, zgadywać lub sprawdzić to na żywo.

Mapa CVA stwierdza, że variant: "destructive" jest bg-destructive text-destructive-foreground. Ta relacja to dane znajdujące się w źródle i podlegające lokalnej kontroli. Nic nie jest ukryte, ponieważ nic nie zostało zapakowane oddzielnie.

Zatem twierdzenie jest dosyć wąskie:

Strukturalna edycja wizualna wymaga wiedzy o tym, co robi dany element sterujący, a nie tylko o jego istnieniu. Komponenty pakietowe ukrywają tę wiedzę w skompilowanych strukturach wewnętrznych. shadcn przechowuje ją jako dosłowny obiekt w repozytorium. Z tego punktu widzenia shadcn powinien być najłatwiejszą biblioteką do edycji wizualnej – a nie najtrudniejszą.

To, czego nadal brakuje, to strukturalna edycja wariantów kierowana przez wasz mapę CVA w waszym drzewie. Istnieją wizualizatory Tailwind oraz edytory tematów shadcn, ale narzędzia do tematów przepisują zmienne CSS. To zmienia to, na co odsyła bg-primary; nie zmienia jednak tego, który wariant używa przycisku ani jakie funkcje zawiera ten wariant. Przeszkoda w realizacji drugiego zadania znajduje się o jeden poziom niżej niż warianty.

Prawdziwa bariera: klasy funkcjonalne stają się CSS dopiero po skanowaniu

MUI stosuje styl w czasie wykonywania. Obiekt sx={{ p: 2 }} przekształca się w CSS podczas renderowania komponentu — nie ma potrzeby żadnych uprzednich obliczeń ani skanowania. Uruchomienie MUI w przeglądarce bez lokalnej instalacji nadal jest trudne, ale trudność ta wynika z rozwiązywania problemów z modułami, instancjami tematów i emocji typu singleton oraz spójności wersji pakietów, a nie z generowania stylu.

Tailwind odwraca ten model. Skanuje tekst źródłowy w poszukiwaniu nazw klas użytecznościowych i emituje tylko te reguły. Brak skanowania oznacza brak CSS. Skanowanie źródła jest etapem budowy.

W przypadku środowiska, które uruchamia drzewo aplikacji bez użycia npm install czy lokalnego nadzorcy, komponenty oparte na klasach użytecznościowych napotykają problemy, których MUI nigdy nie miało. button.tsx może zostać poprawnie przetłumaczony, ale nadal generuje kontrolkę bez stylizacji. Tokeny klas pojawiają się w DOM-ie, ale nie mają żadnego znaczenia, ponieważ żaden proces nie wygenerował odpowiadających im reguł.

Dlatego narzędzia wizualne utykają na progu rozwiązania. Warianty nie są przyczyną podstawową – problemem jest stylizacja.

Istnieje bardziej precyzyjne spojrzenie na ten sam problem. Skaner Tailwind czyta ciągi źródłowe, natomiast komponenty shadcn często w miejscu wywołania w ogóle nie zapisują tych ciągów – zamiast tego wywołują funkcję. Na tej linii nie może pojawić się żaden literal typu "bg-destructive"; kompozycja w czasie wykonywania łączy mapę CVA, przychodzące propy oraz modyfikacje z wywołującego kodu.

Zwykłe procesy budowania radzą sobie z tym dobrze: skanowany jest plik mapy wariantów, tam znajdują się pełne literalne wartości, które są widoczne dla narzędzia do wydobywania informacji. Prawdziwą zasadą Tailwind jest „nie tworzyć ciągów klasy w formie text-${color}-500”. Mapy CVA przechowują pełne literalne wartości. shadcn to zalecany wzorzec, a nie hack; w prawidłowym procesie budowania nie wymaga on żadnej listy bezpieczeństwa.

Bез lokalnego pipeline’u nic nie przeszukuje plików w poszukiwaniu narzędzi — więc kompozycja nie ma gdzie się zarejestrować. Dlatego tryb bez instalacji jest bardziej rygorystyczny niż ogólne przypomnienie, że Tailwind oczekuje procesu budowania.

Rozwiązanie tymczasowe: pozyskiwanie narzędzi z wyrenderowanego markupu

Prawidłowym rozwiązaniem jest rezygnacja z skanowania źródeł kodu.

Canvas uruchamia autentyczny silnik Tailwinda w przeglądarce — kod pochodzący bezpośrednio z projektu, a nie domowy parser. Wybór silnika zależy od wersji: aplikacje v3 ładują wersję z Play CDN dostarczaną przez Studio; wersje v4+ ładują @tailwindcss/browser w ten sam sposób. Pobieranie z sieci jest przeznaczone dla wersji bez wbudowanego odpowiednika — np. hipotetycznego projektu v5. Plik tailwind.config projektu jest przekazywany silnikowi, dzięki czemu rozszerzenia tematyczne pozostają autorytatywne.

Dlaczego nie uruchomić w przeglądarce narzędzia do wydobywania danych z Tailwind i mimo to przeskanować źródło kodu? Pomiary pokazały wyraźnie koszty: już jedna renderizacja powoduje około 40 do 340 odczytów plików hosta, a w dużym szablonie jeden taki odczyt może trwać 5–8 ms, bez żadnej korzyści z równoczesnego wykonywania operacji. Skanowanie repozytorium oznacza odczytanie każdego pliku, a nie tylko tych, które zostały zrenderowane. Pomimo tych kosztów nadal pomijane są nazwy klas składane w czasie wykonywania. Zbieranie danych DOM polega na zamianie „odczytu całego drzewa” na „odczyt jedynie zrenderowanego wyniku”.

Uruchomienie silnika to tylko połowa historii. Wtedy pojawia się ten kłopotliwy ograniczenie.

Silnik działa w zakresie dokumentu i obserwuje jedynie lekki DOM – wywołuje observe(document.documentElement) i nigdy nie wchodzi do korzeni cienia. Tryb projektowania renderuje szablon wewnątrz korzenia cienia w celu izolacji. Dlatego silnik patrzy na zewnętrzny dokument, nie widzi żadnych nazw klas komponentów i nic nie wysyła. Poprawny silnik, poprawna konfiguracja, pusta lista stylów.

Most, który je ponownie łączy:

  1. Po renderowaniu przeglądaj DOM cienia i zbieraj wszystkie tokeny klas obecne na rzeczywistych elementach.
  2. Kopiuj zebrane tokeny do ukrytego węzła odbiorczego lekkiego DOM-u, który jest widoczny dla obserwatora.
  3. Pozwól silnikowi wygenerować reguły dokładnie dla używanych narzędzi.
  4. Wprowadź powstałą listę stylów z powrotem do korzenia cienia.
  • Do korzenia cienia należy dołączyć MutationObserver dla poddrzewa, z ustawieniem attributeFilter: ['class']—aby późniejsze zmiany tras, treści ładowane w tle oraz warunkowa interfejs użytkownika nadal aktualizowały ten element.
  • Jeden szczegół okazał się kluczowy: element ten musi zostać wstawiony za pomocą natywnej funkcji appendChild. Canvas modyfikuje document.body.appendChild, aby kierować elementy do korzenia cienia; użycie tej zmodyfikowanej funkcji chowa element wewnątrz drzewa cienia, gdzie silnik nie może go zobaczyć. W rezultacie most komunikacyjny przestaje działać bez żadnych objawów.

    Taki element pozostaje prawdziwym węzłem light-DOM i nadal gromadzi tokeny w trakcie nawigacji—nowe trasy dostarczają niezbędne funkcje bez konieczności ponownego ładowania canvas.

    Kolejny problem polega na tym, że występuje ta sama awaria, mimo iż nie dotyczy konkretnie shadcn. Wiele szablonów Tailwind v3 Next ustawia important: '#__next', co obejmuje wszystkie funkcje stylizacyjne pod tym identyfikatorem korzeniowym. W pliku canvas nie ma #__next, więc funkcje stylizacyjne przestają pasować. Wybieracz konfiguracji musi zostać skierowany do korzenia renderowania w trybie rzeczywistym, a zamiennik musi pozostać wybieraczem ID, ponieważ specyficzność (1,0,0) jest kluczowa w konfrontacji z własnym CSS szablonu.

    Korzyści płynące z pobierania danych z DOM:

    Każda nazwa klasy utworzona w czasie wykonywania funkcjonuje poprawnie. cn(), clsx, twMerge, CVA, wyrażenia ternarne, tablice wyszukiwania lub łączenie wartości z atrybutów — silnik nie musi nigdy analizować tych form. W momencie zbierania danych kompozycja już się odbyła, a wynik znajduje się w atrybucie class. Szablony takie jak shadcn-admin renderują się poprawnie nie ze względu na specjalne rozwiązanie typu shadcn, ale dlatego, że ten podejście jest obojętne na to, w jaki sposób utworzono ciąg znaków.

    Prawdziwy limit: CSS otrzymują tylko te klasy, które faktycznie zostały wyświetlone. Zwykłe budowanie projektu z Tailwind skanuje kod źródłowy i generuje obie wersje wyrażenia cond ? 'bg-red-500' : 'bg-blue-500', nawet gdy tylko jedna z nich może być widoczna. W takim przypadku niewykorzystana wersja pozostaje bez stylizacji aż do momentu wyświetlenia. MutationObserver uzupełnia ją po zmianie stanu — ale pierwszy kadr po tej zmianie może przez chwilę wyglądać bez stylizacji. W praktyce objawia się to krótkim pustym obszarem przy pierwszym włączeniu wariantu, którego wcześniej nie było widać.

    Kompromis dotyczy obu stron. Weźmy pod uwagę statusColors[res.status], gdzie klucz pochodzi z API. W źródle nie może istnieć pełny literał typu utility, więc proces skanowania nie wytwarza niczego — istnieją listy bezpieczne, aby rozwiązać ten problem. Zbieranie danych z DOM nie ma znaczenia; gdy klasa znajduje się na elemencie, jest ona zebrana. Skanowanie źródła obejmuje gałęzie, które nie zostały wykonyane. Zbieranie danych z DOM obejmuje nazwy klas, które nigdy nie zostały zapisane jako literały. W przeglądarce bez procesu budowania druga opcja jest w dużej mierze wymuszona.

    Czego umożliwia ta podstawa — a czego nie

    Dzięki temu mostowi szablon w stylu shadcn-admin — nazwy klas, warianty CVA, kompozycja cn() — może być renderowany bez konieczności instalacji, bez serwera rozwojowego, przy użyciu prawdziwego silnika Tailwind oraz rzeczywistej konfiguracji projektu.

    Zyskiem nie jest „zakończenie edycji wizualnej”. Jest to usunięcie innego rodzaju obciążenia.

    Otwieranie nieznanej szablonu administracyjnego zazwyczaj oznacza klonowanie, instalację, czekanie, uruchomienie, kilkuminutowe klikanie, a następnie zamknięcie. Większość tego czasu jest pochłaniana przez sam komputer. Ten sam problem występuje podczas pokazywania kolegom z zespołu trzech stanów komponentu, sprawdzania, czy tryb ciemny jest już gotowy, lub analizowania odstępów między elementami karty przed złożeniem propozycji zmiany. Te zadania zajmują sekundy, gdy tylko coś pojawi się na ekranie.

    To właśnie koszt instalacji i czekania znika. Projekt shadcn potrafi w kilka sekund wyświetlić elementy przy użyciu swoich rzeczywistych klas, po czym funkcje Studio skierowane na kod działają niezależnie od systemu stylizacji: dwukierunkowy wybór pomiędzy kodem a obszarem roboczym, możliwość zagłębiania się w komponent zdefiniowany w kilku plikach oraz informacja o tym, co się zmieni po usunięciu danego pliku. Te funkcje opierają się na strukturze źródłowej, a nie na sposobie tworzenia CSS. Wymagają jedynie pierwszego pomyślnego renderowania.

    Brakuje jeszcze inspektora stylów: className jest nadal edytowany jako surowa wartość typu string, nic nie odczytuje mapy CVA w celu utworzenia kontrolerów wariantów, a tokeny z tailwind.config docierają do silnika, ale nie do panelu. Narzędzia MUI już umożliwiają edycję tematu oraz strukturyzowanego sx; strona Tailwind jeszcze tego nie robi. Wskazanie na tę luki jest lepsze niż pozwalanie, by demo sugerowało coś innego.

    Prawdziwym problemem jest kolejność działań, a z zewnątrz łatwo ją przeoczyć:

    Ponieważ Shadcn używa narzędzi pomocniczych zamiast właściwości stylowych, umieszczenie na stronie ciągów klas utworzonych w czasie wykonywania z użyciem prawdziwego CSS musi nastąpić przed tym, jak jakikolwiek zorganizowany inspektor będzie mógł je edytować. Robienie tego w przeglądarce jest niewygodne: oficjalny silnik monitoruje lekki DOM, podczas gdy tryb projektowania rysuje elementy wewnątrz shadow root. Przezwyciężenie tego problemu poprzez pobieranie klas z żywego DOM-u – a nie z plików – zamienia tę luki. To fundament jest wreszcie dostępny.

    Część nadbudowy jest mniejsza od tego fundamentu, ale nie jest prosta. Mapa CVA to dosłowny obiekt w pliku lokalnym; odczytanie jej i narysowanie selektora wariantu to zwykła praca interfejsu użytkownika. Zapisanie zmiany z powrotem już takie nie jest. Nazwa klasy może pochodzić jednocześnie z kilku źródeł kompozycji:

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

    Jeśli panel ustawia odstęp na 6, ktoś musi zdecydować, który element tekstowy należy edytować – podstawową ścieżkę tekstu, warunki lub wspólną definicję wariantu używaną przez wszystkie pozostałe przyciski. Czasami nie ma jednoznacznie poprawnej odpowiedzi, istnieje tylko określona zasada. Ten problem powrotu danych jest kolejnym trudnym elementem, a jego rozwiązanie jest skomplikowane w inny sposób niż w przypadku mostka stylizacyjnego.

    Zanim rozpoczęto prace, łatwo byłoby zgadnąć, która część jest trudniejsza – i się pomylić.

    Zespoły pracujące w shadcn i zależne od tego, jak powinna funkcjonować ta powłoka sterująca – np. spadające listy wariantów sterowane CVA, kontrolery odstępów, które zamiast stylów wstawianych bezpośrednio przepisują funkcje pomocnicze, reguły określające, który element tekstowy powinien zostać zmodyfikowany – to właściwa grupa odbiorców, która może kształtować tę warstwę przed jej wdrożeniem, a nie po nim.

    Literatura pokrewna

  • React micro-frontends z Webpack Module Federation — Zastąp rozwiązania oparte na iframe metodami Webpack 5 Module Federation, aby aplikacje React zarówno lokalne, jak i zdalne mogły dzielić się środowiskiem wykonawczym, być wdrażane niezależnie, a jednocześnie wyglądać jak jeden produkt.
  • Wzory grup przycisków Shadcn dla interfejsów React i Next.js — Poznaj ponad 10 wzorów grup przycisków Shadcn – stronicowanie, paski narzędzi, przełączniki walut, środowiska wdrażania, oceny – oraz zastosowanie każdego z nich w aplikacjach React.