Skip to content

npm package 첫 배포와 그 후의 고도화에 대한 회고록

MyeonghoonNam edited this page Oct 24, 2024 · 1 revision

최근에 테스트 코드와 관련하여 vite 번들러에 대해 공부하다가 CLI(명령줄 인터페이스: Command-Line Interface)를 멍때리며 보다보니 문득 이런 생각이 떠올랐습니다.

“내가 쉽게 사용했던 npm 패키지들은 어떻게 내가 편리하게 사용할 수 있었는가?”

이를 토대로 너무나 당연스럽게 여겼던 npm 패키지들, 더 자세히는 nodejs를 기반으로 동작하는 모듈들을 패키지화하여 저장하고, 불러오고, 실행하는 일련의 과정들에 대해 궁금해졌습니다.

당시에 구현해보고자 했던 사다리 게임을 주제로 하여 CLI에서 동작하는 버전으로 시작하여 웹 버전 확장을 목표로 정하였습니다.

이번에 좀 더 깊게 해보고 싶은 공부 방법은 기획에서부터 시작하여 개발 - 배포 - 고도화의 과정을 거치면서 잠깐의 서치를 통한 해결이 아닌 해결과정에 대해 회고를 통해 내 생각을 정리한 글로 남기는 것입니다.

이 과정에서 기존에 알고 있었던 방법론들에 대해서도 복습하고 새로 학습한 방법론들에 대해서도 깊게 이해하고 싶었습니다.

결론적으로 어떤 결과를 낼 것인지가 아니라, 무엇을 배웠는지에 대한 과정에 집중하고 싶었습니다.

패키지 기획

최근들어 개발자의 역할에 대해 단순히 코드를 구현하는 것이 아니라, 프로그래밍을 통해 해결하고자 하는 문제가 무엇인지 정의하는 것에 대해 많이 고찰해보게 되었습니다.

결국 문제를 해결하려면 올바른 문제의 정의가 필요하다는 뜻인데, 그러기 위해서는 구현 이전에 올바른 분석과 설계가 선행되어야 한다고 생각합니다.

먼저 사다리 게임이라는 큰 틀을 정하였으니 CLI 버전을 개발하기에 앞서 기능명세와 UML을 활용한 여러 다이어그램을 작성하고자 하였습니다.

구체적인 문서화를 통해 올바른 문제의 이해와 더불어 이 프로젝트를 소개할 때 개발을 잘 모르는 사람이 보더라도 이해하기 쉬운 문서들을 남기는 효과를 기대하기 때문입니다.

요구사항 정의

요구사항 정의서(CLI버전)

요구사항 정의를 통해 기능명세를 작성하려고 하니 어느 정도로 구체적인 정의를 시작해야 할지에 대한 고민에 빠졌습니다.

사다리 게임이 세상에 존재하지만 나만의 관점에서는 무에서 유를 만들어가는 과정인 만큼 구현 이전에 필수 기능과 제약조건들에 대해 생각나는 것들을 먼저 작성해보았습니다. 이에 대한 고민의 시간이 너무 길어져 시작이 힘들어진다면 이는 비용 낭비라고 생각합니다.

그래서 결국 내가 생각하는, 흔히 사용되는 사다리 게임의 필수 기능들에 대해 탑-다운 방식의 형태로 큼지막한 부분부터 시작하여 개발 이후에도 계속적으로 요구사항을 수정하고 구현하는 형태로 접근하였습니다.

UML 다이어그램 작성

UML에는 다양한 정적, 동적 다이어그램들이 존재합니다.

구현할 코드에 대해 행위를 단순화 하여 표현하기 위해 어떠한 다이어그램을 작성할지에 대해 고민하였습니다.

현재의 목표는 nodejs기반의 CLI환경에서 사용자와의 대화형 애플리케이션 개발입니다.

그리고 작성해둔 요구사항을 취합했을 때 단일 시스템(CLI)과 사용자 사이의 상태를 기반으로 게임의 핵심 진행과정을 나타낼 수 있는 행위 다이어그램을 표현하는 것이 좋겠다고 생각하였고, 상태 다이어그램을 선택하게 되었습니다.

