[모던 프론트엔드 구성] 2. npm workspace로 각 패키지별 dep 설정하기

도깨비젤리

·

2022. 6. 12. 18:35

 

 

TLDR;


🦄-w 플래그로 각 패키지 별로 다른 버전의 라이브러리를 설치하여 운용할 수 있다🦄
🦄 각 패키지는 자신의 package.json에 선언된 종속성을 우선적으로 따라 간다.🦄
🦄 그러나, 모노레포는 컨테이너 개념이 아닌, 관심사의 분리에 촛점을 맞춘 개념이므로, 패키지 간 종속성 교환을 완전히 막을 수는 없다 🦄

 

 

 

 

 

 

Previously on lastest post...


 

 

지난 포스트에서는 npm workspace를 이용해서 각 모듈을 패키지화하여 하나의 저장소에 관리할 수 있는 모노레포 구조를 구현해봤습니다. 모노레포 구조를 가져가게 된다면, 의존성 지옥에 빠지는 일 없이, 개발자들이 각자의 관심사에만 집중할 수 있고, 작업환경의 통일로 데브옵스에서도 큰 이득을 얻을 수 있다고 했습니다.

 

그러나, 만약 패키지에서 사용하는 라이브러리가 각기 다른 버전의 패키지를 peer denpency를 가지고 있다면 어떻게 할까요? 개발자들 간 합의를 통하여 워크스페이스에서 사용할 버전을 확정 지어야할까요?

 

물론 그럴 수도 있겠지만, 그렇다면 워크스페이스를 사용하는 의미가 없겠습니다.

이번 포스트에서는 각 패키지에서 동일한 종속성을 다른 버전으로 운용하는 법에 대해서 알아보겠습니다

 

 

 

-w 플래그를 사용한 종속성 설치


 

말은 거창하게 했지만, -w 플래그를 통하여 설치하면 끝입니다.

 

지난 포스트에 사용했던 프로젝트에서 진행해봅시다.

 

 

쌤플 코드 ㅇㅏ직 없ㄷㅏ❑면 ヌ1금 ㅇ1 夬➬ㅇㅔㅅㅓ 즉ㅅ1 ㄷㅏ운❤롥❀☪뜵☪

 

https://github.com/SpookyJelly/workspace

 

GitHub - SpookyJelly/workspace: study in workspace for own good

study in workspace for own good. Contribute to SpookyJelly/workspace development by creating an account on GitHub.

github.com

 

 

 

 

 

프로젝트가 준비되었다면 이제 테스트해봅시다.

 

 

타입스크립트는 4.1 버전부터 템플릿 리터럴 타입이라는 기능을 추가했습니다. 기존에 존재하던 스트링 리터럴 타입을 확장해서 새로운 타입을 만드는 것인데, 우리는 이를 사용해서 패키지별 버저닝을 테스트 해볼 것입니다.

 

이를 위해서 ts 4버전과 ts 3 버전이 깔린 패키지가 필요합니다. 만약 샘플 코드를 사용하시는 분이라면 shared 패키지에 이미 ts 4.7.3 이 종속성으로 들어가 있으니, styles 패키지에만 ts를 3.x 버전으로 깔아주시면 됩니다.

 

 

아래와 같이 스크립트를 입력해줍시다.

 

npm install typescript@^3.9 -w @workspace/styles

 

 

그리고 각 패키지의 루트에 아래와 같은 파일을 생성합니다.

 

// .../shared/lorem.ts , .../styles/lorem.ts

type UserName = "SpookyJelly" | "Naram.Dash" | "SloopyCat";
type Message = "Hello" | "Get lost" | "Leave me Alone";
type UserMessage = `${Message}, ${UserName}!`;

const welcomeMessage: UserMessage = "Hello, SpookyJelly!";
console.log(welcomeMessage);

 

package.json을 아래와 같이 수정합니다.

 

 

// styles
{
  "name": "@workspace/styles",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "lorem": "tsc lorem.ts"
  },
  "author": "spookyJelly",
  "license": "ISC",
  "dependencies": {
    "typescript": "^3.9.10"
  }
}


// shared

{
  "name": "@workspace/shared",
  "version": "1.0.0",
  "description": "",
  "main": "./dist/index.js",
  "devDependencies": {
    "typescript": "^4.7.3"
  },
  "scripts": {
    "start": "tsc -d",
    "lorem": "tsc lorem.ts"
  },
  "author": "spookyjelly",
  "license": "ISC"
}

 

 

완료되었으면 아래 스크립트로 styles 패키지와 shared 패키지에서 lorem 스크립트를 실행시켜봅시다.

 

npm run lorem -w @workspace/styles

 

npm run lorem -w @workspace/shared

 

 

styles에서 tsc를 실행한 결과. 3.9.x 버전에서는 템플릿 리터럴 타입이 존재하지 않아 컴파일에 실패

 

3.9.x 버전의 ts를 사용한 styles에서는 컴파일이 실패하였지만,

 

 

shared에서 tsc를 실행한 결과, 정상적으로 컴파일이 된다

 

 

 

4.1 이상의 ts를 사용한 shared에서는 성공적으로 컴파일이 되었음을 확인할 수 있습니다.

 

따라서, npm workspace로 구축한 패키지에서는 각 패키지 별로 라이브러리의 버전을 관리할수 있음을 알수 있습니다.

 

 

 

 

