[Monoepo] Yarn Workspaces의 의존성 Hoisting

2024-04-12 hit count image

Yarn의 Workspaces를 사용하여 모노레포를 구성하는 과정에서, 각 프로젝트에 필요한 의존성을 추가할 때 동작하는 Hoisting에 대하여 알아도록 하겠습니다.

개요

이번 블로그 포스트에서는 YarnWorkspaces를 사용하여 모노레포를 구성하는 과정에서 각 프로젝트에 필요한 의존성(라이브러리)을 추가할 때, 의존성이 추가되는 과정에서 발생하는 Hoisting에 대해서 알아보도록 하겠습니다.

블로그 시리즈

이 블로그는 시리즈로 제작되었습니다. 다음 링크를 통해 다른 블로그 포스트도 확인해 보시기 바랍니다.

NodeJS의 모듈 불러오기

NodeJS에서 import를 사용하여 모듈을 불러올 때, NodeJS는 우선 루트 폴더의 node_modules를 확인하고 해당 모듈이 없는 경우, 부모의 node_modules를 확인하고, 이를 반복하여 최상위 폴더의 node_modules까지 확인합니다.

이번 블로그 포스트에서 소개할 HoistingNodeJS의 모듈 불러오기 과정을 활용한 기능입니다.

의존성 Hoisting

YarnWorkspaces를 사용하여 모노레포를 구성하는 과정에서 각 프로젝트에 필요한 의존성을 추가하면, 효율적으로 의존성을 관리하기 위해 해당 의존성은 Hoisting되어 최상위 폴더의 node_modules에 설치됩니다.

의존성의 Hoisting은 다음과 같은 장점을 가집니다.

  • 각 프로젝트에서 공통으로 사용되는 의존성은 한번만 설치되어 메모리를 절약할 수 있습니다.
  • 동일한 버전의 의존성과 다른 버전의 의존성을 분리하여 관리하기 때문에 의존성의 버전 충돌을 방지할 수 있습니다.
  • 동일한 의존성을 설치하는 경우, 한번만 설치를 하므로 빠르게 설치가 가능하고 이미 설치된 의존성을 사용하는 경우 추가로 설치하지 않기 때문에 캐싱 효과를 얻을 수 있습니다.

예제

의존성의 Hoisting을 확인하기 위해 예제를 만들어 봅시다. 우선, 다음과 같이 폴더와 파일 구조를 생성합니다.

.
├── package.json
└── src
    ├── module-a
    │   ├── index.js
    │   └── package.json
    └── module-b
        ├── index.js
        └── package.json

module-apackage.json은 다음과 같습니다.

// src/module-a/package.json
{
  "name": "module-a",
  "version": "1.0.0",
  "main": "index.js"
}

module-bpackage.json은 다음과 같습니다.

// src/module-b/package.json
{
  "name": "module-b",
  "version": "1.0.0",
  "main": "index.js"
}

그리고 module-bindex.js는 다음과 같습니다.

// src/module-b/index.js
console.log('module-b');

module-aindex.js는 다음과 같습니다.

// src/module-a/index.js
console.log('module-a');

require('module-b');

마지막으로 루트 폴더의 있는 package.json 파일을 다음과 같이 수정합니다.

{
  "name": "monorepo",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "private": true,
  "workspaces": {
    "packages": ["src/*"]
  }
}

YarnWorkspaces를 사용하기 위해 package.jsonwordspacespackages가 설정된 것을 확인할 수 있습니다. YarnWorkspaces에 대한 자세한 내용은 다음 블로그 포스트를 참고하시기 바랍니다.

의존성 설치

이제 다음 명령어를 실행하여 의존성을 설치합니다.

yarn install

그럼 다음과 같이 node_modulesmodule-amodule-bSymlink가 생성된 것을 확인할 수 있습니다.

.
├── node_modules
│   ├── module-a -> ../src/module-a
│   └── module-b -> ../src/module-b
├── package.json
├── src
│   ├── module-a
│   │   ├── index.js
│   │   └── package.json
│   └── module-b
│       ├── index.js
│       └── package.json
└── yarn.lock

의존성 추가

이제 module-a에 의존성을 추가하여 의존성이 Hoisting되는지 확인해 봅시다. module-apackage.json 파일인 src/module-a/package.json 파일을 열고 다음과 같이 수정합니다.

{
  "name": "module-a",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies": {
    "lodash": "^3"
  }
}

그런 다음 다음 명령어를 실행하여 의존성을 설치합니다.

