먼저, npm이 무엇인가?
npm(Node Package Manager)은 node.js를 위한 패키지 매니저이자, node.js를 위한 오픈소스 생태계이다.
npm은 node.js에서 사용하는 모듈들을 패키지로 만들어 관리하고 배포하고 있다.
npm이 생겨난 이유?
모듈 패키징이 엉망으로 완성되는 것을 관찰하고, Isaac Z. Schlueter이 만들게 됬다고 한다.
먼저 npm Docs에서는 package.json에 대해 뭐라고 써놨는지 보자.
You can add a package.json file to your package to make it easy for others to manage and install. Packages published to the registry must contain a package.json file.
- lists the packages your project depends on
- specifies versions of a package that your project can use using semantic versioning rules
- makes your build reproducible, and therefore easier to share with other developers
기본적으로는 package.json은 문서다.
개발자가 배포한 패키지에 대해, 다른 사람들이 관리하고 설치하기 쉽게 하기 위한 문서.
npm에 패키지를 배포하고 npm registry에 올리기 위해서 반드시 필요한 문서파일이다.
- 자신의 프로젝트가 의존하는 패키지의 리스트
- 자신의 프로젝트의 버전을 명시
- 다른 환경에서도 빌드를 재생 가능하게 만들어, 다른 개발자가 쉽게 사용할 수 있도록 한다.
즉, npm이라는 오픈소스 패키지 생태계를 사용하기 위한 명세이자, 프로젝트의 의존성 관리를 위한 명세, 또 이 생태계로의 배포를 위한 명세라고 볼 수 있다.
package.json은 확장자로 알 수 있듯, json파일로 속성-값의 쌍으로 이루어져 있는데,
npm에서 이 속성을 field라고 표현하고 있다.
$ npm init -y
처음 기본 설정 값으로 package.json을 생성하면
{
"name": "PROJECT_DIRECTORY",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
위와 같이 생성된다. npm을 사용하는데 기본적으로 필요한 field니까 생성해 줬다고 생각한다.
각 field에 대해서 왜 필요할지에 대해 생각하면서, 읽어보자.
npm에 따르면, “name”과 “version”은 반드시 있어야 하는 fields이다.
이 fields가 누락되면 패키지는 설치될 수 없다.
기본 규칙
must be lowercase, 소문자로 작성되어야 한다.
must be one word, 한 단어로 작성되어야 한다.
may contain hyphens and underscore, -(하이픈)이나 _(언더스코어)를 포함할 수 있다.
+상세 규칙
"version": semantic versioning guidelines를 따르며, x.x.x의 형태로 작성해야한다.
간단히 말하면, [Major].[Minor].[Patch]의 형태를 따른다.
semantic versioning(유의적 버전)에 대한 내용은 이 글에 잘 설명되어 있다.
버전 네이밍에 대한 규칙은 회사나 단체에 따라 다르지만, 대부분 이와 비슷한 형태를 띄니 한 번 봐두는 것도 좋을 것 같다.
Example
{
"name": "my-awesome-package",
"version": "1.0.0"
}
문자열로 기술한 패키지에 대한 설명. npm에서 검색되었을 때 리스트에 표시되어 사람들이 패키지를 찾아내고 이해할 수 있는데 도움을 준다.
패키지의 진입점(entry point)이 되는 모듈의 ID이다.
예를 들어, 사용자가 foo라는 이름의 패키지를 설치하고, require("foo")를 통해 모듈을 import하면, "main"으로 지정한 모듈의 exports 객체가 반환된다.
패키지 root의 상대경로로 지정해야 한다. 지정하지 않은 경우, root 폴더의 index.js로 기본값이 설정된다.
패키지의 생명주기에서 다양한 타이밍에 자주 사용할 command를 alias(별칭)을 통해 지정해 둘 수 있는 dictionary
Shell Command(CLI)에 대한 상식이 뒷받침된다면, 유용한 command를 지정해두고 사용하는데 편리할 것이다.
Example
create-react-app의 package.json을 일부 예제이다.
facebook/create-react-app/package.json
{
"scripts": {
"build": "cd packages/react-scripts && node bin/react-scripts.js build",
"changelog": "lerna-changelog",
"create-react-app": "node tasks/cra.js",
"e2e": "tasks/e2e-simple.sh",
"e2e:docker": "tasks/local-test.sh",
"postinstall": "npm run build:prod -w react-error-overlay",
"publish": "tasks/publish.sh",
"start": "cd packages/react-scripts && node bin/react-scripts.js start"
// ...
}
}
이처럼 value로 일련의 command(명령어)를 정의해 두고 key로 지정한 alias를 이용해 간편하게 호출 할 수 있다.
ex) npm start
키워드를 문자열 배열로 설명. description과 마찬가지로 npm에서 검색되었을 때 리스트에 표시되어 사람들이 패키지를 찾아내고 이해할 수 있는데 도움을 준다.
배포자 한 사람을 위한 field로, 다수의 사람을 표시하기 위해서는 “contributors” field로 작성해야 한다.
배포한 패키지에 대해 패키지 사용자가 패키지를 사용하는데 어떤 권한과 제한 사항이 있는지 알기 위해 license를 명시해야 한다.
이외에도 더 많은 설정을 위한 field가 있지만, 기본적인 부분만 짚을 예정으로 추가적인 부분은 npm Docs - package.json을 참고
이 부분이 면접에서 물어보셨던 내용이다.
꼭 알고 넘어가자...
ㅠㅠ
“dependencies” & “devDependencies”
패키지의 이름에 해당 패키지의 버전 범위(SemVer)를 매핑한 형태의 객체로 지정한다.
SemVer(유의적 버전)
해당 패키지, 프로젝트가 어떤 외부 라이브러리에 의존성(dependency)을 가지고 있는지,
프로젝트가 의존하고 있는 패키지를 일괄적으로 관리하기 위한 필드이다.
package.json내 기본적인 작성 방법은 dependencies와 devDependencies 둘 다 똑같다.
{
"dependencies": {
"hexo": "^5.0.0",
"hexo-deployer-git": "^3.0.0",
"hexo-generator-archive": "^1.0.0",
"hexo-generator-category": "^1.0.0",
"hexo-generator-feed": "^3.0.0"
// ...
}
}
{
"devDependencies": {
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"alex": "^8.2.0",
"eslint": "^7.30.0",
"lerna-changelog": "~0.8.2"
// ...
}
}
사용하고자 하는 패키지를 아래와 같이 다운받으면, dependencies 필드에 자동으로 install한 ‘패키지이름’과 ‘버전’이 기록된다.
$ npm install
<package>
devDependencies 필드에 기록하려면, 아래와 같이 --save-dev(또는 축약-D)옵션을 넣어주면 된다.
$ npm install --save-dev
<package>
devDependencies는 개발시에만 필요한 의존 패키지들을 의미한다.
예를 들어, 테스트를 위한 패키지나 트랜스파일러와 같이 배포시에는 필요없는 패키지들을 이 devDependencies에 포함 시킨다.
npm Docs에서도 아래와 같이 굵은 글씨로 강조하고 있다.
Please do not put test harnesses or transpilers or other “development” time tools in your dependencies object.
dependencies 항목은 의존성이 추가될 때 패키지 매니저를 통해 아무 옵션을 주지 않으면 리스트에 추가 된다. 예를 들어 npm i dayjs 명령어는 해당 의존성을 dependencies에 추가한다. 아무런 옵션을 주지않으면 기본값으로 dependencies에 리스트된다는 소리다. --save 옵션을 주어 dependencies 항목에 넣는다라는 명시적인 지시를 내릴 수 있다. 기본값으로 dependencies 항목에 추가된다면 왜 --save 옵션을 주어 명시를 하는걸까?
해답은 버전의 차이에 있다. 오래된 npm 버전에서는 --save 옵션을 명시해야만 dependencies 항목에 추가된다. 마찬가지로 devDependencies 항목에 추가하려면 --save-dev 라는 옵션을 주어야 항목에 추가되었다. 버전이 올라가면서 --save 옵션이 없으면 기본값으로 dependencies 항목에 추가되었기 때문에 아직도 많은 사람이 --save를 명시해서 사용한다.
발에 필요한 의존성과 실제로 어플리케이션 운용에 필요한 의존성을 나누는 차이가 있다.
이렇게 개발용 라이브러리와 배포용 라이브러리를 구분하는 이유는 어플을 빌드하고 배포할 때,
dev에 들어가있는 개발용 라이브러리는 포함시키지 않고 빌드하기 때문이다.
때문에 어플의 로직과 연관이 없다고 판단되는 라이브러리가 있다면 dev에 넣는 것이 좋다.