De afwegingen van CSS-in-JS

Foto door Artem Bali

Onlangs schreef ik een overzicht op een hoger niveau van CSS-in-JS, voornamelijk over de problemen die deze aanpak probeert op te lossen. Auteurs van bibliotheken investeren zelden tijd in het beschrijven van de afwegingen van hun oplossing. Soms is het omdat ze te bevooroordeeld zijn, en soms weten ze gewoon niet hoe de gebruikers de tool toepassen. Dus dit is een poging om de wisselwerking te beschrijven die ik tot nu toe heb gezien. Ik vind het belangrijk om te vermelden dat ik de auteur van JSS ben, dus ik moet als bevooroordeeld worden beschouwd.

Sociale impact

Er is een laag mensen die op het webplatform werken en geen JavaScript kennen. Die mensen worden betaald om HTML en CSS te schrijven. CSS-in-JS heeft grote invloed gehad op de workflow van de ontwikkelaars. Een echt transformerende verandering kan nooit worden gedaan zonder dat sommige mensen achterblijven. Ik weet niet of CSS-in-JS de enige manier moet zijn, maar de massale acceptatie is een duidelijk teken van problemen met het gebruik van CSS in moderne toepassingen.

Een groot deel van het probleem is ons onvermogen om nauwkeurig de gebruikssituaties te communiceren waarin CSS-in-JS opvalt en hoe het correct voor een taak te gebruiken. Veel CSS-in-JS-enthousiastelingen zijn succesvol geweest in het promoten van de technologie, maar niet veel critici spraken op een constructieve manier over de afwegingen, zonder goedkope schommelingen op de tools te maken. Als gevolg hiervan hebben we veel afwegingen verborgen gelaten en hebben we niet veel moeite gedaan om de uitleg en oplossingen te bieden.

CSS-in-JS is een poging om complexe gebruikssituaties gemakkelijker te hanteren te maken, dus duw het niet waar het niet nodig is!

Looptijd kosten

Wanneer CSS tijdens runtime in JavaScript wordt gegenereerd, is er een inherente overhead. Runtime overhead varieert van bibliotheek tot bibliotheek. Dit is een goede generieke benchmark, maar zorg ervoor dat u uw eigen tests maakt. Grote verschillen tijdens runtime verschijnen afhankelijk van de behoefte aan een volledige CSS-analyse van sjabloonreeksen, aantal optimalisaties, details van de implementatie van dynamische stijlen, hashing-algoritme en kosten voor framework-integraties. *

Naast de mogelijke runtime-overhead, moet u rekening houden met 4 verschillende bundelstrategieën, omdat sommige CSS-in-JS-bibliotheken meerdere strategieën ondersteunen en het aan de gebruiker is om deze toe te passen. *

Strategie 1: alleen runtime-generatie

Runtime CSS-generatie is een techniek die een CSS-string in JavaScript genereert en die string vervolgens met een stijltag in het document injecteert. Deze techniek produceert een Style Sheet, NIET inline stijlen.

Het nadeel van het genereren van runtime is het onvermogen om gestileerde inhoud in een vroeg stadium te bieden, wanneer het document wordt geladen. Deze aanpak is meestal geschikt voor toepassingen zonder inhoud die onmiddellijk nuttig kan zijn. Meestal vereisen dergelijke toepassingen gebruikersinteracties voordat ze echt nuttig kunnen worden voor een gebruiker. Vaak werken dergelijke applicaties met inhoud die zo dynamisch is dat deze verouderd raakt zodra u deze laadt, dus u moet vroeg een updatepijplijn opzetten, bijvoorbeeld Twitter. Wanneer een gebruiker is ingelogd, is het bovendien niet nodig om HTML voor SEO te verstrekken.

Als de interactie JavaScript vereist, moet de bundel worden geladen voordat de app gereed is. U kunt bijvoorbeeld de inhoud van een standaardkanaal weergeven wanneer Slack in het document wordt geladen, maar het is waarschijnlijk dat de gebruiker het kanaal direct daarna wil wijzigen. Dus als u de eerste inhoud hebt geladen om ze meteen weg te gooien.

De waargenomen prestaties van dergelijke toepassingen kunnen worden verbeterd met tijdelijke aanduidingen en andere trucs om de toepassing directer te laten voelen dan hij in werkelijkheid is. Dergelijke applicaties zijn meestal sowieso data-zwaar, dus ze zullen niet zo snel bruikbaar zijn als een artikel.

Strategie 2: Runtime-generatie met Critical CSS

Kritieke CSS is de minimale hoeveelheid CSS die nodig is om de pagina in de oorspronkelijke staat op te maken. Het wordt weergegeven met een stijltag in de kop van het document. Deze techniek wordt veel gebruikt met en zonder CSS-in-JS. In beide gevallen zult u de CSS-regels waarschijnlijk dubbel laden, eenmaal als onderdeel van de Critical CSS en eenmaal als onderdeel van de JavaScript- of CSS-bundel. De grootte van Critical CSS kan behoorlijk groot zijn, afhankelijk van de hoeveelheid inhoud. Gewoonlijk wordt het document niet in de cache bewaard.

