DevTools에서 CSS-in-JS 지원

Alex Rudenko
Alex Rudenko

이 도움말에서는 Chrome 85부터 제공된 DevTools의 CSS-in-JS 지원과 일반적으로 CSS-in-JS의 의미, 그리고 오랫동안 DevTools에서 지원해 온 일반 CSS와의 차이점에 대해 설명합니다.

CSS-in-JS란 무엇인가요?

CSS-in-JS의 정의는 다소 모호합니다. 넓은 의미에서 JavaScript를 사용하여 CSS 코드를 관리하는 접근 방식입니다. 예를 들어 CSS 콘텐츠가 JavaScript를 사용하여 정의되고 최종 CSS 출력이 앱에서 즉시 생성된다는 의미일 수 있습니다.

DevTools의 맥락에서 CSS-in-JS는 CSS 콘텐츠가 CSSOM API를 사용하여 페이지에 삽입된다는 것을 의미합니다. 일반 CSS는 <style> 또는 <link> 요소를 사용하여 삽입되며 정적 소스 (예: DOM 노드 또는 네트워크 리소스)가 있습니다. 반면 CSS-in-JS에는 정적 소스가 없는 경우가 많습니다. 여기서 특별한 경우는 <style> 요소의 콘텐츠가 CSSOM API를 사용하여 업데이트될 수 있으므로 소스가 실제 CSS 스타일시트와 동기화되지 않게 되는 경우입니다.

CSS-in-JS 라이브러리 (예: styled-component, Emotion, JSS)를 사용하는 경우 개발 모드와 브라우저에 따라 라이브러리가 내부적으로 CSSOM API를 사용하여 스타일을 삽입할 수 있습니다.

CSS-in-JS 라이브러리에서 하는 것과 비슷하게 CSSOM API를 사용하여 스타일시트를 삽입하는 방법을 보여주는 몇 가지 예를 살펴보겠습니다.

// Insert new rule to an existing CSS stylesheet
const element = document.querySelector('style');
const stylesheet = element.sheet;
stylesheet.replaceSync('.some { color: blue; }');
stylesheet.insertRule('.some { color: green; }');

완전히 새로운 스타일시트를 만들 수도 있습니다.

// Create a completely new stylesheet
const stylesheet = new CSSStyleSheet();
stylesheet.replaceSync('.some { color: blue; }');
stylesheet.insertRule('.some { color: green; }');

// Apply constructed stylesheet to the document
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];

DevTools의 CSS 지원

DevTools에서 CSS를 처리할 때 가장 일반적으로 사용되는 기능은 스타일 창입니다. 스타일 창에서 특정 요소에 적용되는 규칙을 확인하고 규칙을 수정하여 페이지에서 변경사항을 실시간으로 확인할 수 있습니다.

작년 이전에는 CSSOM API를 사용하여 수정된 CSS 규칙에 대한 지원이 제한적이었으며 적용된 규칙만 볼 수 있고 수정할 수는 없었습니다. 작년의 주요 목표는 스타일 창을 사용하여 CSS-in-JS 규칙을 수정할 수 있도록 하는 것이었습니다. CSS-in-JS 스타일을 웹 API를 사용하여 생성되었음을 나타내기 위해 '구성된' 스타일이라고도 합니다.

DevTools에서 스타일 수정 작업의 세부정보를 자세히 살펴보겠습니다.

DevTools의 스타일 수정 메커니즘

DevTools의 스타일 수정 메커니즘

DevTools에서 요소를 선택하면 스타일 창이 표시됩니다. 스타일 창은 CSS.getMatchedStylesForNode라는 CDP 명령어를 실행하여 요소에 적용되는 CSS 규칙을 가져옵니다. CDP는 Chrome DevTools 프로토콜의 약자이며 DevTools 프런트엔드가 검사된 페이지에 관한 추가 정보를 가져올 수 있는 API입니다.

