npm 그리고 yarn

sikkzz·2023년 12월 4일
0

React

목록 보기
7/12
post-thumbnail

🖐️ 시작하며

프로젝트를 진행하던 중 지금까지 패키지들을 당연시하게 npm으로 관리했었지만 yarn을 패키지 관리에 많이 사용하는걸 보고 최근에 프로젝트 패키지 매니저를 yarn으로 마이그레이션을 진행했습니다. 진행하면서 학습했던 npm과 yarn의 차이에 대해 기록으로 남기기 위해 글을 작성했습니다.

Package Manager(패키지 매니저)

npm과 yarn은 자바스크립트 런타임 환경인 Node.js(노드)의 패키지 매니저입니다. 여기서 패키지 매니저가 무엇일까요?

패키지 매니저는 패키지를 관리하는 작업을 자동화, 안전처리 하기 위해 사용되는 도구입니다. 쉽게 말하면 설치된 프로그램을 관리하는 것입니다.

패키지는 코드의 배포를 위해서 사용되는 코드의 묶음으로 패키지의 설치, 업데이트, 수정, 삭제 등의 작업 등이 패키지 관리에 속하게 됩니다.

패키지는 일반적으로 라이브러리, 실행파일, 컴파일한 binary, 환경설정(configuration) 정보, 의존성(dependency) 정보를 포함합니다.

Node.js에는 npmyarn이 있듯이 Python에는 pip, Java에는 Maven 등 언어마다 각자의 패키지 매니저들이 존재합니다.

npm

NPM(Node Package Manager)는 Node.js의 패키지 관리자입니다. JavaScript 개발자가 패키지된 코드 모듈을 쉽게 공유할 수 있도록 돕기 위해 2009년 오픈 소스 프로젝트로 만들어졌습니다. - Npm 공식 문서 -

NPM(Node Package Manager)의 이름에서 알 수 있듯이 npm은 node.js의 패키지 매니저입니다. 자바스크립트 언어를 위한 패키지 매니저로 Node.js의 기본 패키지 매니저이기도 합니다. 전세계적으로 만들어진 Node 패키지들을 업로드하고 공유함으로써 누구나 command line을 통한 설치로 패키지들을 사용할 수 있습니다.

Npm은 website, CLI(Command Line Interface), Registry(온라인 데이터베이스)로 이루어져 있습니다.

Npm 공식 사이트에 들어가보면 정말 많은 양의 패키지들이 존재하고 간단한 명령어를 통해 패키지들을 사용할 수 있습니다. 또한 주간 다운로드 수를 통해 해당 라이브러리를 사용하는 개발자의 수를 확인하고 라이브러리의 안정성을 판단할 수 있습니다.

npm 명령어

개발을 위해서 대표적으로 사용하는 npm 명령어는 다음과 같은 명령어들이 있습니다.

  • npm init: package.json 생성
  • npm install: package.json 파일 및 해당 종속성에 나열된 모든 모듈 설치
  • npm install package_name@버전: 특정 패키지의 특정 버전 설치
  • npm install package_name -g: 패키지 글로벌 설치. 로컬의 다른 프로젝트들도 이 패키지 사용이 가능
  • npm uninstall: 패키지 삭제
  • npm update: 설치한 패키지 업데이트
  • npm dedupe: 중복 설치된 패키지들을 정리

package.json

package.json은 프로젝트 정보와 의존성(dependency)을 관리하는 파일입니다.
프로젝트에서 어떤 패키지를 사용하고 패키지의 어떤 버전을 사용하는지 기록되는 파일로 해당 파일의 정보를 통해 다른 프로젝트에서도 동일한 개발 환경을 구축할 수 있습니다.

npm init 명령어를 통해 package.json을 생성해보겠습니다.

그럼 다음과 같은 package.json이 만들어집니다.

{
  "name": "npm-test",
  "version": "1.0.0",
  "description": "설명",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "sikkzz",
  "license": "ISC"
}

아래 코드는 제가 진행하고 있는 프로젝트 package.json의 일부입니다. 해당 파일을 가지고 npm install을 실행시 어디서든 동일하게 프로젝트 개발 환경이 만들어지는 것입니다.

