Modernizzare l'infrastruttura CSS in DevTools

Aggiornamento dell'architettura di 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 alla sua compilazione. Spiegheremo come funzionava il CSS in DevTools in passato e come abbiamo modernizzato il nostro CSS in DevTools in vista della migrazione (eventualmente) a una soluzione standard web per il caricamento del CSS nei file JavaScript.

Stato precedente del CSS in DevTools

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

L'implementazione CSS in DevTools è stata definita molti anni fa e ora è obsoleta. DevTools ha continuato a utilizzare il pattern module.json e ci è voluto un grande impegno per rimuovere questi file. L'ultimo blocco per la rimozione di questi file è la sezione resources, utilizzata per caricare i file CSS.

Volevamo dedicare del tempo all'esplorazione di diverse potenziali soluzioni che potrebbero eventualmente trasformarsi in script del modulo CSS. L'obiettivo era eliminare i problemi tecnici causati dal sistema precedente, semplificando al contempo il processo di migrazione agli script dei moduli CSS.

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

Un esempio di file module.json rimanente:

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

Questi file CSS andranno a compilare una mappa di oggetti globale chiamata Root.Runtime.cachedResources come mappatura da un percorso ai relativi contenuti. Per aggiungere gli stili a 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');
  
}

In questo modo, 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 in linea:

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 i componenti web moderni (utilizzando elementi personalizzati), abbiamo deciso inizialmente di utilizzare CSS tramite tag <style> incorporati nei file dei componenti. Ciò presentava alcune sfide:

  • Manca il supporto dell'evidenziazione della sintassi. I plug-in che offrono l'evidenziazione della sintassi per i CSS in linea non tendono ad avere la stessa qualità delle funzionalità di evidenziazione della sintassi e completamento automatico per i CSS scritti nei file .css.
  • Aumenta il sovraccarico delle prestazioni. Il CSS in linea ha anche comportato la necessità di due passaggi per il linting: uno per i file CSS e uno per il CSS in linea. Si trattava di un sovraccarico delle prestazioni che potevamo rimuovere se tutto il CSS fosse stato scritto in file CSS autonomi.
  • Problema di minimizzazione. Il CSS incorporato non può essere facilmente minimizzato, pertanto nessuno dei CSS è stato minimizzato. Le dimensioni del file della release build di DevTools sono aumentate anche a causa del CSS duplicato introdotto da più istanze dello stesso componente web.

L&#39;obiettivo del mio progetto di stage era trovare una soluzione per l&#39;infrastruttura CSS che funzionasse sia con l&#39;infrastruttura precedente sia con i nuovi componenti web utilizzati in DevTools.

Ricerca di potenziali soluzioni

Il problema potrebbe essere suddiviso in due parti diverse:

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

Abbiamo esaminato diverse potenziali soluzioni per ogni parte, che sono descritte di seguito.

Importazione di file CSS

L'obiettivo dell'importazione e dell'utilizzo del CSS nei file TypeScript era rispettare il più possibile gli standard web, impedire la duplicazione del CSS nel codice HTML e garantire la coerenza in DevTools. Volevamo anche poter scegliere una soluzione che consentisse di eseguire la migrazione delle nostre modifiche ai nuovi standard della piattaforma web, come gli script dei moduli CSS.

Per questi motivi, le istruzioni @import e i tag non sembravano adatti a DevTools. Non sarebbero uniformi con le importazioni del resto di DevTools e genererebbero un Flash di contenuti senza stile (FOUC). La migrazione agli script dei moduli CSS sarebbe più difficile perché le importazioni dovrebbero essere aggiunte esplicitamente 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 che utilizzano @import o <link>.

Abbiamo scelto invece di trovare un modo per importare il file CSS come oggetto CSSStyleSheet in modo da poterlo aggiungere allo Shadow Dom (DevTools utilizza Shadow DOM per un paio di anni) utilizzando la sua proprietà adoptedStyleSheets.

Opzioni del bundler

Ci serviva un modo per convertire i file CSS in un oggetto CSSStyleSheet in modo da poterli manipolare facilmente nel file TypeScript. Abbiamo preso in considerazione sia Rollup che webpack come potenziali bundler per eseguire questa trasformazione per nostro conto. DevTools utilizza già Rollup nella compilazione di produzione, ma l'aggiunta di uno dei bundler alla compilazione di produzione potrebbe causare potenziali problemi di prestazioni quando si utilizza il nostro attuale sistema di compilazione. La nostra integrazione con il sistema di compilazione GN di Chromium rende il bundling più difficile e, di conseguenza, i bundler tendono a non integrarsi bene con l'attuale sistema di compilazione di Chromium.

Abbiamo invece esplorato la possibilità di utilizzare il sistema di compilazione GN attuale per eseguire questa trasformazione al posto nostro.

La nuova infrastruttura per l'utilizzo del CSS in DevTools

La nuova soluzione prevede l'utilizzo di adoptedStyleSheets per aggiungere stili a un determinato DOM ombra, utilizzando al contempo il sistema di compilazione GN per generare oggetti CSSStyleSheet che possono essere adottati da un document o un 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 procinto di diventare uno standard web moderno
  • Impedisce i CSS duplicati
  • Applica gli stili solo a un DOM ombra, evitando così eventuali problemi causati da nomi di classi o selettori ID duplicati nei file CSS
  • Migrazione facile ai futuri standard web, come gli script dei moduli CSS e le asserzioni di importazione

L'unico svantaggio della soluzione era che le istruzioni import richiedevano l'importazione del file .css.js. Per consentire a GN di generare un file CSS durante la compilazione, abbiamo scritto lo script generate_css_js_files.js. Ora il sistema di compilazione elabora ogni file CSS e lo trasforma in un file JavaScript che per impostazione predefinita esporta un oggetto CSSStyleSheet. È fantastico perché possiamo importare il file CSS e adottarlo facilmente. Inoltre, ora possiamo anche ridurre facilmente la build di produzione, riducendo le 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 di iconButton.css.js generato dallo script.

Migrazione del codice precedente utilizzando le regole ESLint

Sebbene la migrazione dei componenti web potesse essere eseguita facilmente manualmente, la procedura per la migrazione degli utilizzi precedenti di registerRequiredCSS era più complessa. 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'Abstract Syntax Tree (abbreviazione di AST) e potremmo eseguire query sui nodi di chiamata specifici che erano chiamate al CSS registrato.

Il problema principale che abbiamo dovuto affrontare durante la scrittura delle regole ESLint della migrazione è stato l'acquisizione di casi limite. Volevamo assicurarci di ottenere il giusto equilibrio tra sapere quali casi perimetrali valevano la pena acquisire e quali dovrebbero essere migrati manualmente. Volevamo anche poter comunicare a un utente quando un file .css.js importato non viene generato automaticamente dal sistema di compilazione, in quanto questo evita errori di file non trovato in fase di esecuzione.

Uno svantaggio dell'utilizzo delle regole ESLint per la migrazione è che non è stato possibile modificare il file di compilazione 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 fosse effettivamente generato dal sistema di compilazione.

Nel complesso, l'utilizzo delle regole ESLint per questa migrazione è stato molto utile perché abbiamo potuto eseguire rapidamente la migrazione del codice precedente alla nuova infrastruttura e, avendo l'AST subito disponibile, abbiamo potuto anche gestire più casi limite nella regola e correggerli automaticamente in modo affidabile utilizzando l'API di correzione di ESLint.

E adesso?

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

Scaricare i canali di 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 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 gli utenti.

Contatta il team di Chrome DevTools

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