프로젝트를 처음 시작하며 어떠한 배경에서 @bcsdlab/koin, @bcsdlab/utils 이 2개의 라이브러리가 탄생하게 되었는지, 각각의 라이브러리에 이후 어떤 것이 담겨 나갈 것인지, 라이브러리 사용자들에게 효과적으로 전달하기 위해 했던 고민들, 마지막으로 진행하며 했던 고민 점과 느낀 점을 간략하게 적어보았습니다.
매년 들어오는 동아리 원들과 새로운 프로젝트를 진행할 때마다 만들어야하는 공용 함수 등과 같은 부분을 라이브러리로 개발을 미리 해둔다면 진행하고 있는, 진행 될 프로젝트의 다이어트 뿐만 아니라 동아리원들에게 인수인계파트에서도 분명 이점이 있을 것이라고 생각이 되었습니다.
따라서 우리들이 제공하고 있는 서비스의 도메인에 맞는 우리들만의 라이브러리와 앞으로의 개발에서 사용될 수 있는 범용적인 라이브러리를 개발해보자!라는 목표를 세워보았습니다.
현재 동아리에서는 Koin이라는 한국기술교육대학교 커뮤니티 서비스를 제공하고있습니다.
해당 프로젝트에서 사용하는 utils 함수나 훅 model등을 라이브러리로 분리해 개발을 하게 된다면 프로젝트의 다이어트를 성공시킬 수 있지 않을까? 라는 생각이 들었습니다.
또한, 이후 동아리에서 진행할 다양한 프로젝트에서 사용 가능한 라이브러리를 만들어보자!라는 2가지의 큰 틀을 가지고 이 프로젝트를 진행하게 되었습니다.
따라서
위와같은 구조로 사용할 수 있게 만들자는 목표를 세우게 되었습니다.
: github action 활용 및 b-bot 활용
라이브러리 개발을 진행하며 main에 pr이 merge가 되는 순간 npm version patch, npm publish 등의 npm 배포 작업이 필요했습니다. 따라서 이 부분을 github action을 통해서 구현하기로 하였습니다.
이후 나아가 해당 라이브러리를 사용하고 있을 동아리에 어떻게 전달을 하면 좋을까? 라는 고민이 생겼습니다.
배포를 할 때마다 슬랙에 공지를 보내는 것도 할 수는 있겠지만 이 부분은 동아리에서 현재 개발하여 사용하고 있는 슬랙봇인 B-Bot을 활용하기로 하였습니다.
삐봇(B-Bot)에게 공유가 될 때에는
- 어떤 패키지의 수정 혹은 업데이트가 생겼는가?
- 어떤 pr인가?
- 어떻게 라이브러리 업데이트 및 추가하는가?
- 어떤 내용 추가 되었는가?
위와 같은 내용과 함께 공유가 되었으면 좋겠다는 요구가 있었습니다.
따라서 삐봇(B-Bot)에게 post 요청을 보내는 부분에 pr의 title과 link 등을 담아 표시하고, merge가 일어난 레포지토리를 확인하여 어떤 패키지의 업데이트가 있던 것인지도 나타낼 수 있었습니다.
패키지의 수정이 업데이트인지, 수정인지를 나타내기 위해 pr의 태그 기능을 활용할까라는 의견도 있었지만, merge가 되는 시점과 해당 정보를 가져오는 부분에서 어려움이 있어 pr title의 컨벤션을 통해 사용자가 pr title만 보아도 구분이 가능 할 수 있게 진행하였습니다.
npm pubilsh가 되고 나서 슬랙에 공지가 위와 같은 모습으로 보내집니다.
이후 실제 프로덕트에서 사용하는 모습
라이브러리를 만들어보았던 경험이 없었고 npm에 배포를 어떻게 하는건지 경험이 없는 상황에서 초반 정보 수집부분에서 시도와 실패의 시간이 많았던 작업이었습니다.
처음 배포를 하고 나니 이후에 수정을 하여 나은 패키지를 만들어 나갈 부분들이 보이기 시작했습니다.
더 나은 패키지를 위해 고민이 되었던 부분들은 아래와 같습니다.
1) 모듈 시스템인 CommonJS, ECMAScript Modules(이하 CJS, ESM)라는 두 가지를 모두 지원하고자 한다.
2) typeScript를 사용하여 진행하는 프로젝트가 대부분인 지금, 라이브러리 또한 Ts를 지원하게끔 만들어보고자 한다.
3) Pr을 올리고 main에 머지가 되는 순간 자동 npm 빌드 및 배포가 되며 수정 사항을 유저들이 빠르고 효율적으로 알 수 있었으면 좋겠다.
위의 큰 고민 3가지의 문제점과 고민점 해결점을 적어보려합니다.
CJS, ESM 지원
두가지의 모듈 시스템은 간단하기 이야기 하자면
CJS: require / module.exports
ESM: import / export
방식을 사용하고 있습니다. 두 방식의 장점과 차이점이 있지만 현재 Koin에서는 99%가 ESM을 사용하여 개발하고 있었습니다. 그러나 굳이 둘중 하나만을 선택히야할까? 라는 생각이 한편으로 들었습니다.
결국 두가지 방식 모두를 지원하고 사용자가 원하는데로 사용할 수 있으면 되는 것이지 않나?라고 생각이 되어 둘 다 지원하는 방식으로 결정하였습니다.
esm을 지원하기 위해서는 package.json에 "type": "module"추가해야합니다.
type field의 기본값은 "commonjs" 이고, 이 때 .js 는 CJS로 해석됩니다.
다른 값으로는 "module" 가 있습니다. 이 때 .js 는 ESM으로 해석됩니다.
이는 ts에서도 동일하게
type field가 "commonjs" 인 경우, .ts 는 CJS로 해석됩니다.
type field가 "module" 인 경우, .ts 는 ESM으로 해석됩니다.
.cts 는 항상 CJS로 해석됩니다.
.mts 는 항상 ESM으로 해석됩니다.

