NPM 패키지를 만들어 보았다.

·2023년 8월 2일
28
post-thumbnail

갑자기 왜?

이전 프로젝트를 하면서, 모듈 함수를 구현할 때 마다, 언젠가 NPM 패키지로 배포해봐야지 하고 고민하곤 했었다. 내가 만든 서비스가 돌아가도록 하는 코드가 아닌, 다른 사람이 쉽고 유용하게 사용하는 코드를 만든다는 점에서, 도전해보고 싶은 분야였다.

7월, 마침 상반기 취업이 끝나가고 잠시 신규 채용 소식이 주춤하였고, 기회는 이때다! 하면서 부리나케 시도하게 되었다.

es5+, node.js , 타입스크립트 등 다양한 자바스크립트 환경을 지원하며, 자동완성 기능이 있고, 친절하게 예제도 담으려고 노력해보았다! @beberiche/validator 생애 첫 모듈 패키지의 개발 과정으로 렛츠게릿!

NPM 패키지 준비사항

먼저 실제 패키지를 만들기 전에 NPM 패키지를 올리는 과정 및 어떤 식으로 배포가 되는지에 대해 알 필요가 있다. 아무 생각 없이 했다가, 며칠동안 봉변을 당한 입장으로써, 구글링이나 유튜브로 NPM 패키지 생성에 대해 찾아보고, 이런 저런 시행착오를 겪어보는 걸 추천하다.

유튜브의 생활코딩 님께서 간단한 NPM 패키지 생성에 대한 강의를 올려두셨다. 영상도 길지 않고 NPM 패키지를 개발하는데 꽤 도움이 되었다.

NPM Package - 1. 수업소개

필수적인 참고 사항은 아래에 작성해두었다.

회원 가입과 이메일 인증

NPM 에 패키지를 올리려 한다면, 회원가입과 이메일 인증을 진행해야한다. 이메일 인증을 진행하지 않는 경우 npm publish 진행 시 403 에러를 반환할 수 있다.

npm ERR! code E403
npm ERR! 403 403 Forbidden - PUT https://registry.npmjs.org/json-range-slider-test - Forbidden
npm ERR! 403 In most cases, you or one of your dependencies are requesting
npm ERR! 403 a package version that is forbidden by your security policy, or
npm ERR! 403 on a server you do not have access to.

프로젝트 이름과 버전 사항

패키지를 올리기 이전 이름과 버전 사항을 정해야 한다. 단, 패키지 이름 선정 시 중복된 이름이 존재하는 경우, 올라가지 않을 수 있다. npm 홈페이지에 가서 동일한 이름이 없는지 검색해보거나 npm info [project_name] 을 통해 확인하는 것이 가능하다.

이름 선정 방법 중에, 스코프 패키지를 활용하는 방식이 있다. @types 와 같이 분류 그룹을 작성한다던지, @조직 혹은 @사용자 를 프로젝트 이름 앞에 덧붙여서, 중복 이름을 회피하는 것이 가능하다.

NPM Login

프로젝트 퍼블리싱 이전 커맨드를 통해 로그인을 진행하자. 원래는 커맨드를 통해 유저 이름과 패스워드를 작성하는 방식이었으나, 현재는 이메일을 통해 OTP 인증 코드 확인 방식의 로그인으로 바뀌었다.

npm login

NPM publish

일반 유저의 경우, public 형태로만 NPM 패키지를 퍼블리싱 할 수 있으며, private 패키지의 경우 매달 요금을 지출해야한다. package.json 에 특정 작업을 하지 않았다면, 기본 설정은 private 으로 되어있으므로, publishpublic 설정을 해줘야 한다.

퍼블리싱 시 public을 설정하는 방법은 다음과 같다.

npm publish access=publish

터미널에 다음과 같은 출력과, 성공했다는 이메일이 도착했다면 정상적으로 수행이 완료된 것이다.

패키지 사용해보기

