DevTools에서 CSS 인프라 현대화

DevTools 아키텍처 업데이트: DevTools의 CSS 인프라 현대화

이 게시물은 DevTools 아키텍처에 적용되는 변경사항과 빌드 방법을 설명하는 블로그 게시물 시리즈의 일부입니다. DevTools에서 CSS가 작동한 방식과 JavaScript 파일에 CSS를 로드하기 위한 웹 표준 솔루션으로 (궁극적으로) 이전하기 위해 DevTools에서 CSS를 현대화한 방법을 설명합니다.

DevTools의 이전 CSS 상태

DevTools는 두 가지 방식으로 CSS를 구현했습니다. 하나는 DevTools의 기존 부분에 사용되는 CSS 파일용이고, 하나는 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는 린팅을 위해 CSS 파일용과 인라인 CSS용의 두 가지 패스가 필요하다는 것을 의미하기도 했습니다. 이는 모든 CSS가 독립형 CSS 파일로 작성된 경우 제거할 수 있는 성능 오버헤드였습니다.
  • 축소의 문제점. 인라인 CSS를 쉽게 축소할 수 없어 CSS가 축소되지 않았습니다. DevTools의 릴리스 빌드 파일 크기 또한 동일한 웹 구성 요소의 여러 인스턴스에 의해 도입된 중복된 CSS로 인해 커졌습니다.

인턴십 프로젝트의 목표는 기존 인프라와 DevTools에서 사용되는 새 웹 구성요소 모두와 호환되는 CSS 인프라 솔루션을 찾는 것이었습니다.

잠재적 솔루션 조사

문제는 두 부분으로 나눌 수 있습니다.

  • 빌드 시스템이 CSS 파일을 처리하는 방법 파악
  • DevTools에서 CSS 파일을 가져오고 사용하는 방법을 파악합니다.

각 부분에 대해 다양한 잠재적 해결책을 살펴본 결과는 다음과 같습니다.

CSS 파일 가져오기

TypeScript 파일에서 CSS를 가져오고 활용하는 목표는 웹 표준에 최대한 가깝게 유지하고 DevTools 전체에서 일관성을 적용하고 HTML에서 중복 CSS를 방지하는 것이었습니다. 또한 변경사항을 CSS 모듈 스크립트와 같은 새로운 웹 플랫폼 표준으로 이전할 수 있는 솔루션을 선택할 수 있기를 바랐습니다.

이러한 이유로 @import 문과 태그는 DevTools에 적합하지 않은 것처럼 보였습니다. DevTools의 나머지 부분의 가져오기와 동일하지 않아 FOUC (Flash Of Unstyled Content)가 발생합니다. CSS 모듈 스크립트로의 이전은 더 어려울 수 있습니다. 가져오기를 <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>`

@import 또는 <link>를 사용하는 가능한 솔루션

대신 CSS 파일을 CSSStyleSheet 객체로 가져와 adoptedStyleSheets 속성을 사용하여 Shadow DOM(DevTools는 몇 년 동안 Shadow DOM을 사용함)에 추가할 수 있는 방법을 찾기로 했습니다.

번들러 옵션

TypeScript 파일에서 쉽게 조작할 수 있도록 CSS 파일을 CSSStyleSheet 객체로 변환하는 방법이 필요했습니다. 이 변환을 실행할 수 있는 번들러로 Rollupwebpack을 모두 고려했습니다. DevTools는 이미 프로덕션 빌드에서 Rollup을 사용하고 있지만 번들러를 프로덕션 빌드에 추가하면 현재 빌드 시스템으로 작업할 때 잠재적인 성능 문제가 발생할 수 있습니다. Google이 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의 기존 사용법을 이전하는 프로세스는 더 복잡했습니다. 기존 스타일을 등록하는 두 가지 기본 함수는 registerRequiredCSScreateShadowRootWithCoreStyles입니다. 이러한 호출을 이전하는 단계가 상당히 기계적이므로 ESLint 규칙을 사용하여 수정사항을 적용하고 기존 코드를 자동으로 이전할 수 있다고 판단했습니다. DevTools는 이미 DevTools 코드베이스에 맞는 여러 맞춤 규칙을 사용합니다. 이 기능은 ESLint가 이미 코드를 추상 구문 트리(abbr. AST)를 가져올 수 있고 CSS 등록을 호출하는 특정 호출 노드를 쿼리할 수 있습니다.

이전 ESLint 규칙을 작성할 때 가장 큰 문제는 특이 사례를 포착하는 것이었습니다. Google에서는 캡처할 만한 특이한 케이스와 수동으로 이전해야 하는 특이 케이스 간에 적절한 균형을 이루고자 했습니다. 또한 가져온 .css.js 파일이 빌드 시스템에서 자동으로 생성되지 않는 경우 사용자에게 이를 알릴 수 있어야 했습니다. 이렇게 하면 런타임에 파일 없음 오류가 방지됩니다.

이전 시 ESLint 규칙을 사용할 경우의 한 가지 단점은 시스템에서 필수 GN 빌드 파일을 변경할 수 없다는 점이었습니다. 이러한 변경사항은 각 디렉터리에서 사용자가 수동으로 수행해야 했습니다. 여기에는 더 많은 작업이 필요하지만 가져오는 모든 .css.js 파일이 실제로 빌드 시스템에서 생성되는지 확인하는 좋은 방법입니다.

전반적으로 이 이전에 ESLint 규칙을 사용하면 기존 코드를 새 인프라로 빠르게 이전할 수 있었고 AST를 쉽게 사용할 수 있으므로 규칙에서 여러 특이 사례를 처리하고 ESLint의 fixer API를 사용하여 안정적으로 자동 수정할 수 있었습니다.

다음 단계

지금까지 Chromium DevTools의 모든 웹 구성요소는 인라인 스타일을 사용하는 대신 새 CSS 인프라를 사용하도록 이전되었습니다. registerRequiredCSS의 기존 사용법 대부분도 새 시스템을 사용하도록 이전되었습니다. 이제 module.json 파일을 최대한 많이 삭제한 다음 이 현재 인프라를 이전하여 향후 CSS 모듈 스크립트를 구현하면 됩니다.

미리보기 채널 다운로드

Chrome Canary, Dev 또는 베타를 기본 개발 브라우저로 사용하는 것이 좋습니다. 이러한 미리보기 채널을 사용하면 최신 DevTools 기능에 액세스하고, 최신 웹 플랫폼 API를 테스트하고, 사용자가 발견하기 전에 사이트에서 문제를 찾을 수 있습니다.

Chrome DevTools팀에 문의하기

다음 옵션을 사용하여 새로운 기능, 업데이트 또는 DevTools와 관련된 다른 내용을 논의하세요.