여러가지 방면에서 npm 보다는 yarn, yarn-berry 가 성능면에서 우수하기 때문에 선호하고 사용해왔습니다.
npm 도 버전을 거듭하며 발전해오고 있지만, 그 사이 yarn 의 이점 때문에 yarn 에 익숙해져 굳이 npm 을 사용하지 않고, 쭉 사용하게 되는거같아요.
그러던 중에 npm 으로 모노레포 구성하는 것이 yarn 과 거의 유사해 실습해보고, yarn 과 비교해서 차이점을 분석해보고자 글을 정리합니다.
npm
- workspaces 를 정의하는 순간, 모든 패키지들이 link 되어, 패키지들 간에는 어떤 의존성 설정 없이도 바로 참조가 가능해집니다.
❗️ npm 과 yarn 1.x 은 node_modules 를 사용하고, 호이스팅을 사용하면서 동일한 문제가 있으니, npm 은 간단히 사용법만 설명하고, yarn 1.x 에 자세히 설명을 해두었습니다.
workspaces 정의하기
// package.json
{
"workspaces": [
"packages/*"
],
}
npm workspace 관련 명령어
-w 와 --workspace 는 동일한 용도이지만 문법만 살짝 다릅니다. 아래 직관적으로 작성된 스크립트를 참고해주세요
"scripts": {
"start:service-a": "npm run start -w service-a",
"start:service-b": "npm run start -w service-b",
"start:multiple": "npm run start -w service-a -w service-b",
"start:all": "npm run start -w packages",
"start:all2": "npm run start --workspaces"
},
workspace 구성하기
- 간단히 packages/* 하위 폴더를 workspaces 로 지정했습니다.
- 패키지간에 어떠한 의존성도 추가하지 않았습니다.
service-b/index.js
function sum(a, b) {
return a + b
}
module.exports ={
sum
}
service-a/index.js
const serviceB = require("service-b");
console.log(serviceB.sum(1,2));
service-a/index.js 를 실행하면 잘 동작합니다.
yarn classic (1.x) 으로 구성하기
npm 과 동일한 방식으로 package.json 에서 workspace 를 수정해주고, CLI 명령어만 다릅니다.
- 의존성을 설치해야지만, 서로 다른 패키지를 사용할 수 있습니다. 이때 버전을 명확하게 명시해줘야합니다(버전을 충족한다면, npm 레지스트리보다 로컬의 workspace 를 우선하여 설치합니다).
특정 workspace 에서 커맨드 실행
yarn workspace <WORKSPACE_NAME> <COMMAND_NAME>
workspace 간 의존 관계 확인하기
yarn workspaces info // 2.x 이후로는 yarn workspaces list
모든 workspace 의 명령어 실행하기
yarn workspaces run <COMMAND> // 2.x 이후로는 yarn workspaces foreach <COMMAND>
nohoist 옵션 사용하기
npm 과 yarn 등 모노레포를 구현한 패키지 매니저들은, 각 패키지별로 중복되는 의존성 설치를 방지하기 위해서 root 경로로 호이스팅(hoisting) 기법을 사용합니다.
아래 그림처럼 package-1 에서 사용하는 패키지들 중 버전까지 동일한 의존성들은 여러번 설치될 필요가 없기 때문에, root 경로로 호이스팅되어 하나만 설치하도록 합니다.
최종적으로 아래와 같은 구조가 됩니다.
하지만 일부 모듈 로더는 호이스팅을 지원하지 않고(metro), 또 의도적으로 패키지를 호이스팅하지 않고, 해당 패키지 내에서만 의존하도록 하고싶을 때 nohoist 속성에 해당 패키지를 명시해주면 됩니다.
package.json
"workspaces": {
"packages": [
"packages/*"
],
"nohoist": [
"**/dayjs"
]
}
glob 패턴을 사용하며, 위와 같이 작성할 경우, 모노레포의 모든 패키지에서 dayjs 패키지는 호이스팅 되지 않습니다.
yarn 2.x (berry) 소개
문제1) 호이스팅
앞서 npm 과 yarn 은 호이스팅을 사용해서 중복된 의존성 문제를 해결하였습니다.
하지만 호이스팅은 의도치 않은 side effect 를 발생시킵니다.
예를들어서 axios 라이브러리를 설치할 경우, axios 의 의존성 패키지들을 모두 호이스팅 해버려서 아래와같이 직접 패키지를 설치한것 과 같이 사용할 수 있어집니다.
const axios = require('axios');
const formData =require('form-data'); // 에러안남
문제2) node_modules
node.js 에서 require() 함수로 모듈을 불러오면, 해당 모듈을 찾기위해서 상위 node_modules 를 계속해서 순회합니다. I/O 동작이 계속해서 발생하여 이미 잘 알려진 문제이기도 합니다. 또한 매우 큰 저장공간을 차지하는 단점도 있습니다.
🎉 위 두 문제를 해결하기 위해서 yarn 2.x 에서는 PnP 를 도입하였습니다.
yarn-berry 의 PnP(Plug & Play)
기존의 npm, yarn1 이 했던 것 처럼 의존 패키지들을 node_modules 로 다운받아 저장하는 대신에 패키지를 압축한 파일을 .yarn/cache 폴더에 수평적으로 저장하는데 이를 PnP 라고 부릅니다.
압축 파일들은 ZipFS를 사용하여 모듈 로드가 필요할 때 메모리에서 압축을 해제하여 접근합니다.
PnP 를 사용함으로써
- 호이스팅으로 인한 유령 의존성 문제 방지
- 수평적 저장으로 인해 모든 패키지에 대한 접근 시간 O(1) 로 단축
- zero-install 전략으로 패키지 설치 과정 생략가능(선택)
의 이점을 누릴 수 있습니다.
시작하기
yarn set version berry
touch yarn.lock
yarn init -2
- global yarn version 이 2.x 이상 이어야합니다.
- 패키지를 초기화하기 위해서는 yarn.lock 파일이 사전에 존재해야합니다.
- yarn init 이 아닌 yarn init -2 를 해야합니다.
패키지 설치
yarn add -D vite
yarn add dayjs
.pnp.cjs 에 의존성 트리가 저장됩니다.
node 실행
yarn-berry 는 node 를 실행할 때 반드시 yarn node 로 실행해야합니다.
yarn node src/index.js // node src/index.js (x)
yarn 2.x (berry) 모노레포 구성하기
npm, yarn1, yarn2 모두 모노레포를 구성하기 위해서 package.json 에서 해줘야할 일은 동일하게 workspaces 를 정의해주는 것입니다.
"workspaces": [
"packages/*"
],
모노레포 초기화
모노레포의 root 경로에서만 "-2" 를 붙여 초기화합니다.
yarn init -2
하위 패키지 추가 생성
mkdir packages/client
mkdir packages/ui
각각 초기화
❗️하위 패키지는 yarn init 만 해줍니다
yarn init
각 패키지별로 필요한 개발환경을 구축하고 개발합니다.
패키지간 의존성 만들기
정확히 버전까지 해서 불러오고자하는 패키지를 설치합니다.
yarn add ui@1.0.0
"dependencies": {
"ui": "1.0.0"
}
패키지로써 잘 동작하도록 package.json 을 수정해줍니다.
// ui/package.json
{
"name": "ui",
"version": "1.0.0",
"packageManager": "yarn@3.6.4",
"main": "src/index.ts",
"exports": {
".": {
"import": "src/index.ts"
},
"./utils": {
"import": "./src/utils/index.ts"
}
}
}
eslint, prettier 설정
이 두 설정은 tsconfig 와 다르게 에디터의 도움을 받는 도구들입니다.
에디터는 yarn-berry 의 압축된 플러그인을 바로 해석할 수 없습니다. 이것은 알려진 이슈로써 에디터마다 해결방법이 있습니다.
저는 webstorm 을 사용하기 때문에 압축을 해제하여 에디터에 인식시켜줍니다.
yarn unplug <압축해제할 패키지명>
위 명령어로 eslint, prettier 를 해제하면 .yarn/unplugged/* 에 압축이 풀린 패키지가 생성됩니다.
이후 에디터를 껏다가 키면 자동으로 인식하고, 안된다면 메뉴얼하게 해당 폴더를 찾아서 지정합니다.
메뉴얼 하게 설정해준다면 경로는 압축해제된패키지/node_modules/패키지명 까지 설정해줍니다.
이 외에도 yarn-berry 는 기존의 node_modules 과 호환되는 방식들에서 충돌이 발생할텐데, 레퍼런스를 찾아서 잘 해결하시길 바랍니다. 간단히 작업한 코드 예제 를 참고해주세요.
마무리
지난 다른 포스트에서 yarn, yarn2 과 모노레포, 마이크로프론트엔드 구축을 다루었었는데요.
내용이 부실하기도하고, 사실 이것도 부실한거 같지만 파도파도 신경쓸껏, 충돌하는 부분이 꽤많은거 같아 한번 더 핵심을 살펴보고 정리해보았습니다.
자잘하게 할말들이 많다보니 글로남기기에는 내용이 너무 많아지네요.
기회가된다면 영상으로 한번 남기도록 해야겠습니다 :)
'FE > 개발환경' 카테고리의 다른 글
[개발환경] webpack esbuild-loader 소개 (0) | 2023.09.21 |
---|---|
[개발환경] micro-frontend 아키텍처 구성해보기 (0) | 2023.09.21 |
[개발환경] yarn workspace 모노레포 구성 (0) | 2023.09.21 |
[개발환경] yarn vs yarn2 (0) | 2023.09.21 |