yarn install

그럼 다음과 같이 루트 폴더의 node_modulesHoistinglodash가 설치된 것을 확인할 수 있습니다.

.
├── node_modules
│   ├── lodash
│   ├── module-a -> ../src/module-a
│   └── module-b -> ../src/module-b
├── package.json
├── src
│   ├── module-a
│   └── module-b
└── yarn.lock

Hoisting된 의존성 사용

Hoisting된 의존성을 사용할 수 있는지 알아보기 위해 src/module-a/index.js 파일을 열고 다음과 같이 수정합니다.

const _ = require('lodash');

console.log('module-a');

require('module-b');

console.log(_.flatten([1, [2, 3, 4]]));

그리고 다음 명령어를 실행하여 module-a를 실행합니다.

node ./src/module-a

그럼 다음과 같이 lodash가 잘 불러와지는 것을 확인할 수 있습니다.

module-a
module-b
[ 1, 2, 3, 4 ]

이를 통해 Hoisting된 의존성을 사용할 수 있음을 확인할 수 있습니다.

의존성 버전 충돌

동일한 의존성의 다른 버전이 설치된 경우, 어떻게 동작하는지 확인해 봅시다. module-bpackage.json 파일인 src/module-b/package.json 파일을 열고 다음과 같이 수정합니다.

{
  "name": "module-b",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies": {
    "lodash": "^4"
  }
}

그런 다음, 다음 명령어를 실행하여 의존성을 설치합니다.

yarn install

그럼 다음과 같이 버전이 다른 의존성은 모듈 폴더안에 node_modules에 설치되는 것을 확인할 수 있습니다.

.
├── node_modules
│   ├── lodash
│   ├── module-a -> ../src/module-a
│   └── module-b -> ../src/module-b
├── package.json
├── src
│   ├── module-a
│   │   ├── index.js
│   │   └── package.json
│   └── module-b
│       ├── index.js
│       ├── node_modules
│       │   └── lodash
│       └── package.json
└── yarn.lock

nohoist 옵션

Hoisting된 의존성을 사용하지 않고, 각 프로젝트의 node_modules에 의존성을 설치하고 싶은 경우, nohoist 옵션을 사용할 수 있습니다. 루트 폴더의 package.json 파일을 열고 다음과 같이 nohoist 옵션을 추가합니다.

{
  "name": "monorepo",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "private": true,
  "workspaces": {
    "packages": ["src/*"],
    "nohoist": ["**/lodash"]
  }
}

그리고 yarn.lock 파일을 삭제한 다음, 다음 명령어를 실행하여 의존성을 설치합니다.

# rm yarn.lock
yarn install

그럼 다음과 같이 lodashHoisting되지 않고 각 프로젝트의 node_modules에 설치된 것을 확인할 수 있습니다.

.
├── node_modules
│   ├── module-a -> ../src/module-a
│   └── module-b -> ../src/module-b
├── package.json
├── src
│   ├── module-a
│   │   ├── index.js
│   │   ├── node_modules
│   │   │   └── lodash
│   │   └── package.json
│   └── module-b
│       ├── index.js
│       ├── node_modules
│       │   └── lodash
│       └── package.json
└── yarn.lock

완료

이것으로 YarnWorkspaces를 사용하여 모노레포를 구성하는 과정에서 각 프로젝트에 필요한 의존성을 추가할 때, 의존성이 추가되는 과정에서 발생하는 Hoisting에 대해서 알아보았습니다. 또한 의존성이 Hoisting되는 것을 방지하기 위한 nohoist 옵션에 대해서도 알아보았습니다.

여러분도 YarnWorkspaces를 사용하여 모노레포를 구성하는 과정에서 의존성의 Hoistingnohoist을 활용하여 효율적으로 의존성을 관리해 보시기 바랍니다.

제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!

앱 홍보

책 홍보

블로그를 운영하면서 좋은 기회가 생겨 책을 출판하게 되었습니다.

아래 링크를 통해 제가 쓴 책을 구매하실 수 있습니다.
많은 분들에게 도움이 되면 좋겠네요.

스무디 한 잔 마시며 끝내는 React Native, 비제이퍼블릭
스무디 한 잔 마시며 끝내는 리액트 + TDD, 비제이퍼블릭
[심통]현장에서 바로 써먹는 리액트 with 타입스크립트 : 리액트와 스토리북으로 배우는 컴포넌트 주도 개발, 심통
Posts