Doc: [Tailwind] pxr
단위 생성 시 예외처리 및 배열 초기화 성능 10% 향상 & DX 향상
#59
Labels
documentation
Improvements or additions to documentation
개발할 때 편의를 위해
px
단위로 스타일링을 진행하고 웹 접근성을 위해 최종적으로rem
단위로 표현하도록 합니다. 이를 위해 임의로 구성 파일에서pxr
단위를 생성해 사용합니다. 이러한 과정에서 발생한 문제와 해결하는 과정에서 배열을 초기화하는 다양한 방법과 Tailwind를 어떻게 사용하면 더 좋을지 등 깨달은 점을 문서로 남겨봅니다.발생한 문제
Uncaught RangeError: Invalid array length
예외가 발생하거나 빈 객체가 반환되어pxr
단위 생성이 무효화 됩니다.재현 방법
목표
start: 2, end: 400, step: 8
로 전달한다고 가정해보겠습니다. 2부터 8의 배수씩 증가하되 400으로 끝나야 한다는 의도일 것입니다. 하지만 이는 약속한 디자인 규칙을 깹니다. 8의 배수씩 증가해야 한다면 400으로 끝나는 게 아닌, 402로 끝나야 합니다. 이는 의도와 별개로 규칙을 위반한 것입니다. 따라서 해당 행위를 막도록 해야 합니다.솔루션
각 예외 사항에 대해 적절한 메시지를 제공
각 예외 사항들에 대해 적절한 메시지를 제공해 올바른 값을 받을 수 있도록 합니다.
step
에 대한 규칙을 강제step
은pxr
단위를 제공할 때 배수 단위로 간격을 조정하기 위한 값입니다.step
이 8이라면 8의 배수로 클래스가 생성됩니다. 기기별로 화소 밀도가 다른데 5px로 디자인된 그리드, 4px로 디자인된 그리드가 있을 때 1.5배 해상도를 가진 기기에서 보면 픽셀이 쪼개져 끝이 흐려보이는 현상이 나타납니다. 이게 배수를 지정해서 관리하는 이유기도 합니다. 이런 이유로step
값을 이용해 필요한 클래스를 생성합니다.하지만
start: 2, end: 400, step: 8
같은 상황일 때 문제가 발생합니다. 사용하는 측에선 8의 배수로 생성하도록 했지만 400으로 끝나야 한다고 합니다. 하지만 생각해보면 8의 배수면 400이 아닌 394 또는 402로 끝나야 정상입니다. 이는 정한 디자인 규칙을 위반합니다. 따라서 의도가 어떻든 이런 경우는 start, end 값에 따라 step의 값에 대해 유효한 값인지 검사하는 과정이 필요합니다.pxr
단위 생성 속도 향상딱히 중요하지 않지만 속도를 향상시킬 지점이 있습니다.
기존
pxr
을 생성하는 로직 일부로Array.from()
함수에서length
객체를 전달해 원하는 길이의 배열을 생성하는 트릭을 사용하고 있었습니다. 두 번째 인수로mapping
함수를 전달할 수 있으므로 값으로 배열을 초기화할 때도 유용합니다.결론부터 말하자면, 이 부분에서
Array.from({ length: N })
을Array(N).prototype.map()
로 변경함으로써 속도 이점을 얻을 수 있습니다. JS에서 객체와 배열을 동일한 방식으로 사용할 수 있습니다. 배열에서 인덱스로 요소에 접근하는 것처럼, 객체에서 key로 속성 값에 접근하는 게 가능합니다. 이 부분에 약간의 속도 차이가 있는데요. V8 엔진은 배열을 일반 객체와 구별하여 보다 배열처럼 동작하도록 최적화되어 구현했기 때문입니다.아래는 기존 코드입니다:
Array.from
함수는 유사 배열 객체로부터 길이(length)를 알아옵니다. 그리고 0부터 N-1 까지k
변수를 인덱스로 사용한다고 가정하면, 유사 배열 객체로부터 키가k
인 속성 값을 가져옵니다.mapFn
을 인자로 받았다면, 해당 함수로부터 얻은 값을 할당합니다. 그리고 이를 반환하게 됩니다.객체의 키를 이용하여 값을 읽어들이기 때문에 iterator를 사용하는
Array(N)
방법보다 느린 것입니다.Array.prototype.map()
을 사용해보면 빈 값은 건너뜁니다:따라서 배열을 초기화할 때
Array.prototype.fill()
과 함께 사용하면 좋을 것 같습니다. 여기에 동적 값을 생성하기 위해Array.prototype.map()
호출을 체인으로 연결하면 됩니다. 따라서 다음과 같이 배열을 초기화하는 함수를 만들어 두고 사용할 수 있습니다.이는 벤치마크 결과 기존 로직에 비해 평균 약 10% 향상된 성능을 보입니다.
유사 배열 객체를 사용하던 기존 방법이 iterable한 객체를 사용한 방법보다 느릴 뿐이지, 충분히 빠릅니다. 하지만 왜 이런 속도 차이가 발생하는지 궁금하기도 하고 간단하게 성능을 높일 수 있는 지점이므로 작업을 해보았습니다.
속도 차이가 발생하는 이유 참고 글
Tailwind
pxr
단위를 Tailwind의 기본 구성에 확장되는 형태로 변경합니다. -> Tailwind는 단순하게 유지할 때 더욱 빛을 발합니다.기존은
pxr
단위를 Tailwind의 기본 구성을 제거하고 생성했습니다. 하지만 이는 TailwindCSS의 장점을 잃게 했습니다. 예로Button
컴포넌트를 구현해야 하는 상황이 있습니다. 하나하나 스타일 속성을 입력하기엔 귀찮고 다른 기능 작업에 더 집중하고 싶죠. 그럼 Tailwind를 사용하는 프로젝트의className
을 그대로 복붙하면 됩니다. 하지만 커스텀한pxr
단위를 사용하면 복붙해서 일일이 수정 작업을 해야 합니다. 따라서 기본 구성에 확장해pxr
단위를 사용할 수 있도록 했습니다.그 전에 생각해봐야 할 부분은 더 커지는 번들 크기였는데요.
Tailwind는 프로덕션 빌드 시에 사용하는 클래스에 대해서만 스타일시트에 추가합니다. 그럼에도
pxr
단위를 사용하게 되면 다음과 같은 상황에 중복이 발생합니다.mx-1
과mx-4pxr
은 같은 역할을 합니다. 하지만 복붙한mx-1
클래스명과mx-4pxr
직접 사용한 경우 모두 번들 결과 스타일시트에 포함됩니다. 그렇다고pxr
단위를 아예 지우는 건 이미 작업이 어느정도 진행된 상황에선 더 번거로운 작업입니다.이는 간단하게 생각해보면 됩니다. '약간 커지는 번들 크기' 그리고 'UI 개발 편리함' 중에 선택하는 것이죠. 저는 'UI 개발 편리함'을 선택했는데요. 번들 크기가 커진다해도 아주 조금 커지는 것 뿐이고, 번들 크기가 너무 큰 상황이 온다면 차라리 UI 스타일 적용에 아낀 시간으로 다른 부분을 최적화하는 게 더 의미 있을 것이고요.
이러한 과정에서 Tailwind는 간단하게 유지할 때 더욱 빛을 발한다는 것을 깨달았습니다. 그래서
@apply
속성을 사용해 특정 스타일을 추상화하는 것도 되도록 사용하지 않을 것 같습니다.@apply
속성을 사용한다는 것은 CSS파일과 작업 파일의 사이를 이동하며 다니지 않아도 되는 Tailwind의 큰 장점을 잃어버리게 할 뿐 아니라 항상 클래스 이름을 작명해줘야 하고 CSS 번들 크기가 커지게 되죠.보통 아래와 같은 상황일 때
@apply
속성을 사용하고 싶은 생각이 듭니다.@apply
를 사용하면 전체 앱에 걸쳐 button 요소에 대한 일관된 스타일을 지정할 수 있게 되고 빠른 수정이 가능하지 않을까요?@apply
를 사용할 수 밖에 없지 않을까요?@apply
없이 복잡한 셀렉터들은 어떻게 다뤄야 할까요? 예를 들어 하위 요소의 상태 기반으로 상위 요소의 스타일을 수정하는 상황 말이죠.위 상황들은 모두
@apply
속성을 사용하지 않고 아래 방법처럼 처리할 수 있는 더 좋은 방법이 있다고 생각합니다:tailwind-merge
와 같은 패키지를 사용하면 되겠죠. 이런 요소들은 애초에 확실히 컴포넌트로 분리되어야 하는 게 옳다고 봅니다.global.css
파일에 넣을 수도 있습니다. 또는 이를 제어하기 위해 클래스를 추가하는tailwind-scrollbar
플러그인을 사용할 수도 있겠죠. 사실 이 부분은 웬만하면 브라우저가 자동으로 처리하는 게 더 합리적인 경우가 많습니다.group
클래스를 지원합니다. 따라서 더 이상 문제가 되지 않습니다.따라서 앞으로 Tailwind를 사용할 때 특별한 일이 없다면 구성을 단순하게 유지하려고 합니다.
TailwindCSS �개발 경험 향상해보기
보통 TailwindCSS를 사용하면 린팅, 자동 완성 기능 등을 제공하는 TailwindCSS 인텔리센스를 사용할텐데요. 이 기능은 개발자가 CSS 클래스 이름을 빠르게 입력하고, 사용 가능한 클래스 이름을 쉽게 찾을 수 있도록 도와줍니다. 하지만, 기본적으로 이 자동 완성 기능은 ** className** 속성에 대해서만 작동합니다.
className
속성 외에도 다른 속성에 대해 TailwindCSS 클래스 자동 완성 기능을 활용하는 방법과 잘 활용하는 방법과 특정 속성에 강력하게 결합되는 정규식을 사용하지 않고, 새로운 속성이 추가될 때마다tailwindCSS.experimental.classRegex
설정을 수정하는 번거로움을 해결하는 방법에 대해 설명하려 합니다.자동 완성 기능이 지원 되지 않는 몇 가지 예를 들어보겠습니다.
공용으로 사용할
Button
컴포넌트에서 다른 UI를 정의하고 싶을 땐 아래와 같은 방법을 많이 사용할텐데요.default
,destructive
속성에선 클래스 이름을 자동 완성하지 않습니다.또는
@headlessui/react
패키지에서 제공하는Transition
컴포넌트에서leave
,leaveFrom
속성들에 대해서도 자동완성이 되지 않습니다:이 경우 그 많은 클래스 이름을 모두 외우고 있지 않기 때문에 공식 문서를 오가며 클래스 이름을 하드 코딩할 수 밖에 없습니다.
tailwindCSS.experimental.classRegex
설정을 사용하면, 정규식을 설정하여className
속성 외에도 다른 속성에 대해 자동 완성 기능을 활용할 수 있습니다.위 예시에 대해서, 다음과 같이 설정할 수 있습니다.:
위에선 간단한 예시라 속성이 별로 없습니다. 여기에
Button
의 크기도 조절할 수 있어야 한다는 상황을 조금 더 추가해보겠습니다.variants
객체에size
속성과 함께sm
,lg
속성이 추가되겠죠. 그럼 자연스럽게tailwindCSS.experimental.classRegex
속성도 수정해줘야 합니다. 이는 조금 번거롭습니다. 🥲그럼 아래처럼
"
,'
, ` 사이에 들어오는 문자열에 대해 자동 완성을 사용하도록 하면 되지 않을까? 라는 생각이 들 수도 있습니다.이제 속성이 추가될 때마다 추가 설정을 할 필요가 없습니다. 하지만 스타일 지정과 전혀 상관 없는
href
,onClick
속성에 대해서도 자동 완성이 지원되겠죠. 이 방법 역시 좋지 못한 방법이라는 것을 금방 깨달을 수 있습니다.그럼 어떻게 새로운 속성이 추가될 때마다
tailwindCSS.experimental.classRegex
설정을 수정할 필요 없이 개발 경험을 향상할 수 있을까요?먼저, 특정 속성에 강력하게 결합되는 정규식을 사용하지 않아야 합니다. 그럼 어떻게 원하는 곳에서 자동 완성 기능을 사용할 수 있을까요?
변수를 할당하는 것처럼 자동 완성을 사용하고 싶은 곳 앞에 특정 키워드(
twa
)를 선언하고 키워드 이후부터 세미콜론(;
)이 오기 전까지의 모든 문자열에 대해 자동 완성을 지원하면 되지 않을까요?백문이 불여일견, 코드로 살펴봅시다:
"/\\*twa\\*/ ([^;]*);"
:/*twa*/
주석 다음에 오는 문자열을 찾습니다.[^;]*
부분이 세미콜론이 나오기 전까지의 모든 문자를 의미합니다. 따라서 이 정규식은 /twa/ 주석 다음에 오는 세미콜론 전까지의 문자열을 찾습니다. (제 프로젝트는 블록이 세미콜론으로 끝나는 프로젝트라 이런 설정이 가능합니다)"'([^']*)'"
: 따옴표('
) 사이의 모든 문자를 찾습니다. 저희가 클래스 이름 자동 완성을 지정할 곳이죠.[^']*
부분은 닫는 따옴표가 나오기 전까지 모든 문자를 의미합니다. 따라서 이 정규식은 따옴표 사이의 문자열을 찾습니다.이제 /twa/ 주석 다음에 오는 문자열에 대해 TailwindCSS 클래스 자동 완성 기능을 사용할 수 있게 됩니다.
twa
인 이유는 상수를 선언하는const
키워드처럼 TailwindCSS(tw)의 자동 완성(Auto Complete)를 사용하겠다는 키워드처럼 사용하기 위함입니다. 이를 간단히 줄여twa
로 칭했습니다.이제 위에서 말했던 번거로움을 피할 수 있게 되었습니다. 아래처럼 말이죠:
또는 다음과 같이 사용할 수도 있습니다:
특정 모듈을 가져와서 사용하는 것도 아니고 속성을 하나하나 지정하는 것도 아닌, 키워드를 선언하는 것처럼 짧은 주석을 이용하면 되므로 편한 개발 경험을 제공하죠. 👍
특정 속성에 강력하게 결합되는 정규식을 사용하지 않으므로, 새로운 속성이 추가될 때마다
tailwindCSS.experimental.classRegex
설정을 수정할 필요가 없게 되었습니다.한 가지 알아두어야 할 점은 아직 이처럼 임의로 지정한 경우
자동 완성
은 지원되지만린팅
은 지원하지 않습니다.The text was updated successfully, but these errors were encountered: