[Web] UnoCSS (Reimagine Atomic CSS 정리)

olwooz·2023년 6월 10일
1

Web

목록 보기
3/3
post-thumbnail

UnoCSS Repository
Reimagine Atomic CSS

Atomic CSS란?

시각적 기능에 기반한 이름을 가지는, 단일 목적을 수행하는 작은 클래스들을 선호하는 CSS 아키텍쳐 접근법

예) 이런 것들

.m-0 {
  margin: 0;
}
.text-red {
  color: red;
}
/* ... */

프레임워크 - Tailwind CSS, Windi CSS, Tachyons
라이브러리 - Bootstrap, Chakra UI

배경

Tailwind는 큰 용량의 utility CSS 생성 → 시작과 HMR 오래 걸림 → Windi CSS를 대안으로 사용했음

Windi CSS - 0 dependency, on-demand → Tailwind보다 20~100x 빠름 → Tailwind가 JIT 엔진을 개발하게 됨

Atomic CSS 분석

기존 방식

기존에 Atomic CSS를 만드는 방식 → 필요할 수도 있는 모든 CSS utility를 만드는 방식

예 )SCSS로 CSS utility 생성

// style.scss

@for $i from 1 through 10 {
  .m-#{$i} {
    margin: $i / 4 rem;
  }
}

위 코드는 아래로 컴파일 됨

.m-1 { margin: 0.25 rem; }
.m-2 { margin: 0.5 rem; }
/* ... */
.m-10 { margin: 2.5 rem; }

기존 방식의 단점

  • margin의 범위가 1~10으로 제한됨
  • 한 개만 써도 10개의 CSS rule 생성
  • margin 방향 갯수를 지원하려면 코드 x 5 (mb, mt, mr, ml, m)
  • hover:나 focus: 같은 variant 지원하려면 코드 x N

Tailwind에서는 PurgeCSS로 해결 → prod에서만 해결

On-demand 방식

on-demand 방식에서는 생성 단계와 스캐닝 단계의 순서를 뒤집어 cost 절약함과 동시에 미리 생성하는 방식에서 해결하기 어려운 동적 need를 해결할 수 있는 유연함도 갖춤

  • dev와 prod 둘 다 사용 가능 → 일관성 보장, HMR 더욱 효율적

불편한 점

이상적인 Atomic CSS라면 사용법이 직관적이고 동일해야 함, 그러나 어떤 때 작동하고 어떤 때 작동하지 않는지 파악하기 어려움
e.g. Tailwind CSS - border-2 ~ 8까지는 2~8px, border-10은 작동하지 않음

.border-10 {
  border-width: 10px;
}
  • 위와 같이 global style로 해결 가능 - 그럴거면 왜 Tailwind를 쓰지?
// tailwind.config.js
module.exports = {
  theme: {
    borderWidth: {
      DEFAULT: '1px',
      0: '0',
      2: '2px',
      3: '3px',
      4: '4px',
      6: '6px',
      8: '8px',
      10: '10px' // <-- here
    }
  }
}
  • 설정 가능하지만, 설정하는 동안 작업하던 일에 대한 흐름 끊김
  • CSS를 생성하는 코드가 CSS 결과보다 더 길어짐
  • 더 이상 on-demand가 아니게 됨

UnoCSS

UnoCSS - the instant atomic CSS engine with maximum performance and flexibility.
“최대 성능과 유연성을 가진 즉석 Atomic CSS 엔진”

core utility가 없기 때문에 framework라기보다는 engine이다 → 모든 기능은 preset이나 inline configuration을 통해 제공됨

직관적이고 완벽한 커스터마이징 가능
UnoCSS의 목표 - 직관성과 커스터마이징

자신만의 utility를 단번에 정의 가능

정적 룰

rules: [
  ['m-1', { margin: '0.25rem' }]
]

코드에서 m-1이 발견되면 아래 CSS 생성:

.m-1 { margin: 0.25rem; }

동적 룰

matcher를 RegExp로 바꾸고 body를 함수로 바꾸면 됨

rules: [
  [/^m-(\\d+)$/, ([, d]) => ({ margin: `${d / 4}rem` })],
  [/^p-(\\d+)$/, match => ({ padding: `${match[1] / 4}rem` })],
]
<div class="m-100">
  <button class="m-3">
    <icon class="p-5" />
    My Button
  </button>
</div>

위 코드가 있으면 아래 CSS가 생성됨

.m-100 { margin: 25rem; }
.m-3 { margin: 0.75rem; }
.p-5 { padding: 1.25rem; }

변형

variants: [
  // support `hover:` for all rules
  {
    match: s => s.startsWith('hover:') ? s.slice(6) : null,
    selector: s => `${s}:hover`,
  },
  // support `!` prefix to make the rule important
  {
    match: s => s.startsWith('!') ? s.slice(1) : null,
    rewrite: (entries) => {
      // append ` !important` to all css values
      entries.forEach(e => e[1] += ' !important')
      return entries
    },
  }
],

프리셋

Tailwind CSS, Windi CSS, Bootstrap 등 기존 프레임워크 방식과 호환되는 다양한 프리셋 제공

유연성

지금까지는 Tailwind의 행동을 따라하는 방법을 보여줬음

지금부터는 UnoCSS의 진정한 힘

속성화 모드

"Attributify"

<button class="bg-blue-400 hover:bg-blue-500 text-sm text-white font-mono font-light py-2 px-4 rounded border-2 border-blue-200 dark:bg-blue-500 dark:hover:bg-blue-600">
  Button
</button>

위 코드를 아래처럼 쓸 수 있음:

<button 
  bg="blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600"
  text="sm white"
  font="mono light"
  p="y-2 x-4"
  border="2 rounded blue-200"
>
  Button
</button>

<div class="m-2 rounded text-teal-400" />

위 코드를 아래처럼 쓸 수 있음:

<div m-2 rounded text-teal-400 />

순수 CSS 아이콘

제작자가 아이콘에 진심, 순수 CSS만으로 아이콘 표시 가능

<!-- A basic anchor icon from Phosphor icons -->
<div class="i-ph-anchor-simple-thin" />
<!-- An orange alarm from Material Design Icons -->
<div class="i-mdi-alarm text-orange-400 hover:text-teal-400" />
<!-- A large Vue logo -->
<div class="i-logos-vue text-3xl" />
<!-- Sun in light mode, Moon in dark mode, from Carbon -->
<button class="i-carbon-sun dark:i-carbon-moon" />
<!-- Twemoji of laugh, turns to tear on hovering -->
<div class="i-twemoji-grinning-face-with-smiling-eyes hover:i-twemoji-face-with-tears-of-joy" />

  • 변형을 사용하면 hover, 라이트/다크 모드 등 기반으로 아이콘 변경 가능
  • 10,000+개의 아이콘 지원

스코프

Tailwind/Windi - preflight로 native element를 reset하고 CSS 변수 fallback 제공 → 다른 UI 프레임워크와 함께 사용하면 conflict 발생 가능

UnoCSS - preflight를 유저가 알아서 하게 함 (다른 프레임워크와 함께 사용 가능)

Vite plugin에 scoped-vue 모드 → 각 컴포넌트마다 scoped 스타일을 생성해 컴포넌트 라이브러리로 안전하게 만들 수 있음 (유저 CSS와 conflict 걱정 X)

<template>
  <div class="m-2 rounded"><slot></div>
<template>

<!-- the following will be inject in the bundler -->
<style scoped>
.m-2{margin:0.5rem;}
.rounded{border-radius:0.25rem;}
</style>

0개의 댓글