DevTools での CSS インフラストラクチャのモダナイゼーション

DevTools のアーキテクチャの更新: DevTools での CSS インフラストラクチャのモダナイゼーション

この投稿は、DevTools のアーキテクチャに加える変更とその構築方法について説明する一連のブログ投稿の一部です。ここでは、これまで DevTools で CSS が機能していた仕組みと、JavaScript ファイルで CSS を読み込むためのウェブ標準ソリューションへの移行(最終的には)に備えて、DevTools で CSS をどのようにモダナイズしたかを説明します。

DevTools での CSS の以前の状態

DevTools では、2 つの異なる方法で CSS を実装します。1 つは、DevTools の以前の部分で使用される CSS ファイル用、もう 1 つは、DevTools で使用されている最新のウェブ コンポーネント用です。

DevTools での CSS 実装は何年も前に定義されたものであり、現在は古くなっています。DevTools は module.json パターンを使用していて、これらのファイルを削除するために多大な労力を費やしてきました。これらのファイルを削除するための最後のブロッカーは、CSS ファイルの読み込みに使用される resources セクションです。

Google は、最終的に CSS モジュール スクリプトに形作る可能性のあるさまざまなソリューションを探求したいと考えました。目的は、以前のシステムに起因する技術的負担を取り除くと同時に、CSS モジュール スクリプトへの移行プロセスを容易にすることでした。

DevTools にあったすべての CSS ファイルは、削除が進められている module.json ファイルを使用して読み込まれているため、「レガシー」ファイルとみなされていました。すべての CSS ファイルは、CSS ファイルと同じディレクトリにある module.json ファイルの resources の下にリストされる必要があります。

残りの module.json ファイルの例を次に示します。

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

次に、これらの CSS ファイルが、そのコンテンツへのパスからのマッピングとして、Root.Runtime.cachedResources というグローバル オブジェクト マップに入力されます。DevTools にスタイルを追加するには、読み込むファイルの正確なパスを指定して registerRequiredCSS を呼び出す必要があります。

registerRequiredCSS 呼び出しの例:

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

これにより、CSS ファイルのコンテンツを取得し、appendStyle 関数を使用して <style> 要素としてページに挿入します。

インライン スタイル要素を使用して CSS を追加する appendStyle 関数:

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);

カスタム要素を使用して最新のウェブ コンポーネントを導入する際、当初はコンポーネント ファイル自体でインライン <style> タグを介して CSS を使用することに決めました。これには固有の課題がありました。

  • 構文ハイライトのサポートの欠如。インライン CSS の構文のハイライト表示を行うプラグインは、.css ファイルで記述された CSS の構文のハイライト表示やオートコンプリートの機能ほどうまくいかない傾向があります。
  • パフォーマンスのオーバーヘッドを構築する。また、インライン CSS では、lint チェックに使用するパスが 2 つ必要でした。1 つは CSS ファイル用、もう 1 つはインライン CSS 用です。これは、すべての CSS をスタンドアロンの CSS ファイルで記述した場合に削減できるパフォーマンス上のオーバーヘッドでした。
  • 圧縮における課題。インライン CSS は簡単に圧縮できなかったため、どの CSS も圧縮されていません。DevTools のリリースビルドのファイルサイズも、同じウェブ コンポーネントの複数のインスタンスによって重複した CSS を導入したことによって大きくなりました。

私のインターンシップ プロジェクトの目標は、DevTools で使用されている従来のインフラストラクチャと新しいウェブ コンポーネントの両方で機能する CSS インフラストラクチャのソリューションを見つけることでした。

潜在的なソリューションの調査

この問題は次の 2 つの部分に分けられます。

  • ビルドシステムが CSS ファイルをどのように扱うかを理解する。
  • DevTools による CSS ファイルのインポートと利用の方法を理解する

各領域で考えられるさまざまなソリューションを検討しました。以下に概要を示します。

CSS ファイルのインポート

TypeScript ファイルで CSS をインポートして使用する目的は、できる限りウェブ標準に近づけ、DevTools 全体に一貫性を持たせ、HTML 内の CSS の重複を避けることでした。また、変更を CSS モジュール スクリプトなどの新しいウェブ プラットフォーム標準に移行できるソリューションも選択したいと考えていました。

