Modernizzare l'infrastruttura CSS in DevTools

Aggiornamento dell'architettura DevTools: modernizzazione dell'infrastruttura CSS in DevTools

Questo post fa parte di una serie di post del blog che descrivono le modifiche che stiamo apportando all'architettura di DevTools e il modo in cui viene creata. Spiegheremo in che modo il CSS funzionava in DevTools e in che modo abbiamo modernizzato il CSS in DevTools in preparazione alla migrazione (finalmente) a una soluzione standard web per il caricamento di CSS nei file JavaScript.

Stato precedente del CSS in DevTools

DevTools ha implementato CSS in due diversi modi: uno per i file CSS utilizzati nella parte precedente di DevTools e uno per i componenti web moderni utilizzati in DevTools.

L'implementazione del CSS in DevTools è stata definita molti anni fa ed è ormai obsoleta. DevTools si è bloccato sull'utilizzo del pattern module.json e si è verificato un impegno notevole per la rimozione di questi file. L'ultimo blocco per la rimozione di questi file è la sezione resources, utilizzata per il caricamento nei file CSS.

Volevamo dedicare del tempo a esplorare diverse soluzioni potenziali che potevano poi trasformarsi in script di moduli CSS. Lo scopo era rimuovere il debito tecnico causato dal sistema precedente, ma anche semplificare il processo di migrazione agli script di moduli CSS.

Tutti i file CSS presenti in DevTools sono stati considerati "legacy" in quanto sono stati caricati utilizzando un file module.json, che è in fase di rimozione. Tutti i file CSS dovevano essere elencati nella sezione resources in un file module.json nella stessa directory del file CSS.

Esempio di file module.json rimanente:

{
  "resources": [
    "serviceWorkersView.css",
    "serviceWorkerUpdateCycleView.css"
  ]
}

Questi file CSS dovrebbero quindi compilare una mappa globale degli oggetti denominata Root.Runtime.cachedResources come mappatura da un percorso ai relativi contenuti. Per aggiungere stili in DevTools, devi chiamare registerRequiredCSS con il percorso esatto del file che vuoi caricare.

Esempio di chiamata registerRequiredCSS:

constructor() {
  …
  this.registerRequiredCSS('ui/legacy/components/quick_open/filteredListWidget.css');
  …
}

I contenuti del file CSS vengono recuperati e inseriti come elemento <style> nella pagina utilizzando la funzione appendStyle:.

Funzione appendStyle che aggiunge CSS utilizzando un elemento di stile incorporato:

const content = Root.Runtime.cachedResources.get(cssFile) || '';

if (!content) {
  console.error(cssFile + ' not preloaded. Check module.json');
}

const styleElement = document.createElement('style');
styleElement.textContent = content;
node.appendChild(styleElement);

Quando abbiamo introdotto componenti web moderni (utilizzando elementi personalizzati), abbiamo deciso inizialmente di utilizzare CSS tramite tag <style> incorporati nei file dei componenti stessi. Ciò ha presentato le sue sfide:

  • Mancanza di supporto per l'evidenziazione della sintassi. I plug-in che forniscono l'evidenziazione della sintassi per il codice CSS incorporato tendono a non avere la stessa qualità delle funzionalità di evidenziazione della sintassi e di completamento automatico per i CSS scritti nei file .css.
  • Crea overhead per il rendimento. Il CSS in linea richiedeva anche due passaggi per l'analisi tramite lint: uno per i file CSS e uno per il CSS in linea. Si trattava di un sovraccarico del rendimento che potevamo rimuovere se tutto il CSS fosse stato scritto in file CSS indipendenti.
  • Sfida nella minimizzazione. Poiché non è stato possibile minimizzare facilmente il codice CSS in linea, nessuno dei CSS è stato minimizzato. Le dimensioni del file della build della release di DevTools sono state aumentate anche dal CSS duplicato introdotto da più istanze dello stesso componente web.