Zonder Critical CSS zal een statische content-zware applicatie met één pagina met runtime CSS-in-JS tijdelijke aanduidingen moeten tonen in plaats van content. Dit is slecht omdat het veel eerder nuttig kon zijn geweest voor een gebruiker, waardoor de toegankelijkheid op low-end apparaten en voor verbindingen met lage bandbreedte werd verbeterd.

Met kritieke CSS kan het genereren van runtime CSS in een later stadium worden gedaan, zonder de UI in de beginfase te blokkeren. Maar wees gewaarschuwd, op low-end mobiele apparaten, die ongeveer 5+ jaar oud zijn, kan CSS-generatie van JavaScript een negatieve invloed hebben op de prestaties. Het hangt sterk af van de hoeveelheid CSS die wordt gegenereerd en de gebruikte bibliotheek, dus het kan niet worden gegeneraliseerd.

Het nadeel van deze strategie zijn de kosten van kritieke CSS-extractie en de kosten van het genereren van runtime CSS.

Strategie 3: alleen bouwtijdextractie

Deze strategie is de standaard op het web zonder CSS-in-JS. Met sommige CSS-in-JS-bibliotheken kunt u statische CSS tijdens het bouwen extraheren. * In dit geval is er geen runtime-overhead, wordt CSS op de pagina weergegeven met een koppelingstag. De kosten voor het genereren van CSS worden eenmaal vooruit betaald.

Er zijn hier 2 belangrijke afwegingen:

  1. U kunt sommige dynamische API's die CSS-in-JS biedt tijdens runtime niet gebruiken, omdat u geen toegang hebt tot de status. Vaak kunt u CSS-aangepaste eigenschappen nog steeds niet gebruiken, omdat deze niet in elke browser worden ondersteund en niet van nature kunnen worden ingevuld. In dit geval moet u tijdelijke oplossingen bieden voor dynamische thema's en op stijlen gebaseerde styling. *
  2. Zonder Critical CSS en met een lege cache blokkeert u de eerste verf, totdat uw CSS-bundel wordt geladen. Een koppelingselement in de kop van het document blokkeert de weergave van HTML.
  3. Niet-deterministische specificiteit met op pagina's gebaseerde bundelsplitsing in toepassingen met één pagina. *

Strategie 4: Build-time extractie met Critical CSS

Deze strategie is ook niet uniek voor CSS-in-JS. Volledige statische extractie met kritieke CSS levert de beste prestaties bij het werken met een meer statische toepassing. Deze benadering heeft nog steeds de bovengenoemde afwegingen van een statische CSS, behalve dat de blokkerende koppelingstag naar de onderkant van het document kan worden verplaatst.

Er zijn 4 hoofd CSS-renderingstrategieën. Slechts 2 daarvan zijn specifiek voor CSS-in-JS en geen van hen is van toepassing op alle bibliotheken.

Toegankelijkheid

CSS-in-JS kan de toegankelijkheid verminderen wanneer het op de verkeerde manier wordt gebruikt. Dit gebeurt wanneer een grotendeels statische inhoudssite wordt geïmplementeerd zonder kritieke CSS-extractie, zodat HTML niet kan worden geschilderd voordat de JavaScript-bundel wordt geladen en geëvalueerd. Dit kan ook gebeuren wanneer een enorm CSS-bestand wordt weergegeven met een blokkerende koppelingstag in de kop van het document, wat het meest populaire huidige probleem is met de traditionele insluiting en niet specifiek voor CSS-in-JS.

Ontwikkelaars moeten verantwoordelijkheid nemen voor toegankelijkheid. Er is nog steeds een sterk misleidend idee dat een onstabiele internetverbinding een probleem is van economisch zwakke landen. We vergeten vaak dat we elke dag verbindingsproblemen hebben wanneer we een ondergronds spoorwegsysteem of een groot gebouw betreden. Een stabiele kabelvrije mobiele verbinding is een mythe. Het is niet eens eenvoudig om een ​​stabiele wifi-verbinding te hebben, bijvoorbeeld een 2,4 GHz wifi-netwerk kan storing van een magnetron krijgen!

De kosten van kritieke CSS met server-side rendering

Om Critical CSS-extractie voor CSS-in-JS te krijgen, hebben we SSR nodig. SSR is een proces voor het genereren van de uiteindelijke HTML voor een bepaalde status van een toepassing op de server. Het kan zelfs een behoorlijk complex en duur proces zijn. Het vereist een bepaald aantal CPU-cycli op de server voor elk HTTP-verzoek.