기능적으로 사용자와 대화형으로 동작하는 CLI는 아래와 같은 과정으로 설계하였습니다. 유효한 정보에 따라 사다리게임 진행여부가 정해집니다.

image

사용자로부터 유효한 정보를 다 입력받았다면 본격적인 사다리타기 게임이 진행되어야 합니다. 사다리타기 게임 진행은 아래와 같은 구조로 설계하였습니다.

image

패키지 개발

CLI를 통한 대화형 기능

CLI를 통해 사용자와 대화형으로 질문을 주고받기 위해 readline 패키지를 활용하였고, storybook, create-react-app, create-next-app과 같은 패키지들을 사용했을 때 콘솔에 색상 디자인이 다양했던 기억이 났고, 저 역시 다양한 색상의 콘솔을 제공하고 싶어졌습니다.

그리하여 ANSI escape code에 대해서 알게되었고, 이를 활용해 게임의 첫 인트로 콘솔과 대화형 콘솔의 강조 색상들을 지정하여 보다 가시적인 콘솔을 제공할 수 있었습니다.

대화형의 콘솔 기능을 구현하기 위해 nodejs의 readline 모듈을 활용하였습니다. 개인적으로 readline 모듈은 알고리즘 문제를 제공해주는 다양한 사이트에서 입출력 제어로 활용하고 있는 것을 보았습니다. 그래서 좀 더 친숙했고 질문을 제공하는 기능을 구현하기 위해 question 메소드를 활용하였고, 사용자로부터 플레이어 수와 플레이어 수 만큼의 출발지 명과 도착지 명을 입력받도록 구현하였습니다.

사다리타기 게임 진행 및 결과 출력 기능

UML 다이어그램을 토대로 흐름도를 간단히 작성해보면 아래와 같습니다.

  1. 사다리 구조 초기화(빈 발판)

  2. 사다리 구조 형성(랜덤한 발판 형성)

  3. 사다리 구조 검증

    검증실패 : 1단계 반복

    검증성공 : 4단계 진행

  4. 플레이어 별 사다리타기

  5. 결과 출력

다이어그램을 작성하여 흐름도를 설계해보니 필요 기능별 함수를 분리하는데 수월했습니다. 그 결과 각 기능함수에 대한 로직만 고민할 수 있는 좋은 설계가 진행된 것 같습니다.

다음으로 각 기능들을 구현하며 고려했던 점을 회고하려고 합니다.

사다리 구조 초기화 기능

사다리 구조에 대해서 너비와 높이가 있는 형태로 사용자에게 나타내려 했기에 2차원 배열을 통해 표현하기에 적합하다고 생각하였습니다.

사다리 높이의 경우는 임의로 5칸으로 고정하였고 사다리 넓이의 경우 참가자 수에 따라 확장되도록 하였습니다. 플레이어 별 일자의 사다리가 필요하고 일자 사다리 사이사이의 발판의 공간이 필요하다 판단하여 너비는 2 * 플레이어수 - 1의 점화식을 만들었습니다.

아래는 초기 사다리 구조에 대한 출력 결과입니다.

// 초기 사다리 구조
// 참가자 3명의 경우
// 높이 5, 너비 5 = 2 * 3(참가자) - 1
1   2   3
|   |   |
|   |   |
|   |   |
|   |   |
|   |   |
1   2   3

사다리 구조 형성(랜덤 발판 생성)

초기 사다리구조에서 1자 사다리 사이의 간격에 랜덤한 위치, 랜덤한 종류의 발판을 채워넣으려고 고민했습니다.

사다리발판 종류를 모두 모아놓은 배열을 토대로 random 메소드를 활용하여 사다리 구조를 형성하였습니다.

위치 정보의 경우 2차원 배열의 너비와 높이의 범위에서 Math.random() 메소드를 활용하여 추출하였고 조건문을 통해 랜덤 위치에 발판이 없는 경우에만 발판을 채워넣을 수 있게 고려하였습니다.

// 초기 사다리에서 발판을 추가한 모습
|\-\|   |
|---|/-/|
|   |---|
|\-\|   |
|---|\-\|