{
  "name": "npm test",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "lint": "eslint './src/**/*.{ts,tsx,js,jsx}'",
    "lint:fix": "eslint --fix './src/**/*.{ts,tsx,js,jsx}'",
    "prepare": "husky install"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-icons": "^4.11.0",
    "react-router-dom": "^6.17.0",
    "styled-components": "^6.1.0",
    "vite-plugin-svgr": "^4.1.0"
  },
  "devDependencies": {
    "@types/node": "^20.8.7",
    "@types/react": "^18.2.15",
    "@types/react-dom": "^18.2.7",
    "@typescript-eslint/eslint-plugin": "^6.8.0",
    "@typescript-eslint/parser": "^6.8.0",
    "@vitejs/plugin-react": "^4.0.3",
    "eslint": "^8.2.0",
    "eslint-config-prettier": "^9.0.0",
    "eslint-import-resolver-typescript": "^3.6.1",
    "eslint-plugin-import": "^2.28.1",
    "eslint-plugin-prettier": "^5.0.1",
    "husky": "^8.0.3",
    "lint-staged": "^15.0.2",
    "prettier": "^3.0.3",
    "typescript": "^5.0.2",
    "vite": "^4.5.0",
    "vite-tsconfig-paths": "^4.2.1"
  },
  "lint-staged": {
    "src/**/*.{js,jsx,ts,tsx}": [
      "eslint --fix"
    ]
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  }
}

dependencies, devDependencies

코드를 확인해보면 dependenciesdevDependencies가 존재합니다.

dependenciesnpm install 명령어를 통해서 설치한 라이브러리들이 들어가 있습니다. 애플리케이션의 동작을 도와주는 라이브러리들이며 해당 라이브러리들은 배포할 때 같이 포함되어 배포됩니다.

devDependencies는 애플리케이션 동작과 직접적인 연관은 없지만 개발할 때 필요한 라이브러리들입니다. npm install package_name -D 또는 npm install package_name --save-dev의 명령어로 설치하면 devDependencies에 설치됩니다.

예를 들어 dependencies에 있는 react를 devDependencies에 포함시키고 배포를 진행하면 애플리케이션이 정상적으로 동작하지 않습니다. 하지만 devDependencies에 있는 라이브러리를 dependencies에 포함시키고 배포를 진행하면 애플리케이션 동작에는 문제가 없습니다. 다만 미사용되는 라이브러리들이기에 빌드 시간도 줄이고 용량을 낭비하지 않을 수 있습니다.

대표적으로 eslint, prettier등이 devDependencies에 속하는데 코드 작성에는 도움을 주지만 애플리케이션 동작에는 영향을 미치지 않기 때문입니다. eslint, prettier을 잘 모르신다면 해당 글을 참고하시길 바랍니다.

yarn

Yarn은 JavaScript 프로젝트의 종속성을 관리하기 위해 확립된 오픈 소스 패키지 관리자입니다. 패키지 종속성을 설치, 업데이트, 구성 및 제거하는 프로세스를 지원하여 방해 요소를 줄이고 목표를 더 빠르게 달성할 수 있도록 도와줍니다. - Yarn 공식 문서 -

Yarn도 npm과 동일하게 Node.js의 패키지 매니저입니다. npm이 있는데도 yarn이 만들어진 이유는 무엇일까요?

Yarn은 2016년 페이스북에서 만든 JavaScript 패키지 매니저로 npm과 같은 기능을 수행합니다. React와 같은 프로젝트를 진행하며 겪었던 어려움을 해결하기 위해 개발되었기에 npm 레지스트리와 호환되면서 속도나 안정성 측면이 npm보다 향상되었다고 합니다. - 관련 페이스북측 아티클 -

yarn의 차별점

yarn이 주장하는 npm의 단점은 속도(performance), 안정성(stability), 보안성(security) 등이 있습니다.

속도(performance)

yarn은 다운받은 패키지 데이터를 cache(캐시)에 저장하기에 중복된 데이터는 다운로드하지않고 캐시에 저장된 파일을 활용합니다. 이로인해 이론적으로 npm에 비해 패키지 설치속도가 매우 빨라지며 여러개의 패키지를 설치할 때 병렬로 처리하기 때문에 속도가 증가됩니다. 이해 비해 npm은 순차적으로 설치하기에 상대적으로 느릴 수 밖에 없다고 합니다.

안정성(stability), 보안성(security)

