Logo

자바스크립트 개발자를 위한 package.json 파일 정리

백엔드와 프론트엔드를 불문하고 대부분의 자바스크립트 프로젝트에는 최상위 경로에 package.json 파일이 있는데요. 그런데 혹시 이 파일이 언제 어떻게 사용되는지를 정확히 알고 계신가요?

이번 포스팅에서는 자바스크립트 개발자로서 알아두면 좋은 package.json 파일에 대해서 자주 사용되는 필드 위주로 정리해드리려고 합니다.

왜 package.json 필요할까?

자바스크립트가 프로그래밍 언어로서 여러 가지 부족한 측면이 있음에도 불구하고 현재의 번영을 누리고 있는 이유는 뭐니 뭐니해도 다른 언어와 비교할 수 없는 어마어마한 오픈 소스 생태계인데요. 단적인 예로, 여러분이 최근에 하셨거나 현재 하고 계신 자바스크립트 프로젝트를 떠올려보시면 외부 패키지에 의존하지 않는 프로젝트는 상상조차 하시기 힘드실 것입니다. 그리고 이 중심에는 방대한 양의 자바스크립트 패키지가 공개되어 있는 npm 패키지 저장소가 있습니다.

package.json 파일은 쉽게 말해서 우리의 자바스크립트 프로젝트를 npm 패키지 저장소와 상호 작용할 수 있도록 만들어주는 중요한 연결 고리인데요. 우리는 package.json 파일을 통해 npm 패키지 저장소로부터 어떤 패키지를 내려받아 설치해야 하는지, 또한 우리의 프로젝트를 다른 프로젝트에서 사용할 수 있도록 어떻게 npm 패키지 저장소에 올릴 것인지를 설정할 수 있습니다. 그러므로 package.json 파일에는 패키지 사용자 입장에서 설정이 필요한 부분이 있고, 패키지 발행자 입장에서 추가로 설정이 필요한 부분도 있죠.

여러분이 개발하시는 자바스크립트 프로젝트가 npm 패키지 저장소로 부터 패키지를 내려 받기만 한다면, package.json 파일에 대해서 아셔야 할 내용이 사실 얼마되지 않아요. scripts, dependencies, devDependencies 필드 정도만 아시면 충분할 것입니다.

하지만 개발하시는 자바스크립트 프로젝트를 npm 패키지 저장소에 올려서 다른 사람들도 쓸 수 있도록 공개하시고 싶다면, package.json 파일에 대해서 잘 알고 계셔야 합니다.

scripts

package.json 파일의 scripts 필드는 프로젝트에서 빈번하게 수행해야 하는 작업을 스크립트로 등록하기 위해서 사용됩니다. 여러분이 터미널에서 npm run 명령어로 실행하는 스크립트는 모두 여기에 정의되어 있다고 보시면 되는데요. 애플리케이션 구동에 쓰이는 npm start 명령어나 테스트 실행에 쓰이는 npm test 명령어처럼 정말 자주 사용되는 스크립트도 여기에 정의됩니다.

예를 들어, NextJS 앱에서는 아래와 같은 스크립트를 매우 흔하게 볼 수 있습니다.

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  }
}

자바스크립트 프로젝트에서 스크립트 활용하는 방법에 대해서는 관련 포스팅을 참고 바랍니다.

dependencies

package.json 파일에서 가장 친숙한 필드를 뽑으라면 의존성 명시를 위한 dependencies일 텐데요. 좀 더 엄밀한 얘기하면 해당 프로젝트가 실행되기 위해서 반드시 필요한 패키지를 의미합니다.

우리가 패키지를 설치하기 위해서 터미널에서 npm install <패키지>@<버전> 명령어를 실행하면 package.json 파일의 dependencies 항목에 자동으로 반영이 되는데요. 그래서 개발자가 직접 dependencies 항목을 수정해야 할 일은 흔치 않으며, 오타 가능성도 있기 때문에 가급적 직접 수정은 피하는 것이 좋겠습니다.

{
  "dependencies": {
    "@tanstack/react-query": "^4.35.0",
    "@types/react": "^18.2.21",
    "@types/react-dom": "^18.2.7",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-hook-form": "^7.46.1",
    "react-router-dom": "^6.15.0",
    "zod": "3.22.2"
  }
}

devDependencies

package.json 파일의 devDependencies 필드는 소위 개발 의존성, 즉 테스트 작성/실행이나 프로젝트 빌드 시에만 필요한 패키지를 명시하기 위해서 사용됩니다.

개발 의존성으로 패키지를 설치할 때는 --save-dev 또는 -D 옵션을 줘서 npm install 명령어를 실행하면 됩니다.

{
  "devDependencies": {
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "eslint": "^8.48.0",
    "jest": "^29.6.4",
    "prettier": "^3.0.1",
    "typescript": "^5.2.2"
  }
}

peerDependencies

라이브러리를 개발하다 보면, 해당 라이브러리에서 직접적으로 의존하지는 않지만, 해당 라이브러리가 사용자 측에서 제대로 작동하려면 사용자가 다른 라이브러리를 설치해 주어야 하는 경우가 있는데요. 대표적으로 다른 라이브러리를 보조하거나 확장하는 플러그인(plug-in)이나 애드온(add-on) 류의 라이브러리를 들 수 있겠습니다.

이럴 때는 package.json 파일의 peerDependencies 필드을 통해서 반드시 같이 설치해줘야하는 패키지를 명시할 수 있습니다. 참고로 npm v7 이전에는 peerDependencies 옵션에 명시된 패키지를 사용자가 설치하지 않으면 경고만 발생했는데, npm v7 부터는 peerDependencies 필드에 명시된 패키지도 자동으로 설치되도록 변경되었습니다.

예를 들어, 라이브러리 사용자가 최소한 React v16.8을 설치해야한다면 다음과 같이 설정해줄 수 있습니다.

{
  "peerDependencies": {
    "react": ">=16.8",
    "react-dom": ">=16.8"
  }
}

private

package.json 파일의 private 필드는 프로젝트가 npm 패키지 저장소로 발행(publish)해도 되는지 여부를 지정하기 위해서 사용됩니다. 기본값은 false이기 때문에 따로 명시해주지 않으면 해당 프로젝트가 실수로 npm 패키지 저장소로 업로드되는 사고가 발생할 수 있습니다. 따라서 npm 패키지 저장소로 올리면 안 되는 프로젝트에서는 private 필드의 값을 반드시 true로 설정하는 것이 보안 측면에서 권장됩니다.

{
  "private": true
}

name & version

package.json 파일에서 nameversion 필드는 각각 사용자들이 npm install 명령어로 해당 패키지를 설치할 때 사용할 패키지 이름과 버전을 명시합니다. 따라서 npm 패키지 저장소에 올리지 않을 프로젝트라면 크게 신경 쓸 필요가 없는 설정입니다.

{
  "name": "our-project",
  "version": "0.1.0"
}

description & keywords

package.json 파일에서 nameversion 필든는 각각 사용자들이 npm search 명령어로 해당 패키지를 검색할 때 보여질 패키지 설명과 키워드 목록을 명시합니다. 따라서 npm 패키지 저장소에 올리지 않을 프로젝트라면 크게 신경 쓸 필요가 없는 설정입니다.

{
  "description": "This is our example project.",
  "keywords": ["example", "javascript", "react"]
}

homepage & repository

package.json 파일의 homepage 옵션에는 해당 프로젝트의 홈페이지나 문서 페이지의 URL을 설정할 수 있습니다. 그러면 터미널에서 npm docs 명령어를 실행하여 간편하게 해당 URL을 열어볼 수 있습니다.

반면에 package.json 파일의 repository 옵션에는 해당 프로젝트의 코드 저장소의 URL을 설정할 수 있습니다. 그러면 터미널에서 npm repo 명령어를 실행하여 간편하게 해당 URL을 열어볼 수 있습니다.

이 두 개의 옵션은 특히 npm 패키지 저장소에 프로젝트를 발행할 때 상당히 중요한데요. npm 웹사이트에서 패키지 상세 페이지에 들어가면 homepage와 repository 항목에 이 두 개의 URL이 링크되기 때문입니다. 사용자들이 좀 더 안심하고 미리 패키지에 대한 정보를 확인 후에 설치할 수 있도록 돕기 위함입니다.

{
  "homepage": "https://www.algodale.com",
  "repository": "github:DaleSeo/AlgoDale"
}

license

package.json 파일의 license 필드를 통해서는 해당 프로젝트의 라이선스를 표시할 수 있습니다. 아무리 좋은 패키지라도 라이선스가 명시되어 있지 않으면 쓰기가 꺼려지기 때문에, npm에 발행할 패키지라면 반드시 명시하는 것이 좋습니다.

{
  "license": "MIT"
}

author & contributors

해당 프로젝트의 저자 및 공헌자는 각각 authorcontributors 필드에 명시할 수 있습니다.

다음과 같이 이름, 이메일, URL을 각각 서브 필드에 명시해줄 수도 있고요.

{
  "author": {
    "name": "Dale Seo",
    "email": "DaleSeo@gmail.com",
    "url": "https://daleseo.com"
  }
}

이름 <이메일> (URL) 형식으로 한 줄에 명시에 줄 수도 있습니다.

{
  "author": "Dale Seo <DaleSeo@gmail.com> (https://daleseo.com)"
}

참고로 이메일과 URL은 선택 입력입니다.

main

package.json 파일의 main 필드는 사용자가 해당 패키지를 npm에서 설치하여 사용할 때 프로젝트 내에 어떤 파일이 불러와질지를 결정하는 매우 중요한 필드인데요. 명시하지 않으면 기본적으로 프로젝트의 최상위 디렉토리에 있는 index.js 파일이 사용됩니다.

예를 들어, 우리가 npm에 our-package라는 이름으로 발행해놓은 패키지가 있다면 사용자는 해당 패키지를 설치한 후에 다음과 같이 불러올 것입니다.

import OurPackage from "our-package";

그러면 해당 프로젝트의 최상위 디렉토리에 있는 index.js 파일에서 내보내기(export)를 하는 함수나 클래스가 불러와질 것입니다.

하지만 요즘에는 프로젝트의 소스 코드를 직접 발행하기 보다는 Babel과 같은 트랜스파일러(transpiler)나 Webpack과 같은 번들러(bundler)를 사용하는 경우가 많은데요. 이런 경우에는 소스 파일 대신에 프로젝트를 빌드한 결과물이 생성되는 디렉토리 내에 파일을 main 필드로 지정해줘야 합니다.

예를 들어서, 필드 결과물이 build 디렉토리에 저장된다면 아래와 같이 package.json 파일의 main 필드를 설정해줄 수 있습니다.

{
  "main": "build/index.js"
}

type & module

Node.js에서 예전에는 CJS(CommonJS)를 모듈 시스템으로 사용했지만 요즘에는 서서히 ESM(ES Module)으로 넘어가는 추세죠?

package.json 파일의 type 필드를 "module"로 설정해주면 해당 프로젝트에서는 CJS 대신에 ESM을 사용하게 됩니다. 만약에 해당 프로젝트를 npm 패키지 저장소에 발행한다면 module 필드를 통해서 사용자가 패키지를 불러올 때 사용되야하는 mjs 파일을 명시해줘야 합니다.

{
  "type": "module",
  "module": "dist/index.mjs"
}

Node.js에서 ES 모듈을 사용하는 방법은 별도 포스팅에서 자세히 다루고 있으니 참고 바랍니다.

types

요즘에는 타입스크립트로 개발하고 자바스크립트로 컴파일한 후 발행하는 프로젝트가 많은데요. types 필드를 통해서 해당 프로젝트에서 정의하고 있는 타입(type) 정보를 담고 있는 파일의 위치를 지정해줄 수 있습니다.

