Att överbrygga Klyftan Mellan CSS och JavaScript: CSS-Moduler, PostCSS och Framtiden för CSS

0
6

I tidigare inlägg i denna två-serie, vi utforskade CSS-i-JS landskap och vi insåg inte bara att CSS-i-JS kan producera kritiska stilar, men även på att vissa bibliotek inte ens har en runtime. Vi såg att användarupplevelsen kan förbättras avsevärt genom att lägga till smart optimeringar, vilket är anledningen till att denna serie fokuserar på utvecklare experience (erfarenhet av redigering stilar).

I denna del, kommer vi att undersöka de verktyg som för “vanlig ol’ CSS” av refactoring Foto komponent från våra befintliga exempel.

Kontroverser och #hotdrama

En av de mest kända CSS debatter är om språket är fin precis som den är. Jag tror att debatten stannar vid liv eftersom det ligger någon sanning i båda sidor. Till exempel, medan det är sant att CSS var ursprungligen konstruerad för att formatera ett dokument snarare än delar av en ansökan, det är också sant att kommande CSS-funktioner kommer att dramatiskt förändra detta, och att många CSS misstag härrör från behandling av styling som en eftertanke istället för att ta tid att lära sig det ordentligt eller att anställa någon som är bra på det.

Jag tror inte att CSS-verktygen i sig är källa till kontroverser, vi kommer nog alltid använda dem till viss del åtminstone. Men metoder som CSS-i-JS är olika i att de patch upp bristerna i CSS med JavaScript på klientsidan. Men CSS-i-JS är inte den enda här, det är bara de nyaste. Kommer du ihåg när vi brukade ha liknande debatter om förbehandling, som Sass? Sass har funktioner som mixins, som inte baseras på någon CSS förslag (för att inte nämna hela indragen syntax). Men, Sass föddes i en mycket annorlunda tid och har nått en punkt där det inte längre är rimligt att inkludera det i debatten, eftersom debatten i sig har förändrats — så vi började kritisera CSS-i-JS eftersom det är ett lättare mål.

Jag tycker att vi bör använda verktyg som låter oss använda föreslagna syntax idag. Låt oss använda JavaScript Löften som en analogi. Den här funktionen stöds inte av Internet Explorer, så många människor har en polyfill för det. Poängen med polyfills är att göra det möjligt för oss att låtsas som om funktionen stöds överallt genom att ersätta personen beter implementeringar med en patch. Samma gäller för transpiling nya syntaxen med verktyg, som Babel. Vi kan använda det i dag, eftersom koden kommer att sammanställas till en äldre, gott stöd för syntax. Detta är en bra metod därför att den tillåter oss att använda nya funktioner som idag, samtidigt som det driver JavaScript framåt vägen förbehandling verktyg, som Sass, har drivit CSS framåt.

Min take på CSS kontroverser är att vi ska använda sig av verktyg som gör det möjligt för oss att använda framtida CSS idag.

Förbehandling

Vi har redan pratat lite om CSS förbehandling, så det är värt att diskutera dem i lite mer detaljer och hur de passar in i CSS-i-JS konversation. Vi har Sass, Mindre och PostCSS (bland andra) som kan genomsyra vårt CSS-koden med alla typer av nya funktioner.

För vårt exempel, ska vi bara kommer att handla om häckning, en av de vanligaste och mest kraftfulla funktioner av förbehandling. Jag föreslår att du använder PostCSS eftersom det ger oss en finmaskig kontroll över de funktioner som vi lägger till, vilket är precis vad vi behöver i detta fall. Den PostCSS plugin som vi kommer att använda är postcss-häckande eftersom den följer den faktiska förslag för infödda CSS häckning.

Det bästa sättet att använda PostCSS med vår sammanställning av verktyg, webpack, är att lägga till postcss-loader efter css-loader i konfigurationen. När du lägger till lastare efter css-loader, är det viktigt att redogöra för dem i css-loader alternativ genom att ställa importLoaders att antalet lyckas lastare, som i detta fall är 1:

{
test: /.css$/,
användning: [
“stil-loader’,
{
loader: “css-loader’,
alternativ: {
importLoaders: 1,
},
},
‘postcss-loader’,
],
}

Detta säkerställer att CSS-filer importeras från andra CSS-filer kommer att behandlas med postcss-loader.

