데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      패키지 매니저 선택을 위한 여정: NPM에서 Yarn으로 그리고 다시 pNPM

      mesquaker 24.07.31
      7,811 6 4
      DEVOTEE 요약
      패키지 매니저로 npm 대신 Yarn Berry를 사용해 보았으나 여러 문제로 인해 pnpm을 선택하게 되었습니다. pnpm은 패키지를 글로벌 저장소에 한 번만 설치하여 여러 프로젝트들이 공유하도록 하여 효율적으로 공간을 사용하며, 설치 속도도 npm보다 약 두 배 빠릅니다.

      안녕하세요. T월드 서비스의 웹 프론트엔드 개발자 구교웅입니다.

      SK텔레콤의 MNO 주요 서비스와 에이닷을 연계하여 고객에게 가치를 전달하는 신규 서비스를 구축하는 과정 중 프론트엔드 패키지 매니저 선정 경험에 대해 공유하고자 합니다.


      패키지 매니저란

      대부분의 개발 환경에서 프로그래밍 언어를 막론하고 대부분 패키지 매니저를 사용합니다.

      패키지 매니저는 프로그래밍에 필요한 라이브러리(일반적으로 오픈소스)를 관리하는 역할을 하며, 구체적으로 다음과 같은 역할을 합니다.

      1. 의존성 관리 : 필요한 다른 프로그램들을 자동으로 찾아 설치해줍니다.

      2. 버전 관리: 특정 버전의 프로그램을 설치하거나 업데이트할 수 있게 해줍니다.

      3. 편리한 설치 및 업데이트: 명령어 하나로 여러 프로그램을 쉽게 설치하거나 최신 상태로 업데이트할 수 있습니다.

      4. 보안 관리: 패키지가 신뢰할 수 있으며, 손상되지 않음을 보장합니다.

      5. 일관선 유지: 개발자 모두가 동일한 프로그램 버전과 설정을 사용할 수 있게 해줍니다.

      언어별 대표 패키지 매니저는 다음과 같습니다.

      언어

      패키지 매니저

      Java

      maven, gradle

      Javascript

      npm, yarn, pnpm

      Python

      pip, conda

      Ruby

      gem

      Go

      go


      더 좋은 패키지 매니저가 있을까?

      대부분의 node 환경 개발자라면 습관적으로 프로젝트 구축시 패키지 매니저로 npm을 사용합니다.

      React, Next, Vue 등 많이 사용하는 프론트엔드 프레임워크에서도 공식 문서의 '시작하기' 섹션에서 npm script를 예시로 들고 있습니다.

      node에서 공식 지원하는 패키지 매니저이기 때문에 어찌보면 당연한 현상이라고 생각이 되네요.

      npm을 사용하며 크게 불편한 점은 없었지만, 신규 프로젝트를 진행하면서 새로운 패키지 매니저를 적용해 보고 싶었습니다.


      가장 먼저 눈에 들어왔던 Yarn Berry

      Yarn은 facebook에서 2016년 npm의 대안으로 처음 만들어 졌습니다.

      Yarn은 Yarn classic과 Yarn Berry가 있는데 Yarn classic은 v1.x 이고 Yarn Berry는 v2 이상을 의미합니다.

      npm에 비해 좋은 점들을 알아볼까요?

      패키지 탐색 과정 효율화 및 유령 의존성 개선

      npm의 패키지 관리 방식은 node_modules 이라는 폴더 아래의 파일 시스템으로 관리됩니다.

      아래 그림처럼 node_modules 폴더는 무겁고 깊기로 유명합니다. 왜인지 알아볼까요?


      출처: https://www.reddit.com/r/ProgrammerHumor/comments/6s0wov/heaviest_objects_in_the_universe/


      require 명령어로 react 패키지를 불러오는 상황이라면 아래와 같은 폴더 리스트를 순서대로 뒤지며 패키지가 존재하는지 검색합니다.

      gyoung@Appleui-MacBookAir > ~/tworld/t3ai-admin > main > node                                  
      Welcome to Node.js v18.18.0.
      Type ".help" for more information.
      > require.resolve.paths('react')
      [
        '/Users/gyoung/tworld/t3ai-admin/repl/node_modules',
        '/Users/gyoung/tworld/t3ai-admin/node_modules',
        '/Users/gyoung/tworld/node_modules',
        '/Users/gyoung/node_modules',
        '/Users/node_modules',
        '/node_modules',
        '/Users/gyoung/.node_modules',
        '/Users/gyoung/.node_libraries',
        '/Users/gyoung/.nvm/versions/node/v18.18.0/lib/node',
        '/Users/gyoung/.node_modules',
        '/Users/gyoung/.node_libraries',
        '/Users/gyoung/.nvm/versions/node/v18.18.0/lib/node'
      ]

      이처럼 복잡한 구조의 폴더와 파일을 메모리상에서 자료구조로 처리하지 않고,

      디스크에서 탐색하다 보니 탐색 비용이 많이 소모되며, node_module의 중첩이 많은 패키지 탐색 시엔 더 많은 시간이 소모됩니다.

      따라서 npm에서는 속도개선을 위해 호이스팅 등 최적화 알고리즘을 도입하였습니다.


      출처: https://classic.yarnpkg.com/blog/2018/02/15/nohoist/


      위 왼쪽 그림과 같은 중복 설치 및 깊은 탐색을 방지하기 위해서,

      오른쪽 그림처럼 하위에 존재하는 패키지들을 호이스팅 & 병합하는데, 이때 루트 경로에서 원하는 패키지를 탐색할 수 있으므로 효율적 입니다.

      다만 이때 유령 의존성(Phantom Dependency)라는 부작용이 발생합니다.

      직접 적으로 설치하지 않은 'B'라는 패키지를 접근 및 사용할 수 있게 되고, 추후 'A' 혹은 'C'패키지 삭제시 의존성에 문제가 발생하게 됩니다.


      Yarn berry에서는 이런식의 호이스팅 동작이 일어나지 않도록 nohoist 옵션이 기본적으로 활성화 되어 있으며,

      궁극적으로 PnP(Plug n Play)라는 기술을 사용해 이러한 문제를 해결합니다.

      Yarn berry는 node_modules 파일 구조를 사용하지 않고, .yarn 경로 하위에 의존성들을 zip 포맷으로 압축 저장 후 .pnp.cjs 파일에 의존성 트리 정보를 저장합니다.

      이를 인터페이스 링커(Interface Linker)라고 합니다.

      패키지 탐색시 .pnp.cjs 파일 내부의 의존성 링크 정보를 메모리에 올려 탐색하게 되므로 비효율적이고 반복적인 Disk I/O를 제거 할 수 있습니다.

      또한 의존성 검증도 쉽게 할수 있게 되었습니다.

      아래는 .pnp.cjs 파일 구조의 일부입니다.

      
      ["axios", [
        ["npm:1.7.2", {
          "packageLocation": "./.yarn/cache/axios-npm-1.7.2-c89264f6f7-6ae80dda97.zip/node_modules/axios/",
          "packageDependencies": [
            ["axios", "npm:1.7.2"],
            ["follow-redirects", "virtual:c89264f6f79513b22a07db5e53adf77eba9e48634cf471fb55eb2e75d910809bbac48d9ce7a920c63c8ff2780624fff91866270d8acf614cbd0c4cb748a8b29a#npm:1.15.6"],
            ["form-data", "npm:4.0.0"],
            ["proxy-from-env", "npm:1.1.0"]
          ],
          "linkType": "HARD"
        }]
      ]],

      또한 pnp 모드에서는 ZipFS(Zip Filesystem)을 사용해서 디스크 용량을 줄일 수 있다는 장점도 있습니다.(이 때문에 추후 설명할 zero-install 도 가능합니다.)


      프로젝트 적용 결과 289mb(node_modules) → 66mb(ZipFS)로 약 80%의 디스크를 절약 할 수 있었습니다


      Zero-Installs

      pnp 모드로 설치한 패키지 파일들(.yarn/cache 폴더의 파일)은 오프라인 캐시 역할도 할 수 있습니다.

      git 커밋에 포함시켜 repository에 올려두면 별도의 패키지 설치 없이 git clone 이후 바로 실행이 가능합니다.

      또한 항상 같은 버전의 패키지가 실행되며, 패키지 변경이 발생하더라도 git에서 diff로 잡히기 때문에 쉽게 파악할 수 있습니다.


      Yarn Berry 적용 방법

      1. Node Corepack 설정

        Corepack 설정 없이 패키지 매니저를 설치할 수 도 있지만, 패키지 매니저의 버전 관리 및 동일 개발환경 보장을 위해 corepack 사용을 권장합니다.

        1. node  14.19 / 16.9 버전 이상이라면 corepack은 기본 설치 되어있으며, 이전 버전이라면 아래 명령어를 실행하여 corepack 설치합니다.

          > npm install -g corepack
        2. package.json에 corepack에서 관리할 패키지 매니저 및 버전을 작성합니다.

          "packageManager": "yarn@4.3.1"
        3. Corepack을 활성화 합니다.

          > corepack enable
        4. packageManager에 명시된 패키지 매니저가 설치 됩니다.

          > yarn --version
          4.3.1
      2. Yarn 설정

        1. pnp 모드를 위한 설정

          1. 프로젝트 루트 디렉토리에 .yarnrc.yml 파일 생성

          2. 아래 설정 입력

            nodeLinker: pnp
            enableGlobalCache: false
        2. 패키지 설치

          > yarn install
          1. 패키지 설치(.yarn 폴더 생성됨)

          2. yarn.lock 생성됨

          3. .pnp.cjs 생성됨

          4. .pnp.loader.mjs 생성됨

        3. .gitignore 설정

          .yarn/*
          !.yarn/cache
          !.yarn/patches
          !.yarn/plugins
          !.yarn/releases
          !.yarn/sdks
          !.yarn/versions
      3. 기존 npm 관련 파일 삭제

        1. package-lock.json 파일 삭제

        2. node_modules 폴더 삭제

      4. 기존 package.json에 정의된 스크립트실행

        > yarn start


      Yarn Berry의 현실

      Zero-install을 도입하여 효율적인 패키지 관리 및 배포시 빌드 속도를 개선시킬 수 있을것이라 생각했지만,, 현실은 달랐습니다. 


      출처: https://writejuo.tistory.com/159


      첫번째 문제, Must-Installs

      Zero-Installs를 활용해서 빌드/배포 컨테이너가 뜨면 소스코드를 git repo에서 clone 받고, 패키지 install 없이 바로 build 명령어를 실행 시킬 수 있을거라 생각했습니다.

      패키지를 repo에서 다운로드 받는 시간을 생략해 배포 속도를 향상 시키려는 목적이었습니다.

      기대감을 안고 install 없이 yarn build 입력! 그러나 결과는 아래와 같은 에러가 발생했습니다.

      gyoung@W5M6QG2F7Y > ~/tworld/t3ai-admin > ➦ 03736ab > yarn build
      /Users/1111769/tworld/t3ai-admin/.yarn/cache/rollup-npm-4.18.0-9eadb97a09-2320fe653c.zip/node_modules/rollup/dist/native.js:59
                      throw new Error(
                            ^
       
      Error: Cannot find module @rollup/rollup-darwin-arm64. npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). Please try `npm i` again after removing both package-lock.json and node_modules directory.
          at requireWithFriendlyError (/Users/1111769/tworld/t3ai-admin/.yarn/cache/rollup-npm-4.18.0-9eadb97a09-2320fe653c.zip/node_modules/rollup/dist/native.js:59:9)
          at Object.<anonymous> (/Users/1111769/tworld/t3ai-admin/.yarn/cache/rollup-npm-4.18.0-9eadb97a09-2320fe653c.zip/node_modules/rollup/dist/native.js:68:76)
          at Module._compile (node:internal/modules/cjs/loader:1467:14)
          at Module._extensions..js (node:internal/modules/cjs/loader:1551:10)
          at require$$0.Module._extensions..js (/Users/1111769/tworld/t3ai-admin/.pnp.cjs:14912:33)
          at Module.load (node:internal/modules/cjs/loader:1282:32)
          at Module._load (node:internal/modules/cjs/loader:1098:12)
          at require$$0.Module._load (/Users/1111769/tworld/t3ai-admin/.pnp.cjs:14760:31)
          at TracingChannel.traceSync (node:diagnostics_channel:315:14)
          at wrapModuleLoad (node:internal/modules/cjs/loader:215:24) {
        [cause]: Error: Required unplugged package missing from disk. This may happen when switching branches without running installs (unplugged packages must be fully materialized on disk to work).

      yarn install 을 진행해보니 .yarn/unplugged 폴더 아래에 몇몇 패키지들이 압축 해제 형태의 폴더구조로 설치 되는것을 확인 했고,

      이후에 yarn build 명령어를 실행하니 정상 작동 하였습니다.


      왜 unplugged라는 폴더가 생기고 install을 피할 수 없는지 확인해보았습니다.

      Yarn Berry는 의존성 패키지를 모두 zip으로 관리하기 때문에 모두 읽기 전용으로 밖에 동작할 수 없습니다.

      하지만 의존성 패키지가 실행되거나 파일쓰기가 필요한 경우라면 zip 파일은 압축 해제되어 .yarn/unplugged 디렉토리에 저장된다고 합니다.

      아래 경우에도 패키지를 필수적으로 unplugged 해야 한다고 합니다.

      • dependenciesMeta[].unplugged  필드를 true로 설정하여 명시적으로 패키지를 분리하는 경우

      • 패키지가 preferUnplugged 필드를 true로 설정한 경우 명시적으로

      • 패키지가 postinstall 스크립트를 나열하는 경우 암시적으로.

      • 패키지에 네이티브 파일이 포함된 경우 암시적으로

      이를 해결하기 위해 .yarnrc.yml 설정 중 enableScripts 를 false(postinstall 스크립트를 무시하는 설정)로 설정해보라는 글도 있었으나, 작동하지 않았습니다.

      위 4가지 항목중 postinstall과는 상관이 없고, 실행이 필요한 네이티브 파일이 포함된 경우로 보여집니다.


      한가지 해결방법은 있었습니다.

      .yarn/unplugged 폴더도 git 커밋에 포함시는 방법입니다.

      문제는 unplugged 폴더의 용량은 22mb(파일수 192개)로 기존 .yarn/cache 폴더의 용량 66mb(파일수 664개)과 합치면 88mb로 꽤 큰 용량과 파일수를 매번 빌드/배포 할때마다 repo에서 받아와야 한다는 이슈가 발생합니다.

      install에서 아낀 시간을 git clone에서 다시 낭비하는 조삼모사의 상황이 발생하게 되는 것이지요.


      두번째 문제, VSCode 호환 문제

      VSCode는 현재 세계에서 가장 많이 사용하는 IDE 입니다.

      물론 저를 포함한 대부분의 우리 회사 개발자들은 사내 지원을 받아 JetBrain의 유료 IDE를 사용 하고 있습니다. (이 자리를 빌어 다시 한번 감사의 말씀 드립니다.)

      JetBrain의 IntellJ 나 WebStorm은 별다른 세팅이 필요없으나 VSCode에서 Yarn Berry를 사용하려면 몇가지 추가 설정이 필요합니다.

      1. VSCode Extension에서 ZipFS 설치(zip 파일로 설치된 패키지를 읽을 수 있도록)

      2. yarn install -D typescript eslint prettier 실행 (기존 package.json에 포함 되어있다면 생략)

      3. yarn dlx @yarnpkg/sdks vscode 실행 (yarn dlx 는 기존 npx와 동일한 명령어)

      4. Typescript 버전 설정

        1. 아무 .ts 파일을 열고, VSCode 명령 팔렛트 실행(cmd + shift + p)

        2. TypeScript: Select TypeScript Version... 실행

        3. Use Workspace Version 선택

      위 설정이 후 모든것이 정상 동작하는 것 처럼 보였습니다.

      그.러.나 ESLint 가 작동하지 않았습니다. 

      물론 서비스에 영향이 있는건 아니지만 일관된 코드 포멧 개발이 어려워지기 때문에 해결하려 했으나, 결국 답을 찾지 못했습니다.

      (혹시 해결하신 분이 계시다면 연락 부탁드리겠습니다.)


      결국 김장을 담글때만 쓴다는 포기라는 단어를 꺼내버렸습니다.


      포기하긴 이르다. 칼을 뺐으니 무라도 썰어야지. pNPM

      패키지 매니저를 변경을 포기하고 다시 npm으로 가자니 개발자의 자존심이 허락하지 않았습니다.

      패키지 매니저 3대장이지만 눈밖에 있었던 pNPM을 적용해보기로 했습니다.

      pNPM은 'performant npm'의 약자이며, 특징은 다음과 같습니다.

      1. 패키지 저장 방식의 효율화

      기존 npm은 프로젝트 별로 node_modules라는 폴더에 패키지를 설치했다면,

      pNPM은 global 저장소에 패키지를 한번 저장하고 여러개의 프로젝트에서 동일한 패키지를 참조한다면 global 저장소의 패키지를 symbolic link로 참조하여 저장공간을 효율적으로 사용합니다.

      또한 설치하려는 패키지가 global 저장소에 이미 있다면 다운로드 과정을 생략하고 참조하기만 합니다.

      2. npm보다 약 2배 빠른 성능

      패키지 별 설치 순서를 계산할 필요가 없어서 병렬 설치를 지원합니다.

      모든 패키지를 설치한 후 프로젝트의 의존성 트리와 똑같은 형태로 링크만 걸어주는 형태이기 때문에 설치 프로세스가 빠릅니다.


      yarn berry의 zero-install 처럼 아주 새로운 기능은 없지만, 기존 npm과 유사한 구조를 유지하면서 비효율 적인 부분만 해결한 것이 pNPM이라고 생각됩니다.

      pNPM으로 마이그레이션 하거나, 구조를 파악하기에도 npm과 유사한 부분이 많아서 러닝커브가 낮다는 장점도 있습니다.


      pNPM 적용 방법

      1. Node Corepack 설정 (yarn berry 설치 과정과 중복된 부분은 생략)

        1. package.json에 corepack에서 관리할 패키지 매니저 및 버전을 작성합니다.

          "packageManager": "pnpm@9.4.0"
        2. Corepack을 활성화 합니다.

          > corepack enable
        3. packageManager에 명시된 패키지 매니저가 설치 됩니다.

          > pnpm --version
          9.4.0
      2. 패키지 설치

        1. 패키지 설치(node_modules 폴더 생성됨)

          > pnpm install
        2. pnpm-lock.yaml 생성됨

      3. 기존 yarn 관련 파일 삭제

        1. .yarn 폴더 삭제

        2. yarn.lock 삭제

        3. .pnp.cjs 삭제

        4. .pnp.loader.mjs 삭제

      4. 기존 package.json에 정의된 스크립트 실행

        > pnpm start


      pNPM 적용 후기

      패키지 install 속도 향상

      npm, pnpm 둘다 모든 캐시 및 lock 파일 삭제 후 테스트 결과입니다.

      npm

      pnpm

      45초

      21초



      명령어 간소화

      run 이라는 명령어를 생략가능합니다.

      npm

      pnpm

      npm run build

      pnpm build

      이외에도 pNPM은 모노레포를 사용할때 큰 장점이 있다고 합니다.

      현재 프로젝트는 모노레포 구성으로 되어있지 않은데, 추후 모노레포 적용여부도 검토하여 적용해볼까 합니다.


      맺음말

      지금까지 yarn의 zero-install에 반해서 프로젝트에 적용해보려다 pNPM을 적용하게된 과정을 공유드렸습니다.

      이 글을 읽는 분들의 패키지 매니저 전환 검토에 도움이 되었으면 합니다.

      마지막으로 pNPM 공식 홈페이지에 있는 패키지 매니저들 간의 기능 비교 표를 첨부하며 이글을 마칩니다.

      기능

      pnpm

      Yarn

      npm

      워크스페이스 지원

      ✔️

      ✔️

      ✔️

      Isolated node_modules

      ✔️ - 기본값

      ✔️

      ✔️

      Hoisted node_modules

      ✔️

      ✔️

      ✔️ - 기본값

      피어 자동 설치

      ✔️

      ❌

      ✔️

      Plug'n'Play

      ✔️

      ✔️ - 기본값

      ❌

      Zero-Installs

      ❌

      ✔️

      ❌

      의존성 패치

      ✔️

      ✔️

      ❌

      Node.js 버전 관리

      ✔️

      ❌

      ❌

      lockfile 보유

      ✔️ - pnpm-lock.yaml

      ✔️ - yarn.lock

      ✔️ - package-lock.json

      재정의 지원

      ✔️

      ✔️ - resolution을 통해

      ✔️

      Content-addressable 저장소

      ✔️

      ❌

      ❌

      동적 패키지 실행

      ✔️ - Via pnpm dlx

      ✔️ - Via yarn dlx

      ✔️ - Via npx

      부작용 캐시

      ✔️

      ❌

      ❌

      Listing licenses

      ✔️ - Via pnpm licenses list

      ✔️ - Via a plugin

      ❌

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      mesquaker 님의 최신 블로그

      더보기
      동영상 기고하기