호출되면 CSS.getMatchedStylesForNode는 문서의 모든 스타일시트를 식별하고 브라우저의 CSS 파서를 사용하여 파싱합니다. 그런 다음 모든 CSS 규칙을 스타일시트 소스의 위치와 연결하는 색인을 빌드합니다.

CSS를 다시 파싱해야 하는 이유는 무엇일까요? 여기서 문제는 성능상의 이유로 브라우저 자체가 CSS 규칙의 소스 위치에 관심이 없으므로 이를 저장하지 않는다는 점입니다. 하지만 DevTools에서 CSS 수정을 지원하려면 소스 위치가 필요합니다. 일반 Chrome 사용자에게 성능 저하가 발생해서는 안 되지만 DevTools 사용자는 소스 위치에 액세스할 수 있어야 합니다. 이 재파싱 접근 방식은 두 가지 사용 사례를 모두 최소한의 단점으로 해결합니다.

그런 다음 CSS.getMatchedStylesForNode 구현은 브라우저의 스타일 엔진에 지정된 요소와 일치하는 CSS 규칙을 제공하도록 요청합니다. 마지막으로 이 메서드는 스타일 엔진에서 반환한 규칙을 소스 코드와 연결하고 CSS 규칙에 관한 구조화된 응답을 제공하여 DevTools가 규칙의 어느 부분이 선택기인지 또는 속성인지 알 수 있도록 합니다. 이를 통해 DevTools에서 선택기와 속성을 독립적으로 수정할 수 있습니다.

이제 수정을 살펴보겠습니다. CSS.getMatchedStylesForNode는 모든 규칙의 소스 위치를 반환한다는 것을 기억하세요. 이는 수정 작업에 매우 중요합니다. 규칙을 변경하면 DevTools에서 실제로 페이지를 업데이트하는 다른 CDP 명령어를 실행합니다. 이 명령어에는 업데이트 중인 규칙의 프래그먼트의 원래 위치와 프래그먼트를 업데이트해야 하는 새 텍스트가 포함됩니다.

백엔드에서 수정 호출을 처리할 때 DevTools는 대상 스타일시트를 업데이트합니다. 또한 유지 관리하는 스타일시트 소스의 사본을 업데이트하고 업데이트된 규칙의 소스 위치를 업데이트합니다. 수정 호출에 대한 응답으로 DevTools 프런트엔드는 방금 업데이트된 텍스트 프래그먼트의 업데이트된 위치를 다시 가져옵니다.

이로 인해 DevTools에서 CSS-in-JS를 수정하는 것이 즉시 작동하지 않았습니다. CSS-in-JS에는 저장된 실제 소스가 없으며 CSS 규칙은 CSSOM 데이터 구조의 브라우저 메모리에 있습니다.

CSS-in-JS 지원을 추가한 방법

따라서 CSS-in-JS 규칙의 수정을 지원하기 위해 위에 설명된 기존 메커니즘을 사용하여 수정할 수 있는 생성된 스타일시트의 소스를 만드는 것이 가장 좋은 해결책이라고 결정했습니다.

첫 번째 단계는 소스 텍스트를 빌드하는 것입니다. 브라우저의 스타일 엔진은 CSS 규칙을 CSSStyleSheet 클래스에 저장합니다. 이 클래스는 앞에서 설명한 대로 JavaScript에서 인스턴스를 만들 수 있는 클래스입니다. 소스 텍스트를 빌드하는 코드는 다음과 같습니다.

String InspectorStyleSheet::CollectStyleSheetRules() {
  StringBuilder builder;
  for (unsigned i = 0; i < page_style_sheet_->length(); i++) {
    builder.Append(page_style_sheet_->item(i)->cssText());
    builder.Append('\n');
  }
  return builder.ToString();
}

CSSStyleSheet 인스턴스에서 발견된 규칙을 반복하고 이를 사용하여 단일 문자열을 빌드합니다. 이 메서드는 InspectorStyleSheet 클래스의 인스턴스가 생성될 때 호출됩니다. InspectorStyleSheet 클래스는 CSSStyleSheet 인스턴스를 래핑하고 DevTools에 필요한 추가 메타데이터를 추출합니다.