Efter att ha ställt upp postcss-loader, ska vi installera postcss häckar och inkludera det i PostCSS konfiguration:

garn lägg till postcss häckar

Det finns många sätt att konfigurera PostCSS. I detta fall kommer vi att lägga till en postcss.config.js fil i roten av våra projekt:

modulen.exporten = {
plugins: {
“postcss häckar”: {},
},
}

Nu kan vi skriva en CSS-fil för vårt Foto komponent. Låt oss kalla det ett Foto.css:

.foto {
width: 200px;
&.rundade {
border-radius: 1rem;
}
}

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

Låt oss också lägga till en fil som heter utils.css som innehåller en klass för visuellt dölja element, som vi berört i den första delen av denna serie:

.visuallyHidden {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
marginal: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
white-space: nowrap;
}

Eftersom vår del förlitar sig på det här verktyget, låt oss inkludera utils.css för att Foto.css genom att lägga till @import uttalande till toppen:

@import url (“utils.css’);

Detta kommer att säkerställa att webpack kräver utils.css, tack vare css-loader. Vi kan placera utils.css någonstans vi vill och justera @import väg. I det här fallet, det är ett syskon till Foto.css.

Nästa, låt oss importera Foto.css till vår JavaScript-fil och använda klasser för att style vår del:

importera Reagerar från ‘reagerar’
import { getSrc, getSrcSet }’. /utils”
import ‘./Foto.css”

const Photo = ({ publicId, alt, rundade }) => (
<bild>
<img
className={rundade ? “foto rundade’ : ‘foto’}
src={getSrc({ publicId, bredd: 200 })}
srcSet={getSrcSet({ publicId, bredder: [200, 400, 800] })}
storlekar=”(min-width: 30rem) 400px, 200px”
/>
<figcaption className=”visuallyHidden”>{alt}</figcaption>
</bild>
)

Foto.defaultProps = {
rundad: false,
}

export-standard Foto

Medan detta kommer att fungera, vår klass namn är alldeles för enkelt och de kommer säkert att kollidera med andra helt orelaterade till vår .foto klass. Ett sätt att arbeta runt detta är att använda en metod för namngivning, som BEM, att byta namn på våra klasser (t ex photo_rounded och foto__vad-är-det-jag-inte-ens) för att hjälpa till att förhindra sammandrabbningar sker, men komponenter snabbt få komplexa och klass namn tenderar att bli långa, beroende på den totala komplexiteten av projektet.

Träffa CSS-Moduler.

CSS-Moduler

Enkelt uttryckt, CSS Moduler är CSS-filer där alla klassnamn och animationer är begränsad lokalt som standard. De ser en hel del som vanlig CSS. Till exempel kan vi använda våra bilder.css och utils.css-filer så som CSS-Moduler utan att ändra dem på alla, helt enkelt genom att skicka moduler: det är sant att css-loader alternativ:

{
loader: “css-loader’,
alternativ: {
importLoaders: 1,
moduler: sant,
},
}

CSS-Moduler är en växande funktion och kan diskuteras ännu större längd. Robin ‘ s tredelade serie om det är en bra översikt och introduktion.

Medan CSS-Moduler som själva ser väldigt likt vanliga CSS, hur vi använder dem är ganska olika. De importeras till JavaScript-objekt där tangenterna motsvarar skrivit klass namn, och värdena som är unikt klassnamn som är automatiskt genererad för oss att hålla den omfattning begränsad till en komponent:

importera Reagerar från ‘reagerar’
import { getSrc, getSrcSet }’. /utils”
importera format från”. /Foto.css”
importera stylesUtils från”. /utils.css”

const Photo = ({ publicId, alt, rundade }) => (
<bild>
<img
className={rundade
? `${stilar.foto} ${stilar.rundade}`
: stilar.foto}
src={getSrc({ publicId, bredd: 200 })}
srcSet={getSrcSet({ publicId, bredder: [200, 400, 800] })}
storlekar=”(min-width: 30rem) 400px, 200px”
/>
<figcaption className={stylesUtils.visuallyHidden}>{alt}</figcaption>
</bild>
)

Foto.defaultProps = {
rundad: false,
}

export-standard Foto

