내 포트폴리오에 기존에 쓰고 있던 스타일링 라이브러리 테일윈드다.
빠르게 뚝딱뚝딱 만들기에 최적이고, 넓은 커버리지로 제공되는 유틸리티 클래스로 디자인 시스템이 있는 것처럼 일관성있는 디자인 가능하고, 새 스타일링 라이브러리 학습 필요성 느껴서
한 번 뚝딱 만들고 마는 게 아니라 스타일이든 기능이든 계속 개선을 하면서 만들고 있는데, 유지보수에서 불편함을 많이 느꼈다.
또한 나만 보는 코드이긴 하지만, 가독성 좋은 코드를 작성하려고 하는 입장에서 DOM과 분리가 되지 않는 테일윈드 스타일링이 내 취향과 잘 안맞았다. (=컴포넌트에는 컴포넌트 관련 내용만 있었으면 좋겠음. 스타일 분리하고 싶음!)
원래 바닐라 익스트랙트는 블로그 앱(Vitriol)에 적용하려고 했는데, 그러려면 먼저 블로그 앱에 ts 마이그레이션을 해야 했다. 그 과정에서 d3라이브러리로 구현한 그래프에 타입을 적용하는 데 어려움을 겪어서 결국 블로그 앱에는 emotion을 사용하기로 했다.
=> 바닐라 익스트랙트의 장점(스타일링의 타입 추론, 빌드 타임 css 등)을 알고 꼭 한번 사용하고 싶었는데, 마침 포트폴리오는 타입스크립트로 구현했으니 적용하기 좋을 것이라고 생각했다.
=> 필요성보다 호기심으로 기술 스택을 선정하는 건 내 나쁜 버릇이긴 함…
원래 마이그레이션은 css-in-js로 하려고 했다. 하지만 styled-components를 쓰면서 스타일 컴포넌트에 하나하나 이름 붙이는 게 어렵기도 하고, css-in-js의 단점(런타임에서 스타일을 생성하기 때문에 상대적으로 성능이 떨어짐)을 생각하면 테일윈드에서 css-in-js로 옮겨가는 건 내가 편하게 스타일링하기 위해 성능 저하를 유발하는 꼴이 되지 않나 싶었다. 바닐라 익스트랙트는 모든 스타일 코드를 빌드타임에서 추출하여 적용하기 때문에 런타임 성능에 영향을 안 미치니까 좋을 거라고 생각했다.
원래는 테일윈드가 설치되어있는 상태에서 바닐라 익스트랙트를 점진적으로 적용하려고 했는데, 두 라이브러리를 동시에 설치해서 사용하려 하니 오류가 발생했다. 바닐라 익스트랙트에서는 스타일을 css.ts에만 정의해야 하는데, 테일윈드 스타일을 불러오면서 이와 충돌이 있었던 것으로 추정된다. 자세히는 모름…
그래서 테일윈드를 완전히 삭제하고, 바닐라 익스트랙트를 설치하고 한 컴포넌트, 한 페이지씩 스타일을 바닐라 익스트랙트로 옮겨나갔음
위에 말했듯이 css.ts에 스타일을 적용해서 export하고, 컴포넌트에서 import해서 스타일링을 적용할 태그의 className에 넣어주면 된다.
페이지의 경우 pages 디렉토리에 pages.css.ts 파일을 만들어 여러 페이지의 스타일을 한 번에 관리하고, 컴포넌트의 경우는 이미 Components 디렉토리 안에 사용처별로 서브디렉토리(Header, About, About/Skills, About/Contacts 등..)를 만들어 두었으므로, 각 서브디렉토리에 HeaderComponents.css.ts와 같은 이름으로 스타일 파일을 만들었다.
스타일파일 내에서는 다음과 같이 스타일 객체를 만들고 export한다.
export const headerStyle = {
container: style({ position: ‘fixed’, …}),
logoSection: style({ display: ‘flex, …}),
.
.
.
}
그리고 스타일 객체 변수명이 컴포넌트 명칭에 따라 엄청 길어지는 경우(projectImageSectionStyle이라든가…)가 있기 때문에, 불러와서 사용할 때는 import { Style as style } 로 축약해서 불러와서 사용한다.
//nonono
<div className={projectImageSectionStyle.container>
//yesyesyes
<div className={style.container}>
컴포넌트 안에 ui 로직이 너무 많은 경우, 부분별로 객체를 세분화했다.
export const projectInfoContainerStyle = {
info: {
title: style({…
}),
type: style({…
}),
description: style({...
}),
stackList: style({…
}),
stackListItem: style({…
}),
},
docs: {
container: style({…
}),
title: style({…
}),
list: style({…
}),
listItem: style({…
}),
},
links: {
container: style({…
}),
title: style({…
}),
list: style({…
}),
listItem: style({…
}),
nextProject: {
container: style({…
}),
title: style({…
}),
link: style({…
}),
},
},
};
이런 식으로 보기 좋게 작성하려고 애썼다!
또한 빌드타임에서 css를 추출하는 것이기 때문에 각 빌드툴 별로 별도의 설정으로 바닐라 익스트랙트를 연결해줘야 한다. 나는 이번에 vite를 사용하고 있기 때문에, 바닐라 익스트랙트와 vite를 연결해주는 플러그인을 설치하고, vite-config.ts 파일에 아래와 같이 추가해줬다.
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), vanillaExtractPlugin()],
assetsInclude: ["**/*.ttf"],
});
처음에 제일 불편했던 것. pt-16이 정확히 몇 rem의 padding-top을 주는 건지, rounded-sm은 border-radius로는 몇인지 하나하나 테일윈드 docs를 찾아봐야 했다. 그러다 이렇게 노가다하는거 너무 아니꼬운 사람이 변환 툴같은거 만들어놓지 않았을까? 하고 찾아보니까 tailwind-to-css라는 앱이 있어서 엄청 유용하게 썼다! css로도 바꿔주고 js 스타일 객체로도 바꿔주는데, 바닐라 익스트랙트에서는 js 스타일 객체와 같은 형식으로 스타일링하기 때문에 그냥 복붙만 하면 되서 편했다.
다만 테일윈드에서 기존 유틸리티 클래스 대신 수동 스타일링 (pt-[16px]처럼 직접 값을 작성하는 경우)에는 잘 감지하지 못하는 경우가 있어서 정신 똑띠 차리고 써야한다!
그리고 반응형의 경우 바꿔주긴 하는데, 바닐라 익스트랙트에서는 미디어쿼리 사용 방식이 해당 앱에서 제공하는 거랑 조금 달라서 수동으로 옮겨야 했다.
{
"@media": {
[mediaQueries.SCREEN_SM]: {
width: "60%",
},
[mediaQueries.SCREEN_MD]: {
width: "40%",
},
},
}
이런 식으로 옮겼다!
그리고 반응형의 경우는 mediaQueries.ts 파일을 만들어서 상수 변수로 관리하기로 했다.
export const mediaQueries = {
SCREEN_SM: "screen and (min-width: 640px)",
SCREEN_MD: "screen and (min-width: 768px)",
SCREEN_LG: "screen and (min-width: 1024px)",
SCREEN_XL: "screen and (min-width: 1280px)",
SCREEN_2XL: "screen and (min-width: 1530px)",
SCREEN_3XL: "screen and (min-width: 1536px)",
SCREEN_4XL: "screen and (min-width: 1850px)",
SCREEN_5XL: "screen and (min-width: 2000px)",
SCREEN_6XL: "screen and (min-width: 2300px)",
};
// 2xl과 3xl이 미묘하게 맞닿아있는 이유는 3xl부터는 내가 직접 정의한 거라서…
// 일단 마이그레이션 후에 고칠 예정
이런 식으로!
런타임의 코드와 상호작용 불가
바닐라 익스트랙트는 빌드타임에 별도의 css파일을 css.ts에서 추출하는 것이기 때문에, 런타임에서 돌아가고있는 코드와 상호작용할 수 없다. css-in-js처럼 프롭을 넘겨준다거나, 그걸로 상태 업데이트 때에 다른 스타일을 적용하려면 다른 로직을 써야 한다.
<li
key={skill.title}
onClick={() => {
onItemClick(skill);
}}
style={{ background: skill.bgColorCode }}
className={`${style.skillItem} ${
selectedSkill &&
selectedSkill.title === skill.title &&
style.skillItemSelected
}
`}
>
그래서 런타임에서 정의된 색상 코드 변수를 직접 넣어줘야 하는 경우에는 어쩔 수 없이 style 프롭으로 인라인 스타일링을 해야 한다. 그리고 조건부 스타일링의 경우에는 위에 나온 것처럼 조건 연산자를 활용해서 특정 조건일 때 selectedStyle같은 이름으로 정의된 ‘특정 조건의 스타일’을 적용해주는 식으로 해야 했다.
이는 createTheme 등의 기능을 이용해서 테마를 생성하면 더 자연스럽게 연결이 가능한 부분인데, 여의치 않은 이유가 있었다.
1. 색상 코드 변수로 스타일링하는 경우는 두 가지다. colorScheme의 색상을 적용할 때, 그리고 firestore db에 저장되어있는 프로젝트 문서 내의 bgColorCode를 불러와서 적용할 때.
2. 두 경우 모두, 바닐라 익스트랙트와는 완전 별개로 존재하는 소스이기 때문에, 바닐라 익스트랙트 테마로 관리하려면 지금까지 만든 colorScheme 전역 상태 로직이나 db 내의 데이터를 모두 뜯어고쳐야 한다. 리소스가 엄청나게 많이 드는 작업은 아니지만, 구현 시간을 고려할 때는 확실히 손해가 더 클 것 같았다.
3. 그리고 colorScheme의 경우, 스타일 사용처가 명확히 정의되지 않은 부분이 있다. 백그라운드에 BG 색상을 넣고 color에 TEXT색상을 넣는 로직은 어디서든 재사용이 가능하니 theme으로 정의할 수 있지만, SECONDARY나 ACCENT같은 보조 색상을 뽑아와서 내 맘대로 적용하는 경우가 더러 있어서, 그런 부분까지 theme으로 세부적으로 적용하면 스타일 별로 들여야 하는 공수가 상당하겠다고 생각했다.
결국 colorScheme을 적용할 때나 프로젝트 문서의 bgColorCode를 사용할 때에만 인라인 스타일링을 하기로 했다.
또한 colorScheme이 적용되어야 하는 부분에서 인라인 스타일링이 너무 자주 등장하면 보기 안좋을 것 같아서, 백그라운드에 BG색상을 넣고 color에 TEXT 색상을 넣는 로직만 뚝 떼서 별도의 컴포넌트로 만들어줬다.
import { ReactNode } from "react";
import { useDarkMode } from "../../utils/hooks/useDarkMode";
import { useColorScheme } from "../../utils/hooks/useColorScheme";
interface BackgroundProps {
children: ReactNode;
}
export default function Background({ children }: BackgroundProps) {
const darkMode = useDarkMode();
const colorScheme = useColorScheme();
return (
<div
style={{
color: darkMode ? colorScheme.DARK.TEXT : colorScheme.LIGHT.TEXT,
background: darkMode ? colorScheme.DARK.BG : colorScheme.LIGHT.BG,
width: "100%",
height: "100%",
}}
>
{children}
</div>
);
}
컴포넌트 내의 특정 부분에 반복해서 등장하는 스타일이 있다. ContactsContainer나 ProfileBox, SkillsContainer의 title에 해당하는 부분이 그러한데, 기존에는 단순히 스타일만 갖고 있는 서브 컴포넌트를 만들기엔 좀 어색한 것 같아서 일일히 같은 스타일을 적용했다. (styled-components였으면 <AboutPageTitleSection>
같은 컴포넌트로 만들면 됐을텐데!) 그런데 이렇게 작성해둔 코드를 단순히 1:1로 바닐라 익스트랙트로 옮기다보니, 분명 동일한 스타일인데 여러 군데에 반복해서 등장하는 경우가 좀 눈에 띄게 되었다. 이 부분은 일단 마이그레이션이 끝난 후에 보이는대로 분리해서 적용할 예정이다. (그대로 두면 하나를 수정하려 할 때 세 군데 네 군데를 똑같이 바꿔야 하는 불편이 생기니까!)
마이그레이션은 문명의 충돌이다 (스승 왈)