만든 패키지를 설치하는 경우, module.export를 통해 나오는 객체 정보를 받게 된다. 다음과 같이 module.export를 통해 객체를 패키지에서 내본다고 하자.

module.exports = {
  ko: "안녕",
  en: "hi",
};

이 경우 해당 패키지를 인스톨 한 곳에서 require() 를 통해 데이터를 불러와 실행하면 다음과 같은 결과과 출력된다.

const obj = require("@beberiche/hi");
console.log(obj);
node index.js
// { ko: '안녕', en: 'hi' }

모듈 업데이트

퍼블리싱한 패키지를 업데이트하기 위해서는 코드에 변경사항을 적용한 후 package.json 의 버전도 함께 변경하여야 한다. 만약 현재 NPM 리포지토리에 존재하는 버전을 업데이트 하는 경우, 퍼블리싱이 거절 당한다.

패키지를 재 업로드 하는 커맨드는 다음과 같다. 한번 public 리포지토리로 설정되면 다시 설정하지 않아도, 자동으로 public 으로 퍼블리싱 된다.

npm publish

NPM 패키지에 타입스크립트 적용하기

NPM 모듈 패키지를 개발하는데 타입스크립트가 과연 필요한가 라고 묻는다면, 개인적으론 필수적이라고 생각한다. 타입스크립트를 적용하는 것만으로도 작업량이 줄어들기 때문이다.

개발을 해본 사람이라면 라이브러리를 사용할 때 이런 저런 실수를 해본 경험이 있을 것이다. 예컨데, 함수를 호출하는 데 있어 매개변수에 정의된 타입이 아닌 다른 값을 넣거나, 매개변수를 2개 보내야 하는데 1개만 보내는 등의 사례를 예로 들 수 있겠다.

일반 자바스크립트로 개발을 진행하면 이러한 예상가능한 에러를 처리하는 로직을 세울 필요가 있다. 패키지 배포자로써 어떤 점이 잘못되었는지를 사용자에게 안내할 필요가 있다. 이러한 문제를 타입스크립트를 적용시키는 것으로, 자체적으로 오류를 사용자에게 알려줄 수 있다는 장점이 있다.

물론, 사용자도 타입스크립트 프로젝트를 개발할 경우에 한하여 그렇다.
현재는 많은 프로젝트에서 타입스크립트를 사용하지만 만약 사용 접근성을 node.js 나 기본 js 범위까지 늘릴거라면 예상 가능한 에러에 대한 작업도 필수적으로 처리해줘야 할 것이다.

아래의 코드는 개발에 사용한 함수인 lenLimitUnder() 로, 본래 2번째 매개변수에는 길이의 미만 값을 정하는 숫자 값이 들어가야 한다. 만약 자바스크립트로 작성하는 경우라면 다음과 같이 에러를 반환하도록 직접 처리해야한다.

하지만 타입스크립트를 적용하는 것으로 이렇게 빨간 줄로 오류 처리를 자동으로 제공해준다는 점에서 개발을 하는 입장에서도, 사용하는 사람의 입장에서도 훨씬 작업이 용이해진다.

타입스크립트 개발 환경 설정

이번 프로젝트의 경우, 패키지 배포에 주안점을 두었기 때문에, 타입스크립트에서는 타입 검사, 추론 정도의 최소한의 기능만 받아올 수 있는 환경으로 구성했다.

타입스크립트 개발 환경 설정 시 다음의 정보를 참조했다.
UZILOG

패키지 개발에 반영시킨 타입스크립트 설정은 다음과 같다.

타입스크립트 설치하기

최종적으로 올릴 패키지는 번들링한 js 파일 형태로 내보내질 예정이므로, --save-dev 형태로 설치했다.

npm i --save-dev typescript

lint, 프리티어 설정

typescript 개발에 필요한 정적 분석 도구를 활용했다. 중요한 부분은 아니므로 최소한으로만 설정하여 사용했다.

npm i --save-dev prettier tslint tslint-config-prettier

필요한 타입 파일 설정

typescript 에서 cjs 기능을 사용하려면, node 타입에 대한 설치가 필요하다.

