React Toolbox
07 Apr 2017Ostatnio natknąłem się na świetne porównanie Angular2, React i Aurelii. Z jednej strony porównanie utwierdziło mnie w przekonaniu, że wybór Reacta to była właściwa droga, a z drugiej... pomogło mi wybrać React Toolbox jako zestaw komponentów Material Design do startera. Choć nie jestem do końca pewien, czy to słuszny wybór.
We wspomnianym artykule autor podał przykład czterech dojrzałych bibliotek do Material Design w React.js (zachwycając się jednocześnie bogactwem reactowego ekosystemu):
Tego drugiego, Material-UI, próbowałem już wcześniej i z niego zrezygnowałem, bo miał kilka rzeczy, które mi odpowiadały. Co ciekawe, Material-UI miał też najwięcej gwiazdek spośród tych czterech bibliotek (24.5 tys. vs 5.8 tys. dla drugiego na tej liście Material Toolbox). Poklikałem trochę na przykładowe komponenty, przejrzałem GitHuba i ostatecznie postanowiłem włączyć do startera właśnie React Toolbox.
No to jedziemy:
npm uninstall --save material-ui
npm install --save react-toolbox
Oczywiście po czymś takim Webpack wypluwa mnóstwo błędów, bo nagle komponenty z Material-UI poznikały, ale projekt jest mały, więc nie chciałem się bawić w spokojne przechodzenie pomiędzy jedną biblioteką a drugą. Na początku zmiany postępowały szybko i wyeliminowałem większość tych błędów, ale jak to (prawie) zawsze w programowaniu bywa, pojawiły się nieprzewidziane okoliczności 😉
CSS Modules
Zainstalowałem bibliotekę, przerobiłem moje pięć komponentów na krzyż, co rozwiązało wszystkie błędy Webpacka, poza jednym, bardzo dziwnym i mało mówiącym:
Error: composition is only allowed when selector is single :local class name not in ".raised", ".raised" is weird
Na szczęście udało mi się znaleźć odpowiedni issue na GitHubie na zbliżony temat, a tam komentarz:
This is a classic. You need to add configuration parameters for css modules.
CSS Modules? A co to takiego?
Generalnie chodzi o to, żeby nazwy klas i animacji z CSS miały domyślnie lokalny scope (stąd wziąłem te informacje). W praktyce polega to na tym, że pisze się kod HTML w JavaScripcie i zamieszcza się w nim zaimportowane style. Przykład:
import styles from "./styles.css";
element.innerHTML =
`<h1 class="${styles.title}">
An example heading
</h1>`;
Odpowiednie loadery dla Webpacka przeprocesują taki kod i nadadzą klasie CSS taką nazwę, żeby styl był lokalny. W wyniku powstanie na przykład coś takiego:
<h1 class="_styles__title_309571057">
An example heading
</h1>
Najwyraźniej React Toolbox używa CSS Modules, dlatego muszę pozwolić na to loaderom Webpacka. W przypadku mojej skromnej aplikacji sprowadza się to zmiany tego:
const CSSLoader = {
test: /\.css$/,
loader: "style-loader!css-loader"
};
na to:
const CSSLoader = {
test: /\.css$/,
loader: "style-loader!css-loader?modules"
};
PostCSS
Kolejny problem pojawił się chwilę później, kiedy utworzyłem sobie komponent na górne menu:
const Header = (props) => (
<AppBar title='Serverless WebApp Starter' leftIcon='menu'></AppBar>
);
Niestety w aplikacji wyglądało to następująco:
Próbowałem to rozwiązać na kilka sposobów, jednak na szczęście stosunkowo szybko zauważyłem, że we wszystkich przykładach z React Toolbox używany jest jeszcze jeden loader dla Webpacka: postcss-loader.
W ogóle PostCSS to wydaje się całkiem inne środowisko, bo jest loader do Webpacka, a oprócz tego mnóstwo pluginów, które przetwarzają pliki CSS.
Postanowiłem spróbować, czy dodanie postcss-loader
rozwiąże mój problem.
Po pierwsze, dodałem postcss-loader
do konfiguracji Webpacka w webpack.config.js
:
const CSSLoader = {
test: /\.css$/,
loader: "style-loader!css-loader?modules!postcss-loader"
};
Następnie do głównego katalogu dodałem plik konfiguracyjny dla tego loadera, postcss.config.js
(skopiowałem go z oficjalnego przykładu dla React Toolbox):
module.exports = {
plugins: {
'postcss-import': {
root: __dirname,
},
'postcss-mixins': {},
'postcss-each': {},
'postcss-cssnext': {}
}
};
A żeby to wszystko zadziałało, musiałem jeszcze zainstalować kilka rzeczy:
npm install --save-dev postcss-loader postcss-import postcss-mixins postcss-each postcss-cssnext
Działa? Działa.
AppBar
Przeniesienie całego projektu na React Toolbox nie było specjalnie trudne, choć jeden z komponentów (AppBar
, czyli górne menu) sprawił trochę problemów, i to podobnych do tych z Material-UI.
W zasadzie to problemy te sprawiły, że zacząłem się zastanawiać, czy React Toolbox, to na pewno był dobry wybór.
Ale nie ma co na razie rezygnować, przede mną jeszcze więcej zabawy z pisaniem formularzy, wtedy zobaczymy, na ile się sprawdzi.
Po pierwsze, okazało się, że w AppBar
, jeśli zamiast ikony po prawej (rightIcon
) zagnieżdżone zostało IconMenu
(czyli taka ikona, po kliknięciu na którą pojawia się menu), to ikona ta jest zawsze czarna.
Musiałem poszukać w strukturze HTML, jak wygląda element, któremu ręcznie trzeba zmienić kolor.
Rozwiązałem to dość siłowo i w globalnym CSS nadpisałem styl, ale działa.
No i działa też na te rozszerzające się kółeczka, które się pojawiają po kliknięciu na przycisk.
(Ticket na GitHubie).
nav button {
color: inherit !important;
}
Pewnie kiedyś będę to musiał zmienić, bo się okaże, że w innym kontekście też mi to napisuje style, a nie powinno. No ale zobaczymy.
Po drugie, miałem straszny problem, żeby poradzić sobie z zagnieżdżeniem linka w tym IconMenu
(tak działa w starterze przejście do Profile
).
Z jednej strony chodzi o style i o to, że jak wrzuciłem Link
z React Routera do pozycji w menu, to kliknięcie z boku tej pozycji nie robi nic, tylko zamyka menu – bo po prostu Link
tam nie sięga, więc klika się obok.
A inny problem jest taki, że komponenty Link
, które pochodzą z React Toolbox, nie korzystają z React Routera, na co zresztą też można znaleźć tickety (np. ten, ten i ten).
Innymi słowy, miałem trochę problemów z konfiguracją React Toolbox i kilka rzeczy robiłem pod górkę. Bilbioteka jak biblioteka, nie jest może super dojrzała, ale daje odpowiednie możliwości. Nie wiem jeszcze, czy jest lepsza od Material-UI, ale mam nadzieję, że taka będzie.
Zmiany związane z wprowadzeniem React Toolbox widoczne są tutaj. (Deklaracje komponentów w React Toolbox są krótsze, dlatego też eksperymentuję z trochę innym formatowaniem kodu).