그러나 하나의 패키지가 CJS/ESM을 동시에 제공할 수 있는가?에 대한 질문에는 패키지의 entry point를 지정하여 가능하다라는 답이었습니다.
package.json 폴터의 exports filed를 사용하면 똑같은 import path에 대해 특정 조건에 따라 다른 모듈을 제공할 수 있습니다.
cjs 방식인 require일때는 ./dist/index.cjs | esm 방식인 import 일때는 ./dist/index.cs/ typescript 지원을 위한 ./dist/index.d.js
파일 명 관련은 TypeScript 지원에서 다룰 예정.
참고 글:
https://toss.tech/article/commonjs-esm-exports-field 위 글을 참고하며 해결할 수 있었습니다.
TypeScript 지원
현재 동아리에서 진행하고 있는 모든 프로젝트는 ts로 진행이 되고 있습니다.
따라서 js만을 지원하는 라이브러리라면 실질적으로 사용에 매우 어려움이 있다라고 판단되었습니다.
타입스크립트 패키지 자체는 빌드 결과물에는 포함되지 않아도 되기 때문에 devDependencies에 설치한 후 그리고 tsconfig.json 파일을 생성해 설정 값을 수정해주었습니다.

위 부분에서 declaration 와 outDir 를 중요하게 볼 필요가 있었습니다.
declaration: 타입스크립트가 자동으로 타입정의 (d.ts) 파일을 생성해 준다는 것
outDir: 컴파일된 결과물을 어디에 저장 할지에 대한 것을 명시해 주는 것
ts는 해당 라이브러리가 ts를 지원하는지 하지 않는지를 d.ts(타입정의 파일)를 찾아 결정하기에 declaration를 true로 설정해야합니다.
그 후 빌드 시 ts로 컴파일을 해주어야하기에 package.json에 build script를 작성하였다.