Faker를 사용해서 한번 더 해보기


 

아직 아리까지하신 분들을 위해 Faker를 사용해서 한번 더 해보겠습니다.

 

https://www.npmjs.com/package/faker

 

faker

Generate massive amounts of fake contextual data. Latest version: 6.6.6, last published: 5 months ago. Start using faker in your project by running `npm i faker`. There are 2508 other projects in the npm registry using faker.

www.npmjs.com

 

 

faker 라이브러리는 더미 데이터를 생산해주는 가장 유명한 자바스크립트 라이브러리 중 하나로, 지난 1월 개발자가 오픈 소스의 한계와 오픈 소스 개발자들이 놓인 취약점에 현타를 느낀 나머지 6.6.6 버전으로 업그레이드 하며 레포 전체를 날려버리는 퍼포먼스를 보여주어 큰 화재를 일으킨 라이브러리입니다.

 

우리는 이 라이브러리를 조금 깜찍하게 사용해봅시다. shared 패키지에는 레포 날아가기 이전의 멀쩡한 버전의 5.5.x 버전을 설치하고, styled 패키지에는 6.6.6 버전을 설치한 뒤 동일한 코드를 실행시켜보겠습니다.

 

 

 

Faker에 얽힌 문제를 더 자세히 알고 싶으면 여기로

 

 

 

 

아래 스크립트를 실행시켜주고, 파일을 생성합시다.

 

npm install faker@5.5.3 -w @workspace/shared
npm install faker -w @workspace/styles

 

 

 

// styles && shared => faker.js

const faker = require("faker");
console.log(faker.internet.email());

 

 

그리고 각 폴더로 이동해서 node로 faker.js를 실행시켜봅시다

 

 

node faker.js

 

 

좌 : shared에서 실행 / 우 : styles에서 실행

 

 

보시다시피, 5.5.3 버전을 설치한 shared에서는 정상적으로 faker 기능을 사용할 수 있는 한편, 레포 자체가 사라진 6.6.6 버전을 쓰는 styles에서는 faker 모듈 자체가 없다는 에러를 확인할 수 있습니다.

 

 

 

한 가지 재미있는 사실은, 두 파일에서 require 문으로 취득하고 있는 faker의 위치를 찾아가보면 다른 버전임에도 불구하고 동일한 출처를 가지고 있습니다. 분명 다른 패키지로 관리하고, 다른 버전을 설치했는데도 참조하는 파일에 아무런 버전 정보가 없다는 점이 신기합니다.

 

추측컨데 아마 ./lib/locales로 이동하면 패키지별 버전 정보가 심볼릭 링크로 걸려있는게 아닐까 하는 생각이 드는데, 제 짧은 지식으로는 이 이상 깊게 들어갈 수가 없어서 막연히 그런거 같다~ 라는 생각만으로 접어두겠습니다.

 

 

 

 

 

요약 : workspace는 패키지 별 버전 혼용을 완전 차단하는게 아니다.

 

 

 

 

난 이거 설치한 적이 없는데...?


 

앞서 말했듯 npm workspace는 컨테이너는 아닙니다. 말인 곧 즉, 패키지 별로 환경을 완전히 분리할 수 없다는 말입니다.

 

이게 무슨 말인지 한번 알아봅시다. 앞서 만들었던 faker.js를 app 패키지의 루트에 옮긴 후 node faker.js로 app 패키지에서 실행시켜봅시다.

 

 

 

 

 

오잉?? app 패키지에서는 faker를 설치한 적이 없음에도 불구하고 faker.js가 실행됩니다!

 

이건 npm workspace가 workspace의 종속성을 루트의 node_modules로 hoist 하기 때문인데요. 그래서 비록 app에서는 faker를 설치한 적이 없지만, 루트의 node_modules에는 faker가 존재하기 때문에 이를 불러서 사용하기 때문입니다.

 

 

고의적으로 종속성을 꼬아버리려는 의도를 가진게 아닌 이상, 실무에서는 이런 일이 일어나지 않을 것이지만 ( 필요하다면 그때 그때 패키지 별로 라이브러리를 설치하면 되므로 ), 이는 워크스페이스를 독립적인 공간으로 사용할 수 없다는 것을 나타냅니다. 이렇듯 npm workspace는 패키지로 컨테이너화 하는 기능은 갖추지 못하는 것을 확인할 수 있습니다. 그렇다고 npm workspace가 잘못 만들어졌다고 말하기는 뭣한게, 애초에 workspace를 사용하는 목적은 다른게 아니라 관심사의 분리가 메인이기 때문입니다.

 

 

그럼 만약 패키지를 독립적인 컨테이너로 사용하고 싶은 경우는 어떻게 해야할 까요?? 답은 간단합니다.

 

yarn workspace를 쓰면 됩니다.

 

 

소개 편에서 말씀드렸듯, workspace 분야는 yarn이 npm 보다 더 강력한 파워를 가지고 있습니다. 그리고 그 파워의 출처는 nohoist 옵션을 이용한 패키지들을 마치 유사 컨테이너처럼 이용할 수 있다는 특징입니다.

 

다음 포스트에서는 yarn workspace를 사용해보면서, 각 패키지를 컨테이너처럼 활용하는 방법에 대해서 알아보겠습니다