📝 참고: 이 글은 코드 구현보다 문제 인식과 해결 접근법에 중점을 두고 작성되었습니다
회사에서 새로운 프로젝트를 시작한 지 몇 주가 지났습니다. 디자인 시안이 나올 때마다 새로운 아이콘들이 추가되었고, 그때마다 개발자인 제가 해야 하는 작업이 있었죠.
😧 "또 아이콘 추가 작업이네..."
이번에도 디자인팀에서 새로운 페이지 시안이 나왔고, 거기에 들어갈 새로운 아이콘들을 추가해야 했습니다. 한두 개라면 모를까, 이번에도 어김없이 5개가 넘는 아이콘들이 추가되어야 했죠.
매번 새로운 아이콘을 추가할 때마다 이런 과정을 거쳐야 했습니다.
😧 "하... 이걸 또 5번 반복하라고?"
피그마와 VSCode를 왔다 갔다 하면서 같은 작업을 반복하다 보니 지루함을 넘어 짜증이 나기 시작했습니다. 실수도 자주 났죠. 'stroke' 속성을 'color' props로 바꾸는 걸 깜빡한다거나, kebab-case를 camelCase로 바꾸는 걸 놓친다거나...
"이렇게 단순 반복적인 일을 계속 하고 있어도 되나?"
문득 그런 생각이 들었습니다. 개발자란 게 결국 문제를 해결하는 사람 아닌가요? 그런데 이렇게 단순 반복적인 일을 손으로 계속하고 있다니. 뭔가 더 나은 방법이 있을 것 같았습니다.
우리 팀은 아이콘을 이렇게 사용하고 있었습니다.
<Icon type="arrow-right" color="#000000" size={24} />
간단해 보이는 이 컴포넌트 뒤에는 복잡한 구조가 있었죠.
export const IconComponentMap = {
'arrow-right': ArrowRightIcon,
'check-circle': CheckCircleIcon,
// ... 수십 개의 아이콘들
}
export type IconType = keyof typeof IconComponentMap;
이 구조를 유지하면서, 새로운 아이콘을 추가하는 과정을 자동화할 수 있지 않을까? 그렇게 해결책을 찾아 나서기 시작했습니다.
검색 끝에 SVGR이라는 도구를 발견했습니다. SVG를 React 컴포넌트로 변환해주는 도구였는데, 특히 Custom Template 기능이 눈에 띄었죠. 이를 활용하면 우리 팀의 아이콘 컴포넌트 형식에 맞게 자동으로 변환할 수 있을 것 같았습니다.
하지만 SVGR만으로는 부족했습니다. 해결해야 할 문제들이 더 있었거든요.
팀의 상황에 맞추기 위해서 해결해야할 문제 외에는 Custom Template 기능을 그대로 따라갔습니다.
해당 기능이 궁금하시다면 아래 문서를 참고해주세요.
SVGR Custom Template 기능 사용법
가장 먼저 마주친 문제는 파일 이름 처리였습니다. 피그마에서 아이콘을 다운로드하면 Name=ArrowRight.svg처럼 prefix가 붙어있었죠. 이걸 우리 팀 컨벤션인 ArrowRightIcon.svg로 변환해야 했습니다.
// 변환 예시
Name=ArrowRight.svg → ArrowRightIcon.svg
Name=check-circle.svg → CheckCircleIcon.svg
arrow-left.svg → ArrowLeftIcon.svg
단순해 보이지만 여러 엣지 케이스를 고려해야 했습니다. 파일명에 하이픈이 있는 경우, 이미 'Icon'이 포함된 경우, 첫 글자가 소문자인 경우 등 다양한 상황을 처리해야 했죠.
두 번째 문제는 기존 아이콘 파일과의 충돌이었습니다. /src/components/Icon
폴더에는 이미 수십 개의 아이콘 컴포넌트가 있었고, 이 파일들은 건드리지 않은 채로 새로운 아이콘만 추가해야 했습니다. 실수로 기존 파일을 덮어쓰면 큰 문제가 될 수 있었죠.
가장 까다로웠던 건 index.ts 파일 수정이었습니다. 이 파일은 모든 아이콘의 진입점 역할을 하는 중요한 파일이었죠.
// index.ts의 구조
import { SVGProps } from 'react';
import { ArrowLeftIcon } from './ArrowLeftIcon';
// ... 수십 개의 import 구문
export const IconComponentMap = {
'arrow-left': ArrowLeftIcon,
// ... 수십 개의 매핑
};
export type IconType = keyof typeof IconComponentMap;
이 파일에 새로운 아이콘을 추가할 때는 세 가지를 고려해야 했습니다
단순히 파일 끝에 새로운 코드를 추가하는 게 아니라, 파일의 구조를 이해하고 적절한 위치에 코드를 삽입해야 했습니다. 이를 위해 AST(Abstract Syntax Tree) 파싱을 사용했죠.
const ast = parser.parse(content, {
sourceType: 'module',
plugins: ['typescript'],
});
// 마지막 import 구문 위치 찾기
const lastImport = ast.program.body.findIndex(
(node) => node.type !== 'ImportDeclaration'
);
// 새로운 import 추가
ast.program.body.splice(lastImport, 0, /* 새로운 import 구문 */);
이렇게 파일을 파싱하고 수정하는 방식을 택한 이유는, 단순한 문자열 처리보다 훨씬 안전하고 정확하기 때문이었습니다. 실수로 기존 코드를 망가뜨릴 위험도 줄일 수 있었죠.
마지막으로는 생성된 컴포넌트의 일관성 문제가 있었습니다. 모든 아이콘 컴포넌트는 동일한 구조를 가져야 했고, 특히 props 처리 방식이 일관되어야 했습니다. SVGR의 custom template을 활용해 이 문제를 해결했습니다.
이런 여러 문제들을 하나씩 해결하면서, 점점 더 안정적이고 사용하기 쉬운 도구가 만들어져 갔습니다.
결과물은 생각보다 단순했습니다.
$ yarn icons
cli 실행이제 10개의 아이콘을 추가하는 시간이 이렇게 바뀌었습니다.
"어, 이거 진짜 편하다!"
팀원들의 첫 반응이었습니다. 특히 좋았던 점들을 꼽자면
이 작업을 통해 새삼 깨달은 것이 있습니다.
"불편함을 그냥 참지 말자"
개발자로서 우리는 종종 작은 불편함들을 그냥 넘어가곤 합니다. "이 정도는 참아야지", "이런 건 그냥 해야지" 하면서요. 하지만 그 작은 불편함들이 쌓이면 결국 큰 시간 낭비가 됩니다.
특히 인상 깊었던 건 팀원들의 반응이었습니다.
"이제 아이콘 추가하는 게 전혀 스트레스가 안 돼요."
"다른 것도 이렇게 자동화할 수 있는 게 있을까요?"
작은 변화가 팀 전체의 생산성과 사기를 높일 수 있다는 걸 직접 경험한 소중한 기회였습니다.
개발자의 시간은 소중합니다. 단순 반복 작업에 시간을 쓰기에는 해야 할 일이 너무 많죠.
여러분의 일상에도 이런 자동화할 수 있는 작업이 있지 않을까요? 당장은 2-3분밖에 안 걸리는 일이라도, 그게 매일 반복된다면 자동화를 고민해볼 만합니다.
"이 정도는 그냥 해야지"라고 생각하는 그 순간, 잠깐 멈춰서서 생각해보세요.
"이걸 자동화하면 어떨까?"
긴 글 읽어주셔서 감사합니다.