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의 정의는 다소 모호합니다. 넓은 의미에서 보면 이는 자바스크립트를 사용하여 CSS 코드를 관리하는 접근 방식입니다. 예를 들어, CSS 콘텐츠가 JavaScript를 사용하여 정의되고 최종 CSS 출력이 앱에서 즉시 생성된다는 의미일 수 있습니다.

DevTools 컨텍스트에서 CSS-in-JS는 CSSOM API를 사용하여 CSS 콘텐츠가 페이지에 삽입됨을 의미합니다. 일반 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를 처리할 때 가장 일반적으로 사용되는 기능은 Styles 창입니다. 스타일 창에서 특정 요소에 적용되는 규칙을 보고, 규칙을 수정하고, 페이지의 변경사항을 실시간으로 확인할 수 있습니다.

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

이제 DevTools의 스타일 편집 작업에 대해 자세히 살펴보겠습니다.

DevTools의 스타일 편집 메커니즘

DevTools의 스타일 편집 메커니즘

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

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

CSS를 다시 파싱해야 하는 이유가 무엇인지 궁금할 수 있습니다. 여기서 문제는 성능상의 이유로 브라우저 자체는 CSS 규칙의 소스 위치와 관련이 없으므로 규칙을 저장하지 않는다는 점입니다. 하지만 DevTools는 CSS 편집을 지원하기 위해 소스 위치가 필요합니다. 우리는 일반 Chrome 사용자가 성능 저하를 겪지 않기를 바라지만, DevTools 사용자가 소스 위치에 액세스할 수 있기를 바랍니다. 이 재파싱 방식은 두 사용 사례 모두 해결하면서도 단점을 최소화합니다.

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

이제 편집에 대해 알아보겠습니다. CSS.getMatchedStylesForNode는 모든 규칙의 소스 위치를 반환합니다. 편집에 매우 중요합니다. 규칙을 변경하면 DevTools가 실제로 페이지를 업데이트하는 다른 CDP 명령어를 실행합니다. 이 명령어에는 업데이트 중인 규칙 프래그먼트의 원래 위치와 프래그먼트를 업데이트해야 하는 새 텍스트가 포함됩니다.

백엔드에서 edit 호출을 처리할 때 DevTools가 대상 스타일시트를 업데이트합니다. 또한 유지관리하는 스타일시트 소스의 사본을 업데이트하고 업데이트된 규칙의 소스 위치를 업데이트합니다. edit 호출에 대한 응답으로 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 규칙이 텍스트 버전과 동기화되지 않을 경우 수정이 작동하지 않습니다. 이를 위해 Google에서는 소위 프로브를 도입했으며, 이 프로브를 사용하면 스타일시트가 변경될 때 브라우저가 DevTools의 백엔드 부분에 알릴 수 있습니다. 그러면 변형된 스타일시트는 CSS.getMatchStylesForNode에 대한 다음 호출 중에 동기화됩니다.

이러한 모든 요소가 준비되었으므로 CSS-in-JS 편집은 이미 작동하지만 우리는 UI를 개선하여 스타일시트가 구성되었는지 여부를 나타내고자 했습니다. 프런트엔드에서 CSS 규칙의 소스를 올바르게 표시하기 위해 사용하는 CDP의 CSS.CSSStyleSheetHeaderisConstructed라는 새 속성을 추가했습니다.

구성 가능한 스타일시트

결론

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

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

미리보기 채널 다운로드

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

Chrome DevTools 팀에 문의하기

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

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