Gatsby cz. 3 - CSS
Data publikacji: 2021-07-26
Ostatnia edycja: 2021-08-03
Stały adres serii wpisów o Gatsbym - /blog/gatsby
Wstęp
Popularne jest sformułowanie, że HTML tworzy (liniową) strukturę treści, CSS nadaje jej wygląd (i układ), a JavaScript zapewnia reaktywność. Postulowano fizyczne rozdzielenie HTML, CSS i JS.
Czasem przy tym samym pliku layoutu wystarczy zastosować różne CSS-y, żeby uzyskać zupełnie różne układy strony. Pierwszą popularną prezentacją możliwości CSS była strona CSS Zen Garden.
CSS-in-JS
Wiele rozwiązań stosowanych we frameworkach JS zaciera ten podział, ale nie jest to skutkiem bałaganiarstwa, czy zwróceniem się w stronę złych praktyk. CSS tak jak wszystkie inne elementy aplikacji jest tylko narzędziem i ma swoje ograniczenia. Włączenie CSS do plików stron, jak np. ma to miejsce w Gatsbym, wynika z dwóch głównych powodów:
- dynamiczne tworzenie stron powoduje przenikanie poza kontekst i łączenie reguł CSS dające nieprzewidywalne rezultaty.
- CSS ma ograniczenia wynikające z tego, że - dziękujemy, kapitanie Oczywisty! - nie jest językiem programowania; to język opisu wyglądu.
Rozwiązaniem obu tych bolączek jest CSS-in-JS, które co trzeba podkreślić, nie jest jakimś fenomenalnym, kolejnym etapem rozwoju CSS, ale skuteczną odpowiedzią na powyższe problemy. CSS-in-JS zapewnia:
- izolację kontekstu
- przewidywalność reguł
- integrację z JS - to jest nadal CSS, ale może odbierać argumenty od aplikacji
Więcej o CSS-in-JS:
- JSS nieomówione tu rozwiązanie CSS-in-JS
- Leonardo Maldonado "Understanding CSS-in-JS"
- Andrei Pfeiffer "A Thorough Analysis of CSS-in-JS"
- Chris Coyier The Differing Perspectives on CSS-in-JS
- tuchk4 / awesome-css-in-js Awesome CSS in JS
- polished A lightweight toolset for writing styles in JavaScript
- Harry Wolff "CSS in JS: My favorite" [YT 17:36]
W tym wpisie
Generalnie rzecz biorąc wszystkie możliwości użycia CSS w Gatsbym można podzielić na pięć grup:
- style globalne - import jak z layout.css lub przy pomocy wrappera jak z global.css
- CSS Modules
- CSS-in-JS - Styled Components, Emotion
- frameworki i biblioteki CSS: Bootstrap i MDB, Chakra UI, Bulma, Tailwind CSS
- preprocesory CSS: Stylus, Less, SASS (oraz SCSS)
W sumie 13 rozwiązań. Na początek dwie uwagi:
- Niniejszy wpis nie jest manualem, poza Styled Components, które być może będzie tematem osobnego wpisu, chodzi mi raczej o typowe how-to-start, czyli jak wygląda proces implementacji danego rozwiązania i jakie ma podstawowe cechy.
- Nie jest też kompletny, ale jak sądzę, wymieniłem większość możliwości, która jest rzeczywiście używana.
Layout.css
Najprostszym sposobem na użycie CSS w Gatsbym jest import pliku CSS bezpośrednio do pliku strony lub co ma więcej sensu do komponentu layoutu, który używany jest przez pliki stron.
Najczęściej, tak jak to jest zalecane w dokumentacji, tworzy się plik layout.css znajdujący się razem z komponentami w katalogu /src/components, importowany potem w pliku layout.js:
import * as React from "react"
import "./layout.css"
//
I działa od razu. Beż żadnej konfiguracji czy restartu.
Na dobrą sprawę, w przypadku prostych stron można tam umieścić cały CSS i mieć tę sprawę z głowy.
W praktyce jednak stosowane jest na początku w procesie budowania strony, żeby mieć jakiś CSS na starcie. Potem można z tego zrezygnować albo zostawić tam bazę responsywnego layoutu, czyli sam układ podstawowych elementów strony i media queries. Przykład takiego pliku jest zaprezentowany we wpisie o komponentach w rozdziale "CSS".
Global styles
Innym rodzajem stylu globalnego jest umieszczenie pliku w /src/styles/global.css.
Żeby zadziałał globalnie, trzeba dodatkowo użyć wrappera, czyli pliku konfiguracyjnego gatsby-browser.js:
/gatsby-browser.js
import "./src/styles/global.css"
Ma to tę przewagę, że wszystkie pliki CSS trzymamy w oddzielnym, specjalnie do tego celu przeznaczonym katalogu. Przybliża to nas do klasycznego postulatu rozdzielenia liniowo ułożonej treści i wyglądu.
Jednak tak jak już napisałem, ten postulat jest tu sztuką dla sztuki. Wszak w całym ekosystemie frameworków JS treść i goły układ w JSX (odpowiedniku HTML) jest owinięta w JS, a wiele innych rozwiązań CSS mieszają to jeszcze bardziej.
Jedynym argumentem, który pozostaje to łatwość w organizacji kodu, pewien porządek, który z tego wynika.
Style globalne tak samo jak plik layout.css mają zastosowanie tylko na stronach, które:
- nie wymagają zróżnicowania stylów
- ani właściwości CSS-in-JS
- frameworków CSS takich jak Bootstrap, czy Bulma
Podstawowym problemem, z którego wynika ich niekompatybilność z innymi rozwiązaniami, jest nieograniczone, trudne do powstrzymania przenikanie i nieprzewidywalność rezultatu.
Natomiast można je to zastosować do plików resetu, czy normalizacji. Kolejne style, czy to layoutu, czy Styled Components nadpisują je.
CSS Modules
Nie wymaga konfiguracji. Zasadniczo polega na imporcie CSS jako obiektu JS.
Konwencją jest nazywanie arkuszy styli z umieszczeniem słowa module, przykład: style.module.css
. Jest to zwyczajny plik CSS. Załóżmy, że są tam klasy .outer
i .inner
.
Import na dwa możliwe sposoby wygląda tak:
import { outer, inner } from "../styles/styles.module.css"
import * as styles from "../styles/styles.module.css"
//
<div className={ outer }>
<p className={ inner }>Treść akapitu</p>
</div>
<div className={ styles.outer }>
<p className={`inner ${styles.inner}`}>Treść akapitu</p>
</div>
Jak widać, można zaimportować wybraną klasę lub klasy jako obiekt i umieścić bezpośrednio w deklaracji klasy elementu JSX, lub zaimportować cały arkusz i odwoływać się do niego.
Dynamicznie modyfikuje nazwy stylów, by zachować ich unikalność. Ostatni zapis zapewni nadanie dwóch klas: jedną będzie zmodyfikowana np. styles-module--inner--1JjqS
, druga w tym przypadku będzie niezmieniony inner
.
Pozwala na trzymanie wszystkich klas w jednym katalogu.
- css-modules / css-modules
- Gatsby Component-Scoped Styles with CSS Modules | Part 2: Use and Style React Components #Style components with CSS Modules
- gatsbyjs / gatsby using-css-modules
- CSS TRickd: Robin Rendle "What are CSS Modules and why do we need them?" | Matija Marohnić "Bridging the Gap Between CSS and JavaScript: CSS Modules, PostCSS and the Future of CSS"
- Glen Maddern "CSS Modules Welcome to the Future"
- React: "Adding a CSS Modules Stylesheet"
- Harry Wolff "CSS Modules: Why are they great?" [YT 12:02]
- Fullstack Academy "CSS Modules in React and Webpack Tutorial" [YT 8:42]
- ReactiveConf "The case for CSS modules - Mark Dalgleish" [YT 45:08]
- Rushing Labs "CSS Modules + Create React App" [YT 26:45]
Styled Components
Z dwóch najpopularniejszych rozwiązań CSS-in-JS za najlepszy uważam Styled Components, dlatego od tego zacznę. Styled Components nie używa osobnych plików - poza możliwością umieszczenia wszystkich definicji w osobnym pliku.
Zainstalowany plugin importuje się do komponentów (plików stron i wszystkich innych komponentów) i wpisane do nich reguły aplikują się wyłącznie lokalnie, nie przenikają na zewnątrz. Właśnie w tym celu powstały.
Jeżeli utworzymy plik komponentu tworzącego dowolny element layoutu, zastosowane tam style, zgodnie z regułami CSS zostaną zastosowane tylko do tego komponentu i wszystkich komponentów w nim zagnieżdżonych.
Mają przy tym właściwości, których nie ma zwykły CSS i to też decyduje o wartości tego rozwiązania.
Po zainstalowaniu zastosowanie wygląda tak:
- import pluginu (linia 2)
- utworzenie elementu stylowanego (linie 4-6)
- umieszczenie go w kodzie JSX (linie 11-13)
import * as React from "react"
import styled from "styled-components"
const StyledDiv = styled.div`
// reguły CSS
`;
const PrzykladowyKomponent = () => {
return (
//
<StyledDiv>
...
</StyledDiv>
Jak widać na powyższym przykładzie, definicje styli umieszcza się poza renderem (przed returnem).
W kodzie HTML w tym przypadku renderowany jest zwykły div, któremu przydzielone zostają wszystkie wpisane reguły CSS.
Przykład:
- zwykły CSS
- zmiana na hover
- CSS dla zagnieżdżonego elementu, ampersand oznacza tu stylowany element, który dla tego diva jest rodzicem
- j.w. z tym że użyto tu selektora oznacząjacego bezpośrednie dziecko
- j.w. ale jak widać, można się odnieść do zewnętrznego kontekstu, co jest przydatne w przypadku włączania/wyłączania trybu ciemnego
- media query
font-size:1.1rem;
&:hover {color: red}
& div {color: red;}
& > div {color: red;}
.dark & > div {color: yellow;}
@media(min-width: 900px) {
//
}
To są normalne właściwości CSS, ale Styled Components potrafi więcej:
Stylować można inne stylowane komponenty, także importowane:
//
import StyledDiv from "./styleddiv"
const StyledHero = styled(StyledDiv)`
//
`;
Przekazywanie propsów - w tym wypadku w stylowanym elemencie jest props background={tutaj_ades_obrazka}
:
background: url(${props => props.background}) no-repeat top center;
W nawiasach klamrowych można przekazać dowolny JS, w tym wypadku jest to funkcja strzałkowa z propsem jako parametrem i ciałem funkcji postaci wyodrębnionej nazwy przekazanego propsa.
Można tam też umieścić operator warunkowy (ternary) lub logiczny OR, w obu poniższych przypadkach istnieje wartość domyślna.
- pojedynczy props w tym wypadku np.
red
, tutaj nazwa jest dowolna: - props przekazany w wartości
color="red"
, tutaj nazwa jest wyciągana z propsów, do zastosowania jeśli mamy kilka propsow w elemencie (analogicznie jak w pierwszym przykładzie):
border-color: ${props => props ? "red" : 'black'};
border-color: ${props => props.color || 'black'};
Jeszcze jeden przykład, tym razem wzięty z mojego rtykułu o Gatsbym. Jestem pewien, że skądś go skopiowałem i trochę zmieniłem. Rezultat - no jest jaki jest. Mniejsza o sensowność, ale dobrze pokazuje możliwości Styled Components:
/src/components/indicator.js
import * as React from "react"
import styled, { keyframes } from 'styled-components'
const fadeIn = keyframes`
0% { opacity: 0; }
100% { opacity: 1; }
`;
const StyledIcon = styled.div`
background-color: ${({ primary }) => primary ? 'green' : 'yellow'};
width: 10rem;
height: 2rem;
border-radius: 1rem;
margin: 5px;
border: ${({ border }) => border || 0};
border-color: ${({ progress }) => {
let numeric = progress.slice(0, -1)
let integer = parseInt(numeric)
if (integer >= 80) return 'red';
else if (integer >= 60) return 'orange';
else if (integer >= 40) return 'yellow';
else return 'green';
}};
animation: 5s ${fadeIn} ease-in;
font-weight: bold;
color: #333;
`;
const TodayIcon = styled(StyledIcon)`
background-color: purple;
color: #eed;
`;
const Indicator = ({ primary, border, day, progress, children }) => {
let today = new Date()
let dayOfTheWeek = today.getDay()
const isToday = day === dayOfTheWeek
return (
<>
{isToday ?
<TodayIcon border={border} primary={primary} progress={progress}>{children}</TodayIcon> :
<StyledIcon border={border} primary={primary} progress={progress}>{children}</StyledIcon>}
</>)
}
export default Indicator
Potem na dowolnej stronie:
//
import Indicator from "../components/indicator"
//
<Indicator primary border='solid 10px' progress='30%' day={3}>inside text</Indicator>
//
Tutaj tylko zarysowałem najważniejsze, najczęściej używane właściwości Styled Components. Podobnie jak w przypadku niżej wymienionych po więcej odsyłam do dokumentacji.
- styled-components
- Wojciech Snopkowski "Gatsby Navigation using Styled Components and useState hook"
- hello roman "Styled components + Gatsby.js" [YT playlista 5 filmów]
- ScottSpence.com "Globally Style the Gatsby Default Starter with styled-components v5"
- Academind "Why you should look into these React component styling options!" [YT 28:35]
- Ben Awad "React Styled Components Tutorial" [YT 13:27]
- Daniel Stout "Using styled-components in Gatsby"
- Adrian Twarog "Styled Components" [YT playlist 3 filmy]
- Hungry Turtle Code "What are Styled Components? - A Beginners Guide" [YT 20:10]
- Classsed "React Styled Components (Next Level CSS?)" [YT 18:27]
- Better Coding Academy "Learn Styled Components In 15 Minutes! | React Tutorials" [YT 15:12]
- Fidalgo "Styled Components Tutorial - Crash Course [2020]" [YT 47:42]
- Wrong Akram "How To Code With Styled Components and React.js" [YT 24:25]
- James Q Quick "Convert CSS in React To Styled Components" [YT 22:17]
- Karl Hadwen "React Tutorial - Compound Components in React - Styled Components - React Tutorial 2020" [YT 23:48]
- ComScience Simplified "Styled Components with React | All you need to know" [YT 10:06]
Emotion
Instalacja:
$ npm i gatsby-plugin-emotion @emotion/react @emotion/styled
/gatsby-config.js
`gatsby-plugin-emotion`,
Jak widać poniżej bardzo przypomina styled-components:
import React from "react"
import styled from "@emotion/styled"
import { css } from "@emotion/react"
const Element = styled.div`
//
`
const Wrapper = styled.div`
//
`
const Card = props => {
<Element>
<h3>{props.title}</h3>
<p>{props.text}</p>
</Element>
}
const PageEmotion = () => {
return (
<Wrapper>
<Card
title = "Pierwsza Karta"
text = "Treść pierwszej karty"
/>
<Card
title = "Druga Karta"
text = "Treść drugiej karty"
/>
</Wrapper>
)
}
export default PageEmotion
Dodatkowo Emotion umożliwia trzymanie wszystkich stylów i utworzonych komponentów w plikach JS, np. w katalogu /src/styles/emotion.
/src/styles/emotion/styles.js
import styled from "@emotion/styled"
export const Element = styled.div`
//
`
export const Wrapper = styled.div`
//
`
Import potem wygląda tak:
import { Element, Wrapper } from "../styles/emotion/styles"
import Card from "./Card"
Bootstrap i MDB
Instalacja:
$ npm i bootstrap
/gatsby-browser:
import "bootstrap/dist/css/bootstrap.min.css"
I po restarcie możemy korzystać z klas Bootstrapa i jego metod globalnie na całej stronie, trzeba tylko pamiętać, żeby wszystkie class zamienić na className.
Innym rozwiązaniem niewymagającym instalacji jest ściągnięcie pakietu, rozpakowanie go w np. w src/styles i importowanie bezpośrednio do pliku strony:
import "../styles/bootstrap-5.0.2-dist/css/bootstrap.min.css"
MDB
Instalacja - z powodu starych zależności musiałem użyć opcji --legacy-peer-deps, potem Gatsbny zgłosił brak modułu react-is, więc w końcu wygląda to tak:
$ npm i mdbreact react-is --legacy-peer-deps
Wrapper.
/gatsby-browser.js
import '@fortawesome/fontawesome-free/css/all.min.css'
import 'bootstrap-css-only/css/bootstrap.min.css'
import 'mdbreact/dist/css/mdb.css'
I wszystko działa. Dzięki wrapperowi nie trzeba do plików wpisywać importów.
- MDB Download & installation guide
- Anna Morawska "Gatsby & MDB React - quick start quide"
Chakra UI
Biblioteka komponentów.
Instalacja:
$ npm i gatsby-plugin-chakra-ui @chakra-ui/core @emotion/core @emotion/styled emotion-theming
/gatsby-config.js
"@chakra-ui/gatsby-plugin",
Import potrzebnych komponentów do pliku, proszę zwrócić uwagę, że trzeba zmienić nazwę w imporcie natywnego komponentu Link, żeby użyć komponentu Chakra UI o tej samej nazwie:
import * as React from "react"
import { Link as GatsbyLink } from "gatsby"
import { Box, Text, Link, ListItem, UnorderedList } from "@chakra-ui/core";
//
<Box as="header">
<Link as={GatsbyLink} activeClassName="menuLinkActive" className="link-header" to="/">
<Text as="h1">Tytuł strony</Text>
</Link>
<UnorderedList className="menu">
<ListItem><Link as={GatsbyLink} activeClassName="menuLinkActive" to="/">home</Link></ListItem>
<ListItem><Link as={GatsbyLink} activeClassName="menuLinkActive" to="/about">about</Link></ListItem>
//
</UnorderedList>
</Box>
Tailwind CSS
Wzbudzający wiele kontrowersji framework CSS. Kocha się go lub nienawidzi.
Instalacja:
$ npm i gatsby-plugin-postcss tailwindcss@latest postcss@latest autoprefixer@latest
$ npx tailwindcss init -p
/gatsby-config.js
'gatsby-plugin-postcss'
/src/styles/global.css
@tailwind base;
@tailwind components;
@tailwind utilities;
/gatsby-browser.js
import './src/styles/global.css';
I to już wszystko. W największym uproszczeniu, bo reguły Tailwinda działają, ale pozostaje jeszcze jego konfiguracja, optymalizacja, no i wejście w głąb tego frameworka.
- Tailwind CSS | Install Tailwind CSS with Gatsby
- Gatsby Tailwind CSS
- hello roman "Testuję Tailwind + Gatsby.js! ⌨️ hello roman #126" [YT 29:46]
Bulma
Biblioteka CSS bez JS, dzięki czemu łatwo się integruje w środowisku JS.
Najprościej ściągnąć i rozpakować w katalogu /src/styles i zaimportować lokalny plik przez wrappera.
/gatsby-browser.js
import "/src/styles/bulma/css/bulma.min.css"
I działa jak specyfikacja chciała. Jeżeli dopisujemy własne arkusze stylów trzeba je importować. Bulma jest silnie zorientowana na użycie SCSS i SASS.
Oczywiście można też zainstalować pakiet npm, najlepiej od razu z Sassem.
$ npm i bulma sass gatsby-plugin-sass
i dodać w gatsby-config.js:
`gatsby-plugin-sass`,
- Bulma | With node-sass
- Gatsby Bulma
Stylus
Preprocesor CSS.
Instalacja:
$ npm i gatsby-plugin-stylus
/gatsby-config.js
`gatsby-plugin-stylus`,
Pliki Stylusa mają rozszerzenie .styl. Można je trzymać w katalogu /src/styles/stylus. Importuje się je do plików.
Less
Instalacja:
$ npm i gatsby-plugin-less
/src/gatsby-config.js
`gatsby-plugin-less`,
Pliki Lessa mają rozszerzenie .less. Tak samo jak w przypadku Stylusa, można je trzymać w katalogu /src/styles i trzeba je zaimportować do pliku.
SASS
Sass ma dwie składnie (i jak widać dwie pisownie, czasem pisze się jego nazwę jak typowy akronim, a czasem jak nazwę własną):
- starsza i mniej popularna operująca na wcięciach, nazywana po prostu Sass
- SCSS - nowsza, częściej używana, zgodna z CSS-em
Instalacja:
$ npm i sass gatsby-plugin-sass
/gatsby-config.js
`gatsby-plugin-sass`,
Fonty i glify
Ze względu na opóźnienie spowodowane przez łączenie się z zewnętrznymi bibliotekami, dokumentacja Gatsby'ego odradza importowanie bibliotek fontów i glifów. Zdecydowanie lepiej jest ściągnąć je do katalogu strony i zaimportować lokalnie.
Font Awesome
Instalacja:
$ npm i @fortawesome/fontawesome-svg-core @fortawesome/free-brands-svg-icons @fortawesome/react-fontawesome
Import do pliku:
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faReact } from "@fortawesome/free-brands-svg-icons"
//
<FontAwesomeIcon icon={faReact} />
Można też bez instalacji, ściągnąć dostępną bez nagabywania o rejestracje wersję 4.7 i tutaj interesuje nas tylko plik /css/font-wesome.css (do produkcji oczywiście zminifikowany). Na stronie FA wyszukujemy potrzebną ikonę, szukamy jej nazwy w pliku i pobieramy wartość content. Zakładając, że chcemy zrobić fancy listę bulletpointem będzie fa-check
a jego wartością content \f00c
. W docelowym pliku komponentu wyglda to potem tak:
//
import styled from "styled-components"
import "../styles/font-awesome-4.7.0/css/font-awesome.css"
//
const StyledLi = styled.li`
position: relative;
list-style-type: none;
&::before {
font-family: "FontAwesome";
display: block;
content: "\f00c";
position: absolute;
top: 0;
left: -2rem;
color:red;
font-size:20px;
`;
//
Google Fonts
Instalacja:
$ npm i @fontsource/open-sans
Import:
import "@fontsource/open-sans"
Bootstrap Icons
Instalacja:
$ npm i react-bootstrap-icons
Import:
import { ArrowRight } from 'react-bootstrap-icons';
//
<ArrowRight className="bigredone" />
TODO
Odnośniki
- Gatsby Plugin Purgecss usuwa nieużywany CSS z css/sass/less/stylus i SCC Modules; wsparcie dla Tailwind CSS, Bootstrap, Bulma itd.
- Adebiyi Adedotun 10 Gatsby styling methods
- Arley McBlain Saving the Day with Scoped CSS
- Active classnames in menu on a one-page website using GatsbyJS