리액트 공식문서에서 생소한 Hook인 useIntersectionEffect에 대해서 공부하다가 CSS-in-JS 라이브러리를 활용 시에만 사용하는 훅임을 알게 되었습니다. 공식문서를 읽으면서 CSS-in-JS이 어떻게 구동되는지에 대해서 알아야 했는데, 생각해보니 저는 styled-components, emotion과 같은 라이브러리를 활용만 하고 있었을 뿐, 제대로된 등장배경이나 구동원리에 대해서 제대로 파악하고 있지 않은 것 같아 블로그 글로 남기게 되었습니다. 여러 블로그를 찾아본 후, 가장 쉽게 설명을 적어보자 합니다.
● CSS-in-JS 이전 SCSS, SASS 전처리기
이전에 저는 styled-components, emotion을 사용해보기 전에 SCSS를 사용해본적이 있었는데요, css와 비슷한 방식으로 클래스로 스타일을 부여하지만 변수를 지정한다든지, 클래스 내부 안에 있는 요소(자식노드)들을 선택하는 셀렉터 사용이 조금 더 용이하다는 장점을 실감할 수 있었습니다. 하지만 클래스명을 사용하기 때문에 매번 클래스명을 지어줘야한다는(BEM준수 등) 단점이 여전히 존재했으며, SAS나 SCSS같은 경우 전처리기 때문에 컴파일 후에 css파일로 합쳐줘야한다는 단점이 있습니다. 이런 컴파일 과정에서 파일의 크기가 더 커지게 되고, 에러발생 시 어느부분에서 에러가 일어났는지 예측하기 어렵다는 단점이 있습니다.
장점
- 변수 지정이 쉬움
- 셀렉터 사용이 용이
- 생 css만 사용했던 거보단 확실히 장점을 느낄 수 있음
단점
- 여전히 네이밍 규칙 준수해야함
- 컴파일 시, 파일크기가 커지는 경향 있음
- 에러 발생 시, 예측하기 어려움
- 아직 완전한 캡슐화는 불가능
SCSS(SASS) vs CSS
SCSS 📌 CSS의 한계 선택자(Selector)를 만드는 관점에서 볼 때 불필요한 부모 요소 선택자를 매번 적어야 한다. 규모가 커지는 프로젝트의 경우에도 일원화된 자동화가 어렵고 수동으로 모든 것들
nyol.tistory.com
● CSS-in-JS에 접근하는 3가지 방식
앞서 존재하던 단점들을 보완하기 위해, CSS-in-JS가 등장하게 되었습니다. css를 자바스크립트로도 적자는 취지에서 시작되었고 대표적인 라이브러리로는 styled-components, emotion이 있습니다.
위의 리액트 공식문서를 살펴보다 보면 사용방법(Usage)에 CSS-in-JS에 접근하는 3가지 방식이 적혀있습니다.
- 컴파일러를 통한 정적 CSS 파일 추출
- 인라인 스타일
- 런타임에 style 태그 삽입
저는 이 3가지 방식에 대해서 제대로 알고 있지 못했던 것 같아, 이 3가지 방식 + GPT와의 대화를 정리해 보겠읍니다.
useInsertionEffect – React
The library for web and native user interfaces
react.dev
❍ 컴파일러를 통한 정적 CSS 파일 추출
아무래도 자바스크립트 파일로 코드가 적히다 보니, 바벨과 같은 컴파일러를 통해 정적으로 CSS 파일만 추출하는 방법이 있습니다. 이를 통해 리액트에서 JSX코드와 CSS파일을 분리해 컴파일시킬 수 있습니다. (바벨설정)
❍ 인라인 스타일
인라인 스타일은 말 그대로 html에 직접적으로 주입하는 것을 말합니다. 예시는 아래와 같습니다.
<div style={{ opacity: 1 }}>
인라인 스타일로 style을 직접적으로 주입하는 경우, SSR에서 돌아가고 있다면 서버에 대한 요청을 줄일 수 있습니다.
❍ 런타임에 style 태그 삽입
코드를 읽어가는 런타임에 head에 style태그를 삽입하는 방식을 뜻합니다.
● 위의 개념들을 기반으로 GPT에게 궁금했던 점 묻기
Q. 개발 환경에서 css-in-js 이모션이나 스타일 컴포넌트를 사용하는 경우 인라인 스타일로 적용되는 것 같나요? 배포 환경에서는 js로 컴파일된 css 코드가 css 파일로 컴파일되어 정적으로 추출된 후 html에 삽입되나요?
Emotion과 스타일 컴포넌트는 모두 자바스크립트 코드에서 직접 CSS 스타일을 작성할 수 있는 CSS-in-JS 라이브러리입니다. 하지만 전통적인 의미의 인라인 스타일을 적용하지는 않습니다. 대신 런타임 중에 스타일을 동적으로 생성하고 관리합니다.
개발 중에 React 또는 기타 JavaScript 애플리케이션에서 Emotion 또는 스타일 컴포넌트를 사용하면 일반적으로 스타일이 문서 헤드에 <style> 태그로 삽입됩니다. 이를 통해 렌더링될 때 해당 컴포넌트에 스타일을 동적으로 적용할 수 있습니다.
그러나 프로덕션 환경에서는 이러한 라이브러리에서 생성된 CSS 코드를 다른 방식으로 처리할 수 있습니다:
인라이닝: 일부 빌드 설정에서는 "CSS-in-JS 서버 측 렌더링(SSR)"이라는 기술을 사용하여 CSS를 HTML에 직접 인라인 처리하도록 선택할 수 있습니다. 즉, CSS 스타일이 HTML 문서에 직접 포함되므로 스타일을 로드하는 데 필요한 네트워크 요청 횟수가 줄어듭니다.
정적 추출: 또 다른 접근 방식은 빌드 프로세스 중에 생성된 CSS를 별도의 정적 CSS 파일로 추출하는 것입니다. 이를 "정적 추출" 또는 "수화 포함 정적 추출"이라고 합니다. 추출된 CSS는 별도의 파일로 제공되며 프로덕션 빌드에서 자바스크립트 코드는 이 파일을 참조합니다. 이 접근 방식은 캐싱 및 중복 감소와 같은 CSS 사용의 이점을 여전히 허용합니다.
인라이닝과 정적 추출 중 어떤 것을 선택할지는 특정 사용 사례, 라이브러리 구성, 애플리케이션의 성능 요구 사항에 따라 달라질 수 있습니다.
요약하자면, Emotion과 스타일 컴포넌트를 사용하면 자바스크립트로 스타일을 작성할 수 있지만 기존의 인라인 스타일을 적용하지는 않습니다. 프로덕션 환경에서는 구성 및 빌드 설정에 따라 이러한 라이브러리에서 생성된 CSS 코드를 HTML에 직접 인라인하거나 별도의 CSS 파일로 추출할 수 있습니다.
=> 즉, CSS-in-JS 라이브러리들은 개발 환경에서는 style태그를 head에 직접적으로 주입하면서 동적으로 스타일을 적용할 수 있었던 것이었고 배포환경에서는 인라인 방식 or 정적 추출 방식 둘중 하나를 택해 적용하고 있던 것이었습니다.
Q. 그렇다면 UI 변경이 적으면 인라인으로 적용하고, 변경이 많으면 CSS 정적 추출을 하는 경우가 많겠죠? (배포환경시)
예, 고려할 만한 합리적인 접근 방식입니다. 스타일 인라이닝과 CSS 정적 추출 사용 간의 결정은 UI 변경 사항의 양과 프로젝트의 특정 요구 사항에 따라 영향을 받을 수 있습니다.
스타일 인라이닝: UI 변경이 적고 생성되는 CSS 코드의 양이 적은 경우 "CSS-in-JS 서버 측 렌더링(SSR)"을 사용하여 스타일을 인라이닝하는 것이 좋은 옵션이 될 수 있습니다. 스타일을 인라이닝하면 HTTP 요청 횟수를 줄일 수 있으므로 초기 렌더링 속도가 빨라지고 소규모 프로젝트의 성능이 향상될 수 있습니다.
CSS 정적 추출: 반면에 프로젝트에 UI 변경 사항이 많고 상당한 양의 CSS 코드가 생성되는 경우 별도의 CSS 파일로 CSS 정적 추출을 사용하는 것이 유리할 수 있습니다. 스타일을 별도의 파일로 추출하면 캐싱이 개선되고 캐싱된 CSS 파일을 직접 제공할 수 있어 대규모 프로젝트의 성능이 향상됩니다.
애플리케이션의 크기와 복잡성, 성능 요구 사항을 고려하여 결정해야 합니다. 또한 빌드 도구 및 배포 환경도 특정 프로젝트에 어떤 접근 방식이 더 적합한지 결정하는 데 중요한 역할을 할 수 있습니다.
실제로는 두 가지 기술을 조합하여 사용하는 다양한 시나리오가 발생할 수 있습니다. 예를 들어 일부 라이브러리 및 도구는 동적 청킹 및 코드 분할 기능을 제공하여 특정 스타일은 인라인으로 처리하고 다른 스타일은 별도의 CSS 파일로 추출하여 성능과 유지 관리성 간의 균형을 맞출 수 있습니다.
저는 위의 질문 후, 배포환경에서 그럼 css-in-js는 변경사항이 있을때마다 파싱되어 head태그에 적용이 되는건지 궁금점이 들었습니다.
Q. 그렇다면 개발 환경에서 스타일 컴포넌트와 이모션을 사용할 경우, 스타일 태그가 헤드에 직접 주입되기 때문에 변경이 있을 때마다 계속 파싱되어 표시되나요?
질문과 관련하여, 스타일이 적용된 컴포넌트와 이모션을 사용할 때 개발 환경에서는 스타일이 실제로 <스타일> 태그로 헤드에 주입됩니다. 컴포넌트나 스타일을 변경하면 이러한 라이브러리는 업데이트된 스타일을 동적으로 핫 로드합니다. 즉, 변경 사항이 있을 때마다 전체 페이지를 새로 고치지 않고도 업데이트된 스타일이 즉시 적용됩니다.
동적 스타일 주입 및 핫 리로드 프로세스를 통해 빠른 개발 워크플로우를 구현할 수 있습니다. 작업하면서 변경 사항이 실시간으로 적용되는 것을 확인할 수 있으므로 개발 및 디버깅 단계에서 특히 유용합니다.
개발 과정에서 이러한 라이브러리는 Webpack의 핫 모듈 교체(HMR) 또는 React의 빠른 새로고침과 같은 다양한 메커니즘을 사용하여 스타일을 핫 리로딩합니다. 이러한 도구는 컴포넌트나 스타일에 대한 변경 사항을 감지하여 전체 페이지를 다시 로드할 필요 없이 실행 중인 애플리케이션에 자동으로 업데이트를 적용합니다.
핫 리로드 동작은 개발 환경에 따라 다르며 원활한 개발 환경을 제공하여 개발자의 생산성을 높이는 데 도움이 된다는 점에 유의하세요. 프로덕션 빌드에서는 앞서 설명한 대로 스타일이 추출되어 다르게 제공됩니다.
● head에 style 직접 주입 시, 문제점
리액트 공식문서에서도, head에 style을 직접 주입 시, 렌더링 된 이후에 head에 style을 주입한다면 또 렌더를 발생시키기때문에 자주 계산된다는 단점이 있다고 말하고 있습니다. 따라서 이런 점을 해결하기 위해서 사용하라는 것이 useIntersectionEffect hook인 것 같습니다.
● 그럼 tailwind는 무엇?
Q. 테일윈드를 사용한다고 했을때, 테일윈드가 쓰인 코드를 컴파일하여 헤드의 링크 태그에 삽입해?
예, Tailwind CSS를 사용할 때는 Tailwind 코드를 컴파일한 다음 HTML 파일의 <head> 섹션에 링크하여 결과 CSS 파일을 포함시켜야 합니다.(PostCSS)
컴파일 프로세스는 모든 유틸리티 클래스와 사용자 정의(있는 경우)가 포함된 Tailwind CSS 소스 코드를 가져와서 일반 CSS 파일을 생성하도록 처리합니다. 이 CSS 파일에는 HTML에서 사용한 유틸리티 클래스에 해당하는 모든 스타일이 포함됩니다.
컴파일된 CSS 파일이 있으면 다음과 같이 <head> 섹션에 <link> 태그를 사용하여 HTML 파일에 포함시킵니다:
테일윈드로 배포시, 최적화하는 방법
Optimizing for Production - Tailwind CSS
Getting the most performance out of Tailwind CSS projects.
tailwindcss.com