Å bygge bro over Gapet Mellom CSS og JavaScript: CSS-Moduler, PostCSS og Fremtiden av CSS

0
6

I forrige innlegg i denne to-del-serien, vi utforsket CSS-i-JS landskapet, og vi skjønte ikke bare at CSS-i-JS kan produsere kritiske stiler, men også at noen biblioteker ikke engang har en runtime. Vi så at brukeropplevelsen kan forbedre ved å legge til smart optimalisering, som er grunnen til at denne serien fokuserer på utvikleren erfaring (opplevelsen av authoring stiler).

I denne delen ser vi nærmere på verktøy for “vanlig ol “CSS” refactoring av Bildet komponent fra våre eksisterende eksempel.

Uenighet og #hotdrama

En av de mest berømte CSS debatter om språket er fin akkurat slik som det er. Jeg tror denne debatten holder i live, fordi det ikke er noen sannhet til begge sider. For eksempel, mens det er sant at CSS var opprinnelig laget for å style et dokument snarere enn komponenter av et program, er det også sant at kommende CSS funksjoner vil dramatisk endre dette, og at mange CSS-feil stammer fra styling behandling som en ettertanke i stedet for å ta tid til å lære det ordentlig, eller ansette noen som er god på det.

Jeg tror ikke at CSS verktøy i seg selv er kilden til uenighet, og vi vil sannsynligvis alltid til å bruke dem til en viss grad i det minste. Men tilnærminger som CSS-i-JS er forskjellige ved at de patch opp mangler og CSS med klient-side JavaScript. Imidlertid, CSS-i-JS er ikke den eneste tilnærmingen her; det er bare den nyeste. Husk at når vi har hatt lignende debatter om preprocessors, som Sass? Sass har funksjoner som mixins, som ikke er basert på noen CSS-forslag (for ikke å nevne hele rykket inn syntaks). Imidlertid, Sass ble født i en helt annen tid og har nådd et punkt hvor det ikke lenger er rimelig å inkludere det i debatten, fordi debatten i seg selv har endret seg — så vi begynte å kritisere CSS-i-JS fordi det er en enklere mål.

Jeg tror vi bør bruke verktøy som lar oss bruke foreslått syntaks i dag. La oss bruke JavaScript Løfter som en analogi. Denne funksjonen støttes ikke av Internet Explorer, slik at mange mennesker er en polyfill for det. Poenget med polyfills er å gjøre det mulig for oss å late som funksjonen støttes overalt ved å erstatte innebygde nettleseren implementeringer med en oppdatering. Samme gjelder for transpiling ny syntaks med verktøy, som Babel. Vi kan bruke det i dag, fordi koden vil bli samlet til en eldre, godt støttet syntaks. Dette er en god tilnærming fordi det tillater oss å bruke i fremtiden har i dag samtidig som du skyver JavaScript frem den måten forbehandling verktøy, som Sass, har presset CSS fremover.

Min ta på CSS-striden er at vi bør bruke verktøy som gjør det mulig for oss å bruke i fremtiden CSS i dag.

Preprocessors

Vi har allerede snakket litt om CSS preprocessors, så det er verdt å diskutere dem i en litt mer detaljer og hvordan de passer inn i CSS-i-JS samtale. Vi har Sass, Mindre og PostCSS (blant andre) som kan fylle våre CSS-koden med alle typer nye funksjoner.

I vårt eksempel skal vi bare være opptatt med hekkende, en av de mest vanlige og kraftige funksjoner av preprocessors. Jeg foreslår at du bruker PostCSS fordi det gir oss finkornet kontroll over har vi legger til, som er akkurat det vi trenger i dette tilfellet. Den PostCSS plugin som vi kommer til å bruke er postcss-hekkende fordi det følger av den faktiske forslag til innfødte CSS hekkende.

Den beste måten å bruke PostCSS med våre lage verktøy, webpack, er å legge til postcss-loader etter css-lasteren i konfigurasjonen. Når du legger til lastere etter css-loader, er det viktig å ta høyde for dem i css-loader alternativer ved innstillingen importLoaders til nummeret for å lykkes lastere, som i dette tilfellet er 1:

{
test: /.css$/,
bruk: [
‘stil-loader’,
{
magasin: “css-loader’,
valg: {
importLoaders: 1,
},
},
‘postcss-loader’,
],
}

Dette sikrer at CSS-filer som er importert fra andre CSS-filer vil bli behandlet med postcss-lasteren så godt.

Etter å ha satt opp postcss-loader, vi vil installere postcss-hekkende og inkludere det i PostCSS konfigurasjon:

garn legge til postcss-nesting

Det er mange måter å konfigurere PostCSS. I dette tilfellet, vi kommer til å legge til en postcss.config.js fil i roten av vårt prosjekt:

modul.eksport = {
plugins: {
“postcss-hekkende”: {},
},
}

Nå, vi kan skrive en CSS-fil for våre Foto-komponent. La oss kalle det Bildet.css:

.foto {
width: 200px;
og.avrundet {
border-radius: 1rem;
}
}

@media (min-width: 30rem) {
.foto {
bredde: 400px;
}
}

La oss også legge til en fil som heter utils.css som inneholder en klasse for visuelt å skjule elementer, som vi behandlet i første del av denne serien:

.visuallyHidden {
border: 0;
klipp: rect(0 0 0 0);
høyde: 1px;
marg: -1px;
overflow: hidden;
padding: 0;
position: absolute;
bredde: 1px;
white-space: nowrap;
}

Siden vår komponent er avhengig av dette verktøyet, la oss inkluderer utils.css Photo (foto).css ved å legge til en @import uttalelse til toppen:

@import url(‘utils.css’);

Dette vil sikre at webpack krever utils.css, takket være css-loader. Vi kan plassere utils.css-uansett hvor vi vil-og justere @import banen. I dette tilfellet, det er et søsken av Bildet.css.

Neste, la oss importere Bilde.css i vår JavaScript-fil og bruke klasser til stil vår komponent:

import Reagere fra “reagerer’
import { getSrc, getSrcSet } fra ‘./utils’
import ‘./Foto.css’

const Bilde = ({ publicId, alt, avrundet }) => (
<figur>
<img
className={avrundet? “foto avrundet’ : ‘bilde’}
src={getSrc({ publicId, bredde: 200 })}
srcSet={getSrcSet({ publicId, bredder: [200, 400, 800] })}
størrelser=”(min-width: 30rem) 400px, 200px”
/>
<figcaption className=”visuallyHidden”>{alt}</figcaption>
</figure>
)

Foto.defaultProps = {
avrundet: false,
}

eksport standard Bilde

Selv om dette vil fungere, vår klasse navn er altfor enkel og de vil helt sikkert komme i konflikt med andre fullstendig irrelevant for vår .foto klasse. En av måtene å jobbe rundt dette er å bruke et navn metodikk, som BEM, for å gi våre klasser (f.eks. photo_rounded og foto__hva-er-dette-jeg-ikke-selv) å bidra til å hindre sammenstøt skjer, men komponenter raskt få komplekse og klasse navn tendens til å bli lang, avhengig av den totale kompleksiteten i prosjektet.

Møte CSS-Moduler.

CSS-Moduler

Enkelt sagt, CSS Moduler er CSS-filer i som alle klasse navn og animasjoner er omfattet lokalt som standard. De ser mye som vanlige CSS. Vi kan For eksempel bruke vårt Bilde.css og utils.css-filer som CSS Moduler uten å endre dem i det hele tatt, bare ved å passere moduler: true til css-loader alternativer:

{
magasin: “css-loader’,
valg: {
importLoaders: 1,
moduler: true,
},
}

CSS-Moduler er et utviklende funksjon og kan bli diskutert i enda større lengde. Robin er tredelt serie om det er en god oversikt og introduksjon.

Mens CSS Moduler seg selv ser veldig lik vanlig CSS, hvordan vi bruker dem er ganske forskjellige. De er importert inn i JavaScript som objekter, hvor taster svarer til forfattet klasse navn, og verdiene er unike klasse navn som er auto-generert for oss som holder virkeområdet begrenset til en komponent:

import Reagere fra “reagerer’
import { getSrc, getSrcSet } fra ‘./utils’
import stiler fra ‘./Foto.css’
import stylesUtils fra ‘./utils.css’

const Bilde = ({ publicId, alt, avrundet }) => (
<figur>
<img
className={avrundede
? `${stiler.foto} ${stiler.avrundet}`
: stiler.foto}
src={getSrc({ publicId, bredde: 200 })}
srcSet={getSrcSet({ publicId, bredder: [200, 400, 800] })}
størrelser=”(min-width: 30rem) 400px, 200px”
/>
<figcaption className={stylesUtils.visuallyHidden}>{alt}</figcaption>
</figure>
)

Foto.defaultProps = {
avrundet: false,
}

eksport standard Bilde

Siden vi bruker utils.css som en CSS-Modulen, kan vi fjerne @import uttalelse på toppen av Bildet.css. Også, legg merke til at ved hjelp av camelCase å format klasse navn gjør dem enklere å bruke i JavaScript. Hvis vi hadde brukt bindestrek, vil vi ha for å skrive ting ut i sin helhet, som stylesUtils[‘visuelt-skjult’].