사다리 구조 검증

형성된 사다리 구조에 대해서 요구사항 정의를 통해 구성했던 예외처리에 대한 조건부 처리를 통해 사다리 구조를 검증하려고 하였습니다.

// 초기 사다리에서 발판을 추가한 모습
|   |   |
|---|---| // 연속된 1자 발판이 존재하므로 검증 실패
|   |   |
|\-\|/-/| // 우하향 + 좌하향 연속 발판은 검증 실패
|/-/|\-\| // 좌하향 + 우하향 연속 발판은 검증 실패

플레이어 별 사다리타기

현재까지 다루고 있는 2차원 배열은 사다리 발판 종류에 대한 문자열로 구성되어 있었습니다. 이 배열은 출력용으로 활용하기 유용하지만 직접적인 사다리 이동에는 좀 더 개선이 필요하다 생각했습니다.

제가 생각한 방법은 사다리 연결정보를 포함한 배열을 통해 백트래킹 알고리즘을 적용하여 하나의 출발지에서 하나의 도착지로 1대1 대응이 가능한 효율적인 사다리게임 이동로직의 구현입니다.

그래서 결국 필요한 데이터 구조는 기존의 문자열 2차원 배열을 참조하여 사다리의 구조를 0과 1로 표현하는 배열이 필요하다고 판단하였습니다.

문자열로 구성된 기존 사다리 구조 배열을 반복문으로 순회하면서 3배 크기로 확장하여 사다리에 해당하는 부분을 1, 사다리에 해당하지 않는 부분을 0으로 표현하여 탐색이 가능하도록 하였습니다.

3배로 확장하고자 한 이유는 대각 이동이 가능한 발판들과 일자형 발판들을 표현하기에있어서 3*3 크기로 표현하는 것이 가장 직관적이다고 판단하였기 때문입니다.

// --- 일자형 발판
10001
11111
10001

// \-\ 우하향 발판
11001
10101
10011

// /-/ 좌하향 발판
10011
10101
11001

이를 토대로 아래와 같이 새로운 사다리 구조 배열을 생성하였습니다.

// 기존 문자열 사다리 구조
|   |/-/|
|---|   | 
|   |\-\|

// 사다리 이동 기능을 위해 새로 확장한 배열
// 기존 사다리의 발판 정보에 따라 너비와 높이를 각각 3배씩 확장
100010011
100010101
100011001
100010001
111110001
100010001
100011001
100010101
100010011

이렇게 생성된 배열을 토대로 플레이어들의 시작 위치를 기반으로 백트래킹 알고리즘을 재귀 함수 형태로 구현하였습니다.

그 후 가장 고민한 문제는 현재 위치에서 어느 위치의 사다리 정보를 확인하는지에 대한 조건 처리였습니다. 기본적으로 사다리게임은 사다리 발판에 위치하지 않았다면 아래로 이동하는 특징을 고려하여 좌, 우, 하의 3가지 방향을 우선 탐색하고 대각 4가지 방향을 탐색하였습니다. 여기서 이전에 이동해온 경로의 방향을 토대로 다음 이동 위치에 대한 예외 조건을 처리하였습니다.

게임 결과 출력

모든 플레이어의 사다리 이동 종료 후 게임 결과에 대해 출발위치, 사다리 구조, 도착위치를 알려주기로 설계하였으므로 시작위치로 부터 어떠한 사다리를 통해 어떠한 위치에 도착했는지를 아래와 같이 보여주었습니다.

1   2   3
|\-\|\-\|
|\-\|\-\|
|   |---|
|\-\|   |
|---|\-\|
3   1   2

최종적으로 아래와 같이 게임을 진행할 수 있습니다.

image

패키지 배포

이제 초기 기획에 대한 개발이 완료되었으므로 나의 패키지를 npm에 배포하려고 합니다. npm 공식문서와 여러 기술 블로그들을 참고하며 다시금 package.json 파일의 속성들과 npm에 패키지를 배포하는 방법에 대해 공부하는 시간을 가질 수 있었습니다.

