최근 Chrome DevTools의 스타일 탭에서 CSS 속성이 좀 더 세련되게 표시되는 것을 눈치채셨나요? Chrome 121과 128 사이에 출시된 이러한 업데이트는 CSS 값을 파싱하고 표시하는 방식이 크게 개선된 결과입니다. 이 도움말에서는 정규 표현식 일치 시스템에서 더 강력한 파서로 전환하는 기술적 세부정보를 설명합니다.
현재 DevTools를 이전 버전과 비교해 보겠습니다.

상당히 큰 차이죠? 주요 개선사항은 다음과 같습니다.
color-mix.color-mix함수 내의 두 색상 인수를 시각적으로 나타내는 편리한 미리보기입니다.pink. 이름이 지정된 색상pink에 대해 클릭 가능한 색상 미리보기입니다. 클릭하면 색상 선택 도구가 열려 쉽게 조정할 수 있습니다.var(--undefined, [fallback value]). 정의되지 않은 변수의 처리가 개선되어 정의되지 않은 변수가 비활성화되고 활성 대체 값(이 경우 HSL 색상)이 클릭 가능한 색상 미리보기와 함께 표시됩니다.hsl(…):hsl색상 함수에 대한 또 다른 클릭 가능한 색상 미리보기로, 색상 선택 도구에 빠르게 액세스할 수 있습니다.177deg: 클릭 가능한 각도 시계로, 각도 값을 대화형으로 드래그하고 수정할 수 있습니다.var(--saturation, …): 맞춤 속성 정의에 대한 클릭 가능한 링크로, 관련 선언으로 쉽게 이동할 수 있습니다.
차이가 확연히 드러납니다. 이를 위해 DevTools가 CSS 속성 값을 이전보다 훨씬 더 잘 이해하도록 가르쳐야 했습니다.
이러한 프리뷰는 이미 제공되고 있지 않았나요?
이러한 미리보기 아이콘은 익숙해 보일 수 있지만, 특히 위 예시와 같은 복잡한 CSS 문법에서는 항상 일관되게 표시되지 않았습니다. 작동하는 경우에도 올바르게 작동하도록 하려면 상당한 노력이 필요한 경우가 많았습니다.
그 이유는 값을 분석하는 시스템이 DevTools 초기부터 유기적으로 성장해 왔기 때문입니다. 하지만 CSS의 최근 놀라운 새로운 기능과 그에 따른 언어 복잡성 증가를 따라잡지 못했습니다. 진화에 발맞추기 위해 시스템을 완전히 재설계해야 했고, Google은 이를 실현했습니다.
CSS 속성 값이 처리되는 방식
DevTools에서 Styles 탭의 속성 선언을 렌더링하고 장식하는 프로세스는 두 가지 단계로 나뉩니다.
- 구조 분석. 이 초기 단계에서는 속성 선언을 분석하여 기본 구성요소와 구성요소 간의 관계를 식별합니다. 예를 들어
border: 1px solid red선언에서1px는 길이로,solid는 문자열로,red는 색상으로 인식합니다. - 렌더링 중입니다. 구조 분석을 기반으로 렌더링 단계에서 이러한 구성요소를 HTML 표현으로 변환합니다. 이렇게 하면 상호작용 요소와 시각적 단서를 통해 표시되는 속성 텍스트가 풍부해집니다. 예를 들어 색상 값
red은 클릭 가능한 색상 아이콘으로 렌더링되며, 이 아이콘을 클릭하면 색상 선택 도구가 표시되어 쉽게 수정할 수 있습니다.
정규 표현식
이전에는 정규 표현식(정규식)을 사용하여 구조 분석을 위해 속성 값을 분석했습니다. Google은 장식으로 간주되는 속성 값의 비트와 일치하는 정규식 목록을 유지했습니다. 예를 들어 CSS 색상, 길이, 각도, 더 복잡한 하위 표현식(예: var 함수 호출) 등과 일치하는 표현식이 있었습니다. 텍스트를 왼쪽에서 오른쪽으로 스캔하여 값 분석을 수행하고, 목록에서 다음 텍스트 조각과 일치하는 첫 번째 표현식을 계속 찾았습니다.
대부분의 경우 이 방법이 잘 작동했지만, 그렇지 않은 사례가 계속 늘어났습니다. 지난 몇 년 동안 일치가 제대로 이루어지지 않는다는 버그 신고가 많이 접수되었습니다. 간단한 수정도 있고 정교한 수정도 있었지만, 문제를 해결하면서 기술 부채를 줄이기 위해 접근 방식을 재고해야 했습니다. 몇 가지 문제를 살펴보겠습니다.
color-mix() 일치
color-mix() 함수에 사용한 정규식은 다음과 같습니다.
/color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g
이는 다음 구문과 일치합니다.
color-mix(<color-interpolation-method>, [<color> && <percentage [0,100]>?]#{2})
다음 예시를 실행하여 일치 항목을 시각화해 보세요.
const re = /color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g;
// it works - simpler example
const simpler = re.exec('color-mix(in srgb, pink, hsl(127deg 100% 50%))');
console.table(simpler.groups);
re.exec('');
// it doesn't work - complex example
const complex = re.exec('color-mix(in srgb, pink, var(--undefined, hsl(127deg var(--saturation, 100%) 50%)))');
console.table(complex.groups);

간단한 예시는 잘 작동합니다. 하지만 더 복잡한 예시에서는 <firstColor> 일치가 hsl(177deg var(--saturation이고 <secondColor> 일치가 100%) 50%))이므로 완전히 무의미합니다.
이것이 문제라는 것을 알고 있었습니다. 결국 공식 언어로서의 CSS는 정규가 아니므로 var 함수와 같은 더 복잡한 함수 인수를 처리하기 위해 이미 특수 처리를 포함했습니다. 하지만 첫 번째 스크린샷에서 볼 수 있듯이 이 방법도 모든 경우에 작동하지는 않았습니다.
tan() 일치
보고된 버그 중 가장 재미있는 버그 중 하나는 삼각함수 tan() 함수에 관한 것이었습니다 . 색상 일치에 사용한 정규 표현식에는 red 키워드와 같은 명명된 색상과 일치하는 하위 표현식 \b[a-zA-Z]+\b(?!-)가 포함되어 있었습니다. 그런 다음 일치하는 부분이 실제로 이름이 지정된 색상인지 확인했는데, tan도 이름이 지정된 색상입니다. 따라서 tan() 표현식을 색상으로 잘못 해석했습니다.
var() 일치
다른 예시를 살펴보겠습니다. 다른 var() 참조가 포함된 대체가 있는 var() 함수입니다. var(--non-existent, var(--margin-vertical))
var()의 정규식은 이 값과 일치합니다. 단, 첫 번째 닫는 괄호에서 일치가 중지됩니다. 따라서 위의 텍스트는 var(--non-existent, var(--margin-vertical)로 일치합니다. 이는 정규 표현식 일치의 전형적인 한계입니다. 일치하는 괄호가 필요한 언어는 기본적으로 정규 언어가 아닙니다.
CSS 파서로 전환
정규 표현식을 사용한 텍스트 분석이 작동하지 않는 경우(분석된 언어가 정규 언어가 아니기 때문) 다음 단계는 상위 유형 문법에 파서를 사용하는 것입니다. CSS의 경우 문맥 자유 언어의 파서를 의미합니다. 실제로 이러한 파서 시스템은 DevTools 코드베이스에 이미 존재했습니다. CodeMirror의 Lezer는 소스 패널에 있는 편집기인 CodeMirror의 문법 강조 표시 등의 기반이 됩니다. Lezer의 CSS 파서를 사용하면 CSS 규칙에 대한 (비추상) 구문 트리를 생성할 수 있으며 바로 사용할 수 있었습니다. 승리

하지만 정규 표현식 기반 일치에서 파서 기반 일치로 직접 이전하는 것은 불가능하다는 것을 알게 되었습니다. 두 접근 방식은 서로 반대 방향으로 작동하기 때문입니다. 정규 표현식으로 값의 일부를 일치시킬 때 DevTools는 입력을 왼쪽에서 오른쪽으로 스캔하여 순서가 지정된 패턴 목록에서 가장 먼저 일치하는 항목을 반복적으로 찾습니다. 구문 트리를 사용하면 일치가 상향식으로 시작됩니다. 예를 들어 함수 호출을 일치시키기 전에 먼저 호출의 인수를 분석합니다. 산술 표현식을 평가하는 것으로 생각하면 됩니다. 먼저 괄호로 묶인 표현식을 고려한 다음 곱셈 연산자, 덧셈 연산자를 고려합니다. 이 프레임에서 정규식 기반 일치는 산술 표현식을 왼쪽에서 오른쪽으로 평가하는 것에 해당합니다. 전체 일치 시스템을 처음부터 다시 작성하고 싶지 않았습니다. 15개의 서로 다른 일치 및 렌더러 쌍이 있었고 수천 줄의 코드가 있었기 때문에 단일 마일스톤으로 출시할 수 없었습니다.
따라서 점진적인 변경을 허용하는 솔루션을 고안했으며, 이에 대해서는 아래에서 자세히 설명하겠습니다. 요약하자면 2단계 접근 방식을 유지했지만 첫 번째 단계에서는 하위 표현식을 상향식으로 일치시키려고 시도하고(따라서 정규식 흐름을 중단) 두 번째 단계에서는 하향식으로 렌더링합니다. 두 단계 모두에서 기존 정규 표현식 기반 매처와 렌더러를 거의 변경하지 않고 사용할 수 있었기 때문에 하나씩 이전할 수 있었습니다.
1단계: 상향식 매칭
첫 번째 단계는 표지에 적힌 내용을 거의 정확하게, 그리고 독점적으로 수행합니다. 트리를 아래에서 위로 순서대로 순회하고 방문하는 각 문법 트리 노드에서 하위 표현식을 일치시키려고 시도합니다. 특정 하위 표현식을 일치시키기 위해 매처는 기존 시스템에서와 마찬가지로 정규 표현식을 사용할 수 있습니다. 버전 128부터는 길이 일치와 같은 몇 가지 경우에 여전히 이 작업을 실행합니다. 또는 매처가 현재 노드에 루트가 있는 하위 트리의 구조를 분석할 수 있습니다. 이를 통해 문법 오류를 포착하고 구조 정보를 동시에 기록할 수 있습니다.
위의 문법 트리 예시를 살펴보세요.

이 트리의 경우 일치자는 다음 순서로 적용됩니다.
hsl(177degvar(--saturation, 100%) 50%): 먼저hsl함수 호출의 첫 번째 인수인 색조 각도를 찾습니다. 각도 일치 도구와 일치시켜 각도 값에 각도 아이콘을 표시할 수 있습니다.hsl(177degvar(--saturation, 100%)50%): 두 번째로, var 매처를 사용하여var함수 호출을 검색합니다. 이러한 통화에서는 주로 다음 두 가지를 수행해야 합니다.- 변수 선언을 조회하고 값을 계산한 다음 변수 이름에 링크와 팝오버를 추가하여 각각에 연결합니다.
- 계산된 값이 색상인 경우 색상 아이콘으로 호출을 장식합니다. 사실 세 번째가 있지만 나중에 이야기하겠습니다.
hsl(177deg var(--saturation, 100%) 50%): 마지막으로hsl함수의 호출 표현식을 일치시켜 색상 아이콘으로 장식할 수 있습니다.
장식하려는 하위 표현식을 검색하는 것 외에도 일치 프로세스의 일부로 실행하는 두 번째 기능이 있습니다. 2단계에서 변수 이름에 대해 계산된 값을 조회한다고 했습니다. 사실, 한 단계 더 나아가 결과를 트리 위로 전파합니다. 변수뿐만 아니라 대체 값도 마찬가지입니다. var 함수 노드를 방문할 때 하위 요소가 미리 방문되었으므로 대체 값에 표시될 수 있는 var 함수의 결과를 이미 알고 있습니다. 따라서 var 함수를 결과로 즉시 쉽고 저렴하게 대체할 수 있으므로 2단계에서와 같이 '이 var 호출의 결과가 색상인가요?'와 같은 질문에 간단하게 답할 수 있습니다.
2단계: 하향식 렌더링
두 번째 단계에서는 방향을 바꿉니다. 1단계의 일치 결과를 가져와 위에서 아래로 순서대로 순회하여 트리를 HTML로 렌더링합니다. 방문한 각 노드에 대해 일치하는지 확인하고 일치하는 경우 일치자의 해당 렌더러를 호출합니다. 텍스트 노드에 대한 기본 일치자와 렌더러를 포함하여 텍스트만 포함하는 노드(예: NumberLiteral '50%')에 대한 특수 처리의 필요성을 방지합니다. 렌더러는 단순히 HTML 노드를 출력하며, 이 노드를 결합하면 장식을 포함한 속성 값의 표현이 생성됩니다.

예시 트리의 경우 속성 값이 렌더링되는 순서는 다음과 같습니다.
hsl함수 호출을 방문합니다. 일치하므로 색상 함수 렌더러를 호출합니다. 이 함수는 다음 두 가지 작업을 실행합니다.var인수에 대한 즉석 대체 메커니즘을 사용하여 실제 색상 값을 계산한 다음 색상 아이콘을 그립니다.CallExpression의 하위 요소를 반복적으로 렌더링합니다. 이렇게 하면 함수 이름, 괄호, 쉼표(단순한 텍스트)가 자동으로 렌더링됩니다.
hsl호출의 첫 번째 인수를 방문합니다. 일치하므로 각도 아이콘과 각도 텍스트를 그리는 각도 렌더러를 호출합니다.- 두 번째 인수(
var호출)를 방문합니다. 일치하므로 var renderer를 호출하면 다음과 같이 출력됩니다.- 시작 부분의 텍스트
var( - 변수 이름은 변수 정의 링크 또는 정의되지 않았음을 나타내는 회색 텍스트 색상으로 장식됩니다. 또한 변수에 팝오버를 추가하여 값에 관한 정보를 표시합니다.
- 쉼표를 렌더링한 다음 대체 값을 재귀적으로 렌더링합니다.
- 닫는 괄호
- 시작 부분의 텍스트
hsl호출의 마지막 인수를 방문합니다. 일치하지 않으므로 텍스트 콘텐츠만 출력합니다.
이 알고리즘에서 렌더링이 일치하는 노드의 하위 요소가 렌더링되는 방식을 완전히 제어한다는 점을 눈치채셨나요? 하위 요소를 재귀적으로 렌더링하는 것은 사전 대응적입니다. 이 방법을 통해 정규 표현식 기반 렌더링에서 구문 트리 기반 렌더링으로 단계별로 이전할 수 있었습니다. 기존 정규식 일치 도구와 일치하는 노드의 경우 해당 렌더러를 원래 형식으로 사용할 수 있습니다. 문법 트리 측면에서 전체 하위 트리를 렌더링하는 역할을 담당하며, 그 결과(HTML 노드)는 주변 렌더링 프로세스에 깔끔하게 연결될 수 있습니다. 이를 통해 매처와 렌더러를 쌍으로 포팅하고 하나씩 교체할 수 있었습니다.
일치하는 노드의 하위 요소 렌더링을 제어하는 렌더기의 또 다른 멋진 기능은 추가하는 아이콘 간의 종속 항목을 추론할 수 있다는 것입니다. 위의 예에서 hsl 함수로 생성된 색상은 색조 값에 따라 달라집니다. 즉, 색상 아이콘에 표시되는 색상은 각도 아이콘에 표시되는 각도에 따라 달라집니다. 사용자가 이 아이콘을 통해 각도 편집기를 열고 각도를 수정하면 이제 색상 아이콘의 색상을 실시간으로 업데이트할 수 있습니다.
위의 예에서 볼 수 있듯이 이 메커니즘은 color-mix() 및 두 색상 채널 또는 대체에서 색상을 반환하는 var 함수와 같은 다른 아이콘 페어링에도 사용됩니다.
성능 영향
안정성을 개선하고 오래된 문제를 해결하기 위해 이 문제를 자세히 살펴보면서 본격적인 파서를 실행하기 시작했다는 점을 고려할 때 성능 저하가 발생할 것으로 예상했습니다. 이를 테스트하기 위해 약 3, 500개의 속성 선언을 렌더링하는 벤치마크를 만들고 M1 머신에서 6배 제한을 적용하여 정규 표현식 기반 버전과 파서 기반 버전을 모두 프로파일링했습니다.
예상대로 이 경우 파싱 기반 접근 방식이 정규 표현식 기반 접근 방식보다 27% 느린 것으로 나타났습니다. 정규식 기반 접근 방식은 렌더링하는 데 11초가 걸렸고 파서 기반 접근 방식은 렌더링하는 데 15초가 걸렸습니다.
새로운 접근 방식에서 얻을 수 있는 이점을 고려하여 이 방식을 진행하기로 결정했습니다.
감사의 말씀
이 게시물을 편집하는 데 큰 도움을 주신 소피아 에멜리아노바와 예린 지에게 깊은 감사를 드립니다.
미리보기 채널 다운로드
Chrome Canary, Dev 또는 베타를 기본 개발 브라우저로 사용하는 것이 좋습니다. 미리보기 채널을 사용하면 최신 DevTools 기능에 액세스하고, 최신 웹 플랫폼 API를 테스트하고, 사용자가 문제를 발견하기 전에 사이트에서 문제를 찾을 수 있습니다.
Chrome DevTools팀에 문의하기
다음 옵션을 사용하여 DevTools와 관련된 새로운 기능, 업데이트 또는 기타 사항에 관해 논의하세요.
- crbug.com에서 의견 및 기능 요청을 제출합니다.
- DevTools에서 옵션 더보기 > 도움말 > DevTools 문제 신고를 사용하여 DevTools 문제를 알립니다.
- @ChromeDevTools에 트윗합니다.
- 'DevTools의 새로운 기능' YouTube 동영상 또는 'DevTools 팁' YouTube 동영상에 댓글을 남깁니다.