CSS-Modulene har flere funksjoner, som sammensetning. Akkurat nå, vi importerer utils.css inn Photo.js å bruke vår komponent stiler, men la oss si at vi ønsker å flytte ansvaret for utforming av tekst til Bildet.css i stedet. På den måten, så langt det gjelder våre JSX koden er opptatt av, stiler.bildeteksten er bare en annen klasse navn, det bare så skjer til visuelt å skjule elementet, men det kan være utformet på en annen måte i fremtiden. Uansett, Foto.css vil være å ta disse avgjørelsene.

Så la oss legge til en bildetekst stil til Bildet.css for å utvide egenskaper av visuallyHidden utility ved hjelp av komponerer:

.caption {
komponerer: visuallyHidden fra ‘./utils.css’;
}

Vi kan like godt legge til mer regler til klassen, men dette er alt vi trenger i dette tilfellet. Nå, vi trenger ikke lenger å importere utils.css inn Photo.js vi kan ganske enkelt bruke stiler.bildetekst i stedet:

<figcaption className={stiler.bildetekst}>{alt}</figcaption>

Hvordan fungerer dette? Gjør stiler fra visuallyHidden få kopiert over til bildetekst? La oss se nærmere på verdien av stiler.bildetekst — jøss, to klasser! Det er riktig: den ene er fra visuallyHidden og den andre vil bruke alle andre stiler vi legge til bildetekst. CSS-i-JS gjør det også enkelt å kopiere stiler med biblioteker, som polert, men CSS Moduler oppfordrer deg til å bruke eksisterende stiler. Du trenger ikke å opprette en ny VisuallyHidden Reagere komponent til bare å gjelde flere CSS-regler.

La oss ta det enda videre ved å undersøke dette ubehagelig klasse sammensetning:

avrundede
? `${stiler.foto} ${stiler.avrundet}`
: stiler.bilde

Det er biblioteker for disse situasjonene, som klassenavn, som er nyttige for mer komplekse klasse sammensetning. I vårt eksempel, om du vil, kan vi holde på med komponerer og gi nytt navn .avrundet til .roundedPhoto:

.foto {
width: 200px;
}

.roundedPhoto {
komponerer: foto;
border-radius: 1rem;
}

@media (min-width: 30rem) {
.foto {
bredde: 400px;
}
}

.caption {
komponerer: visuallyHidden fra ‘./utils.css’;
}

Nå kan vi bruke klassenavn til vår komponent i en mye mer lesbar måte:

avrundet ? stiler.roundedPhoto : stiler.bilde

Men vent, hva hvis vi tilfeldigvis plasser .roundedPhoto regelsett før .bilde og noen regler .foto ende opp med overordnede regler fra .roundedPhoto på grunn av spesifisitet? Ikke bekymre deg, CSS Moduler hindre oss fra å komponere klasser definert etter gjeldende klasse ved å kaste en feilmelding som dette:

det refereres til klasse navn “bilde” i komponerer ikke funnet (2:3)

1 | .roundedPhoto {
> 2 | komponerer: foto;
| ^
3 | border-radius: 1rem;
4 | }

Merk at det vanligvis en god idé å bruke en file naming convention for CSS-Moduler, for eksempel ved hjelp av utvidelsen .modul.css, fordi det er vanlig å ønske å bruke noen global stiler, så vel.

Dynamisk stiler

Så langt, vi har vært betinget søker forhåndsdefinert sett med stiler, som kalles betinget styling. Hva hvis vi ønsker også å være i stand til å finjustere border-radius av avrundet bilder? Dette kalles dynamisk styling fordi vi ikke vet hva verdien er kommer til å være i forkant; det kan endre seg, mens programmet kjører.

Det er ikke mange bruksområdene for dynamisk styling — vanligvis er vi styling betinget, men i tilfelle når vi trenger dette, hvordan ville vi nærme seg dette? Mens vi kan få av med innebygde stiler, en innebygd løsning for denne typen problemer er tilpasset egenskaper (en.k.a. CSS-variabler). En virkelig verdifulle aspekter av denne funksjonen er at nettlesere vil oppdatere stiler ved hjelp av egendefinerte egenskaper når JavaScript endringer dem. Vi kan angi en tilpasset bolig på et element gjennom inline styles, som betyr at den skal knyttes til som element og som element:

style={typeof borderRadius !== ‘undefined’ ? {
‘–border-radius’: borderRadius,
} : null}