npm linknpm publish 를 활용하여 로컬 테스트 후 배포를 할 수 있었고 배포된 패키지를 npx 명령어로 실행 결과를 확인할 수 있었습니다.

이렇게 최초 배포를 완료하는데에 상당히 많은 공부를 하게 되었습니다.

패키지 고도화

첫 배포 후 npm 다운로드 수가 짧은 시간에 400회가 넘어가길래 깜짝 놀랐습니다. 고도화 시킬 부분들을 계속해서 찾아가던 중에 이러한 상황이 발생하여 나의 코드에 혹시라도 피해를 보는 사람이 없어야 한다는 책임감에 좀 더 시간을 투자하여 고도화에 집중하였습니다.

최신 리뉴얼된 eslint와 prettier 설정 추가하기

개인 프로젝트로 시작하였고 나의 로컬 환경에는 이미 여러 lint 설정들이 적용되어 있어 불편하지 않았지만 추후에 협업으로 개발 확장성을 넓힌다고 한다면 고유의 설정이 필요하겠다라는 생각이 들었습니다.

그리하여 설정들을 추가하는 과정에서 eslint 최신 버전에 대한 새로운 내용들을 학습하게 되었습니다. 기존에는 .eslint.rc 파일을 활용하였는데 eslint.config.js 로 대체되었고 이 파일에 eslint와 prettier를 혼합하는 과정에서 많은 에러들을 해결해나갔는데 역시 최신버전이다 보니 자료들도 적어 꽤나 시간이 걸렸습니다. 결국 해결방법을 서치하여 문제를 해결할 수 있었음에 뿌듯했고 최신버전의 eslint 설정도 어느정도 가능해진 것 같습니다.

Typescript 마이그레이션과 JS module 시스템

기존 패키지는 javascript cjs 버전으로 개발하였습니다. 이는 패키지가 브라우저가 아닌 nodejs 환경에서 중점적으로 동작하기에 선택하였습니다.

하지만 개발 당시에도 cjs 모듈의 동적 임포트에 의해 불러온 모듈들의 타입 추론이 불가능하여 개발에 번거로움이 있었습니다. 그리하여 추후 기능 확장에 대한 개발 편의성 증진을 위하여 타입스크립트를 도입하기로 하였고 나의 패키지가 여러 환경에서 호환이 되는 모듈로 개선하는 경험을 하고 싶었습니다.

그리하여 cjs 모듈을 esm 모듈로 전환하고 정적 임포트를 지원하여 타입 추론이 가능하도록 마이그레이션 하였습니다. 이 과정에서 cjs와 esm 모듈을 모두 지원하도록 배포하면 어떨까? 라는 생각을 하게 되었습니다. 하지만 현재 저의 프로젝트의 경우 패키지를 임포트해와서 코드 개발에 사용하는 것이 아닌 create-react-app, create-next-app과 같이 npx 기반으로 CLI에서 즉시 실행하는 패키지이므로 다른 패키지에서 중점적으로 다루려고 합니다.

Github actions를 통한 CI/CD 구축

패키지 고도화를 진행하면서 매번 패키지 버전을 올리고 패키지를 배포하는 과정이 반복되는 점을 인지하기 시작했습니다.

패키지 버전이 변경된다면 npm 패키지에 배포하는 자동화 시스템을 구축하고 싶었습니다. 그 과정을 Github actions의 workflow로 개발하고 싶었고 이 과정에서 공식문서를 통해 Github actions CI/CD에 대해 공부할 수 있었습니다.

actions/checkout@v3, actions/setup-node@v3 , JS-DevTools/npm-publish@v2 와 npm 토큰 정보를 활용하여 npm 배포 자동화를 구축하였습니다.

번들러를 통한 배포용 파일 구성하기

고도화도 계속해서 진행하고 npm 패키지의 code를 보니 의문이 들기 시작했습니다. 이 모든 코드들이 npm 패키지에 배포될 필요가 있는가? 입니다. 기능 동작에 기여하는 코드들이 아닌 개발 편의성 증진을 위한 코드들도 상당수 포함되어 있었기 때문입니다.

