에셋이 있으니까 이식한다? (그건 이식할 이유가 아니었습니다)
제가 lucide_icons라는 Flutter 아이콘 패키지를 하나 만들고 나서 생긴 부작용이 있습니다.
아이콘 세트만 보면 손이 근질근질해지는 병에 걸렸어요.
이 패키지를 만들면서 웹폰트 아이콘을 Flutter 패키지로 바꿔주는 생성기 파이프라인을 짜뒀거든요.
동작은 이렇습니다. 아이콘 세트의 CSS 파일을 파싱해서 각 아이콘 이름과 코드포인트를 뽑아내고, 폰트 파일(TTF)을 에셋 폴더로 복사한 다음, 그 매핑을 전부 IconData Dart 상수로 자동 생성합니다.
사람이 손으로 아이콘 하나하나 상수를 적는 게 아니라, 스크립트가 CSS를 읽고 통째로 뱉어내는 거죠.
한번 만들어두니까, 다른 아이콘 라이브러리도 CSS랑 TTF만 있으면 명령어 한 줄로 새 패키지가 뚝딱 나오더라고요.
업스트림에서 아이콘이 100개 늘어나든 500개 늘어나든, 저는 그냥 스크립트를 다시 돌리기만 하면 되고요.
그러니 눈에 보이는 아이콘 세트마다 자꾸 이런 생각이 드는 겁니다. "어? 이것도 에셋 있네. 이것도 포트할까?"
(망치를 손에 쥐면 세상 모든 게 못으로 보인다고 하잖아요. 딱 그 꼴이었습니다.)
후보 목록을 만들다가
그래서 2026년 7월에, 제 생성기 흐름을 재활용할 수 있는 아이콘 라이브러리를 쭉 훑는 서베이를 한번 돌렸습니다. "에셋 레이아웃이 호환되는가"를 기준으로 후보를 추렸죠.
그중에 Bootstrap Icons가 있었습니다.
에셋 구조도 맞고, 제 파이프라인에 태우면 그냥 나올 것 같았어요.
심지어 로컬에서 파일럿까지 한번 돌려봤습니다. WOFF/WOFF2를 TTF로 변환하는 경로랑, 숫자로 시작하는 아이콘 이름(1-circle 같은 것들)을 어떻게 처리할지 검증하는 용도로요.
그런데 여기서 정정이 들어왔습니다. 이미 활발히 관리되고 있는 커뮤니티 Flutter 패키지가 있다는 거예요. (제가 확인한 2026년 7월 시점 기준입니다. 지금은 상황이 다를 수 있으니, 궁금하면 직접 pub.dev 들어가서 최근 커밋 날짜를 보세요.)
그 순간, 후보에서 Bootstrap Icons를 빼면서 좀 멋쩍었습니다. 왜냐하면 제가 던지고 있던 질문 자체가 틀렸었거든요.
저는 "내가 이걸 만들 수 있나?"를 묻고 있었는데, 진짜 질문은 "이걸 만들어야 하나?"였던 겁니다.
"만들 수 있다"는 이식할 이유가 아니다
이 둘은 완전히 다른 질문입니다.
업스트림에 에셋이 존재한다는 건, 그냥 재료가 냉장고에 있다는 뜻이에요. 재료가 있다고 해서 그걸로 꼭 요리를 해야 하는 건 아니잖아요. 옆집에서 이미 같은 반찬을 맛있게, 그것도 매일 새로 만들어서 나눠주고 있다면, 내가 굳이 하나 더 만들 이유가 없는 거죠. 설령 내 주방이 더 자동화돼 있고 조리 시간이 더 짧더라도 말입니다.
그래서 규칙을 하나 세웠습니다.
아이콘 라이브러리 이식은 "충족되지 않은 유지보수 필요(unmet maintenance need)"로 정당화된다. 업스트림 에셋이 그냥 존재한다는 것만으로는 부족하다.
잘 관리되는 커뮤니티 Flutter 패키지가 이미 있으면, 내 생성기 흐름이 더 자동화돼 있더라도 그건 새로 만들지 않을 이유가 됩니다. 새 패키지를 하나 더 낸다는 건, 세상에 유지보수해야 할 물건을 하나 더 늘리는 일이니까요. 이미 누군가 잘 돌보고 있는 걸 중복으로 만드는 건, 성의가 아니라 그냥 소음입니다.
게다가 중복 패키지는 은근히 남에게 민폐입니다. pub.dev에 같은 아이콘 세트를 감싼 패키지가 두 개 떠 있다고 생각해보세요. 사용자는 검색하다가 "어? 똑같은 게 두 갠데 뭘 써야 하지?" 하고 멈칫하게 됩니다. 별점과 좋아요는 두 패키지로 쪼개지고, 이슈 제보도 반씩 나눠지고, 결국 둘 다 어중간하게 관리되는 상황이 오기도 하죠. 하나로 모였으면 건강했을 커뮤니티를, 굳이 반으로 갈라놓는 셈입니다. (내가 편하자고 만든 게 남의 선택을 피곤하게 만드는, 좀 얄궂은 구조예요.)
사실 이건 완전히 새로운 원칙도 아니에요. 저는 예전부터 "버려진 패키지의 커뮤니티 포크" 라는 패턴을 따르고 있었습니다. 관리가 끊긴 패키지를 발견하면 포크해서 이어받는 거죠. 이번 규칙은 그 패턴의 확장판입니다. 포트의 근거는 언제나 "업스트림 라이브러리가 존재하느냐"가 아니라 "충족되지 않은 유지보수 필요가 있느냐" 라는 것.
그럼 뭘 후보로 남겼냐면
이 기준으로 다시 걸러보니, 살아남은 후보는 MingCute와 Codicons였습니다.
둘 다 호환되는 에셋 레이아웃을 갖고 있었고, 결정적으로 stale한(관리가 지지부진한) Flutter 패키지 대안만 있었어요.
특히 Codicons 쪽은, 기존 codicon 패키지가 stale이라 관리되는 포트가 정당화됐습니다.
여기서 다시 한번 강하게 못 박아둡니다. 방금 제가 말한 "이건 관리되고 저건 stale하다"는 판단은 전부 2026년 7월에 확인했을 때의 스냅샷이에요. 오픈소스 생태계는 살아 있는 생물이라, 어제 죽은 것 같던 패키지가 오늘 갑자기 메인테이너를 만나 부활하기도 합니다. 그러니 이 글을 읽는 여러분이 실제로 뭔가를 포트할지 정할 때는, 제 판단을 그대로 믿지 마시고 직접 그 패키지의 최근 활동을 확인하세요. 저도 그렇게 확인하다가 Bootstrap Icons에서 한 방 먹은 사람입니다. (누구 말을 믿어요, 눈으로 봐야지.)
자기 후보 목록에서 자기를 빼는 일
그래서 MingCute는 실제로 flutter_mingcute라는 이름으로 출시했습니다.
그리고 그 작업을 하다 보니, 생성기를 특정 라이브러리에만 묶여 있지 않게 손봐야 할 부분들이 보이더라고요.
그 재사용 가능한 개선들은 lucide_icons에 PR #2로 다시 올려서 업스트림에 반영했습니다.
lucide-static 전용이던 생성기를, 다른 아이콘 세트도 받을 수 있게 일반화하는 작업이었어요.
원래는 CSS를 파싱하는 규칙이나 폰트 패밀리 이름이 죄다 Lucide에 딱 맞춰져 있었거든요.
그걸 명령어 인자로 밖에서 넣어줄 수 있게(어떤 npm 패키지를 쓸지, 폰트 패밀리 이름은 뭘로 할지) 바꾸고 나니, 같은 생성기로 MingCute도 Lucide도 돌릴 수 있게 됐습니다.
파일럿을 하나 만들면서 얻은 교훈을, 뿌리가 되는 저장소에 되먹인 거죠.
솔직히 이런 정리 작업은 티도 안 나고 재미도 없습니다. 새 아이콘 패키지 하나 뚝딱 만들어서 "짜잔!" 하는 게 훨씬 재밌죠. (생성기 리팩터링으로 SNS에 자랑할 순 없잖아요.) 그래도 파이프라인 자체에 남아 있던 숙제들 — 버전 비교를 방해하던 Dart 빌드 메타데이터라든가, CSS 코드포인트가 실제 폰트에 다 들어갔는지 확인하는 테스트라든가 — 은 그 뒤로 하나씩 PR로 정리됐습니다. 아이콘 하나 늘리는 것보다, 이런 뒷정리가 결국 더 오래 남더라고요.
안 만드는 것도 실력이다
이 서베이에서 제가 배운 건 코드가 아니었습니다.
만들 줄 아는 것보다, 안 만들 줄 아는 게 더 어렵다는 거였어요. 생성기라는 망치를 손에 쥐고 나면, 안 두드리고 지나가는 게 제일 힘듭니다. 그런데 후보 목록에서 스스로를 빼는 판단 — "이건 이미 잘 돌아가고 있으니 내가 손대지 말자" — 이게 결국 생태계에 민폐를 안 끼치는 길이더라고요.
정리하면 이렇습니다.
- 업스트림에 에셋이 있다 → 이식해도 된다는 뜻이 아니다.
- 이미 잘 관리되는 커뮤니티 패키지가 있다 → 새로 만들지 않을 이유다.
- 이식의 근거는 "만들 수 있음"이 아니라 "충족되지 않은 유지보수 필요" 다.
혹시 여러분도 "이거 내가 만들면 더 잘 만들 텐데" 싶은 오픈소스가 눈에 밟힌다면, 딱 하나만 먼저 확인해보세요. 지금 그거, 관리가 안 되고 있는 게 맞아요? 맞다면 반갑게 손대시고요, 아니라면 그냥 별 하나 눌러주고 지나가는 게 서로에게 이롭습니다. 헤헤.
혹시 제가 놓친 관점이 있거나, "아니 그래도 이런 경우엔 만드는 게 맞지 않냐" 하는 반례가 있다면, 언제든 알려주세요. 저도 아직 제 후보 목록에서 저를 빼는 연습 중이라서요.