I Bilde.css, vi kan bruke denne tilpassede eiendel ved hjelp var() og passerer standard verdi som andre argument:

.roundedPhoto {
komponerer: foto;
border-radius: var(–border-radius, 1rem);
}

Så langt som JavaScript er bekymret, det er bare passerer en dynamisk parameter for å CSS, så når CSS tar over, det kan gjelde den verdien som det er, kalkulere en ny verdi fra det å bruke calc(), osv.

Fallback

På den tiden dette ble skrevet, nettleser støtte for egendefinerte egenskaper er… vel, du bestemmer selv. Ikke støtte for disse nettleserne er (sannsynligvis) uaktuelt for en real-world program, men husk at noen stiler er mindre viktig enn andre. I dette tilfellet, det er ikke en stor avtale hvis grensen radius på IE er alltid 1rem. Programmet trenger ikke å se på samme måte på hver nettleser.

På den måten kan vi automatisk gi fallbacks for alle egendefinerte egenskaper er å installere postcss-custom-egenskaper og legge den til vår PostCSS konfigurasjon:

garn legge til postcss-custom-egenskaper
modul.eksport = {
plugins: {
‘postcss-hekkende’: {},
‘postcss-custom-egenskaper’: {},
},
}

Dette vil generere et tilbakefall for våre border-radius-regelen:

.roundedPhoto {
komponerer: foto;
border-radius: 1rem;
border-radius: var(–border-radius, 1rem);
}

Nettlesere som ikke forstår var() vil ignorere at regelen og bruk den forrige. Ikke la navnet på plugin lure deg, er det bare delvis forbedrer støtte for egendefinerte egenskaper ved å gi statisk fallbacks. Det dynamiske aspektet kan ikke være polyfilled.

Å utsette verdier for JavaScript

I forrige del av denne serien, har vi utforsket hvordan CSS-i-JS tillater oss å dele nesten alt mellom CSS og JavaScript, ved hjelp av media queries som et eksempel. Det er ikke mulig måte å oppnå dette her, ikke sant?

Takk til Jonathan Neal, det kan du!

Første møte postcss-preset-konv, etterfølgeren til cssnext. Det er en PostCSS plugin som fungerer som en forhåndsinnstilling som ligner @babel/preset-konv. Den inneholder plugins som postcss-hekkende, postcss-custom-egenskaper, autoprefixer etc. så vi kan bruke i fremtiden CSS i dag. Den deler seg plugins over fire stadier av standardisering. Noen av funksjonene jeg ønsker å vise at du ikke er inkludert i standard utvalg (stadium 2+), så vi vil ikke eksplisitt aktiverer de vi trenger:

garn legge til postcss-preset-konv.
modul.eksport = {
plugins: {
‘postcss-preset-konv’: {
funksjoner: {
‘hekkende-reglene”: true,
“custom-egenskaper’: true, // som allerede er inkludert i stadium 2+
“custom-media-søk’: true, // oooh, hva er dette? 🙂
},
},
},
}

Vær oppmerksom på at vi byttet ut våre eksisterende plugins fordi dette postcss-preset-konv konfigurasjonen inkluderer dem, noe som betyr våre eksisterende kode skal fungere på samme måte som før.

Ved hjelp av egendefinerte egenskaper i media queries er ugyldig fordi det er ikke hva de var ment for. I stedet vil vi bruke egendefinert media queries:

@custom-media –foto-stoppunkt (min-width: 30em);

.foto {
width: 200px;
}

@media (–foto-stoppunkt) {
.foto {
bredde: 400px;
}
}

Selv om denne funksjonen er i det eksperimentelle stadiet, og derfor ikke støttes i alle nettlesere, takket være postcss-preset-konv det bare fungerer! En fange er at PostCSS fungerer på en per-file basis, så på denne måten bare Bilde.css kan bruke –foto-stoppunkt. La oss gjøre noe med det.

Jonathan Neal nylig gjennomført en importFrom alternativ i postcss-preset-konv, som er gått over til andre plugins som støtter det, så vel som postcss-custom-egenskaper og postcss-custom-media. Verdien kan være mange ting, men i den hensikt vårt eksempel, det er en bane til en fil som skal importeres til filer PostCSS prosesser. La oss kalle dette en global.css og flytte vår custom media query det:

@custom-media –foto-stoppunkt (min-width: 30em);

…og la oss definere importFrom, som gir vei til en global.css:

modul.eksport = {
plugins: {
‘postcss-preset-konv’: {
importFrom: ‘src/global.css’,
funksjoner: {
‘hekkende-reglene”: true,
“custom-egenskaper’: true,
“custom-media-søk’: true,
},
},
},
}