void InspectorStyleSheet::UpdateText() {
  String text;
  bool success = InspectorStyleSheetText(&text);
  if (!success)
    success = InlineStyleSheetText(&text);
  if (!success)
    success = ResourceStyleSheetText(&text);
  if (!success)
    success = CSSOMStyleSheetText(&text);
  if (success)
    InnerSetText(text, false);
}

이 스니펫에는 내부적으로 CollectStyleSheetRules를 호출하는 CSSOMStyleSheetText가 있습니다. CSSOMStyleSheetText는 스타일시트가 인라인이 아니거나 리소스 스타일시트가 아닌 경우 호출됩니다. 기본적으로 이 두 스니펫을 사용하면 new CSSStyleSheet() 생성자를 사용하여 생성된 스타일시트를 기본적으로 수정할 수 있습니다.

CSSOM API를 사용하여 변형된 <style> 태그와 연결된 스타일시트는 예외입니다. 이 경우 스타일시트에는 소스 텍스트와 소스에 없는 추가 규칙이 포함됩니다. 이 사례를 처리하기 위해 이러한 추가 규칙을 소스 텍스트에 병합하는 메서드를 소개합니다. 여기서 순서는 중요합니다. CSS 규칙이 원본 소스 텍스트 중간에 삽입될 수 있기 때문입니다. 예를 들어 원래 <style> 요소에 다음 텍스트가 포함되어 있다고 가정해 보겠습니다.

/* comment */
.rule1 {}
.rule3 {}

그런 다음 페이지에서 JS API를 사용하여 몇 가지 새 규칙을 삽입하여 다음과 같은 규칙 순서를 생성했습니다. .rule0, .rule1, .rule2, .rule3, .rule4 병합 작업 후 결과 소스 텍스트는 다음과 같아야 합니다.

.rule0 {}
/* comment */
.rule1 {}
.rule2 {}
.rule3 {}
.rule4 {}

규칙의 소스 텍스트 위치가 정확해야 하므로 원래 주석과 들여쓰기를 보존하는 것이 편집 프로세스에 중요합니다.

CSS-in-JS 스타일시트의 또 다른 특징은 페이지에서 언제든지 변경할 수 있다는 점입니다. 실제 CSSOM 규칙이 텍스트 버전과 동기화되지 않으면 수정이 작동하지 않습니다. 이를 위해 스타일시트가 변경될 때 브라우저가 DevTools의 백엔드 부분에 알릴 수 있는 프로브를 도입했습니다. 그러면 다음에 CSS.getMatchedStylesForNode를 호출할 때 변형된 스타일시트가 동기화됩니다.

이러한 모든 부분이 있으면 JS의 CSS 편집이 이미 작동하지만 스타일시트가 생성되었는지 나타내는 UI를 개선하고자 했습니다. 프런트엔드에서 CSS 규칙의 소스를 올바르게 표시하는 데 사용하는 isConstructed라는 새 속성이 CDP의 CSS.CSSStyleSheetHeader에 추가되었습니다.

구성 가능한 스타일시트

결론

여기에서 살펴본 내용을 요약하면 DevTools에서 지원하지 않는 CSS-in-JS와 관련된 사용 사례를 살펴보고 이러한 사용 사례를 지원하는 솔루션을 살펴봤습니다. 이 구현의 흥미로운 점은 CSSOM CSS 규칙에 일반 소스 텍스트를 포함하여 기존 기능을 활용할 수 있었기 때문에 DevTools에서 스타일 편집을 완전히 재구성할 필요가 없다는 것입니다.

자세한 배경 정보는 설계 제안서 또는 모든 관련 패치를 참조하는 Chromium 추적 버그를 확인하세요.

미리보기 채널 다운로드

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

Chrome DevTools팀에 문의하기

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