Eftersom vi använder utils.css som en CSS-Modul, kan vi ta bort @import uttalande i toppen av Foto.css. Lägg också märke till att med camelCase att formatera klass namn som gör dem enklare att använda i JavaScript. Om vi hade använt streck, vi skulle ha att skriva ut saker i sin helhet, som stylesUtils[‘visuellt-dolda’].

CSS-Moduler har ytterligare funktioner, som sammansättning. Just nu håller vi importerar utils.css till Photo.js att tillämpa vår del stilar, men låt oss säga att vi vill flytta ansvaret för styling bildtext till Fotot.css istället. På det sättet, så långt som våra JSX kod är berörda stilar.bildtexten är bara en annan klass namn, och det råkar vara så att visuellt dölja elementet, men det kan vara utformade på olika sätt i framtiden. Antingen sätt, Foto.css kommer att fatta dessa beslut.

Så låt oss lägga till en bildtext stil till Foto.css för att utöka egenskaperna för visuallyHidden utility med hjälp komponerar:

.caption {
komponerar: visuallyHidden från”. /utils.css’;
}

Vi kunde lika väl lägga till fler regler för den klassen, men detta är allt vi behöver i detta fall. Nu behöver vi inte längre att importera utils.css till Photo.js vi kan helt enkelt använda stilar.bildtext istället:

<figcaption className={stilar.bildtext}>{alt}</figcaption>

Hur fungerar det? Gör stilar från visuallyHidden får kopieras över till bildtext? Låt oss undersöka värdet av stilar.bildtext — oj, två klasser! Det är rätt: en från visuallyHidden och den andra kommer att tillämpa andra stilar vi lägga till en bildtext. CSS-i-JS gör det alltför lätt att kopiera stilar med bibliotek, som polerad, men CSS-Moduler uppmuntrar dig att återanvända befintliga stilar. Inget behov av att skapa en ny VisuallyHidden Reagera komponent för att bara applicera flera CSS-regler.

Låt oss ta det ännu längre genom att betrakta den här obekväma klass sammansättning:

rundad
? `${stilar.foto} ${stilar.rundade}`
: stilar.foto

Det finns bibliotek för dessa situationer, som klassnamn, som är användbara för mer komplexa klassammansättning. I vårt exempel, men vi kan hålla på med komponerar och byta namn .avrundat till .roundedPhoto:

.foto {
width: 200px;
}

.roundedPhoto {
ingår i: foto;
border-radius: 1rem;
}

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

.caption {
komponerar: visuallyHidden från”. /utils.css’;
}

Nu kan vi tillämpa klass namn till vår komponent i en mycket mer lättläst sätt:

rundade ? stilar.roundedPhoto : stilar.foto

Men vänta, vad händer om vi av misstag plats .roundedPhoto regeluppsättning innan .foto och vissa regler .foto slut upp tvingande regler .roundedPhoto på grund av specificitet? Oroa dig inte, CSS Moduler hindra oss från att komponera klasser som definieras efter den aktuella klassen genom att kasta ett felmeddelande liknande detta:

refererade klass namn “foto” i komponerar inte finns (2:3)

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

Observera att det är generellt en bra idé att använda en fil namngivning för CSS-Moduler, till exempel med hjälp av förlängningen .modulen.css, eftersom det är vanligt att man vill tillämpa vissa globala stilar också.

Dynamisk stilar

Hittills har vi varit villkorligt tillämpa fördefinierade stilar, som kallas villkorlig styling. Vad händer om vi också vill kunna finjustera border radie av den rundade foton? Detta kallas för dynamisk styling eftersom vi inte vet vad det värde som kommer att vara i förväg, det kan ändras när programmet körs.

Det finns inte många användningsfall för dynamic styling — brukar vi är styling villkorligt, men i de fall när vi behöver det här, hur skulle vi vända detta? Medan vi kunde bli av med inline styles, en inbyggd lösning för denna typ av problem är anpassade egenskaper (en.k.en. CSS-variabler). En riktigt värdefull aspekt av denna funktion är att webbläsare kommer att uppdatera formatmallar med hjälp av anpassade egenskaper när JavaScript-ändringar av dem. Vi kan ställa in en egen fastighet på ett element genom infogade format, vilket innebär att det kommer att vara begränsad till att element och det enda inslag:

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

I Foto.css, kan vi använda den anpassade egenskapen med hjälp av var – () och passerar standard värde som det andra argumentet:

