Supporto di CSS-in-JS in DevTools

Alex Rudenko
Alex Rudenko

Questo articolo illustra il supporto di CSS-in-JS in DevTools a partire da Chrome 85 e, in generale, cosa intendiamo per CSS-in-JS e in che modo si differenzia dal normale CSS supportato da DevTools da molto tempo.

Che cos'è CSS-in-JS?

La definizione di CSS-in-JS è piuttosto vaga. In generale, si tratta di un approccio alla gestione del codice CSS mediante JavaScript. Ad esempio, ciò potrebbe significare che i contenuti CSS vengono definiti mediante JavaScript e che l'output CSS finale viene generato all'istante dall'app.

Nel contesto di DevTools, il comando CSS-in-JS indica che i contenuti CSS vengono inseriti nella pagina utilizzando le API CSSOM. Il CSS normale viene inserito utilizzando elementi <style> o <link> e ha un'origine statica (ad esempio un nodo DOM o una risorsa di rete). Al contrario, CSS-in-JS spesso non ha una sorgente statica. Un caso speciale in questo caso è che i contenuti di un elemento <style> possono essere aggiornati utilizzando l'API CSSOM, causando la mancata sincronizzazione dell'origine con il foglio di stile CSS effettivo.

Se utilizzi una libreria CSS in JS (ad es. componente con stile, Emotion, JSS), la libreria potrebbe inserire stili utilizzando le API CSSOM in background, a seconda della modalità di sviluppo e del browser.

Diamo un'occhiata ad alcuni esempi di come inserire un foglio di stile utilizzando l'API CSSOM, in modo analogo alle librerie CSS-in-JS.

// Insert new rule to an existing CSS stylesheet
const element = document.querySelector('style');
const stylesheet = element.sheet;
stylesheet.replaceSync('.some { color: blue; }');
stylesheet.insertRule('.some { color: green; }');

Puoi anche creare un foglio di stile completamente nuovo:

// Create a completely new stylesheet
const stylesheet = new CSSStyleSheet();
stylesheet.replaceSync('.some { color: blue; }');
stylesheet.insertRule('.some { color: green; }');

// Apply constructed stylesheet to the document
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];

Supporto CSS in DevTools

In DevTools, la funzionalità più usata nell'ambito dei CSS è il riquadro Stili. Nel riquadro Stili puoi vedere quali regole vengono applicate a un determinato elemento, nonché modificare le regole e vedere le modifiche sulla pagina in tempo reale.

Prima dell'anno scorso, il supporto delle regole CSS modificate utilizzando le API CSSOM era piuttosto limitato: era possibile visualizzare solo le regole applicate ma non era possibile modificarle. L'obiettivo principale che avevamo l'anno scorso era consentire la modifica delle regole CSS-in-JS utilizzando il riquadro Stili. A volte chiamiamo anche gli stili CSS-in-JS "constructed" per indicare che sono stati creati utilizzando API web.

Approfondiamo i dettagli del funzionamento dell'editing degli stili in DevTools.

Meccanismo di modifica degli stili in DevTools

Meccanismo di modifica degli stili in DevTools

Quando selezioni un elemento in DevTools, viene visualizzato il riquadro Stili. Il riquadro Styles invia un comando CDP denominato CSS.getMatchedStylesForNode per ottenere le regole CSS applicabili all'elemento. CDP è l'acronimo di Chrome DevTools Protocol; è un'API che consente al frontend di DevTools di ottenere ulteriori informazioni sulla pagina ispezionata.

Se richiamato, CSS.getMatchedStylesForNode identifica tutti i fogli di stile nel documento e li analizza utilizzando l'analizzatore sintattico CSS del browser. Quindi crea un indice che associa ogni regola CSS a una posizione nell'origine del foglio di stile.

Potresti chiedere, perché deve analizzare di nuovo il CSS? Il problema è che, per motivi di prestazioni, il browser stesso non è interessato alle posizioni di origine delle regole CSS e, di conseguenza, non le memorizza. Tuttavia, DevTools ha bisogno delle posizioni di origine per supportare la modifica CSS. Non vogliamo che gli utenti normali di Chrome paghino una penalizzazione delle prestazioni, ma vogliamo che gli utenti di DevTools abbiano accesso alle posizioni di origine. Questo approccio di rianalisi affronta entrambi i casi d'uso con svantaggi minimi.

Successivamente, l'implementazione CSS.getMatchedStylesForNode chiede al motore degli stili del browser di fornire le regole CSS corrispondenti all'elemento specificato. Infine, il metodo associa le regole restituite dal motore di stile al codice sorgente e fornisce una risposta strutturata sulle regole CSS in modo che DevTools sappia quale parte della regola sono il selettore o le proprietà. Consente a DevTools di modificare il selettore e le proprietà in modo indipendente.

Ora passiamo all'editing. Ricordi che CSS.getMatchedStylesForNode restituisce posizioni di origine per ogni regola? Questo è fondamentale per l'editing. Quando modifichi una regola, DevTools invia un altro comando CDP che aggiorna effettivamente la pagina. Il comando include la posizione originale del frammento della regola in fase di aggiornamento e il nuovo testo con cui il frammento deve essere aggiornato.

Nel backend, quando gestisce la chiamata di modifica, DevTools aggiorna il foglio di stile di destinazione. Inoltre, aggiorna la copia dell'origine del foglio di stile che conserva e aggiorna le posizioni di origine per la regola aggiornata. In risposta alla chiamata di modifica, il frontend di DevTools recupera le posizioni aggiornate per il frammento di testo appena aggiornato.

Questo spiega perché la modifica di CSS-in-JS in DevTools non è andata a buon fine: CSS-in-JS non dispone di un'origine effettiva archiviata da nessuna parte e le regole CSS risiedono nella memoria del browser nelle strutture dati CSSOM.