L'obiettivo del mio progetto di tirocinio era trovare una soluzione per l'infrastruttura CSS che funzioni sia con l'infrastruttura precedente che con i nuovi componenti web utilizzati in DevTools.

Ricerca di potenziali soluzioni

Il problema può essere suddiviso in due parti diverse:

  • Capire come il sistema di compilazione gestisce i file CSS.
  • Capire come i file CSS vengono importati e utilizzati da DevTools.

Abbiamo esaminato diverse soluzioni potenziali per ogni parte e le abbiamo descritte di seguito.

Importazione dei file CSS

L'obiettivo dell'importazione e dell'utilizzo di CSS nei file TypeScript era di rispettare il più possibile gli standard web, garantire la coerenza in DevTools ed evitare CSS duplicati nel nostro codice HTML. Volevamo anche poter scegliere una soluzione che ci permettesse di migrare le nostre modifiche ai nuovi standard delle piattaforme web, come gli script di moduli CSS.

Per questi motivi, le istruzioni @import e i tag non ci sono sembrati adatti a DevTools. Non sarebbero uniformi alle importazioni nel resto di DevTools e generano un Flash di contenuti senza stile (FOUC). La migrazione agli script di moduli CSS sarebbe più difficile perché le importazioni dovrebbero essere aggiunte in modo esplicito e gestite in modo diverso rispetto ai tag <link>.

const output = LitHtml.html`
<style> @import "css/styles.css"; </style>
<button> Hello world </button>`
const output = LitHtml.html`
<link rel="stylesheet" href="styles.css">
<button> Hello World </button>`

Potenziali soluzioni utilizzando @import o <link>.

