Supporto di CSS-in-JS in DevTools

Alex Rudenko
Alex Rudenko

Questo articolo parla del supporto di CSS in JS in DevTools, disponibile da Chrome 85, e, in generale, di cosa si intende per CSS in JS e di come si differenzia dal CSS normale supportato da DevTools da molto tempo.

Che cos'è CSS-in-JS?

La definizione di CSS-in-JS è piuttosto vaga. In senso ampio, è un approccio per gestire il codice CSS utilizzando JavaScript. Ad esempio, potrebbe significare che i contenuti CSS sono definiti utilizzando JavaScript e l'output CSS finale viene generato dinamicamente dall'app.

Nel contesto di DevTools, CSS-in-JS indica che i contenuti CSS vengono iniettati nella pagina utilizzando le API CSSOM. Il CSS normale viene inserito utilizzando gli elementi <style> o <link> e ha un'origine statica (ad es. un nodo DOM o una risorsa di rete). Al contrario, CSS-in-JS spesso non ha un'origine statica. Un caso speciale è 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. styled-component, Emotion, JSS), la libreria potrebbe iniettare gli stili utilizzando le API CSSOM sotto il cofano, a seconda della modalità di sviluppo e del browser.

Vediamo alcuni esempi di come puoi iniettare uno stile utilizzando l'API CSSOM in modo simile a quanto fanno 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 nuovo foglio di stile:

// 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 quando si tratta di CSS è il riquadro Stili. Nel riquadro Stili, puoi visualizzare le regole applicate a un determinato elemento, modificarle e vedere le modifiche nella pagina in tempo reale.

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

Vediamo in dettaglio come funziona la 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 emette un comando CDP denominato CSS.getMatchedStylesForNode per ottenere le regole CSS che si applicano all'elemento. CDP sta per Chrome DevTools Protocol ed è un'API che consente al frontend di DevTools di ottenere ulteriori informazioni sulla pagina ispezionata.

Quando viene richiamato, CSS.getMatchedStylesForNode identifica tutti gli stili nel documento e li analizza utilizzando il parser CSS del browser. Quindi, crea un indice che associa ogni regola CSS a una posizione nell'origine del foglio di stile.

Potresti chiederti perché è necessario analizzare nuovamente il CSS. Il problema è che, per motivi di prestazioni, il browser stesso non si occupa delle posizioni di origine delle regole CSS e, di conseguenza, non le memorizza. Tuttavia, DevTools ha bisogno delle posizioni di origine per supportare la modifica del CSS. Non vogliamo che gli utenti normali di Chrome paghino la penalizzazione del rendimento, ma vogliamo che gli utenti di DevTools abbiano accesso alle posizioni dell'origine. Questo approccio di analisi nuovamente consente di gestire entrambi i casi d'uso con svantaggi minimi.

Successivamente, l'implementazione di 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 dall'engine dello stile 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 vediamo la modifica. Ricordi che CSS.getMatchedStylesForNode restituisce le posizioni delle origini per ogni regola? Questo è fondamentale per la modifica. 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 che viene aggiornato e il nuovo testo con cui deve essere aggiornato il frammento.

Sul backend, durante la gestione della chiamata di modifica, DevTools aggiorna lo stile CSS di destinazione. Aggiorna anche la copia del codice sorgente del foglio di stile che gestisce e le posizioni di origine per la regola aggiornata. In risposta alla chiamata di modifica, il frontend di DevTools restituisce le posizioni aggiornate per il frammento di testo appena modificato.

Questo spiega perché la modifica di CSS-in-JS in DevTools non ha funzionato immediatamente: CSS-in-JS non ha 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 di CSS-in-JS

Pertanto, per supportare la modifica delle regole CSS-in-JS, abbiamo deciso che la soluzione migliore sarebbe creare un codice sorgente per gli stili CSS costruiti che possa essere modificato utilizzando il meccanismo esistente descritto sopra.

Il primo passaggio consiste nel creare il testo di origine. Il motore degli stili del browser memorizza le regole CSS nella classe CSSStyleSheet. Questa è la classe di cui puoi creare istanze da JavaScript, come discusso in precedenza. Il codice per creare il testo di origine è 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 sulle regole trovate in un'istanza CSSStyleSheet e ne crea una singola stringa. Questo metodo viene invocato quando viene creata un'istanza della classe InspectorStyleSheet. La classe InspectorStyleSheet racchiude un'istanza CSSStyleSheet ed estrae 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 invocato se lo stile non è in linea o se si tratta di uno stile delle risorse. In sostanza, questi due snippet consentono già la modifica di base degli stili di pagina creati utilizzando il costruttore new CSSStyleSheet().

Un caso speciale sono gli stili associati a un tag <style> che sono stati sottoposti a mutazione 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, abbiamo introdotto un metodo per unire queste regole aggiuntive al testo di origine. In questo caso l'ordine è importante perché le regole CSS possono essere inserite nel mezzo del testo sorgente originale. Ad esempio, immagina che l'elemento <style> originale contenga il seguente testo:

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

Poi 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 dovrebbe essere il seguente:

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

La conservazione dei commenti e dell'a capo originali è importante per il processo di modifica perché le posizioni del testo di origine delle regole devono essere precise.

Un altro aspetto speciale degli stili CSS-in-JS è che possono essere modificati dalla pagina in qualsiasi momento. Se le regole CSSOM effettive non fossero più sincronizzate con la versione di testo, la modifica non funzionerebbe. Per questo abbiamo introdotto un cosiddetto sondaggio, che consente al browser di notificare la parte di backend di DevTools quando viene modificata una regola CSS. Le stylesheet mutate vengono poi sincronizzate durante la chiamata successiva a CSS.getMatchedStylesForNode.

Con tutti questi elementi in atto, la modifica CSS-in-JS funziona già, ma volevamo migliorare l'interfaccia utente per indicare se è stato creato uno stile. Abbiamo aggiunto un nuovo attributo denominato isConstructed a CSS.CSSStyleSheetHeader di CDP, di cui il frontend fa uso per visualizzare correttamente l'origine di una regola CSS:

Foglio di stile costruibile

Conclusioni

Per riepilogare la nostra storia, abbiamo esaminato i casi d'uso pertinenti relativi a CSS-in-JS non supportati da DevTools e abbiamo illustrato la soluzione per supportarli. La parte interessante di questa implementazione è che siamo riusciti a sfruttare la funzionalità esistente facendo in modo che le regole CSSOM abbiano un testo di origine normale, evitando la necessità di riorganizzare completamente la modifica degli stili in DevTools.

Per ulteriori informazioni, consulta la nostra proposta di design o il bug di monitoraggio di Chromium che fa riferimento a tutte le patch correlate.

Scaricare i canali di anteprima

Valuta la possibilità di utilizzare Chrome Canary, Dev o Beta come browser di sviluppo predefinito. Questi canali di anteprima ti consentono di accedere alle funzionalità più recenti di DevTools, di testare API di piattaforme web all'avanguardia e di trovare i problemi sul tuo sito prima che lo facciano i tuoi utenti.

Contatta il team di Chrome DevTools

Utilizza le seguenti opzioni per discutere di nuove funzionalità, aggiornamenti o qualsiasi altro argomento relativo a DevTools.