Come abbiamo aggiunto il supporto per CSS-in-JS

Pertanto, per supportare la modifica di regole CSS-in-JS, abbiamo deciso che la soluzione migliore sarebbe creare un'origine per i fogli di stile creati che possono essere modificati utilizzando il meccanismo esistente descritto sopra.

Il primo passaggio consiste nel creare il testo di origine. Il motore di stile del browser archivia le regole CSS nella classe CSSStyleSheet. Quella è quella di cui puoi creare le istanze da JavaScript, come discusso in precedenza. Il codice per creare il testo sorgente è il seguente:

String InspectorStyleSheet::CollectStyleSheetRules() {
  StringBuilder builder;
  for (unsigned i = 0; i < page_style_sheet_->length(); i++) {
    builder.Append(page_style_sheet_->item(i)->cssText());
    builder.Append('\n');
  }
  return builder.ToString();
}

Esegue l'iterazione delle regole trovate in un'istanza CSSStyleSheet e crea una singola stringa da questa. Questo metodo viene richiamato quando viene creata un'istanza della classe InspectorStyleSheet. La classe InspectorStyleSheet esegue il wrapping di un'istanza CSSStyleSheet ed estrae i metadati aggiuntivi richiesti da DevTools:

void InspectorStyleSheet::UpdateText() {
  String text;
  bool success = InspectorStyleSheetText(&text);
  if (!success)
    success = InlineStyleSheetText(&text);
  if (!success)
    success = ResourceStyleSheetText(&text);
  if (!success)
    success = CSSOMStyleSheetText(&text);
  if (success)
    InnerSetText(text, false);
}

In questo snippet vediamo CSSOMStyleSheetText che chiama CollectStyleSheetRules internamente. CSSOMStyleSheetText viene richiamato se il foglio di stile non è in linea o non è un foglio di stile della risorsa. Fondamentalmente, questi due snippet consentono già la modifica di base dei fogli di stile creati con il costruttore new CSSStyleSheet().

Un caso speciale sono i fogli di stile associati a un tag <style> che sono stati modificati utilizzando l'API CSSOM. In questo caso, il foglio di stile contiene il testo di origine e regole aggiuntive che non sono presenti nel codice sorgente. Per gestire questo caso, introduciamo un metodo per unire queste regole aggiuntive nel testo di origine. In questo caso, l'ordine è importante perché le regole CSS possono essere inserite al centro del testo di origine originale. Ad esempio, immagina che l'elemento <style> originale contenga il seguente testo:

/* comment */
.rule1 {}
.rule3 {}

Quindi la pagina ha inserito alcune nuove regole utilizzando l'API JS producendo il seguente ordine di regole: .rule0, .rule1, .rule2, .rule3, .rule4. Il testo di origine risultante dopo l'operazione di unione deve essere il seguente:

.rule0 {}
/* comment */
.rule1 {}
.rule2 {}
.rule3 {}
.rule4 {}

La conservazione dei commenti e del rientro originali è importante per il processo di modifica, in quanto le posizioni del testo di origine delle regole devono essere precise.

Un altro aspetto speciale dei fogli di stile CSS-in-JS è che possono essere modificati dalla pagina in qualsiasi momento. Se le effettive regole CSSOM non fossero sincronizzate con la versione testuale, la modifica non andrebbe a buon fine. Per questo abbiamo introdotto un cosiddetto probe, che consente al browser di notificare la parte del backend di DevTools quando un foglio di stile viene modificato. I fogli di stile modificati vengono quindi sincronizzati durante la successiva chiamata a CSS.getMatchStylesForNode.

Con tutti questi elementi a disposizione, la modifica CSS-in-JS funziona già, ma volevamo migliorare l'interfaccia utente per indicare se è stato creato un foglio di stile. Abbiamo aggiunto un nuovo attributo denominato isConstructed alla sezione CSS.CSSStyleSheetHeader di CDP che viene utilizzato dal frontend per visualizzare correttamente l'origine di una regola CSS:

Foglio di stile costruibile

Conclusioni

Per ricapitolare, abbiamo analizzato i casi d'uso pertinenti relativi a CSS-in-JS non supportati da DevTools e abbiamo esaminato la soluzione a supporto di questi casi d'uso. La parte interessante di questa implementazione è che siamo stati in grado di sfruttare le funzionalità esistenti facendo sì che le regole CSSOM abbiano un testo di origine regolare, evitando la necessità di riprogettare completamente l'architettura di modifica degli stili in DevTools.

Per maggiori informazioni, leggi la nostra proposta di progettazione o il bug di monitoraggio di Chromium che fa riferimento a tutte le patch correlate.

Scaricare i canali in anteprima

Prendi in considerazione l'utilizzo di Chrome Canary, Dev o Beta come browser di sviluppo predefinito. Questi canali di anteprima ti consentono di accedere alle ultime funzionalità di DevTools, di testare le API delle piattaforme web all'avanguardia e di individuare i problemi sul tuo sito prima che lo facciano gli utenti.

Contattare il team di Chrome DevTools

Utilizza le seguenti opzioni per discutere delle nuove funzionalità e modifiche nel post o di qualsiasi altra informazione relativa a DevTools.

  • Inviaci un suggerimento o un feedback tramite crbug.com.
  • Segnala un problema di DevTools usando Altre opzioni   Altre   > Guida > Segnala un problema di DevTools in DevTools.
  • Invia un tweet all'indirizzo @ChromeDevTools.
  • Lascia commenti sulle novità nei video di YouTube di DevTools o nei video di YouTube dei suggerimenti di DevTools.