Abbiamo invece optato per trovare un modo per importare il file CSS come oggetto CSSStyleSheet in modo da poterlo aggiungere a Shadow Dom (DevTools utilizza Shadow DOM da un paio d'anni) usando la sua proprietà adoptedStyleSheets.

Opzioni Bundler

Eravamo alla ricerca di un modo per convertire i file CSS in un oggetto CSSStyleSheet, in modo da poter intervenire facilmente nel file TypeScript. Abbiamo considerato sia Rollup sia webpack come potenziali bundler per fare questa trasformazione per noi. DevTools utilizza già la funzionalità di aggregazione nella sua build di produzione, ma l'aggiunta di uno dei due bundler alla build di produzione potrebbe comportare potenziali problemi di prestazioni quando lavori con il nostro attuale sistema di build. La nostra integrazione con il sistema di build GN di Chromium rende più difficile il raggruppamento, pertanto i bundle tendono a non integrarsi bene con l'attuale sistema di build di Chromium.

Abbiamo invece pensato di utilizzare l'attuale sistema di compilazione GN per effettuare questa trasformazione al posto nostro.

La nuova infrastruttura per l'utilizzo di CSS in DevTools

La nuova soluzione prevede l'uso di adoptedStyleSheets per aggiungere stili a un particolare DOM shadow, mentre si utilizza il sistema di compilazione GN per generare oggetti CSSStyleSheet che possono essere adottati da document o ShadowRoot.

// CustomButton.ts

// Import the CSS style sheet contents from a JS file generated from CSS
import customButtonStyles from './customButton.css.js';
import otherStyles from './otherStyles.css.js';

export class CustomButton extends HTMLElement{
  …
  connectedCallback(): void {
    // Add the styles to the shadow root scope
    this.shadow.adoptedStyleSheets = [customButtonStyles, otherStyles];
  }
}

L'utilizzo di adoptedStyleSheets offre diversi vantaggi, tra cui:

  • È in corso per diventare uno standard web moderno
  • Impedisce la duplicazione di CSS
  • Applica gli stili solo a un DOM Shadow per evitare eventuali problemi causati da nomi di classi o selettori ID duplicati nei file CSS.
  • Migrazione facile agli standard web futuri come gli script di moduli CSS e le asserzioni di importazione

L'unico avvertimento alla soluzione era che le istruzioni import richiedevano l'importazione del file .css.js. Per consentire a GN di generare un file CSS durante la creazione, abbiamo scritto lo script generate_css_js_files.js. Il sistema di compilazione ora elabora ogni file CSS e lo trasforma in un file JavaScript che per impostazione predefinita esporta un oggetto CSSStyleSheet. Questa è un'ottima soluzione, perché possiamo importare il file CSS e adottarlo facilmente. Inoltre, ora possiamo anche minimizzare facilmente la build di produzione, risparmiando dimensioni del file:

const styles = new CSSStyleSheet();
styles.replaceSync(
  // In production, we also minify our CSS styles
  /`${isDebug ? output : cleanCSS.minify(output).styles}
  /*# sourceURL=${fileName} */`/
);

export default styles;

Esempio generato iconButton.css.js dallo script.

Migrazione del codice legacy utilizzando le regole ESLint

La migrazione manuale dei componenti web era semplice, ma il processo di migrazione degli utilizzi precedenti di registerRequiredCSS era più impegnativo. Le due funzioni principali che hanno registrato gli stili precedenti erano registerRequiredCSS e createShadowRootWithCoreStyles. Abbiamo deciso che, poiché i passaggi per la migrazione di queste chiamate erano piuttosto meccanici, potevamo utilizzare le regole ESLint per applicare le correzioni ed eseguire automaticamente la migrazione del codice precedente. DevTools utilizza già una serie di regole personalizzate specifiche per il codebase DevTools. Questo è stato utile perché ESLint analizza già il codice in un albero di sintassi astratto(abbr. AST) e potevamo interrogare i nodi di chiamata specifici che erano chiamate alla registrazione del CSS.

Il problema maggiore che abbiamo affrontato durante la scrittura delle regole ESLint per la migrazione è stato l'acquisizione dei casi limite. Volevamo trovare il giusto equilibrio tra sapere quali casi limite valevano la pena acquisire e quali dovrebbero essere migrati manualmente. Volevamo inoltre essere sicuri di poter comunicare a un utente quando un file .css.js importato non viene generato automaticamente dal sistema di compilazione, in quanto ciò impedisce che gli errori di file non vengano rilevati durante il runtime.

Uno svantaggio dell'uso delle regole ESLint per la migrazione era che non potevamo modificare il file di build GN richiesto nel sistema. Queste modifiche dovevano essere apportate manualmente dall'utente in ogni directory. Anche se questa operazione richiedeva più lavoro, era un buon modo per verificare che ogni file .css.js importato sia effettivamente generato dal sistema di compilazione.

Nel complesso, l'utilizzo delle regole ESLint per questa migrazione è stato davvero utile, dal momento che siamo riusciti a migrare rapidamente il codice legacy nella nuova infrastruttura. Inoltre, avere AST prontamente disponibile ci ha permesso di gestire più casi limite nella regola e di correggerli in modo automatico in modo affidabile utilizzando l'API di correzione di ESLint.

E adesso?

Finora, è stata eseguita la migrazione di tutti i componenti web in Chromium DevTools in modo da utilizzare la nuova infrastruttura CSS anziché utilizzare gli stili incorporati. Anche la maggior parte degli utilizzi precedenti di registerRequiredCSS è stata migrata per utilizzare il nuovo sistema. Non ti resta che rimuovere il maggior numero possibile di file module.json e poi eseguire la migrazione di questa infrastruttura attuale per implementare gli script di moduli CSS in futuro.

Scarica 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 funzionalità di DevTools più recenti, di testare le API per piattaforme web all'avanguardia e di individuare eventuali problemi sul tuo sito prima che lo facciano gli utenti.

Contattare il team di Chrome DevTools

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

  • Inviaci un suggerimento o un feedback tramite crbug.com.
  • Segnala un problema 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 in DevTools Video di YouTube.