.roundedPhoto {
ingår i: foto;
border-radius: var(–border-radius, 1rem);
}

Så långt som JavaScript är berörda, det är bara passerar en dynamisk parameter till CSS, sedan när CSS tar över, det kan gälla värdet som det är, beräkna nytt värde från det att använda calc(), etc.

Fallback

Vid tidpunkten för denna skrift, den webbläsare som har stöd för anpassade egenskaper är… ja, du bestämmer själv. Inte stöd för dessa webbläsare är (antagligen) inte i fråga för en verkliga världen ansökan, men tänk på att vissa stilar är mindre viktiga än andra. I det här fallet, det är inte en big deal om gränsen radie på IE är alltid 1rem. Ansökan behöver inte se ut på samma sätt på alla webbläsare.

Hur kan vi automatiskt ge fallbacks för alla egna fastigheter är att installera postcss-custom-egenskaper och lägga till den till vår PostCSS konfiguration:

garn lägg till postcss-custom-egenskaper
modulen.exporten = {
plugins: {
‘postcss-häckande’: {},
‘postcss-custom-egenskaper”: {},
},
}

Detta kommer att generera en fallback för vårt border-radius regel:

.roundedPhoto {
ingår i: foto;
border-radius: 1rem;
border-radius: var(–border-radius, 1rem);
}

Webbläsare som inte förstår var() kommer att bortse från denna regel och använda den tidigare. Låt inte namnet på plugin lura dig; det är bara delvis förbättrar stöd för anpassade egenskaper genom att ge statisk fallbacks. Den dynamiska aspekten kan inte vara polyfilled.

Att utsätta värden till JavaScript

I den tidigare delen av denna serie, vi utforskade hur CSS-i-JS tillåter oss att dela nästan allt mellan CSS och JavaScript, med hjälp av media queries som ett exempel. Det finns inget sätt att uppnå detta här, eller hur?

Tack till Jonathan Neal, du kan!

För det första, att uppfylla postcss-preset-env, efterföljaren till cssnext. Det är en PostCSS plugin som fungerar som en förinställd liknande @babel/preset-env. Det innehåller plugins som postcss häckar, postcss-custom-egenskaper, autoprefixer etc. så vi kan använda i framtiden CSS idag. Det delar de plugins i fyra stadier av standardisering. Några av de funktioner jag vill visa dig inte ingår i standard-sortiment (steg 2+), så vi ska uttryckligen aktivera dem vi behöver:

garn lägg till postcss-preset-env
modulen.exporten = {
plugins: {
‘postcss-preset-env’: {
funktioner: {
häckningsplatser-regler”: true,
anpassad-egenskaper”: true, // som redan ingår i steg 2+
anpassad-media-frågor”: true, // oooh, vad är det här? 🙂
},
},
},
}

Observera att vi har ersatt våra befintliga plugins eftersom detta postcss-preset-env konfigurationen inkluderar dem, vilket innebär att våra befintliga koden ska fungera på samma sätt som tidigare.

Med hjälp av anpassade egenskaper i media frågor är ogiltiga på grund av att det är inte vad de var avsedda för. I stället ska vi använda anpassade media frågor:

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

.foto {
width: 200px;
}

@media (–foto-brytpunkt) {
.foto {
width: 400px;
}
}

Även om den här funktionen är i experimentstadiet och därför inte stöds i alla webbläsare, tack vare postcss-preset-env det bara fungerar! En hake är att PostCSS fungerar på ett per-fil, så på detta sätt bara Foto.css kan använda –foto-brytpunkt. Låt oss göra något åt det.

Jonathan Neal nyligen genomfört en importFrom alternativ i postcss-preset-kuvert, som skickas till andra plugins som har stöd för det, som postcss-custom-egenskaper och postcss-custom-media. Dess värde kan vara många saker, men för att vårt exempel, det är en sökväg till en fil som ska importeras till filer PostCSS processer. Låt oss kalla detta en global.css och flytta våra egna media query det:

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

…och låt oss definiera importFrom, som ger sökvägen till den globala.css:

modulen.exporten = {
plugins: {
‘postcss-preset-env’: {
importFrom: “src/global.css’,
funktioner: {
häckningsplatser-regler”: true,
anpassad-egenskaper”: true,
anpassad-media-frågor”: true,
},
},
},
}

