프로젝트 기록

[Meetable] Styled-components를 twin.macro로 마이그레이션하기

lerrybe 2023. 8. 20. 20:09

 

 

들어가며

기존에는 styled-components로 스타일링을 작성했었고, 이를 가져와 사용했습니다.

import styled from 'styled-components';

export const Wrapper = styled.div`
  display: flex;
  justify-content: center;
  width: 100%;
  height: 70px;
  background: #fff;
  box-shadow: 0 4px 15px 0 rgba(0, 0, 0, 0.15);
`;

export const ContentWrapper = styled.div`
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  height: 100%;
  max-width: 1080px;
`;

 

각 스타일을 담당하는 태그들에 이름이 붙여져서 가독성은 좋았지만 반응형, 다크모드 등 다양한 처리를 할 때 불편함이 있어서, 가독성을 살리면서도 쉽게 스타일링을 붙일 수 있는 방법은 없을까 고민했습니다. 🤔

 


Utility-first css ? 

utility-first CSS는 각 class가 담당할 스타일을 미리 정의하고 필요한 class들을 조합해서 적용하는 식으로 사용할 수 있습니다.

 

예시

width padding으로 간단한 예시를 들어보자면 아래와 같습니다.

  • w-full : width: 100%; 와 동일하게 적용한다.
  • pl-3 : padding-left 값을 '3'에 해당하는 정의된 속성에 따라 적용한다.
  • w-24: width 값을 '24'에 해당하는 정의된 속성에 따라 적용한다. 

 

tailwindcss는 이러한 utility-first CSS를 기반으로 만들어진 프레임워크인데요,

다크모드, 반응형 등을 클래스 이름만으로 정의해줄 수 있습니다.

<p className='bg-slate-900 text-white py-2 text-sm text-center'>
  {"저는 p 태그 입니다."}
</p>

 

또한 기본 속성이 아니더라도 커스텀을 통해 원하는 속성을 정의해줄 수 있어 사용에 편리하다는 느낌을 받았습니다.

/** @type {import("tailwindcss").Config} */
module.exports = {
  content: ['./src/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {
      spacing: {  // 지정해준 커스텀 속성들
        70: '70px',  
        72: '72px',
        100: '100px',
        1080: '1080px',
      },
      maxWidth: {
        1080: '1080px',
      },
    },
  },
  plugins: [],
};

 

 


✅ twin.macro

이렇게 tailwindcss는 여러 장점이 있지만 인라인으로 클래스 이름을 주입해야해서 코드가 깔끔해지지 않을 수 있다는 것, 구조의 의도를 알기가 어렵다는 것이 개인적으로는 큰 단점으로 느껴졌습니다. 그러나, twin.macro를 사용하면 CSS-in-JS와도 함께 tailwindcss를 사용할 수 있습니다.

 

그렇다면 styled-components? emotion?

styled-components과 emotion이 비슷한 기능을 제공해주고 있습니다. 번들 사이즈는 둘 다 비슷한데, emotion에서 더 편한 css props 기능을 제공해주고 있기 때문에 tailwindcss + emotion 조합을 사용해보도록 합시다.

 

[styled-components] 번들 사이즈 및 다운로드 타임
[@emotion/react] 번들 사이즈 및 다운로드 타임
[@emotion/styled] 번들 사이즈 및 다운로드 타임

 


Installation

 

GitHub - ben-rogerson/twin.examples: Packed with examples for different frameworks, this repo helps you get started with twin a

Packed with examples for different frameworks, this repo helps you get started with twin a whole lot faster. - GitHub - ben-rogerson/twin.examples: Packed with examples for different frameworks, th...

github.com

 

위 링크에 CRA + emotion + twin 조합에서의 간략한 이용 방법이 담겨 있으니, 참고하시면 좋을 것 같아요.

$ npm install twin.macro tailwindcss @emotion/react @emotion/styled

 

혹은 

$ yarn add twin.macro tailwindcss @emotion/react @emotion/styled

 


tailwindcss 시작하기

 

다음과 같은 명령어로 tailwindcss에 필요한 config 파일들을 만들어낼 수 있습니다. 

$ npx tailwindcss init -p

 

그 후, 내용물들의 경로를 지정해줍니다. src안에 소스코드들이 있으므로 아래와 같이 작성해줬습니다.

 📂 tailwind.config.js 

/** @type {import("tailwindcss").Config} */
module.exports = {
  content: ['./src/**/*.{js,ts,jsx,tsx}'],
  theme: {},
  plugins: [],
};

 

그 다음, global하게 적용되는 css 파일에 다음과 같이 작성하면 준비 끝!

 📂 global.css or index.css 

@tailwind base;
@tailwind components;
@tailwind utilities;

 


twin config 추가하기 (optional)

 

a) Either in babel-plugin-macros.config.js:

// babel-plugin-macros.config.js
module.exports = {
  twin: {
    preset: 'emotion',
  },
}

 

b) Or in package.json:

// package.json
"babelMacros": {
  "twin": {
    "preset": "emotion"
  }
},

 

 


컴포넌트 작성 예시

emotion 혹은 styled-components를 작성했던 것처럼 tw를 이용해 가독성 좋게 작성할 수 있습니다.

 

import tw from 'twin.macro';

export const Wrapper = tw.div`
  flex justify-center 
  w-full h-70 
  bg-white shadow-md
`;

export const ContentWrapper = tw.div`
  flex justify-between items-center
  w-full h-full 
  max-w-1080
`;

 

 

 📂 tailwind.config.js 

아래와 같이 커스텀 옵션 필요하면 추가할 수 있고, 이제 기본 속성에 정의되지 않아 h-[70px]와 같이 사용되었던 클래스 이름을 h-70처럼 사용할 수 있게 됩니다.

/** @type {import("tailwindcss").Config} */
module.exports = {
  content: ['./src/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {
      spacing: {
        70: '70px',
        72: '72px',
        100: '100px',
        1080: '1080px',
      },
      maxWidth: {
        1080: '1080px',
      },
    },
  },
  plugins: [],
};

 

 

또한 필요에 따라 다음과 같이 twin.macro의 기본 타입을 지정해 적용해줄 수도 있습니다.

 

 📂 types/twin.d.ts 

// 'twin.macro'에서 가져온 함수와 '@emotion/styled'에서 가져온 함수를 사용할 수 있도록 import합니다.
import 'twin.macro';
import styledImport from '@emotion/styled';
import { css as cssImport } from '@emotion/react';
import { CSSInterpolation } from '@emotion/serialize';

// 'twin.macro' 모듈을 확장하여 Emotion 관련 함수들을 사용할 수 있도록 선언합니다.
declare module 'twin.macro' {
  // 'css' 함수를 'cssImport' 함수와 동일한 타입으로 사용할 수 있도록 합니다.
  const css: typeof cssImport;
  // 'styled' 함수를 'styledImport' 함수와 동일한 타입으로 사용할 수 있도록 합니다.
  const styled: typeof styledImport;
}

// 'react' 모듈을 확장하여 React 요소의 타입을 확장합니다.
declare module 'react' {
  // DOMAttributes 인터페이스에 'tw'와 'css' 속성을 추가합니다.
  interface DOMAttributes<T> {
    // 'tw' 속성은 문자열 값을 가질 수 있습니다. 이는 'twin.macro'의 특별한 스타일링 기능을 사용할 때 사용됩니다.
    tw?: string;
    // 'css' 속성은 Emotion의 스타일 객체를 가질 수 있습니다.
    css?: CSSInterpolation;
  }
}

 

 

 📂 package.json

// ...
"include": [ "src", "types" ]

 

 

 

 


Results

 

 



마무리하며

tailwindcss와 twin.macro를 사용해본 결과, 문법에 익숙해지는 순간부터는 확실히 작업 속도에 대한 장점을 얻을 수 있었습니다. 또한 Next.js와 같이 서버 사이드 렌더링을 많이 사용하는 현재 CSS-in-JS의 필요성에 대해서도 많은 논의가 있는 요즘, 필요한 상황마다 적절히 쓸 수 있게 여러 툴을 사용해보는 것도 괜찮은 것 같아요.

 

결론은..  프로젝트 사이즈가 많이 커지기 전에 마이그레이션해서 다행이고, styled-compenents와 인터페이스가 꽤나 비슷했던 탓인지 편한 이주였던 것 같습니다.