npm은 패키지가 설치될 때 자동으로 코드와 의존성을 실행할 수 있도록 허용했습니다. 이 특징은 편리한 기능이지만 안정성을 위협할 수도 있습니다. 보장된 정책 없이 등록한 패키지가 존재할 수 있기에 위험도가 높기 마련입니다. 반면 yarn은 yarn.lock이나 package.json으로부터 설치만 하고 yarn.lock은 모든 디바이스에 같은 패키지를 설치하는 것을 보장하기 때문에 버전의 차이로 인해 생기는 버그를 방지해줄 수 있다고 합니다.

yarn 명령어

yarn의 대표적인 명령어들은 다음과 같습니다.

  • yarn init: package.json 생성
  • yarn/yarn install: package.json 파일 및 해당 종속성에 나열된 모든 모듈 설치
  • yarn add package_name@버전: 특정 패키지의 특정 버전 설치
  • yarn global add package_name: 패키지 글로벌 설치. 로컬의 다른 프로젝트들도 이 패키지 사용이 가능
  • yarn remove: 패키지 삭제
  • yarn upgrade: 설치한 패키지 업데이트
  • yarn dedupe: 중복 설치된 패키지들을 정리

package.json

yarn init 실행시 npm과 동일하게 package.json을 생성합니다.

다음과 같은 package.json이 만들어지며 이외의 몇개의 파일들이 추가로 생성됩니다.

{
  "name": "yarn test",
  "packageManager": "yarn@4.0.2"
}

npm init 후 npm install을 실행시켜보면 package-lock.json이 생성되는데 yarn.lock이 동일한 역할을 한다고 생각하시면 됩니다.

npm과 yarn의 문제점

유령 의존성

npm의 node_modules 폴더는 용량이 크기로 악명이 높습니다. 패키지가 늘어나면 늘어날수록 모듈의 용량은 배로 늘어나게 됩니다. npm은 node_module의 경량화를 위해 호이스팅을 도입하고 있습니다. 속도 문제 개선을 위해 최적화를 시도했지만 부작용으로 유렁 의존성이란 문제가 생겼습니다.

용량을 줄이는 가장 간단한 방법은 중복을 제거하는 것입니다. npm과 yarn은 node_modules 내부의 중복된 패키지를 최소화하기 위해 각 패키지가 의존하고 있는 패키지들을 최상단으로 끌어 올립니다. 최상단 패키지에 의존함으로써 패키지 내부에 존재하던 중복된 패키지들을 최상단에 유지시키고 나머지는 제거할 수 있었습니다.

다만 이로인해 직접 설치하지 않고 간접 설치된 종속성에 개발자가 접근할 수 있는 상황이 만들어질 수 있었습니다. 존재하지 않는 종속성에 의존하는 코드가 생기게 되는 것입니다. 이를 유령 의존성이라고 합니다.

내가 사용하는 A 패키지는 B패키지에 의존하고 있습니다. 내가 설치한 패키지는 A이고 B패키지는 설치하지도 않았는데 B패키지를 사용할 수 있게 되고 마치 유령처럼 설치한 적이 없는 패키지가 최상단에 존재하는 것입니다.

뒤에 언급할 yarn berry는 이런 호이스팅을 막기 위해 nohoist 옵션이 기본적으로 활성화되어 있습니다.

이외에도 비효율적인 설치와 의존성 검색 등은 여전히 npm과 yarn에게 해결해야하는 과제로 남아있었습니다.

node_modules 구조에서 모듈을 검색하는 방식은 기본적으로 디스크 I/O 작업입니다. 절대 경로의 경우 대략 아래 예시와 같은 순서로 순회하며 모듈을 검색합니다. 이 규칙은 Node.js 공식 문서에서 확인할 수 있습니다.

'/home/ry/projects/foo.js' 에서 require('bar.js')를 탐색하는 경우

  • /home/ry/projects/node_modules/bar.js
  • /home/ry/node_modules/bar.js
  • /home/node_modules/bar.js
  • /node_modules/bar.js

이처럼 매 탐색마다 수 많은 폴더와 파일을 실제로 열고 닫으면서 검색하게 되며 중첩 등 경우에 따라 순회 경로가 상당히 복잡해집니다. 모듈 탐색을 자료구조가 아닌 I/O로 처리하다보니 추가적인 최적화가 어렵고 실제로 yarn측에서도 이러한 이유들로 더 이상 최적화 할 여지가 없다고 언급했습니다.

