[Daily morning study] CSS-in-JS vs CSS Modules ๋น„๊ต

#daily morning study

Image


CSS ์Šคํƒ€์ผ๋ง ๋ฐฉ์‹์˜ ๋ฐœ์ „

์ „ํ†ต์ ์œผ๋กœ CSS๋Š” ๋ณ„๋„์˜ .css ํŒŒ์ผ๋กœ ์ž‘์„ฑํ•˜๊ณ  HTML์— ๋งํฌํ•˜๋Š” ๋ฐฉ์‹์ด์—ˆ๋‹ค. React ๊ฐ™์€ ์ปดํฌ๋„ŒํŠธ ๊ธฐ๋ฐ˜ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ๋“ฑ์žฅํ•˜๋ฉด์„œ ์Šคํƒ€์ผ๋„ ์ปดํฌ๋„ŒํŠธ ๋‹จ์œ„๋กœ ๊ด€๋ฆฌํ•˜๋ ค๋Š” ํ๋ฆ„์ด ์ƒ๊ฒผ๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฌผ๋กœ CSS Modules์™€ CSS-in-JS๊ฐ€ ๋“ฑ์žฅํ–ˆ๋‹ค.

CSS Modules

CSS ํŒŒ์ผ์„ ๋ชจ๋“ˆ์ฒ˜๋Ÿผ importํ•ด์„œ ํด๋ž˜์Šค ์ด๋ฆ„์„ ์ž๋™์œผ๋กœ ๊ณ ์œ ํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฐฉ์‹์ด๋‹ค.

๋™์ž‘ ์›๋ฆฌ

๋นŒ๋“œ ํƒ€์ž„์— ํด๋ž˜์Šค ์ด๋ฆ„์„ [filename]__[classname]__[hash] ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•ด์„œ ์ „์—ญ ์ถฉ๋Œ์„ ๋ฐฉ์ง€ํ•œ๋‹ค.

/* Button.module.css */
.button {
  padding: 8px 16px;
  background-color: #007bff;
  color: white;
  border-radius: 4px;
}

.primary {
  background-color: #0056b3;
}
import styles from './Button.module.css';

function Button({ children, variant }) {
  return (
    <button className={`${styles.button} ${variant === 'primary' ? styles.primary : ''}`}>
      {children}
    </button>
  );
}

๋นŒ๋“œ ํ›„ ์‹ค์ œ HTML์—๋Š” Button__button__a1b2c ๊ฐ™์€ ๊ณ ์œ ํ•œ ํด๋ž˜์Šค ์ด๋ฆ„์ด ์ ์šฉ๋œ๋‹ค.

์žฅ์ 

  • ๋ณ„๋„์˜ ๋Ÿฐํƒ€์ž„ ์˜ค๋ฒ„ํ—ค๋“œ ์—†์Œ (๋นŒ๋“œ ํƒ€์ž„์— ์ฒ˜๋ฆฌ)
  • ๊ธฐ์กด CSS ๋ฌธ๋ฒ• ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • ๋ฒˆ๋“ค ํฌ๊ธฐ๊ฐ€ ์ž‘์Œ
  • ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง(SSR)์—์„œ๋„ ๋ฌธ์ œ์—†์Œ

๋‹จ์ 

  • ๋™์  ์Šคํƒ€์ผ๋ง์ด ๋ฒˆ๊ฑฐ๋กœ์›€ (์ธ๋ผ์ธ ์Šคํƒ€์ผ์ด๋‚˜ ํด๋ž˜์Šค ์กฐ๊ฑด๋ถ€ ์ถ”๊ฐ€๋กœ ํ•ด๊ฒฐ)
  • JS ๋ณ€์ˆ˜๋ฅผ CSS์— ์ง์ ‘ ์‚ฌ์šฉํ•˜๊ธฐ ์–ด๋ ค์›€
  • ์ปดํฌ๋„ŒํŠธ์™€ ์Šคํƒ€์ผ ํŒŒ์ผ์„ ๋”ฐ๋กœ ๊ด€๋ฆฌํ•ด์•ผ ํ•จ

CSS-in-JS

JavaScript ์ฝ”๋“œ ์•ˆ์—์„œ CSS๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค. ๋Œ€ํ‘œ์ ์ธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ styled-components์™€ Emotion์ด ์žˆ๋‹ค.

styled-components ์˜ˆ์‹œ

import styled from 'styled-components';

const Button = styled.button`
  padding: 8px 16px;
  background-color: ${props => props.primary ? '#0056b3' : '#007bff'};
  color: white;
  border-radius: 4px;

  &:hover {
    opacity: 0.9;
  }
`;

function App() {
  return (
    <>
      <Button>๊ธฐ๋ณธ ๋ฒ„ํŠผ</Button>
      <Button primary>์ฃผ์š” ๋ฒ„ํŠผ</Button>
    </>
  );
}

๋™์ž‘ ์›๋ฆฌ

  1. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ฒ˜์Œ ๋ Œ๋”๋ง๋  ๋•Œ JS๊ฐ€ CSS ๋ฌธ์ž์—ด์„ ํŒŒ์‹ฑ
  2. ๊ณ ์œ ํ•œ ํด๋ž˜์Šค ์ด๋ฆ„ ์ƒ์„ฑ (์˜ˆ: sc-aBcDeF)
  3. <style> ํƒœ๊ทธ์— CSS ์‚ฝ์ž…
  4. props ๋ณ€ํ™”์— ๋”ฐ๋ผ ๋™์ ์œผ๋กœ CSS ์—…๋ฐ์ดํŠธ