그 후, 컴파일 된 파일에 맞게 컴파일 결과물인 dist/index.js 을 main에 넣어주며, package.json의 export 필드도 수정을 해주면 ts 지원을 할 수 있는 라이브러리를 개발 할 수 있게 됩니다.
export에 각 속성들이 존재할 때 types가 제일 위에 와야합니다. exports 필드가 순서 기반으로 동작하기 때문에 타입스크립트를 사용하는 환경에서는 d.ts 파일이 먼저 참조돼서 타입 유형을 제공해야 합니다.
https://junghyeonsu.com/posts/deploy-simple-util-npm-library/#typescript-%EC%A7%80%EC%9B%90%ED%95%98%EA%B8%B0 위 글을 참고하며 해결할 수 있었습니다.
자동 배포
라이브러리 개발을 마친 뒤, tsc를 통해 타입스크립트를 컴파일 돌려줍니다.
이후 npm빌드 후, version을 올리기 위한 patch, 배포를 위한 publish 등의 작업이 필요했습니다. 그러나 이런 작업을 개개인이 진행하기 보다는 자동으로 진행 되기를 원했습니다.
따라서 Github Action을 통해 해당 과정을 자동화 해보자! 라는 결론을 내보았습니다.
npm이 배포가 되어야 하는 시점은 수정 사항을 담고 있는 pr이 main에 merge되는 순간에 함께 배포가 되어야합니다. 따라서 시점을 pr이 merge 된 시점으로 지정하였습니다.
해당 시점에서 진행되어야하는 목록들은 아래와 같았습니다.
- npm 빌드
- npm version patch
- npm publish
그러나 하고 싶은 것을 위해 고려해야하는 부분들이 있었습니다.
npm 으로 publish를 하기 위해서는 npm의 고유한 token이 필요해 중간 과정에서 npm token을 확인 하는 절차가 필요했습니다.
version patch와 관련한 commit을 git에 올리려다보니 git에 대한 고유 유저의 name과 email을 입력해야하는 부분도 필요했습니다.
action을 통한 배포가 이루어지니 romote 레포지토리에 해당 부분의 기록이 남지 않는 이슈가 있어 로직에 커밋을 하는 부분 추가가 필요했습니다.
위와 같은 요소들을 하나씩 보안해가며 진행하니 pr을 merge하는 순간 action을 통해 npm이 배포되는 것을 확인할 수 있었습니다.
그러나 개발을 진행하고 보니 새로 배포가 된 라이브러리를 사용자에게 효과적으로 공지하는 법에 대한 고민이 들기 시작하였습니다.
라이브러리는 업데이트가 되었지만 사용자가 알지 못한다면 사용하는 의미에 대해서 다시 생각해봐야하는 순간이라고 생각되었습니다.
현재 동아리에서는 B-Bot이라는 슬랙봇을 활용하고 있는 상태였습니다. 따라서 위 action에서 B-Bot에 새로 배포가 되는 패키지에서 중요한 정보를 담아 호출하는 로직을 추가하면 사용자들에게 효과적으로 전달 할 수 있을 것이라고 생각하였습니다.
- 레포지토리명을 활용하여 어떤 패키지에서 업데이트가 일어났는지 확인
- pr 링크를 활용하여 빠르게 접근 가능
- 업데이트 or 에러 수정 등 배포 목적 확인
위와 같은 기능을 수행하는 호출을 하고자 하였습니다.
github의 token을 활용하여 해당 pr의 정보를 가져와 pr의 title와 link, repository의 제목을 담아 B-Bot을 호출하여 backend 측에서 해당 자료를 통해 슬랙에 호출을 하는 방식으로 사용자에게 업데이트 정보를 알릴 수 있었습니다.
그러나 해당 배포가 패키지의 업데이트인지 에러 수정인지 목적이 명확하게 들어나면 좋겠다는 생각을 하였습니다.
해당 부분을 해결하기 위해 pr을 작성할때 라벨을 통하여 구분을 하는 것이 좋겠다고 생각하였습니다.
그러나 해당 방식을 구현하기 위해서 label이 붙게 되는 순간과 값을 가져오는 부분에서 매끄럽지 못하다는 판단이 들어 pr의 title을 작성하는 컨벤션을 정하여 pr의 title을 확인함으로 해당 배포가 어떤 것을 담고 있는지 파악 가능하게 진행하는 방식으로 진행하였습니다.
예시)
[type] title
peerDependency
개발 후 peerDependency라는 부분의 피드백을 받았습니다.
peerDependency를 이야기 하기 전 Dependencies와 dev Dependencies 에 대해 간략하게 이야기 해보려 합니다.
Dependencies: 앱에 종속된 가장 일반적인 종속성. 런타임과 빌드타임, 개발중 모두에서 이 종속성 패키지들이 필요하기 때문에, 앱이 빌드 될 때 이 종속성 패키지들이 번들에 포함되어 배포.
dev Dependencies: 런타임에서는 필요하지 않고 빌드타임 & 개발중에만 필요한 패키지. 빌드타임에서 이 종속성들은 빌드에 도움을 주거나 참조가 되지만, 번들에는 포함되지 않는 종속성 패키지.
그렇다면 peer Dependencies란 무엇일까요
peer Dependencies: 실제 패키지에서 import(필요하지)않지만 호환성을 위해 가지고 있는(명시하는) 종속성
이 말을 라이브러리 종속성에 예시를 들어 설명을 해보겠습니다.
- @bcsd/koin 라이브러리가 axios의 버전을 1.4.6을 가지고 있습니다.
- 그러나 Koin프로젝트는 현재 1.3 버전의 axios를 사용하고 있습니다.
이런 상황에서 Koin프로젝트에 @bcsd/koin 을 install 해서 패키지 설치한다면 axios의 버전 2개가 모두 설치 될 것입니다.
버전이 다른 동일한 패키지를 설치하는 것은 어쩌면 이후 버전 차이로 인한 큰 이슈를 야기할 수 있는 문제입니다.
따라서 이는 peer Dependencies로 @bcsd/koin의 axios의 1.x.x 버전을 설치하게 된다면
@bcsd/koin 라이브러리를 사용하게 될 프로젝트에게, axios ^1.x.x 버전을 사용해달라고 이야기 하는 것과 동일한 기능을 하게 됩니다.
위에서 설명한 peer dependencies를 사용하게 된다면 보다 성공적인 패키지 다이어트를 성공할 수 있게 되었다
지금껏 해보지 않은 작업을 처음 시작한다는 것은 걱정도 많았지만 팀원들과 상의하며 진행했던 경험이 매우 좋은 기억으로 남는 것 같습니다.
라이브러리를 만든다는 것은 DX (Developer Experience )즉 이것을 사용하는 다른 개발자들의 경험도 파악하게 되는 순간이 되었습니다. (cjs 방식이 아닌 esm 방식으로 불러온다 등)
UX/UI, 사용자 관점에서의 고민이 중심이었지만 이제는 다른 개발자들의 경험에도 많은 관심을 가지게 된 계기가 되었습니다.
패키지의 업데이트가 있을때마다 다른 사용자들에게 어떻게 공지 및 전달을 하는 것이 좋을까?라는 유저 관점에서의 고민을 꾸준하게 했던 것이 기억에 남는 작업입니다.
이는 동아리 내의 슬랙봇을 활용하여 공지를 하는 것으로 정하고 공지에는 어떤 내용을 담아야 사용자가 단번에 이해할 수 있을까?라는 세부 목표를 수행하기 위해 팀원들과 다양한 방면으로 pr 템플릿과 컨벤션의 소통하며 완성도를 높이기 위해 노력했던 것이 좋은 경험으로 남았습니다.
개발에 참고한 글
https://junghyeonsu.com/posts/deploy-simple-util-npm-library/
https://toss.tech/article/commonjs-esm-exports-field