Nu kan vi ta bort @custom-media linjen på toppen av Foto.css och våra –foto-brytpunkt värde kommer fortfarande att fungera, eftersom postcss-preset-env kommer att använda en från global.css för att sammanställa den. Samma gäller för egna fastigheter och egna väljare.

Nu, hur man kan utsätta det för JavaScript? När experimentella funktioner som anpassade media frågor få standardiserade och genomförs i större webbläsare, vi kommer att kunna hämta dem direkt från CSS. Exempel på detta är hur vi skulle få tillgång till en egen fastighet kallade-font-family definieras på :root:

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

Om anpassade medier frågor få standardiserad vi kommer förmodligen att kunna få tillgång till dem på ett liknande sätt, men under tiden måste vi hitta ett alternativ. Vi kan använda exportTo möjlighet att skapa en JavaScript-eller JSON-fil, som vi skulle importera till JavaScript. Men det alternativet var inte designade för detta arbetsflöde, eftersom webpack skulle försöka kräva det innan det är genererade. Även om vi skapade det innan du kör webpack, varje uppdatering till global.css skulle orsaka webpack för att kompilera två gånger, en gång för att skapa utdatafilen, och en gång till för att importera den. Jag ville ha en lösning som är fri från dess genomförande.

För denna serie, jag har skapat en helt ny webpack loader som kallas css-tull-loader bara för dig! Det gör den här uppgiften lätt: allt vi behöver är att inkludera det i vår webpack konfiguration innan css-loader:

{
test: /.css$/,
användning: [
“stil-loader’,
“css-tull-loader’,
{
loader: “css-loader’,
alternativ: {
importLoaders: 1,
},
},
‘postcss-loader’,
],
}

Detta visar på anpassade media frågor, samt anpassade egenskaper, till JavaScript. Vi kan komma åt dem enkelt genom att importera globala.css:

importera Reagerar från ‘reagerar’
import { getSrc, getSrcSet }’. /utils”
importera format från”. /foto.modulen.css”
import { customMedia }’. /global.css”

const Photo = ({ publicId, alt, rundade, borderRadius }) => (
<bild>
<img
className={rundade ? stilar.roundedPhoto : stilar.foto}
style={
typeof borderRadius !== ‘undefined’
? { [‘–border-radius’]: borderRadius }
: null
}
src={getSrc({ publicId, bredd: 200 })}
srcSet={getSrcSet({ publicId, bredder: [200, 400, 800] })}
storlekar={`${customMedia[‘–foto-brytpunkt’]} 400px, 200px`}
/>
<figcaption className={stilar.bildtext}>{alt}</figcaption>
</bild>
)

Foto.defaultProps = {
rundad: false,
}

export-standard Foto

Det är det!

Jag har skapat ett arkiv som visar alla de begrepp som diskuteras i denna serie. Dess readme innehåller också några avancerade tips om den metod som beskrivs i detta inlägg.

Visa Reporäntan

Slutsats

Det är säkert att säga att de verktyg som CSS-Moduler och PostCSS och kommande CSS-funktioner upp till uppgift att hantera många utmaningar av CSS. Vilken sida av CSS-debatten du är på, detta tillvägagångssätt är värt att utforska.

Jag har en stark CSS-i-JS bakgrund, men jag är mycket känsliga för hype, så att hålla med om att världen är hård. Vilket bibliotek ska jag använda nu, stil-komponenter eller känslor? Också, samtidigt med stilar bredvid beteende kan vara kortfattad, det är också att blanda två mycket olika språk — och CSS är mycket detaljerad i förhållande till JavaScript. Detta incitament mig att skriva mindre CSS, eftersom jag ville undvika att få filen för trångt. Detta kan vara en fråga om personliga preferenser, men jag ville inte att vara ett problem. Med hjälp av en separat fil för CSS slutligen gav min kod lite luft.

Samtidigt bemästra denna strategi kan inte vara så enkelt som CSS-i-JS, jag tror att det är mer givande i det långa loppet. Det kommer att förbättra din CSS-kunskaper och gör dig bättre förberedd för framtiden.

Artikel-Serien:

  1. CSS-i-JS
  2. CSS-Moduler, PostCSS och Framtiden för CSS (Detta inlägg)