I compromessi di CSS-in-JS

Foto di Artem Bali

Di recente ho scritto una panoramica di livello superiore di CSS-in-JS, principalmente parlando dei problemi che questo approccio sta cercando di risolvere. Gli autori delle biblioteche raramente investono tempo nella descrizione dei compromessi della loro soluzione. A volte è perché sono troppo distorti, a volte non sanno come gli utenti applicano lo strumento. Quindi questo è un tentativo di descrivere i compromessi che ho visto finora. Penso che sia importante menzionare che sono l'autore di JSS, quindi dovrei essere considerato di parte.

Impatto sociale

C'è un livello di persone che lavorano sulla piattaforma web e non conoscono JavaScript. Quelle persone vengono pagate per scrivere HTML e CSS. CSS-in-JS ha avuto un impatto enorme sul flusso di lavoro degli sviluppatori. Un cambiamento veramente trasformativo non può mai essere fatto senza che alcune persone vengano lasciate indietro. Non so se CSS-in-JS debba essere l'unico modo, ma l'adozione di massa è un chiaro segno di problemi con l'utilizzo dei CSS in applicazioni moderne.

Una grande parte del problema è la nostra incapacità di comunicare con precisione i casi d'uso in cui splende CSS-in-JS e come usarli correttamente per un'attività. Molti appassionati di CSS-in-JS hanno avuto successo nel promuovere la tecnologia, ma non molti critici hanno parlato dei compromessi in modo costruttivo, senza fare oscillazioni economiche degli strumenti. Di conseguenza, abbiamo lasciato molti compromessi nascosti e non abbiamo fatto uno sforzo per fornire spiegazioni e soluzioni alternative.

CSS-in-JS è un tentativo di semplificare la gestione di casi d'uso complessi, quindi non spingerlo dove non è necessario!

Costo di runtime

Quando CSS viene generato da JavaScript in fase di runtime, nel browser si verifica un sovraccarico intrinseco. L'overhead di runtime varia da libreria a libreria. Questo è un buon benchmark generico, ma assicurati di fare i tuoi test. Le principali differenze in fase di esecuzione appaiono in base alla necessità di disporre di un'analisi CSS completa di stringhe di modelli, quantità di ottimizzazioni, dettagli di implementazione degli stili dinamici, algoritmo di hashing e costo delle integrazioni del framework. *

Oltre al potenziale sovraccarico di runtime, è necessario considerare 4 diverse strategie di raggruppamento, poiché alcune librerie CSS-in-JS supportano più strategie e spetta all'utente applicarle. *

Strategia 1: solo generazione runtime

La generazione CSS di runtime è una tecnica che genera una stringa CSS in JavaScript e quindi inserisce quella stringa utilizzando un tag di stile nel documento. Questa tecnica produce un foglio di stile, NON stili incorporati.

Il compromesso della generazione di runtime è l'incapacità di fornire contenuti in stile nella fase iniziale, quando il documento inizia a caricarsi. Questo approccio di solito si adatta alle applicazioni senza contenuto che possono essere utili immediatamente. Di solito, tali applicazioni richiedono interazioni dell'utente prima che possano davvero diventare utili per un utente. Spesso tali applicazioni funzionano con contenuti così dinamici da diventare obsoleti non appena caricati, quindi è necessario stabilire una pipeline di aggiornamento in anticipo, ad esempio Twitter. Inoltre, quando un utente ha effettuato l'accesso, non è necessario fornire HTML per SEO.

Se l'interazione richiede JavaScript, il pacchetto deve essere caricato prima che l'app sia pronta. Ad esempio, è possibile mostrare il contenuto di un canale predefinito quando si carica Slack nel documento, ma è probabile che l'utente voglia cambiare il canale subito dopo. Quindi, se hai caricato i contenuti iniziali solo per buttarli via immediatamente.

Le prestazioni percepite di tali applicazioni possono essere migliorate con segnaposto e altri trucchi per rendere l'applicazione più immediata di quanto non sia in realtà. Tali applicazioni di solito sono comunque pesanti per i dati, quindi non saranno utili quanto un articolo.

Strategia 2: generazione di runtime con CSS critico

Il CSS critico è la quantità minima di CSS necessaria per dare uno stile alla pagina nel suo stato iniziale. Viene eseguito il rendering utilizzando un tag di stile nella parte superiore del documento. Questa tecnica è ampiamente utilizzata con e senza CSS-in-JS. In entrambi i casi, è probabile che tu carichi due volte le regole CSS, una volta come parte del Critical CSS e una volta come parte del bundle JavaScript o CSS. La dimensione del CSS critico può essere piuttosto grande a seconda della quantità del contenuto. Di solito, il documento non viene memorizzato nella cache.

