Supporto di CSS-in-JS in DevTools

Alex Rudenko
Alex Rudenko

In questo articolo viene illustrato il supporto di CSS-in-JS in DevTools disponibile a partire da Chrome 85 e, in generale, cosa intendiamo per CSS-in-JS e come 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, rappresenta un approccio alla gestione del codice CSS tramite JavaScript. Ad esempio, i contenuti CSS possono essere definiti utilizzando JavaScript e l'output CSS finale viene generato immediatamente dall'app.

Nel contesto di DevTools, CSS-in-JS indica che i contenuti CSS vengono inseriti nella pagina utilizzando le API CSSOM. Il CSS normale viene inserito utilizzando gli 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 è rappresentato dal fatto che i contenuti di un elemento <style> possono essere aggiornati utilizzando l'API CSSOM, causando la mancata sincronizzazione della sorgente con l'effettivo foglio di stile CSS.

Se utilizzi una libreria CSS-in-JS (ad es. styled-component, Emotion, JSS), la libreria potrebbe inserire gli stili utilizzando le API CSSOM in base alla modalità di sviluppo e al browser.

Di seguito sono riportati alcuni esempi di come inserire un foglio di stile utilizzando l'API CSSOM, analogamente a quanto accade con le 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ù utilizzata per gestire i CSS è il riquadro Stili. Nel riquadro Stili puoi visualizzare le regole applicate a un determinato elemento, modificarle e vedere le modifiche sulla pagina in tempo reale.

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

Analizziamo in dettaglio le operazioni di modifica degli Stili in DevTools.

Meccanismo di modifica dello stile in DevTools

Meccanismo di modifica dello stile in DevTools

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

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

Potresti chiederti perché deve analizzare di nuovo il CSS. Il problema è che, per motivi legati alle 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 nell'origine per supportare la modifica CSS. Non vogliamo che gli utenti di Chrome abituali siano soggetti a penalità di prestazioni, ma vogliamo che gli utenti DevTools abbiano accesso alle posizioni di origine. Questo approccio di rianalisi si occupa di entrambi i casi d'uso con svantaggi minimi.

Successivamente, l'implementazione CSS.getMatchedStylesForNode chiede al motore degli stili del browser di fornire regole CSS che corrispondano all'elemento specificato. Infine, il metodo associa le regole restituite dal motore degli stili al codice sorgente e fornisce una risposta strutturata sulle regole CSS, in modo che DevTools sappia quale parte della regola è 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 le posizioni di origine per ogni regola? È fondamentale per l'editing. Quando modifichi una regola, DevTools emette 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 è necessario aggiornare il frammento.

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

Questo spiega perché la modifica di CSS-in-JS in DevTools non funzionava subito: CSS-in-JS non dispone di un'origine effettiva archiviata da nessuna parte e le regole CSS si trovano nella memoria del browser nelle strutture di dati CSSOM.

Come abbiamo aggiunto il supporto per CSS-in-JS

Per questo motivo, per supportare la modifica delle regole CSS-in-JS, abbiamo deciso che la soluzione migliore sarebbe quella di creare un codice sorgente per i fogli di stile costruiti che possa essere modificato utilizzando il meccanismo esistente descritto in precedenza.

Il primo passaggio consiste nel creare il testo di origine. Il motore degli stili del browser archivia le regole CSS nella classe CSSStyleSheet. Questa è quella di cui puoi creare le istanze da JavaScript, come spiegato 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 presenti in un'istanza CSSStyleSheet e ne crea una singola stringa. Questo metodo viene richiamato quando viene creata un'istanza della classe InspectorStyleSheet. La classe InspectorStyleSheet include 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 è incorporato o non è un foglio di stile di una risorsa. Fondamentalmente, questi due snippet consentono già la modifica di base dei fogli di stile creati utilizzando 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 non presenti nell'origine. Per gestire questo caso, introduciamo un metodo per unire queste regole aggiuntive nel testo di origine. In questo caso l'ordine è importante, in quanto 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 {}

Successivamente, 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 poiché 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 regole CSSOM effettive non fossero sincronizzate con la versione di testo, le modifiche non funzioneranno. Per questo abbiamo introdotto un cosiddetto probe, che consente al browser di notificare la parte backend di DevTools quando un foglio di stile viene mutato. I fogli di stile mutati vengono quindi sincronizzati alla successiva chiamata a CSS.getMatchStylesForNode.

Con tutte queste parti aggiunte, la modifica CSS-in-JS funziona già, ma volevamo migliorare l'interfaccia utente per indicare se un foglio di stile è stato creato. Abbiamo aggiunto un nuovo attributo isConstructed al 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 la nostra storia, abbiamo esaminato i casi d'uso pertinenti relativi a CSS-in-JS che DevTools non supportava e abbiamo individuato 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 impostando le regole CSSOM come testo di origine regolare, evitando di dover riprogettare completamente l'architettura delle modifiche in DevTools.

Per saperne di più, dai un'occhiata alla nostra proposta di design o al 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 in anteprima ti consentono di accedere alle ultime funzionalità DevTools, testare le API delle piattaforme web all'avanguardia e individuare eventuali 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 qualsiasi altra informazione relativa a DevTools.

  • Inviaci un suggerimento o un feedback tramite crbug.com.
  • Segnala un problema di DevTools utilizzando Altre opzioni   Altre   > Guida > Segnala i problemi di DevTools in DevTools.
  • Tweet all'indirizzo @ChromeDevTools.
  • Lascia commenti sui video di YouTube o sui suggerimenti di DevTools sui video di YouTube di DevTools.