์žฅ์ 

  • props ๊ธฐ๋ฐ˜ ๋™์  ์Šคํƒ€์ผ๋ง์ด ์ž์—ฐ์Šค๋Ÿฌ์›€
  • JS ๋ณ€์ˆ˜, ํ•จ์ˆ˜, ํ…Œ๋งˆ๋ฅผ CSS์— ์ง์ ‘ ํ™œ์šฉ ๊ฐ€๋Šฅ
  • ์ปดํฌ๋„ŒํŠธ์™€ ์Šคํƒ€์ผ์„ ํ•œ ํŒŒ์ผ์—์„œ ๊ด€๋ฆฌ
  • ์ž๋™ vendor prefixing

๋‹จ์ 

  • ๋Ÿฐํƒ€์ž„ ์˜ค๋ฒ„ํ—ค๋“œ: JS์—์„œ CSS๋ฅผ ์ฒ˜๋ฆฌํ•˜๋ฏ€๋กœ ์„ฑ๋Šฅ์— ์˜ํ–ฅ
  • ๋ฒˆ๋“ค ํฌ๊ธฐ ์ฆ๊ฐ€ (styled-components ๊ธฐ์ค€ ์•ฝ 15KB gzip)
  • SSR ์„ค์ •์ด ๋ณต์žกํ•จ (์Šคํƒ€์ผ ์ง๋ ฌํ™” ํ•„์š”)
  • ํด๋ž˜์Šค ์ด๋ฆ„์ด ๋™์ ์œผ๋กœ ์ƒ์„ฑ๋˜์–ด ๋””๋ฒ„๊น…์ด ์–ด๋ ค์›€

์„ฑ๋Šฅ ๋น„๊ต

ํ•ญ๋ชฉCSS ModulesCSS-in-JS (๋Ÿฐํƒ€์ž„)
๋ฒˆ๋“ค ํฌ๊ธฐ์ž‘์Œ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€๋จ
์ดˆ๊ธฐ ๋ Œ๋”๋ง๋น ๋ฆ„์•ฝ๊ฐ„ ๋А๋ฆผ (JS ํŒŒ์‹ฑ)
๋™์  ์Šคํƒ€์ผํด๋ž˜์Šค ์กฐํ•ฉ์œผ๋กœ ์ฒ˜๋ฆฌprops๋กœ ์ง๊ด€์  ์ฒ˜๋ฆฌ
SSR๊ฐ„๋‹จ๋ณ„๋„ ์„ค์ • ํ•„์š”
CSS ํŒŒ์‹ฑ ์ฃผ์ฒด๋นŒ๋“œํƒ€์ž„๋Ÿฐํƒ€์ž„

Zero-runtime CSS-in-JS์˜ ๋“ฑ์žฅ

๊ธฐ์กด CSS-in-JS์˜ ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋นŒ๋“œ ํƒ€์ž„์— CSS๋ฅผ ์ถ”์ถœํ•˜๋Š” zero-runtime ๋ฐฉ์‹์ด ๋“ฑ์žฅํ–ˆ๋‹ค.

  • Linaria: ๋Ÿฐํƒ€์ž„ ์—†์ด static CSS ์ถ”์ถœ, CSS Modules์ฒ˜๋Ÿผ ๋™์ž‘
  • vanilla-extract: TypeScript ๊ธฐ๋ฐ˜ type-safe CSS, ๋นŒ๋“œํƒ€์ž„ ์ฒ˜๋ฆฌ
  • Panda CSS: utility-first + ๋นŒ๋“œํƒ€์ž„ ์ถ”์ถœ
// vanilla-extract ์˜ˆ์‹œ
import { style } from '@vanilla-extract/css';

export const button = style({
  padding: '8px 16px',
  backgroundColor: '#007bff',
  color: 'white',
  borderRadius: '4px',
});

๋™์  ์Šคํƒ€์ผ์€ CSS custom properties(๋ณ€์ˆ˜)๋ฅผ ํ™œ์šฉํ•ด์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค.

์„ ํƒ ๊ธฐ์ค€

์ƒํ™ฉ์ถ”์ฒœ ๋ฐฉ์‹
์„ฑ๋Šฅ ์ค‘์š”, SSR ํ™˜๊ฒฝCSS Modules ๋˜๋Š” zero-runtime CSS-in-JS
๋™์  ์Šคํƒ€์ผ์ด ๋งŽ์€ UICSS-in-JS (styled-components, Emotion)
ํƒ€์ž… ์•ˆ์ „์„ฑ ์ค‘์š”vanilla-extract
Next.js App RouterCSS Modules (์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ ํ˜ธํ™˜)
์†Œ๊ทœ๋ชจ ํ”„๋กœ์ ํŠธCSS Modules๋กœ ์‹œ์ž‘์ด ๋ฌด๋‚œ

Next.js 13+ App Router์—์„œ๋Š” ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ธฐ๋ณธ์ด๊ธฐ ๋•Œ๋ฌธ์—, ํด๋ผ์ด์–ธํŠธ ๋Ÿฐํƒ€์ž„์ด ํ•„์š”ํ•œ styled-components ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” 'use client' ์ง€์‹œ์–ด๊ฐ€ ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ์—์„œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค. ๊ทธ๋ž˜์„œ Next.js ๊ณต์‹ ๋ฌธ์„œ์—์„œ๋„ CSS Modules๋ฅผ ๊ธฐ๋ณธ ์Šคํƒ€์ผ๋ง ๋ฐฉ๋ฒ•์œผ๋กœ ๊ถŒ์žฅํ•œ๋‹ค.