정규 표현식 이상의 기능: Chrome DevTools에서 CSS 값 파싱 향상

Philip Pfaffe
Ergün Erdogmus
Ergün Erdogmus

최근 Chrome DevTools의 스타일 탭에 있는 CSS 속성이 좀 더 세련된 느낌을 주는 것 같나요? Chrome 121과 128 사이에서 출시된 이러한 업데이트는 CSS 값을 파싱하고 표시하는 방식을 대폭 개선한 결과입니다. 이 도움말에서는 정규식 일치 시스템에서 더 강력한 파서로 전환하는 이 변환의 기술적 세부정보를 설명합니다.

현재 DevTools를 이전 버전과 비교해 보겠습니다.

위: 최신 Chrome, 아래: Chrome 121

상당한 차이가 있죠? 다음은 주요 개선사항입니다.

  • color-mix. color-mix 함수 내의 두 색상 인수를 시각적으로 나타내는 편리한 미리보기입니다.
  • pink. 이름이 지정된 색상 pink의 클릭 가능한 색상 미리보기입니다. 클릭하면 색상 선택 도구가 열려 쉽게 조정할 수 있습니다.
  • var(--undefined, [fallback value]). 정의되지 않은 변수를 비활성화하고 활성 대체 값 (이 경우 HSL 색상)을 클릭 가능한 색상 미리보기와 함께 표시하여 정의되지 않은 변수의 처리를 개선했습니다.
  • hsl(…): hsl 색상 함수의 또 다른 클릭 가능한 색상 미리보기로, 색상 선택 도구에 빠르게 액세스할 수 있습니다.
  • 177deg: 클릭 가능한 각도 시계로, 각도 값을 상호작용 방식으로 드래그하고 수정할 수 있습니다.
  • var(--saturation, …): 맞춤 속성 정의로 연결되는 클릭 가능한 링크로, 관련 선언으로 쉽게 이동할 수 있습니다.

차이가 상당합니다. 이를 위해 DevTools가 이전보다 CSS 속성 값을 훨씬 더 잘 이해하도록 가르쳐야 했습니다.

이러한 미리보기는 이미 제공되지 않았나요?

이러한 미리보기 아이콘은 익숙해 보이지만, 특히 위의 예와 같이 복잡한 CSS 문법에서는 항상 일관되게 표시되지는 않았습니다. 작동하는 경우에도 제대로 작동하도록 하려면 상당한 노력이 필요했습니다.

이는 DevTools 초창기부터 값을 분석하는 시스템이 유기적으로 성장해 왔기 때문입니다. 하지만 최근 CSS에서 제공하는 놀라운 새 기능과 이에 따른 언어 복잡성 증가에 따라가지 못했습니다. 시스템을 진화의 속도에 맞게 완전히 재설계해야 했고, 바로 그렇게 했습니다.

CSS 속성 값이 처리되는 방식

DevTools에서 스타일 탭에서 속성 선언을 렌더링하고 장식하는 프로세스는 두 가지 고유한 단계로 나뉩니다.

  1. 구조 분석 이 초기 단계에서는 속성 선언을 분석하여 기본 구성요소와 그 관계를 식별합니다. 예를 들어 선언 border: 1px solid red에서 1px는 길이로, solid는 문자열로, red는 색상으로 인식됩니다.
  2. 렌더링 렌더링 단계는 구조 분석을 기반으로 이러한 구성요소를 HTML 표현으로 변환합니다. 이렇게 하면 양방향 요소와 시각적 신호로 표시되는 숙박 시설 텍스트가 보강됩니다. 예를 들어 색상 값 red는 클릭 가능한 색상 아이콘으로 렌더링되며, 이 아이콘을 클릭하면 색상 선택 도구가 표시되어 쉽게 수정할 수 있습니다.

정규 표현식

이전에는 정규 표현식 (정규식)을 사용하여 구조 분석을 위한 속성 값을 분석했습니다. 장식하는 것으로 간주되는 속성 값의 비트와 일치하는 정규식 목록을 유지했습니다. 예를 들어 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);

color-mix 함수의 일치 결과입니다.