npm i --save-dev @types/node

tslint.json 설정

기존 권장 사항에서 사용했으며, 예외사항은 추가하지 않았다.

{
   "extends": ["tslint:recommended", "tslint-config-prettier"]
}

.prettierrc

자동 저장에 따른 포맷팅만 사용할 것이기에 lint 와 동일하게 최소한만 설정했다.

{
  "printWidth": 120,
  "trailingComma": "all",
  "singleQuote": true
}

package.json

package.json에 정적분석도구 커맨드를 추가했다.

"scripts": {
	"build" : "tsc",
  	"format": "prettier --write \"src/**/*.ts\" 	\"src/**/*.js\"",
  	"lint": "tslint -p tsconfig.json",
},

cjs, esm 제어하기

자바스크립트에는 모듈을 불러오는 방법이 2가지가 존재한다. node.js 에서 많이 사용하는 common.jsimport 를 활용하는 es module 이 그 예이다. 두 기능의 특징 차이는 이전 포스팅한 내용이 있으니 참고해보시길 바란다.

CJS 와 ESM

중요한 건 어찌 되었든 두 가지 모두 모듈 패키지를 불러오는 방법이며, 내가 만드는 패키지가 되도록 일반 cjs 든, esm 이든 모두 불러오는 것이 가능하도록 하고 싶었다.

처음에는 cjs, esm 전용의 두 개의 파일을 생성하여 파일별로 별도의 빌드작업을 진행하려 했으나 모듈 번들러를 활용하여, 하나의 파일만으로 다양한 환경의 여러 파일을 생성하는 방법이 있어, 이를 활용해보기로 했다. 이 프로젝트 에서는 rollup 을 활용했다.

rollup 설정하기

우선 rollup 모듈을 설치하고, typescript 빌드 설정, node 모듈 기능, cjs 빌드 기능을 제공하는 플러그인들을 설치했다. cjs 빌드 설정의 경우, 관련 사례를 찾아보니 node 플러그인과 함께 사용해야 더 광범위한 호환이 가능하기 때문에 함께 사용할 것을 권장한다고 한다.

해당 프로젝트에서 사용한 rollup 모듈과 플러그인은 다음과 같다.

# 기본 rollup
npm i --save-dev rollup
# 타입스크립트 빌드 플러그인
npm i --save-dev rollup-plugin-typescript2
# node 모듈 플러그인
npm i --save-dev @rollup/plugin-node-resolve
# cjs 모듈 플러그인
npm i --save-dev @rollup/plugin-commonjs

rollup 을 설치했다면 rollup 관련 설정 내용을 작성해야한다. rollup.config.js 를 생성하여 다음의 설정을 작성해주었다. input 에 빌드할 파일의 경로를 output 에 빌드 결과물에 대한 경로를 작성한다.

특이사항으로는 package.json 의 일부 속성을 참조하여 output 에 반영할 수 있는데, package.jsonimport 하는 경우 반드시 단언 assert 을 통해 type : "json" 을 명시해야 한다.

// rollup.config.js
import typescript from 'rollup-plugin-typescript2';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import pkg from './package.json' assert { type: 'json' }; 

const extensions = ['.js', '.ts'];

/**
 * @type {import('rollup').RollupOptions}
 */
const config = {
  input: 'src/main.ts', // 변환시킬 파일
  output: [	// 변환된 결과물
    {
      file: pkg.main, // 반환 경로 작성
      format: 'cjs',  // 확장자
      sourcemap: true, // sourcemap 설정, 웹 브라우저 로드 시 개발자 도구에서 원본 소스 코드를 보여주며 디버깅이 가능하게 함 
    },
    {
      file: pkg.module,
      format: 'esm',
      sourcemap: true,
    },
  ],
  plugins: [
    resolve({ extensions }),
    commonjs(),
    typescript({
      tsconfig: 'tsconfig.json',
      clean: true,
    }),
  ],
};

export default config;

package.json 설정하기