CSS-in-JS maakt meestal gebruik van het feit dat het is gekoppeld aan de HTML-renderingpijplijn. * Het weet welke HTML is gerenderd en welke CSS het nodig heeft, zodat het de absoluut minimale hoeveelheid ervan kan produceren. Kritieke CSS voegt extra overhead toe aan HTML-rendering op de server omdat die CSS ook moet worden gecompileerd in een definitieve CSS-reeks. In sommige scenario's is het echter moeilijk of zelfs onmogelijk om op de server te cachen.

Zwarte doos weergeven

U moet weten hoe een CSS-in-JS-bibliotheek die u gebruikt uw CSS weergeeft. Mensen weten bijvoorbeeld vaak niet hoe Styled Components en Emotion dynamische stijlen implementeren. Dynamische stijlen is een syntaxis waarmee JavaScript-functies in uw stijldeclaratie kunnen worden gebruikt. Die functies accepteren rekwisieten en retourneren een CSS-blok.

Om de specificatie van de bronvolgorde consistent te houden, genereren beide bovengenoemde bibliotheken een nieuwe CSS-regel als deze een dynamische aangifte bevat en worden de componenten bijgewerkt met nieuwe rekwisieten. Om te laten zien wat ik bedoel, heb ik deze sandbox gemaakt. In JSS hebben we besloten een andere afweging te maken, waardoor we de dynamische eigenschappen kunnen bijwerken zonder nieuwe CSS-regels te genereren. *

Steile leercurve

Voor mensen die bekend zijn met CSS, maar nog geen ervaring hebben met JavaScript, kan de initiële hoeveelheid werk om aan de slag te gaan met CSS-in-JS behoorlijk groot zijn.

U hoeft geen professionele JavaScript-ontwikkelaar te zijn om CSS-in-JS te schrijven, tot het punt waarop complexe logica een rol speelt. We kunnen de complexiteit van styling niet generaliseren, omdat het echt afhankelijk is van de use case. In gevallen waarin CSS-in-JS complex wordt, is het waarschijnlijk dat de implementatie met vanille CSS nog complexer zou zijn.

Voor eenvoudige CSS-in-JS-stijl moet men weten hoe variabelen te declareren, hoe sjabloonreeksen te gebruiken en JavaScript-waarden te interpoleren. Als objectnotatie wordt gebruikt, moet men weten hoe te werken met JavaScript-objecten en de bibliotheekspecifieke objectgebaseerde syntaxis. Als het gaat om dynamische styling, moet men weten hoe JavaScript-functies en -voorwaardes moeten worden gebruikt.

Over het algemeen is er een leercurve, we kunnen het niet ontkennen. Deze leercurve is meestal echter niet veel groter dan het leren van Sass. In feite heb ik deze egghead-cursus gemaakt om dit aan te tonen.

Geen interoperabiliteit

De meeste CSS-in-JS bibliotheken zijn niet interoperabel. Dit betekent dat stijlen die met de ene bibliotheek zijn geschreven, niet kunnen worden weergegeven met een andere bibliotheek. Praktisch betekent dit dat u niet eenvoudig uw hele applicatie kunt overschakelen van de ene implementatie naar de andere. Het betekent ook dat u uw gebruikersinterface niet eenvoudig op NPM kunt delen zonder uw gewenste CSS-in-JS-bibliotheek in de bundel van de consument te brengen, tenzij u een statische extractie voor uw CSS heeft gebouwd.

We zijn begonnen met het werken aan het ISTF-formaat dat dit probleem zou moeten oplossen, maar helaas hebben we nog geen tijd gehad om het in een productie-klaar staat te krijgen. *

Ik denk dat het delen van herbruikbare framework-agnostische UI-componenten in het publieke domein nog steeds een over het algemeen moeilijk op te lossen probleem is.

Beveiligingsrisico's

Het is mogelijk om beveiligingslekken te introduceren met CSS-in-JS. Zoals bij elke client-side applicatie, moet je altijd ontsnappen aan gebruikersinvoer voordat je deze weergeeft.

Dit artikel geeft je meer inzicht en enkele schurende voorbeelden.

Onleesbare klassennamen

Sommige mensen vinden het nog steeds belangrijk dat we betekenisvolle leesbare klassenamen op het internet houden. Momenteel bieden veel CSS-in-JS-bibliotheken betekenisvolle klassenamen op basis van de declaratienaam of componentnaam in de ontwikkelingsmodus. Met sommige kunt u zelfs de functie voor het genereren van klassennamen aanpassen.

In de productiemodus genereren de meeste echter kortere namen voor een kleinere nuttige lading. Dit is een afweging die de gebruiker van de bibliotheek moet maken en indien nodig de bibliotheek moet aanpassen.

Gevolgtrekking

Er zijn afwegingen en ik heb ze waarschijnlijk niet eens allemaal genoemd. Maar de meeste zijn niet universeel van toepassing op alle CSS-in-JS. Ze zijn afhankelijk van welke bibliotheek u gebruikt en hoe u deze gebruikt.

* Er is een speciaal artikel voor nodig om deze zin uit te leggen. Laat het me op Twitter (@ oleg008) weten over welke je meer wilt lezen.