더 간단한 예시는 잘 작동합니다. 하지만 더 복잡한 예시에서는 <firstColor> 일치가 hsl(177deg var(--saturation이고 <secondColor> 일치가 100%) 50%))이므로 완전히 의미가 없습니다.

YouTube는 이 문제가 있다는 것을 알고 있었습니다. 결국 공식 언어인 CSS는 일반이 아니므로 var 함수와 같이 더 복잡한 함수 인수를 처리하기 위한 특수 처리가 이미 포함되어 있습니다. 하지만 첫 번째 스크린샷에서 볼 수 있듯이, 이 방법이 모든 경우에 작동하지는 않았습니다.

tan() 일치

가장 재미있는 신고된 버그 중 하나는 삼각함수 tan() 함수에 관한 버그였습니다 . 색상 일치에 사용한 정규식에는 red 키워드와 같은 이름이 지정된 색상을 일치시키기 위한 하위 표현식 \b[a-zA-Z]+\b(?!-)가 포함되어 있었습니다. 그런 다음 일치하는 부분이 실제로 이름이 지정된 색상인지 확인했습니다. tan도 이름이 지정된 색상입니다. 따라서 tan() 표현식을 색상으로 잘못 해석했습니다.

var() 일치

다른 var() 참조(var(--non-existent, var(--margin-vertical)))가 포함된 대체가 있는 var() 함수의 또 다른 예를 살펴보겠습니다.