以上の理由から、@import ステートメントと タグは DevTools には適していないようでした。DevTools の残りの部分でインポートが統一されないため、Flash Of Unstyled Content(FOUC)が発生します。インポートを明示的に追加して処理を <link> タグの場合とは異なる方法で行う必要があるため、CSS モジュール スクリプトへの移行はより困難になります。

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>`

@import または <link> を使用した考えられる解決策。

代わりに、CSS ファイルを CSSStyleSheet オブジェクトとしてインポートし、adoptedStyleSheets プロパティを使用して Shadow Dom に追加する方法を見つけることにしました(DevTools では数年前から Shadow DOM が使用されています)。

Bundler のオプション

TypeScript ファイルで簡単に操作できるように、CSS ファイルを CSSStyleSheet オブジェクトに変換する方法が必要でした。Rollupwebpack の両方が、この変換を行うためのバンドラ候補だと考えました。DevTools はすでに本番環境ビルドで Rollup を使用していますが、いずれかのバンドラを本番環境ビルドに追加すると、現在のビルドシステムではパフォーマンスの問題が発生する可能性があります。Chromium の GN ビルドシステムとの統合により、バンドルが難しくなるため、バンドラは現在の Chromium ビルドシステムとの統合が難しくなる傾向があります。

代わりに、現在の GN ビルドシステムを使用して、代わりにこの変換を行うオプションを検討しました。

DevTools で CSS を使用するための新しいインフラストラクチャ

新しいソリューションでは、adoptedStyleSheets を使用して特定の Shadow DOM にスタイルを追加し、GN ビルドシステムを使用して document または ShadowRoot で採用できる CSSStyleSheet オブジェクトを生成します。

// 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];
  }
}

adoptedStyleSheets を使用すると、次のような複数のメリットがあります。

  • 最新のウェブ標準になりつつある
  • CSS の重複を防止します
  • スタイルを Shadow DOM にのみ適用することで、CSS ファイル内のクラス名または ID セレクタの重複による問題を回避できます
  • CSS モジュール スクリプトやインポート アサーションなど、将来のウェブ標準に簡単に移行できる

この解決策の唯一の注意点は、import ステートメントには .css.js ファイルをインポートする必要があることです。ビルド時に GN が CSS ファイルを生成できるように、generate_css_js_files.js スクリプトを作成しました。ビルドシステムですべての CSS ファイルが処理され、デフォルトで CSSStyleSheet オブジェクトをエクスポートする JavaScript ファイルに変換されるようになりました。これは、CSS ファイルをインポートして簡単に採用できる点で優れています。さらに、製品版ビルドを簡単に圧縮し、ファイルサイズを削減できるようになりました。

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;

スクリプトから生成された iconButton.css.js の例

ESLint ルールを使用したレガシーコードの移行

ウェブ コンポーネントは簡単に手動で移行できましたが、registerRequiredCSS の従来の使用方法を移行するプロセスは複雑でした。以前のスタイルを登録していた主な 2 つの関数は、registerRequiredCSScreateShadowRootWithCoreStyles でした。これらの呼び出しを移行する手順はかなり機械的なものであったため、ESLint ルールを使用して修正を適用し、以前のコードを自動的に移行できると判断しました。DevTools ではすでに、DevTools コードベースに固有の多くのカスタムルールが使用されています。これは、ESLint がすでにコードを解析して抽象構文ツリー(略称AST など)を使用して、CSS の登録呼び出しである特定の呼び出しノードをクエリできます。

移行 ESLint ルールを記述するときに直面した最大の問題は、エッジケースをキャプチャすることでした。キャプチャする価値のあるエッジケースと、手動で移行する必要があるエッジケースを判断するうえで、適切なバランスを取る必要がありました。また、インポートされた .css.js ファイルがビルドシステムによって自動的に生成されない場合、実行時に「ファイルが見つからない」エラーが発生することを防ぐために、ユーザーに通知できるようにすることも必要でした。

移行に ESLint ルールを使用するデメリットの 1 つは、システム内の必要な GN ビルドファイルを変更できないことです。これらの変更は、各ディレクトリでユーザーが手動で行う必要がありました。これにはより多くの作業が必要ですが、インポートされるすべての .css.js ファイルが実際にビルドシステムによって生成されていることを確認するのに適した方法でした。

全体として、この移行に ESLint ルールを使用したことが非常に役立ちました。以前のコードを新しいインフラストラクチャに迅速に移行でき、AST をすぐに利用できるため、ルール内の複数のエッジケースに対応し、ESLint の修正 API を使用して確実に自動的に修正することもできました。

次のステップ

これまでのところ、Chromium DevTools のすべてのウェブ コンポーネントは、インライン スタイルを使用するのではなく、新しい CSS インフラストラクチャを使用するように移行されています。registerRequiredCSS の従来の使用のほとんどは、新しいシステムを使用するように移行されました。あとは、できるだけ多くの module.json ファイルを削除し、この現在のインフラストラクチャを移行して、将来的に CSS モジュール スクリプトを実装するだけです。

プレビュー チャネルをダウンロードする

Chrome CanaryDevBeta を既定の開発ブラウザとして使用することをご検討ください。これらのプレビュー チャンネルでは、最新の DevTools 機能にアクセスしたり、最先端のウェブ プラットフォーム API をテストしたり、ユーザーが実際に体験する前にサイト上の問題を検出したりできます。

Chrome DevTools チームへのお問い合わせ

投稿内の新機能や変更点、または DevTools に関するその他のことについて話し合うには、次のオプションを使用します。

  • crbug.com からご提案やフィードバックをお送りください。
  • DevTools の問題を報告するには、DevTools でその他のオプション アイコン その他   > [ヘルプ] > [DevTools の問題を報告する] を選択します。
  • @ChromeDevTools にツイートします。
  • 「DevTools の新機能」の YouTube 動画または DevTools のヒントの YouTube 動画でコメントを残してください。