pkg 의 정보를 받아오는 만큼, package 에도 설정 양식이 필요하다. 기존 ts 컴파일 작업을rollup 으로 이관하여, cjs, esm 에 대한 통합된 결과물을 받아오도록 할 것이다. package.jsonscripts 항목에, rollup 명령문을 작성한다.

# 최상위 디렉토리의 rollup.config.js의 내용을 기반으로 빌드를 실행한다.
rollup -c
"scripts": {
  //"build": "tsc",
	"build" : "rollup -c"
  "format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
  "lint": "tslint -p tsconfig.json",
},

앞서 작성했던 rollup.config.js 에서 pkg 정보에서는 modulemain을 참조하고 있다. 반드시 module , main 으로 써야 하는 것은 아니며, package.json 객체 내에 찾으려는 속성이 존재하여 해당 정보를 불러올 수 있으면 된다. package.json 내부의 해당 속성에 cjsesm 파일을 반환할 경로를 작성한다.

"files": [
	"dist/*.{js,ts,map}"
]
"main": "dist/main.cjs", // cjs로 반환한다.
"types": "dist/main.d.ts", // 타입스크립트로 설정한 타입들이 반환된다.
"module": "dist/main.esm.js", // esm이 반환된다.

rollup 필요한 설정은 끝이 났다. 이후 원하는 코드 작업을 진행하고 build 커맨드를 실행하면 된다, main.ts 를 input 경로로 설정하고, 해당 설정에 따라 build 커맨드를 실행하면 다음과 같은 결과물들이 나오게 된다. -esm.jses module 을 사용하는 경우, 참조되는 파일이고, -.cjsrequire() 를 사용하는 경우, 참조되는 파일이다.

각각 반환된 .cjsesm.js 의 파일을 살펴보면 내보내기 형식이 다르다는 것을 알 수 있다.

-.cjs

-.esm.js

다음은 실제 패키지를 프로젝트에 인스톨하여, 사용하는 모습이다. 보시다시피, import 로도, require 로도 모두 정상적으로 함수를 가져온다.

프로젝트 기능 소개

어느정도의 준비가 완료되었으니 이제 개발을 진행해보자!

만든 NPM 패키지는 @beberiche/validator 로, 사용자 입력 정보를 기반으로 유효성 검사를 진행하는 함수를 제공한다. 예를들어 현재 입력 값에 대한 길이 범위를 제한하거나, 입력 값이 이메일 형식인지, 전화번호 형식인지 등을 확인할 수 있다.

각각의 함수들은 유효성이 정상적으로 검증된 경우에는 true 값을 반환하며, 그렇지 않은 경우는 반환 값은 없고, Error 를 출력하도록 설정했다.

자세한 사항은 깃 리포지토리의 README.md 문서를 확인해보시기 바란다. 참고로 막 그렇게 대단한 건 없다. 엄청난 걸 만들기 보다는 "이전에 구현했던 코드를 패키지로 사용하도록 만들었다" 에 의의를 두는 프로젝트이다.

beberiche/npm-validator-module

개발을 진행하며, 깊이 고민했던 점 혹은 어려웠던 점에 대해 이야기해보도록 하겠다.

사례 1: 비대해지는 함수

길이 제한을 확인하는 함수를 만들려고 한다. 함수에 필요한 것들을 찾아보니, 조건은 다음과 같았다.

  • 입력값이 꼭 있어야 한다.
  • 시작 길이 매개변수가 반드시 포함되어야 한다.
    • 시작 값은 무조건 숫자여야 한다.
  • 끝 길이 매개변수는 선택적으로 존재할 수 있다.
    • 끝 길이가 있다면 이상, 미만의 조건이 설정되어야 한다.
    • 끝 값은 숫자여야 한다.
  • 설정을 위한 오브젝트 매개변수를 만든다.
    • trim 설정을 하는 경우 띄어쓰기(공백)을 제외시킨다.
    • 설정을 위한 오브젝트 매개변수는 선택적이다.