Senza CSS critico, un'applicazione a pagina singola ricca di contenuti statici con runtime CSS-in-JS dovrà mostrare segnaposto anziché contenuto. Ciò è negativo perché avrebbe potuto essere utile a un utente molto prima, migliorando l'accessibilità su dispositivi di fascia bassa e per connessioni a larghezza di banda ridotta.

Con i CSS critici, la generazione dei runtime CSS può essere effettuata in una fase successiva, senza bloccare l'interfaccia utente nella fase iniziale. Tuttavia, sui dispositivi mobili di fascia bassa, che hanno circa 5 anni e più, la generazione CSS da JavaScript può avere un impatto negativo sulle prestazioni. Dipende fortemente dalla quantità di CSS generati e dalla libreria utilizzata, quindi non può essere generalizzata.

Il compromesso di questa strategia è il costo dell'estrazione CSS critica e il costo della generazione CSS runtime.

Strategia 3: solo estrazione in fase di build

Questa strategia è quella predefinita sul web senza CSS-in-JS. Alcune librerie CSS-in-JS consentono di estrarre CSS statici al momento della compilazione. * In questo caso, non è previsto alcun sovraccarico di runtime, il CSS viene visualizzato sulla pagina utilizzando un tag link. Il costo della generazione CSS viene pagato una volta in anticipo.

Ci sono 2 importanti compromessi qui:

  1. Non puoi utilizzare alcune delle API dinamiche offerte CSS-in-JS in fase di esecuzione, perché non hai accesso allo stato. Spesso non è ancora possibile utilizzare le proprietà personalizzate CSS, poiché non sono supportate in tutti i browser e non possono essere riempite con il polifilo al momento della creazione per natura. In questo caso, dovrai eseguire soluzioni alternative per temi dinamici e stile basato sullo stato. *
  2. Senza CSS critico e con una cache vuota, bloccherai il primo disegno, fino a quando il tuo pacchetto CSS non verrà caricato. Un elemento di collegamento nella parte superiore del documento blocca il rendering di HTML.
  3. Specificità non deterministica con suddivisione in bundle basata su pagina in applicazioni a pagina singola. *

Strategia 4: estrazione in tempo di costruzione con CSS critico

Questa strategia non è anche unica per CSS-in-JS. L'estrazione statica completa con CSS critico offre le migliori prestazioni quando si lavora con un'applicazione più statica. Questo approccio presenta ancora i suddetti compromessi di un CSS statico, tranne per il fatto che il tag del link di blocco può essere spostato nella parte inferiore del documento.

Esistono 4 principali strategie di rendering CSS. Solo 2 di questi sono specifici per CSS-in-JS e nessuno di essi si applica a tutte le librerie.

Accessibilità

CSS-in-JS può ridurre l'accessibilità se usato in modo errato. Ciò accadrà quando un sito di contenuto in gran parte statico viene implementato senza estrazione CSS critica in modo che l'HTML non possa essere disegnato prima che il bundle JavaScript venga caricato e valutato. Questo può accadere anche quando viene eseguito il rendering di un enorme file CSS utilizzando un tag di collegamento bloccante nella parte superiore del documento, che è il problema attuale più popolare con l'incorporamento tradizionale e non specifico di CSS-in-JS.

Gli sviluppatori devono assumersi la responsabilità dell'accessibilità. C'è ancora una forte idea fuorviante che una connessione Internet instabile sia un problema di paesi economicamente deboli. Tendiamo a dimenticare che abbiamo problemi di connettività ogni singolo giorno quando entriamo in un sistema ferroviario sotterraneo o in un grande edificio. Una connessione mobile stabile e senza cavi è un mito. Non è nemmeno facile avere una connessione WiFi stabile, ad esempio una rete WI-FI da 2,4 GHz può ottenere interferenze da un forno a microonde!

Il costo del CSS critico con il rendering lato server

Per ottenere l'estrazione CSS critica per CSS-in-JS, abbiamo bisogno di SSR. SSR è un processo di generazione dell'HTML finale per un determinato stato di un'applicazione sul server. In effetti, può essere un processo piuttosto complesso e costoso. Richiede un certo numero di cicli CPU sul server per ogni richiesta HTTP.

CSS-in-JS di solito sfrutta il fatto che è collegato alla pipeline di rendering HTML. * Sa quale HTML è stato renderizzato e di quali CSS ha bisogno in modo che sia in grado di produrne una quantità minima assoluta. Il CSS critico aggiunge un overhead aggiuntivo al rendering HTML sul server perché anche quel CSS deve essere compilato in una stringa CSS finale. In alcuni scenari, è difficile o addirittura impossibile memorizzare nella cache sul server.