{
  "types": "dist/index.d.ts"
}

files

우리가 npm에 패키지를 발행할 때는 기본적으로 프로젝트 내의 모든 파일이 포함이 되는데요. (package-lock.json 파일이나 .git 폴더와 같이 일부 불필요한 파일은 제외) 만약에 프로젝트 내의 특정 파일만 발행할 패키지에 포함하고 싶다면 package.json 파일의 files 필드를 통해서 명시해줄 수 있습니다.

예를 들어, 타입스크립트로 작성된 소스 코드를 제외하고, 최종 컴파일된 자바스크립트 코드만 패키지에 포함하고 싶다면 어떻게 해야 할까요? 타입스크립트의 outDir 설정이 ./dist로 되어 있다고 가정하면 다음과 같이 files 필드를 지정해줄 수 있을 것입니다.

{
  "files": ["dist"]
}

이렇게 패키지를 사용할 때 없어도 상관없는 파일을 제외시키면 패키지의 크기가 줄어들어 사용자에게 더 나은 경험을 제공할 수 있습니다.

overrides

package.json 파일의 overrides 필드는 프로젝트의 의존성 트리 내에서 특정 패키지의 버전을 덮어쓸 수 있도록 해줍니다. 이 기능은 원래 Yarn이라는 패키지 매니저에서 resolutions 필드를 통해서 지원했었는데, npm에서도 v8.3부터 지원하기 시작했습니다.

보통 보안적인 이유로 특정 패키지의 버전을 강제로 고정하거나 업데이트를 방지하고 싶을 때 유용하게 사용됩니다.

{
  "overrides": {
    "colors": "1.4.0"
  }
}

publishConfig

package.json 파일의 overrides 필드는 프로젝트를 npm 패키지 저장소에 발행할 때 필요한 설정을 담아두는 곳입니다. 만약에 해당 프로젝트를 npm 패키지 저장소가 아닌 GitHub Packages 저장소에 올린다면 다음과 같이 registry를 설정해줄 수 있습니다.

{
  "publishConfig": {
    "registry": "https://npm.pkg.github.com"
  }
}

이렇게 해주면 외부 패키지를 설치할 때는 npm 패키지 저장소를 사용하지만, 해당 프로젝트를 발행할 때는 GitHub 패키지 저장소를 사용하게 됩니다.

비표준 필드

많은 자바스크립트 도구들이 package.json 파일의 특정 필드를 통해 설정을 가능하도록 지원하고 있습니다.

예를 들어, Prettier 포맷터는 prettier 필드를 통해 설정할 수 있고, ESLint 린터는 eslintConfig 필드를 통해 설정할 수 있으며, Babel 트랜스파일러는 babel 필드를 통해 설정할 수 있습니다.

이렇게 package.json 파일에 각종 개발 도구의 설정까지 저장하면 여러 설정 파일을 관리하지 않아도 되서 편리할 수 있지만, 프로젝트의 규모가 커지게 되면 package.json 파일이 너무 복잡해지고 개발자 간에 변경 충돌(conflict) 가능성도 커지는 문제가 발생할 수 있습니다. 따라서 프로젝트의 규모에 따라서 적절한 시점에 package.json 파일로 부터 개발 도구의 설정을 별도의 설정 파일로 분리하는 것을 고려바랍니다.

{
  "prettier": {
    "singleQuote": true
  },
  "eslintConfig": {
    "extends": ["react-app", "react-app/jest"]
  },
  "browserslist": {
    "production": [">0.2%", "not dead", "not op_mini all"],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

마치면서

이상으로 자바스크립트 개발자로서 알아두면 유용한 package.json 파일의 여러 필드에 대해서 살펴보았습니다. 본 포스팅에서 다루지 않은 필드에 대해서 package.json 파일에 대한 npm 공식 문서를 참고하시면 좋을 것 같습니다.