다음의 코드를 구현하려니 함수 하나에 코드양이 엄청 커졌다.

// 길이제한
function lenLimit(inputValue, len1, len2 = undefined, obj = { trim: true }) {
  if (inputValue == undefined || len1 == undefined) {
    return {
      error: "limitError",
      msg: "입력 값 혹은 길이가 정해지지 않았습니다.",
    };
  }

  const val = obj.trim ? inputValue.trim() : inputValue;

  if (len1 > 0 && len2 > 0) {
    if (lenStart == undefined || lenEnd == undefined) {
      return {
        error: "limitError",
        msg: "이상, 미만의 값이 제대로 설정되어 있지 않습니다.",
      };
    }
    if (len[0] > len[1]) {
      return {
        error: "limitError",
        msg: "시작 값이 끝 값보다 커서는 안됩니다.",
      };
    }

    if (val.length() < len[1] && len[0] <= val.length()) {
      return true;
    } else {
      return {
        error: "limitError",
        msg: "시작 값보다 작거나, 끝 값보다 큽니다.",
      };
    }
  } else if (obj.under) {
    if (val.length >= len) {
      return true;
    } else {
      return {
        error: "limitError",
        msg: "주어진 값보다 큽니다.",
      };
    }
  } else {
		...
		...
  }
}

module.exports = {
  lenLimit,
};

이 경우에 가독성도 좋지 않고, 유지 보수나 확장도 힘들다. 더불어 구현하는데 생각이 너무 많아졌다.

해결 1 : 함수 분할하기

광범위한 조건을 하나의 함수로만 해결하려고 했던 점이 코드가 비대해지는 원인이였다. 비대한 함수를 여러 갈래의 함수로 나눠보자. 해당 문제의 경우, 길이 이상 함수, 길이 미만 함수, 길이 범위 함수 등으로 나누어 길이 범위 함수를 사용하는 경우, 길이 이상 함수, 길이 미만 함수를 호출하여 두 가지 유효성 검사를 모두 통과하도록 유도했다.

// 길이 범위
function lenLimit(inputValue: string, len1: number, len2: number) {
  let ret1 = lenLimitMore(inputValue, len1);
  let ret2 = lenLimitMore(inputValue, len2);
  if (ret1 === true && ret2 === true) return true;

  return {
    error: 'limitError',
    msg: '주어진 제한 범위에 입력값이 해당되지 않습니다.',
  };
}
// 길이 이상
function lenLimitMore(inputValue: string, len: number) {
  if (inputValue.length >= len) return true;
  return {
    error: 'limitError',
    msg: '주어진 제한 길이보다 입력 값이 작습니다.',
  };
}
// 길이 제한
function lenLimitUnder(inputValue: string, len: number) {
  if (inputValue.length < len) return true;
  return {
    error: 'limitError',
    msg: '주어진 제한 길이보다 입력 값이 큽니다.',
  };
}

module.exports = {
  lenLimit,
  lenLimitMore,
  lenLimitUnder,
};

사례 2: Error 는 어떻게 처리할 것인가

패키지가 유효성 검사인 만큼, 입력값이나 사용용도에 따라 에러 타입은 다양할 필요가 있다. 다만 사이드 프로젝트 정도의 패키지이고 시간도 많지 않아서, 최소 조건만 설정하도록 적용했다. 내가 생각한 Error 의 최소조건은 Error 객체를 상속하는 customError 클래스를 생성하는 것이다.

customError를 만드는 것으로, 다음 확장시, 또다른 customError 클래스를 만들거나, 클래스에 새로운 속성을 넣는 등, 다양한 확장 접근이 가능하기 때문에, Error 클래스를 생성을 최소조건으로 잡은 것이다.

해결 2: Validation Error 생성하기

Error 객체를 상속하는 Validation Error 를 만든다. 일반적으로 Error 객체는 메시지를 넣어 인스턴스를 생성하는데, Validation Error 의 경우, 메시지와 더불어 errorType 을 명시하도록 했다.