yarn berry 에서는 PnP라는 기술을 통해 이를 개선합니다.

yarn berry

yarn berry는 2020년 출시되었으며 yarn v2 이상의 modern version yarn을 지칭하는 이름입니다. 기존의 yarn v1은 yarn classic이라고 부릅니다.

Plug'n'Play(PnP)

Plug'n'Play는 yarn berry가 제공하는 새로운 패키지 관리 시스템입니다. 기존 무거운 용량의 node_modules 대신 패키지들에 대한 정보는 .zip 파일로 압축하여 .yarn/cache 폴더에 저장하고 이를 찾기 위한 정보를 .pnp.cjs 파일에 생성 후 의존성 트리 정보를 단일 파일에 저장합니다. 이를 인터페이스 링커(Interface Linker)라고 합니다.

위에서 언급한 yarn init으로 생성된 파일중 .pnp.cjs을 확인해보면 의존성 트리를 중첩된 맵으로 표현했습니다. 기존 Node가 파일 시스템에 접근하여 직접 I/O를 실행하던 require문의 비효율을 자료구조를 메모리에 올리는 방식으로 탐색을 최적화한 것입니다.

별도의 I/O 작업 없이도 패키지의 위치를 정확히 알 수 있기 때문에 시간도 단축되고, 중복 설치를 방지하며 node_modules를 만들고 패키지들을 호이스팅시킬 필요가 없는 Plug'n'Play의 특성 덕분에 유령 의존성 문제도 해결하고 패키지 압축으로 인한 용량 축소도 가능했습니다.

Zero install

Plug'n'Play 전략으로 무거운 용량의 node_modules를 획기적으로 제거했기에 의존성까지 github에 올릴 수 있게 되었습니다.

github는 파일당 최대 용량이 500mb이고 원활한 이용을 위해 레포지토리당 1gb미만의 크기를 유지할 것을 권장하고 있습니다.

yarn berry를 통해 만든 의존성 폴더는 보통 200mb를 넘지 않기에 github에 올릴 수 있었고 git clone 이후 별도의 설치 없이 바로 사용할 수 있는 zero-install이 가능했습니다.

zero-install로 인해 로컬에서의 편리함도 있지만 CI/CD 파이프라인을 구축한 경우 클론이 끝나자마자 빌드가 가능하기에 배포까지 걸리는 속도도 대폭 단축될 수 있었습니다.

🔚 마치며

yarn에 대해 장점들을 많이 언급했지만 현재 시점에서는 npm 또한 몇년간 발전을 거듭하며 단점을 많이 보완했기 때문에 npm과 yarn의 속도 및 안정성 차이는 그리 크지 않다고 합니다. 각자의 장단점이 현재도 존재하지만 yarn도 결국은 npm의 단점에서 비롯하여 개발된 패키지 매니저이기 때문에 npm을 먼저 사용해보고 문제점에 봉착시 yarn을 사용해보는걸 추천한다고 합니다.

저는 학습겸 마이그레이션을 해보았을때 거대한 프로젝트는 아니지만 속도의 차이는 가시적으로 느낄 수 있었습니다. 다만 이외에 장,단점에 대해서는 아직 크게 체감하지 못하는걸 보아 더 많은 개발과 학습을 통해 알아가야 한다고 생각합니다.

다음에는 yarn berry로 마이그레이션하면서 학습한 내용에 대해 작성해보겠습니다.

감사합니다.

참조

npm 공식 문서
https://www.npmjs.com/

npm docs
https://docs.npmjs.com

yarn 공식 문서
https://yarnpkg.com/

패키지 매니저( Pakage Manager ) 란?
https://velog.io/@doomchit_3/%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%A7%A4%EB%8B%88%EC%A0%80-Pakage-Manager-%EB%9E%80

npm이란 무엇인가?
https://velog.io/@yoojinpark/npm

npm vs yarn vs yarn berry
https://velog.io/@remon/npm-vs-yarn-vs-yarn-berry

yarn
https://velog.io/@dudgus1670/yarn

[개발상식] npm과 yarn
https://velog.io/@kysung95/%EA%B0%9C%EB%B0%9C%EC%83%81%EC%8B%9D-npm%EA%B3%BC-yarn

profile
FE Developer

0개의 댓글