Rendering scatola nera

Devi essere consapevole di come una libreria CSS-in-JS che stai utilizzando sta eseguendo il rendering del tuo CSS. Ad esempio, le persone spesso non sono consapevoli di come i componenti stilizzati e le emozioni implementano stili dinamici. Gli stili dinamici sono una sintassi che consente l'uso delle funzioni JavaScript all'interno della dichiarazione degli stili. Queste funzioni accettano oggetti di scena e restituiscono un blocco CSS.

Al fine di mantenere coerente la specificità dell'ordine di origine, entrambe le librerie sopra citate generano una nuova regola CSS se contiene una dichiarazione dinamica e il componente si aggiorna con nuovi oggetti di scena. Per dimostrare cosa intendo, ho creato questo sandbox. In JSS abbiamo deciso di prendere un altro compromesso, che ci consente di aggiornare le proprietà dinamiche senza generare nuove regole CSS. *

Ripida curva di apprendimento

Per le persone che hanno familiarità con i CSS, ma non conoscono JavaScript, la quantità iniziale di lavoro per essere aggiornati con CSS-in-JS potrebbe essere piuttosto grande.

Non è necessario essere uno sviluppatore JavaScript professionista per scrivere CSS-in-JS, fino al punto in cui viene coinvolta una logica complessa. Non possiamo generalizzare la complessità dello stile, poiché dipende davvero dal caso d'uso. Nei casi in cui CSS-in-JS diventa complesso, è probabile che l'implementazione con CSS vanilla sia ancora più complessa.

Per lo stile CSS-in-JS di base, è necessario sapere come dichiarare le variabili, come utilizzare le stringhe del modello e interpolare i valori JavaScript. Se si utilizza la notazione oggetto, è necessario sapere come lavorare con gli oggetti JavaScript e la sintassi basata su oggetti specifici della libreria. Se si tratta di uno stile dinamico, è necessario sapere come utilizzare le funzioni e i condizionali JavaScript.

Nel complesso c'è una curva di apprendimento, non possiamo negarlo. Questa curva di apprendimento di solito non è molto più grande dell'apprendimento del Sass. In effetti, ho creato questo corso a base di uova per dimostrarlo.

Nessuna interoperabilità

La maggior parte delle librerie CSS-in-JS non sono interoperabili. Ciò significa che gli stili scritti utilizzando una libreria non possono essere resi utilizzando una libreria diversa. In pratica significa che non è possibile passare facilmente l'intera applicazione da un'implementazione all'altra. Significa anche che non puoi facilmente condividere la tua UI su NPM senza portare la tua libreria CSS-in-JS preferita nel pacchetto del consumatore a meno che tu non abbia un'estrazione statica in tempo reale per il tuo CSS.

Abbiamo iniziato a lavorare sul formato ISTF che dovrebbe risolvere questo problema, ma sfortunatamente non abbiamo ancora avuto il tempo di portarlo a uno stato pronto per la produzione. *

Penso che condividere i componenti riutilizzabili dell'interfaccia utente agnostica di dominio pubblico sia ancora un problema generalmente difficile da risolvere.

Rischi per la sicurezza

È possibile introdurre falle nella sicurezza con CSS-in-JS. Come con qualsiasi applicazione lato client, è necessario evitare l'input dell'utente prima di renderizzarlo, sempre.

Questo articolo ti darà maggiori informazioni e alcuni esempi di deturpazione.

Nomi di classi illeggibili

Alcune persone pensano ancora che sia importante mantenere sul Web nomi di classe leggibili significativi. Attualmente, molte librerie CSS-in-JS forniscono nomi di classe significativi in ​​base al nome della dichiarazione o al nome del componente in modalità di sviluppo. Alcuni di essi consentono persino di personalizzare la funzione del generatore di nomi di classe.

Nella modalità di produzione, tuttavia, la maggior parte di essi genera nomi più brevi per un payload più piccolo. Questo è un compromesso che l'utente della libreria deve creare e personalizzare la libreria, se necessario.

Conclusione

Esistono dei compromessi e probabilmente non li ho nemmeno menzionati tutti. Ma la maggior parte di essi non si applica universalmente a tutti i CSS-in-JS. Dipendono dalla libreria che usi e da come la usi.

* Ci vorrà un articolo dedicato per spiegare questa frase. Fammi sapere su Twitter (@ oleg008) su quale vorresti leggere di più.