class ValidationError extends Error {
  errorType: string;
  constructor(errorType: string, message: string) {
    super(message);
    this.errorType = errorType;
  }
}

errorType 명시를 통해, 다양한 에러를 다루는 것이 가능해진다. 예를 들어 타입에 대한 설정이 잘못 이루어지면 typeError 를 유효성 조건이 거짓인 경우는 validError 를 출력하도록 했다.

export function isPhoneNumber(inputValue: string): true | void {
  if (typeof inputValue !== 'string')
    throw new ValidationError('typeError', '정의된 데이터 타입과 일치하지 않는 인자가 존재합니다.');

  try {
    const str = inputValue.replace(/-/g, '').trim();
    let regxPhoneNumber = /^(070|02|01[016789]{1}|0[3-9]{1}[0-9]{1})[0-9]{3,4}[0-9]{4}$/;
    if (regxPhoneNumber.test(str.trim())) return true;
    throw new ValidationError('validError', '올바른 휴대폰 번호 혹은 전화번호 형식이 아닙니다.');
  } catch (e) {
    console.log(e);
  }
}

사례 3: 유효성 함수 배열을 받아 순차적으로 실행하는 함수

여러 유효성 함수를 받아, 순차적으로 실행하는 함수를 만들려고 한다. 함수별 인덱스 순서에 따라 우선순위 제어가 가능하며, 연속적인 유효성 검사를 한번의 실행으로 여러 유효성 검사를 실행하는 함수이다.

아이디어는 굉장히 좋았으나, 몇가지 문제가 발생했다.

// 이런 형태의 함수이다.
function go(funcs:Function[]): boolean {
	for(const func of funcs) {
		let ret = func();
		if(ret === undefined) return ret;
	}
	return true;
}

각 함수의 인자는 어떻게 보낼 것인가?

연속적인 유효성 함수들을 인자로 집어넣어 한 번에 사용할 수 있도록 함수 배열로 받도록 했다. 그러나 유효성 함수에 따라 검증에 필요한 인자의 타입이나 수가 각각 다르다. 함수 배열을 통해, 여러 유효성 함수를 보내는 상황에서 함수에 대한 인자들을 어떻게 보내야 하는가를 고민했다.

현재 go() 함수는 유효성 함수들을 반복문으로 실행하기 위해, 함수 배열로 인자를 받으므로, 유효성 함수들이 사용할 인자 역시 배열로 만들어 보았다. 단, 인자가 2개, 3개, … n개 들어가는 유효성 함수도 있을터이니 이를 해결하기 위해, 2차원 배열을 설정하여 보내도록 한다.

인자 배열과 유효성 함수 배열의 길이는 동일해야하며, 각 인덱스에 맞게 진행하기 때문에, 인자 배열 인덱스와 유효성 함수 배열의 인덱스는 동일해야 한다.

function go(args: Array<Array<String | number>>, funcs: Array<Function>) {
  let ret: Result;
  if (args.length !== funcs.length) return reportError('인수 배열의 길이와 함수 배열의 길이는 서로 일치해야 합니다');
  for (let i = 0; i < funcs.length; i++) {
    ret = funcs[i](...args[i]);
    if (ret !== true) return ret;
  }
  return true;
}

// 실행 시
go([
		["asdfasd",4],
		["asdfasdf",5],
		["asdasdf",4,5],
	 ],[lenlimitUnder,lenlimitMore,lenlimit]); 

만드는 것 까지는 좋았으나, 이 시점 부터 유용한 함수라기 보다는 오히려 긴 타이핑 덕분에 괜히 작성하기 꺼려지는 함수로 느껴지기 시작했다.

에러 처리는 어떻게 할 것인가?

유효성 함수별로 지정한 타입이 들어오지 않거나, 예외 상황이 접촉되는 경우는 어떻게 할 것 인가? 인자와 함수에 대한 인덱스 순서도 맞아야 하며, 함수별 인자 수나 타입도 일치해야한다. 더불어 유효성 함수 자체가 많아질수록 제어해야하는 범위는 더욱 커질 것이다. 즉 에러처리를 진행해야할 부분이 너무나 광범위하다고 느껴졌다.

