DevTools에서 CSS 인프라 현대화

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

이 게시물은 DevTools의 아키텍처에 적용되는 변경사항과 빌드 방법을 설명하는 블로그 게시물 시리즈 중 일부입니다. 지금까지 DevTools에서 CSS가 어떻게 작동했는지, 그리고 자바스크립트 파일에서 CSS를 로드하기 위한 웹 표준 솔루션으로 (최종적으로) 마이그레이션하기 위해 DevTools에서 CSS를 현대화한 방법을 설명해 드리겠습니다.

DevTools의 이전 CSS 상태

DevTools는 두 가지 방식으로 CSS를 구현했습니다. 하나는 DevTools의 기존 부분에서 사용되는 CSS 파일을 위한 것이고 다른 하나는 DevTools에서 사용 중인 최신 웹 구성요소를 위한 것입니다.

DevTools의 CSS 구현은 수년 전에 정의되었으며 지금은 구식입니다. DevTools는 module.json 패턴을 사용해 왔으며, 이러한 파일을 삭제하기 위해 많은 노력을 기울였습니다. 이러한 파일의 삭제를 방해하는 마지막 차단 도구는 CSS 파일에서 로드하는 데 사용되는 resources 섹션입니다.

최종적으로 CSS 모듈 스크립트로 변형될 수 있는 다양한 잠재적 솔루션을 탐색하는 데 시간을 할애하기를 원했습니다. 목표는 기존 시스템으로 인해 발생한 기술 부채를 없애는 동시에 CSS 모듈 스크립트로의 이전 프로세스를 더 쉽게 만드는 것이었습니다.

DevTools에 있던 모든 CSS 파일은 삭제 중인 module.json 파일을 사용하여 로드되기 때문에 '기존' CSS 파일로 간주되었습니다. 모든 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);

최신 웹 구성요소 (맞춤 요소 사용)를 도입했을 때 Google은 처음에는 구성요소 파일 자체에서 인라인 <style> 태그를 통해 CSS를 사용하기로 결정했습니다. 여기에는 다음과 같은 과제가 있었습니다.

  • 구문 강조표시를 지원하지 않습니다. 인라인 CSS에 문법 강조표시를 제공하는 플러그인은 .css 파일로 작성된 CSS의 구문 강조 표시 및 자동 완성 기능만큼 좋지 않은 경향이 있습니다.
  • 성능 오버헤드를 빌드합니다. 또한 인라인 CSS는 린트를 위해 두 개의 패스, 즉 CSS 파일용 패스와 인라인 CSS용 패스가 필요하다는 것을 의미합니다. 이는 모든 CSS가 독립형 CSS 파일로 작성되는 경우 제거할 수 있는 성능 오버헤드였습니다.
  • 축소의 문제점. 인라인 CSS는 쉽게 축소될 수 없으므로 어떤 CSS도 축소되지 않았습니다. 동일한 웹 구성 요소의 여러 인스턴스에서 도입한 중복 CSS로 인해 DevTools의 릴리스 빌드 파일 크기도 늘어났습니다.

인턴십 프로젝트의 목표는 레거시 인프라와 DevTools에서 사용 중인 새로운 웹 구성 요소 모두에서 작동하는 CSS 인프라의 솔루션을 찾는 것이었습니다.

잠재적 솔루션 조사

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

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

각 부분의 다양한 해결 방법을 살펴보았으며 아래에 간략하게 설명되어 있습니다.

CSS 파일 가져오기

TypeScript 파일에서 CSS를 가져오고 활용하는 목표는 최대한 웹 표준을 따르고, DevTools 전체에서 일관성을 유지하며, HTML에서 CSS가 중복되지 않도록 하는 것이었습니다. 또한 CSS 모듈 스크립트와 같은 새로운 웹 플랫폼 표준으로 변경사항을 마이그레이션할 수 있는 솔루션을 선택하고 싶었습니다.

이러한 이유로 @import 문과 태그는 DevTools에 적합하지 않은 것 같습니다. 이러한 요소는 DevTools의 나머지 부분에서 가져오기에서 동일하지 않으므로 FOUC (스타일이 지정되지 않은 콘텐츠 플래시)가 발생합니다. 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>를 사용한 가능한 솔루션

대신 Google에서는 adoptedStyleSheets 속성을 사용하여 CSS 파일을 Shadow Dom (DevTools에서 몇 년 동안 Shadow DOM 사용)에 추가할 수 있도록 CSS 파일을 CSSStyleSheet 객체로 가져오는 방법을 선택했습니다.

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의 기존 사용을 마이그레이션하는 프로세스는 더 복잡했습니다. 기존 스타일을 등록한 두 가지 기본 함수는 registerRequiredCSScreateShadowRootWithCoreStyles였습니다. 우리는 이러한 호출을 마이그레이션하는 단계가 상당히 기계적이기 때문에 ESLint 규칙을 사용하여 수정사항을 적용하고 기존 코드를 자동으로 마이그레이션할 수 있다고 결정했습니다. DevTools는 이미 DevTools 코드베이스를 위한 맞춤 규칙을 많이 사용하고 있습니다. 이는 ESLint가 이미 코드를 추상 구문 트리(abbr. AST)를 생성하고 CSS 등록을 호출하는 특정 호출 노드를 쿼리할 수 있습니다.

마이그레이션 ESLint 규칙을 작성할 때 직면한 가장 큰 문제는 특이 사례를 캡처하는 것이었습니다. 우리는 캡처할 가치가 있는 극단적인 케이스와 수동으로 이전해야 하는 케이스 사이에서 적절한 균형을 유지하고자 했습니다. 또한 가져온 .css.js 파일이 빌드 시스템에서 자동으로 생성되지 않을 때 사용자에게 이를 알릴 수 있도록 했습니다. 이를 통해 런타임 시 파일을 찾을 수 없는 오류를 방지할 수 있기 때문입니다.

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

전반적으로 이 마이그레이션에 ESLint 규칙을 사용하는 것이 매우 유용했습니다. 기존 코드를 새 인프라로 빠르게 마이그레이션할 수 있었고 AST를 쉽게 사용할 수 있게 되면 규칙에서 여러 극단적 사례를 처리하고 ESLint의 수정자 API를 사용하여 자동으로 문제를 안정적으로 자동 수정할 수 있었기 때문입니다.

다음 단계

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

미리보기 채널 다운로드

Chrome Canary, 개발자 또는 베타를 기본 개발 브라우저로 사용하는 것이 좋습니다. 이러한 Preview 채널을 통해 개발자는 최신 DevTools 기능에 액세스하고 최첨단 웹 플랫폼 API를 테스트하며 다른 사용자보다 먼저 사이트에서 문제를 찾을 수 있습니다.

Chrome DevTools팀에 문의하기

게시물에서 새로운 기능과 변경사항 또는 DevTools와 관련된 다른 항목에 대해 논의하려면 다음 옵션을 사용하세요.

  • crbug.com을 통해 제안 또는 의견을 제출하세요.
  • DevTools에서 옵션 더보기   더보기   > 도움말 > DevTools 문제 신고를 사용하여 DevTools 문제를 신고합니다.
  • @ChromeDevTools로 트윗을 보냅니다.
  • DevTools의 새로운 기능 YouTube 동영상 또는 DevTools 팁 YouTube 동영상에 댓글을 남겨주세요.