var()의 정규식은 이 값과 일치합니다. 단, 첫 번째 닫는 괄호에서 일치가 중지됩니다. 따라서 위 텍스트는 var(--non-existent, var(--margin-vertical)로 일치합니다. 이는 정규 표현식 일치의 교과서적인 제한사항입니다. 괄호 일치를 요구하는 언어는 기본적으로 정규 언어가 아닙니다.

CSS 파서로 전환

분석된 언어가 정규 언어가 아니기 때문에 정규 표현식을 사용한 텍스트 분석이 작동하지 않는 경우 다음과 같은 표준적인 다음 단계가 있습니다. 더 높은 유형의 문법에 파서를 사용하는 것입니다. CSS의 경우 컨텍스트 자유 언어의 파서를 사용합니다. 사실 이러한 파서 시스템은 이미 DevTools 코드베이스에 있었습니다. CodeMirror의 LezerSources 패널에 있는 편집기인 CodeMirror의 구문 강조 표시의 기반이 되는 예입니다. Lezer의 CSS 파서를 사용하면 CSS 규칙의 (추상적이지 않은) 문법 트리를 생성할 수 있으며 바로 사용할 수 있습니다. 승리.

속성 값 `hsl(177deg var(--saturation, 100%) 50%)`의 문법 트리입니다. Lezer 파서에서 생성한 결과의 단순화된 버전으로, 쉼표와 괄호의 순수한 문법 노드는 생략됩니다.

단, 정규식 기반 일치에서 파서 기반 일치로 직접 이전하는 것은 불가능합니다. 두 접근 방식은 서로 반대 방향으로 작동하기 때문입니다. 값을 정규 표현식과 일치시키는 경우 DevTools는 입력을 왼쪽에서 오른쪽으로 스캔하여 순서가 지정된 패턴 목록에서 가장 일찍 일치하는 항목을 반복적으로 찾으려고 시도합니다. 구문 트리를 사용하면 일치가 아래에서 위로 시작됩니다. 예를 들어 함수 호출을 일치시키기 전에 먼저 호출의 인수를 분석합니다. 산술식을 평가하는 것과 같이 생각해 보세요. 먼저 괄호로 묶인 표현식을 고려한 다음 곱셈 연산자를 고려한 다음 더하기 연산자를 고려합니다. 이 프레임워크에서 정규식 기반 일치는 산술식을 왼쪽에서 오른쪽으로 평가하는 것에 해당합니다. 전체 일치 시스템을 처음부터 다시 작성하고 싶지는 않았습니다. 수천 줄의 코드가 포함된 15개의 서로 다른 매처와 렌더러 쌍이 있었기 때문에 단일 마일스톤으로 출시할 가능성은 낮았습니다.

이에 따라 점진적으로 변경할 수 있는 솔루션을 마련했습니다. 자세한 내용은 아래를 참고하세요. 간단히 말해 두 단계 접근 방식을 유지했지만 첫 번째 단계에서는 하위 표현식을 하향식 방식으로 일치시키려고 시도하여 정규식 흐름을 중단하고 두 번째 단계에서는 상향식 방식으로 렌더링합니다. 두 단계 모두 기존 정규식 기반 매처와 렌더를 거의 변경하지 않고 사용할 수 있었으므로 하나씩 이전할 수 있었습니다.

1단계: 하향식 일치

첫 번째 단계는 표지에 표시된 내용을 거의 정확하고 배타적으로 실행합니다. 트리를 아래에서 위로 순서대로 탐색하고 방문하는 각 문법 트리 노드에서 하위 표현식을 일치시키려고 시도합니다. 특정 하위 표현식을 일치시키기 위해 일치 항목은 기존 시스템에서와 마찬가지로 정규식을 사용할 수 있습니다. 버전 128부터는 길이 일치와 같은 일부 경우에 실제로 여전히 사용합니다. 또는 일치 항목 검색자가 현재 노드에 루팅된 하위 트리의 구조를 분석할 수 있습니다. 이를 통해 구문 오류를 포착하고 구조 정보를 동시에 기록할 수 있습니다.

위의 구문 트리 예시를 살펴보세요.

1단계: 문법 트리에서 아래에서 위로 일치시키기

이 트리의 경우 매처가 다음 순서대로 적용됩니다.

  1. hsl(177degvar(--saturation, 100%) 50%): 먼저 hsl 함수 호출의 첫 번째 인수인 색조 각도를 찾습니다. 각도 값을 각도 아이콘으로 장식할 수 있도록 각도 매처와 일치시킵니다.
  2. hsl(177degvar(--saturation, 100%)50%): 두 번째로, var 매처를 사용하여 var 함수 호출을 찾습니다. 이러한 호출의 경우 주로 다음 두 가지 작업을 수행합니다.
    • 변수의 선언을 조회하고 값을 계산한 다음 변수 이름에 링크와 팝오버를 각각 추가하여 연결합니다.
    • 계산된 값이 색상인 경우 호출을 색상 아이콘으로 장식합니다. 사실 세 번째 사항이 있지만 나중에 말씀드리겠습니다.
  3. hsl(177deg var(--saturation, 100%) 50%): 마지막으로 hsl 함수의 호출 표현식을 일치시켜 색상 아이콘으로 장식합니다.

장식할 하위 표현식을 검색하는 것 외에도 일치 프로세스의 일부로 실행되는 두 번째 기능이 있습니다. 2단계에서 변수 이름의 계산된 값을 조회한다고 했습니다. 사실 한 단계 더 나아가 결과를 트리 위로 전파합니다. 변수뿐만 아니라 대체 값에도 적용됩니다. var 함수 노드를 방문할 때는 그 하위 요소가 이미 방문되었으므로 대체 값에 표시될 수 있는 모든 var 함수의 결과를 이미 알고 있습니다. 따라서 var 함수를 그 결과로 쉽게 그리고 저렴하게 대체할 수 있으므로 2단계에서와 같이 '이 var 호출의 결과가 색상인가요?'와 같은 질문에 간단하게 답변할 수 있습니다.

2단계: 위에서 아래로 렌더링

두 번째 단계에서는 방향을 반대로 합니다. 1단계의 일치 결과를 사용하여 트리를 위에서 아래로 순서대로 탐색하여 HTML로 렌더링합니다. 방문한 각 노드에 대해 일치하는지 확인하고 일치하는 경우 매처의 해당 렌더러를 호출합니다. 텍스트 노드의 기본 일치자와 렌더러를 포함하여 텍스트만 포함된 노드 (예: NumberLiteral '50%')에 대한 특수 처리가 필요하지 않습니다. 렌더러는 HTML 노드를 출력하기만 하면 됩니다. 이러한 노드를 조합하면 장식을 포함한 속성 값의 표현이 생성됩니다.

2단계: 문법 트리의 상위에서 하위로 렌더링

예시 트리의 경우 속성 값이 렌더링되는 순서는 다음과 같습니다.

  1. hsl 함수 호출을 방문합니다. 일치하므로 색상 함수 렌더러를 호출합니다. 다음 두 가지 작업을 실행합니다.
    • 모든 var 인수에 대한 실시간 대체 메커니즘을 사용하여 실제 색상 값을 계산한 다음 색상 아이콘을 그립니다.
    • CallExpression의 하위 요소를 재귀적으로 렌더링합니다. 이렇게 하면 텍스트인 함수 이름, 괄호, 쉼표가 자동으로 렌더링됩니다.
  2. hsl 호출의 첫 번째 인수를 방문합니다. 일치하므로 각도 아이콘과 각도 텍스트를 그리는 각도 렌더러를 호출합니다.
  3. 두 번째 인수인 var 호출을 방문합니다. 일치하므로 var renderer를 호출하면 다음이 출력됩니다.
    • 시작 부분의 텍스트 var(
    • 변수 이름을 변수 정의 링크로 장식하거나 정의되지 않았음을 나타내는 회색 텍스트 색상으로 장식합니다. 또한 변수에 팝오버를 추가하여 값에 관한 정보를 표시합니다.
    • 쉼표 뒤에 대체 값이 재귀적으로 렌더링됩니다.
    • 닫는 괄호.
  4. hsl 호출의 마지막 인수를 방문합니다. 일치하지 않으므로 텍스트 콘텐츠만 출력합니다.

이 알고리즘에서 렌더링은 일치하는 노드의 하위 요소가 렌더링되는 방식을 완전히 제어한다는 점에 유의하세요. 하위 요소를 재귀적으로 렌더링하는 것은 사전 예방적입니다. 이 트릭을 사용하면 정규식 기반 렌더링에서 문법 트리 기반 렌더링으로 단계적으로 이전할 수 있습니다. 기존 정규식 매처와 일치하는 노드의 경우 해당 렌더러를 원래 형식으로 사용할 수 있습니다. 문법 트리 용어로, 전체 하위 트리를 렌더링하는 책임을 지며 그 결과 (HTML 노드)는 주변 렌더링 프로세스에 깔끔하게 연결될 수 있습니다. 이렇게 하면 매처와 렌더러를 쌍으로 포팅하고 하나씩 교체할 수 있습니다.

일치하는 노드의 자식 렌더링을 제어하는 렌더러의 또 다른 멋진 기능은 추가하는 아이콘 간의 종속 항목을 추론할 수 있다는 점입니다. 위의 예에서 hsl 함수에서 생성된 색상은 색조 값에 따라 달라집니다. 즉, 색상 아이콘에 표시되는 색상은 각도 아이콘에 표시되는 각도에 따라 달라집니다. 사용자가 이 아이콘을 통해 각도 편집기를 열고 각도를 수정하면 이제 색상 아이콘의 색상을 실시간으로 업데이트할 수 있습니다.

위 예에서 볼 수 있듯이 color-mix() 및 두 색상 채널, 대체에서 색상을 반환하는 var 함수와 같은 다른 아이콘 페어링에도 이 메커니즘이 사용됩니다.

성능 영향

안정성을 개선하고 오래된 문제를 해결하기 위해 이 문제를 조사할 때는 완전한 파서를 실행하기 시작했음을 고려하여 성능이 다소 저하될 것으로 예상했습니다. 이를 테스트하기 위해 약 3, 500개의 속성 선언을 렌더링하고 M1 머신에서 6배 제한으로 정규식 기반 버전과 파서 기반 버전을 모두 프로파일링하는 벤치마크를 만들었습니다.

예상대로 이 사례에서 파싱 기반 접근 방식은 정규식 기반 접근 방식보다 27% 느렸습니다. 정규식 기반 접근 방식은 렌더링하는 데 11초가 걸렸고 파서 기반 접근 방식은 15초가 걸렸습니다.

새로운 접근 방식에서 얻을 수 있는 이점을 고려하여 이 접근 방식을 계속 진행하기로 결정했습니다.

감사의 말씀

이 게시물을 수정하는 데 큰 도움을 주신 Sofia Emelianova님과 Jecelyn Yeen님께 감사드립니다.

미리보기 채널 다운로드

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

Chrome DevTools팀에 문의하기

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