이런 방식보다는 사용자가 원할 때 그냥 2,3개의 유효성 검사를 호출하여 진행하는 것이 더욱 유용하고 깔끔한 사용방식이라고 판단하여 해당 함수 구현은 포기하게 되었다.

JSDoc 으로 사용자 경험 향상시키기

NPM 패키지를 설치하여 원본 코드를 탐색해본 사람이라면 이러한 특이한? 주석을 본 경험이 있을 것이다.

/**
* @constructor
* @param {number} data
*/

아는 사람들은 다 알겠지만 JSDocJavascript 소스 코드에 대한 설명을 하기 위해 사용되는 마크업 언어로 코드에 대한 문서화를 도와주는 도구이다.

사실 패키지dp 만들고 싶은 기능이 인텔리센스 이다. 바쁘다 바빠 한국 사회에서 짜잘한 패키지를 사용하는데 구글링을 하거나 공식문서를 읽어야하는 것은 프론트엔드 개발자로서 용납 할 수 없는 사용자 경험이다.

vscode, webstorm 등 웹 프론트엔드에서 많이 사용하는 IDE 의 경우, 기본적으로 JSDoc을 내장하여 자동완성 기능을 제공하고 있다. 이를 활용하여 나의 패키지를 보다 사용하기 쉽도록 사용자 경험을 높여보자.

JSDoc 작성하기

JSDoc으로 코드에 대한 여러가지 정의할 수 있지만, 반드시 필요하다고 생각되는 요소만 넣어 작성했다.

  • @param : 매개변수의 타입 및 설명을 나타낸다.
  • @returns : 함수의 반환 값을 나타낸다.
  • @description : 함수에 대한 설명을 나타낸다.
  • @example : 해당 코드에 대한 예제를 보여준다.

다음은 JSDoc 을 통해, 작성한 패키지 코드의 일부이다.

/**
 *
 *
 * @param {string} inputValue   유효성 검사를 진행할 입력값 입니다.
 * @param {number} len1         유효 길이에 대한 시작 값 입니다. (이상)
 * @param {number} len2         유효 길이에 대한 끝 값 입니다. (미만)
 *
 * @returns {true | void}
 *
 * @description 길이 제한 유효성 함수로, 입력값이 유효 길이 범위 안에 속해 있는지 체크합니다.
 *
 * @example
 * let ret = lenLimit('asdf', 2, 8); // 2이상 8미만
 * console.log(ret); // true
 * ret = lenLimit('sadfa', 6, 8);
 * console.log(ret); // undefined, Error : 입력값이 주어진 범위에 포함되지 않습니다.
 *
 */
export function lenLimit(inputValue: string, len1: number, len2: number): true | void {
  try {
    if (typeof inputValue !== 'string' || typeof len1 !== 'number' || typeof len2 !== 'number')
      throw new ValidationError('typeError', '정의된 데이터 타입과 일치하지 않는 인자가 존재합니다.');

    let ret1 = lenLimitMore(inputValue, len1);
    let ret2 = lenLimitUnder(inputValue, len2);
    if (ret1 === true && ret2 === true) return true;
    else throw new ValidationError('validError', '입력값이 주어진 범위에 포함되지 않습니다.');
  } catch (e) {
    console.log(e);
  }
}

이후 패키지를 프로젝트에 설치하여 만약 JSDoc을 작성한 함수에 마우스 포인터를 가져다 놓으면 다음과 같이 코드에 대한 정보를 제공한다.

뿐만 아니라 한번이라도 해당 패키지를 호출했다면 자동완성 기능도 제공한다. 확실히 이러한 편의기능이 있으니 한층 더 패키지스러운? 느낌이 든다.

JSDoc 로 공식문서 만들기