Nå kan vi slette @custom-media-linjen øverst i Bildet.css og vår –foto-stoppunkt verdi vil fortsatt fungere, fordi postcss-preset-konv vil bruke en fra global.css til å gjere det. Samme gjelder for egendefinerte egenskaper og tilpasset velgere.

Nå, hvordan å utsette det til at JavaScript? Når eksperimentelle funksjoner som egendefinert media queries få standardisert og implementert i store nettleserne, vil vi være i stand til å hente dem direkte fra CSS. Dette er For eksempel hvordan vi ville få tilgang til en tilpasset bolig som kalles –font-family definert på :root:

const rootStyles = getComputedStyle(dokument.kroppen)
const fontFamily = rootStyles.getPropertyValue(‘–font-family’)

Hvis egendefinert media queries få standardiserte vi vil trolig være i stand til å få tilgang til dem på en lignende måte, men i mellomtiden er vi nødt til å finne et alternativ. Vi kunne bruke exportTo alternativet for å generere en JavaScript-eller JSON-fil, som vi ville importere inn i JavaScript. Imidlertid, at alternativet er ikke utformet for denne arbeidsflyten fordi webpack ville prøve å kreve det før det er generert. Selv om vi som genereres det før du kjører webpack, hver oppdatering til global.css ville føre webpack for å re-sette sammen to ganger, én gang for å generere filen, og en gang til for å importere den. Jeg ønsket en løsning som er uhemmet av sin gjennomføring.

For denne serien, jeg har opprettet en ny webpack loader kalt css-toll-loader bare for deg! Det gjør denne oppgaven enkel: alt vi trenger er å ta det med i våre webpack konfigurasjon før css-loader:

{
test: /.css$/,
bruk: [
‘stil-loader’,
“css-toll-loader’,
{
magasin: “css-loader’,
valg: {
importLoaders: 1,
},
},
‘postcss-loader’,
],
}

Dette eksponerer egendefinert media spørringer, samt egendefinerte egenskaper, til JavaScript. Vi kan få tilgang til dem ved å importere global.css:

import Reagere fra “reagerer’
import { getSrc, getSrcSet } fra ‘./utils’
import stiler fra ‘./foto.modul.css’
import { customMedia } fra ‘./global.css’

const Bilde = ({ publicId, alt, avrundet, borderRadius }) => (
<figur>
<img
className={avrundet ? stiler.roundedPhoto : stiler.foto}
style={
typeof borderRadius !== ‘undefined’
? { [‘–border-radius’]: borderRadius }
: null
}
src={getSrc({ publicId, bredde: 200 })}
srcSet={getSrcSet({ publicId, bredder: [200, 400, 800] })}
størrelser={`${customMedia[‘–foto-breakpoint’]} 400px, 200px`}
/>
<figcaption className={stiler.bildetekst}>{alt}</figcaption>
</figure>
)

Foto.defaultProps = {
avrundet: false,
}

eksport standard Bilde

Det er det!

Jeg lagde et depot viser alle begrepene som diskuteres i denne serien. Dens viktig inneholder også noen avanserte tips om tilnærming som er beskrevet i dette innlegget.

Vis Repo

Konklusjon

Det er trygt å si at verktøy som CSS Moduler og PostCSS og kommende CSS funksjoner er opp til oppgaven med å håndtere mange utfordringer i CSS. Uansett hvilken side av CSS debatt du er på, kan denne tilnærmingen er verdt å utforske.

Jeg har en sterk CSS-i-JS bakgrunn, men jeg er svært utsatt for å hype, så holder opp med at verden er vanskelig. Hvilket bibliotek bør jeg bruke nå, stylet-komponenter eller følelser? Også, mens du har stiler ved siden av atferd kan være tydelige, det er også blande to svært ulike språk — CSS er svært detaljert i forhold til JavaScript. Dette incentivized meg til å skrive mindre CSS fordi jeg ønsket å unngå å få filen for trangt. Dette kan være et spørsmål om personlige preferanser, men jeg ønsker ikke at det skal være et problem. Ved hjelp av en separat fil for CSS til slutt ga min kode litt luft.

Mens mestre denne tilnærmingen kan ikke være så enkelt som CSS-i-JS, jeg tror det er mer lønnsomt i det lange løp. Det vil forbedre din CSS ferdigheter og gjøre deg bedre rustet for fremtiden.

Artikkel-Serien:

  1. CSS-i-JS
  2. CSS-Moduler, PostCSS og Fremtiden av CSS (Dette innlegget)