처음엔 오픈소스생태계에 코드를 올리는 것이니 괜찮지 않을까? 라고 생각하였는데 그 범위를 줄여 오히려 본질적인 코드에만 집중하는 것이 더 도움이 될 것 같다라는 생각이 들었습니다.

그리하여 npmignore를 통해 여러 구성 파일들을 생략하여 배포해야겠다고 생각하였습니다. 그리고 타입스크립트를 통한 컴파일 및 빌드 과정에 대한 결과물들에 대해 지저분하지 않게 통합된 형태의 배포용 파일도 제공하면 어떨까? 라는 생각이 들었습니다.

우선 다른 유명한 패키지들은 어떠한 형태로 패키지들을 배포하는지 궁금해졌고 axios, react 등의 패키지 코드 내역에서 package.json의 build 관련 스크립트들을 참고해보았습니다.

다양한 번들러도구들을 활용한 스크립트 내용들을 확인할 수 있었고, 모두가 동일하진 않지만 기능 동작에 필수적인 개발용 소스코드와 빌드용 소스코드를 모두 배포한 경우를 많이 볼 수 있었습니다.

그렇게 저도 webpack과 같은 번들러들에 대해 공부한 내용들을 활용하여 번들러를 통해 배포하자고 마음먹게 되었습니다.

저는 webpack과 vite 번들러에 대한 사용 경험은 있었지만 다른 많은 번들러의 코드들을 보니 다른 번들러 역시 사용해보고 싶었고 각 번들러들의 장,단점에 대해 다시금 집중할 수 있는 시간을 가질 수 있었습니다.

저는 rollup 번들러를 선택하였습니다. rollup의 효율적인 번들링과 빠른 빌드 속도, 유연성 등이 패키지나 라이브러리에 활용 가능하기에 용이하다고 생각했기 때문입니다.

결론적으로 번들러를 통해 최종적으로 dist 폴더에 배포용 파일들을 구성하였고 타입선언파일들은 types폴더에 통합하였고 js로 컴파일된 파일들은 하나의 index 파일을 통해 불필요한 리소스 낭비를 줄일 수 있게 되었습니다.

마치며

npm에 나의 첫 패키지를 배포하고 고도화하는 시작부터 현재까지의 과정을 회고해보았습니다. 회고 과정에서 나온 키워드들에 대한 구체적인 학습 내용들에 대해서는 ladder-game 리포지토리의 Wiki를 활용하여 계속해서 업데이트하려고 합니다.

개발을 공부하면 할수록 전공시간에 배웠던 소프트웨어 설계의 중요성을 인지하게 되는 것 같습니다. 전공시간에는 단순히 코드 한 줄이라도 더 작성하는 것이 재밌었는데, 요즘에서는 설계의 부실에 따른 개발 결과의 부실에 대한 두려움 때문에 설계 과정을 더 꼼꼼히 하려고 하는 습관이 생기는 것 같습니다.

이번 토이 프로젝트 역시 저런 설계에 대해 좀 더 익숙해지고자 시도한 경험이였는데 여기까지 초반의 목적을 달성한 것도 기분이 좋지만 나의 패키지가 예상치도 못하게 다운로드수가 600을 넘어갈정도로 어느정도 사람들에게 호기심에 의해서라도 사용되어졌다는 점이 너무 흥미롭고 재밌었습니다.

나는 개발자를 직업으로 가져도 괜찮을까? 라는 고민도 많이 했었는데 이렇게 나의 코드들이 누군가에 의해 관심을 받고 사용도 되어진다는 것 만으로도 흥미를 느끼고 더 개선해보고 싶어지는 점이 위안을 주는 것 같습니다.

ladder-game은 CLI 버전에서 출발하였지만 지금의 저는 Web 버전까지 제공하는 버전으로 고도화를 시키는 것으로 목표를 확장하고 있습니다. 항상 개발자가 되기 위한 지식들에 대해 필요 이유를 먼저 경험하고 공부하기 보단 해야하니까 하는 경우가 많았는데 직접 기획하고 관리하는 경험을 통해 좀 더 깊은 학습들을 할 수 있게 되었습니다.