JSDoc의 정보를 바탕으로, 공식문서 처럼 자동으로 마크다운을 생성하는 것이 가능하다. 이를 위해서는 IDE 에서 제공하는 내장된 JSDoc 기능이 아닌 실제 JSDoc 패키지가 필요하다.

npm i --save-dev jsdoc

jsdoc.config.json 파일을 생성하자. JSDoc 에 대한 환경 설정을 통해, 현재 코드의 JSDoc 정보를 보다 한 눈에 보기 쉽게 제공하는 정적 페이지를 자동 구축해준다.

이 프로젝트에 반영한 설정 사항은 다음과 같다.

{
  "plugins": ["plugins/markdown"], // 마크다운 플러그인을 생성한다.
  "source": {
    "include": ["./dist"], // 반영시킬 jsdoc 코드의 경로를 정한다.
    "includePattern": ".+\\.js(doc|x)?$", // 해당 경로내에 포함시킬 파일을 설정한다, (js 파일 모두)
  },
  "sourceType": "module",
  "opts": {
    "encoding": "utf8", // 인코딩 설정
    "readme": "README.md" // 페이지 맨처음에 REAEME.md의 정보를 포함시킨다.
  }
}

이후 jsdoc 커맨드를 실행하면, out 폴더 형태의 정적 페이지가 결과물로 나타난다. jsdoc 커맨드를 사용하기 위해, package.json 에서 커맨드를 생성하자.

jsdoc -c jsdoc.config.json
"scripts": {
  "test": "tsc",
  "build": "rollup -c",
  "format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
  "lint": "tslint -p tsconfig.json",
  "jsdoc": "jsdoc -c jsdoc.config.json"
},

out 폴더 내부의 .html 파일을 브라우저에 띄우면 다음과 같은 페이지를 볼 수 있다.

한편, JSDoc은 해당 페이지 외에도 다양한 형태의 템플릿이 존재한다. 나의 경우, docdash 템플릿을 적용시켰다. 템플릿 적용을 위해, 템플릿 패키지를 설치한다.

npm i --save-dev docdash

이후, jsdoc.config.jsonopts 객체에 template 속성을 추가하자.

"opts": {
  "template": "node_modules/docdash",
  "encoding": "utf8",
  "readme": "README.md"
}

이후 다시 jsdoc 커맨드를 실행하여 결과물을 생성하면 다음과 같은 페이지를 볼 수 있다.

README 작성하기

프로젝트의 끝에는 결국 문서화 작업이 필요하고, 이 문서화 작업에서 매우 중대한 영향을 미치는 것이 README.md 라고 생각한다.

이 영역은 회사나 개인에 따라 작성 방식이 천차만별 일 것이다. 특별히 왈가왈부 할 이야기는 없지만, 소신 발언을 해보자면 리포지토리의 가장 처음을 담당하는 영역인 만큼, 본인이 기여한 부분이 있다면 뭐라도 작성하는 것이 좋다고 생각한다.

프로젝트 종료

자 이렇게 직접 패키지를 만들어 본 경험과, NPM 패키지 개발 시 느낀 중요한 점들을 얘기해보았다. 패키지의 기능을 설명하기 보다는, 패키지를 만들 때 필요하다고 생각되는 설정을 중점적으로 이야기 했다. 실제 NPM 패키지를 만들려고 시도하는 개발자들에게 도움이 되는 내용이었으면 한다.

부족함이 많은 유효성 패키지지만 해당 패키지가 궁금하다면 한번 프로젝트에 적용해보시길 바란다. 사용하시는 김에 star 도 한번 꾸욱 눌러주시길.

npm i --save-dev @beberiche/validator

깃 리포지토리 : beberiche/npm-validator-module

NPM 리포지토리 : @beberiche/validator

profile
새로운 것에 관심이 많고, 프로젝트 설계 및 최적화를 좋아합니다.

4개의 댓글

comment-user-thumbnail
2023년 8월 2일

이런 유용한 정보를 나눠주셔서 감사합니다.

1개의 답글
comment-user-thumbnail
2023년 8월 3일

좋은 글 감사합니다~~

1개의 답글