[TIL] 모노레포 (Lerna, yarn workspace)

seo young park·2022년 7월 27일
1

TIL

목록 보기
6/7

Mono-Repo

여러 Package를 한 Repository에서 관리한다.

  • 장점
    • 공통 설정 및 모듈 단일화
    • package 공유를 통한 중복 코드 감소
    • 단일 이슈 트래킹
    • 효율적인 의존성 관리
    • 통합 CI 및 테스트
    • 프로젝트 간 협업
  • 단점
    • repository의 거대화
    • CI build 저하
    • Package 간 무분별한 의존성
    • Dev Tools의 인덱싱 저하

Lerna

git과 npm을 활용하여 mono-repo와 workflow를 최적화하는 도구다.

  • 기능
    • 다중 패키지 종속성 관리 및 모듈의 중복성 제거
    • 다중 패키지 단일 버전 및 독립적 버전 관리정책 선택 가능
    • 변경된 패키지 일괄적으로 git remote repository에 push
    • 변경된 패키지 일괄적으로 npm repository에 publish

패키지 버전 관리 정책

세팅

npm install -g lerna
mkdir {폴더 이름}
cd {폴더 이름}

// (Fixed/Locked mode)
lerna init  
// (indepedent mode)
lerna init --independent) 

Fixed/Locked mode vs indepedent mode

  • Fixed/Locked mode (기본값)

    • 패키지 버전을 프로젝트 root에서 관리
    • lerna publish할 경우, 하위 패키지의 모듈 변경사항이 있으면 전체 새 버전으로 업데이트
    • 하나의 패키지가 업데이트되면 모든 패키지가 업데이트
  • independent mode

    • 버전은 각 패키지의 package.json에 명시
    • lerna publish할 경우, 변경된 Package에 한해 새 버전 업데이트

yarn workspaces

lerna bootstrap 는 lerna에서 자주 사용하는 명령어 중 하나로 패키지들 내에 공통 모듈에 symbolic link를 걸어 중복 설치를 방지한다. 여기서 -hoist 옵션을 사용하면 가장 많이 사용되고 있는 의존성 모듈이 루트 node_moduleshoisting된다. 이 명령어를 생략할 수 있는 것이 바로 yarn workspaces다.

yarn workspaces란 monorepo 내 멀티 패키지 의존성 관리를 도와주는 툴이다. 중복 모듈을 한 곳에 모아 관리할 수 있고, 공유 모듈의 참조가 쉬워진다는 것이 장점이다. lerna에선 npm을 패키지 관리 도구로 사용하고 있지만, npmClient를 yarn으로 설정하면 yarn을 이용할 수 있다. 다만 패키지별 버전 관리 및 배포에 용이한 것은 yarn보다 lerna이기 때문에 프로젝트의 배포와 버전관리는 lerna, 패키지 의존성 관리는 yarn으로 하는 것이다. (참고로 lerna도 yarn workspaces를 사용했다고 한다.)

세팅

  • lerna.json 에서 npmClient, useWorkspaces 설정 값 추가
"npmClient": "yarn",
"useWorkspaces": true,

// "packages" # 배포와 관련된 packages 명시할 때 사용
  • 루트 package.json 에서 workspaces 위치 추가
{
  "private: true" // 작업 영역은 publish목적이 아니므로 해당 코드 필수로 추가
	"workspaces": [
			"packages/*/*"
		]
}

Dependency Module Hoisting

yarn 명령어를 입력하면, 루트의 package.json과 각 패키지의 package.json에 명시되어있는 dependency가 최대한 중복을 줄인 상태로 루트의 node_modules 안에 호이스팅되어 설치된다. 이 때, dependency로 명시되어있는 모듈은 symbolic link가 연결되어 최신 버전이 아니라 로컬에서 의존하는 버전을 기준으로 설치된다.

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

예를 들어 package-1는 A와 ‘A를 의존성 모듈로 이용하는’ C를 의존성 모듈로 이용하고 있다. 동일한 의존성을 지니는 모듈을 중복 설치할 필요가 없기 때문에 함께 쓸 수 있도록 루트의 node_moduleshoisting된다.

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

기본적으로 모노 레포의 각 패키지는, 위의 방식처럼 node_modules를 가지고 dependency를 관리한다. 그렇지만 여러 패키지에서 사용되는 모듈은 중복설치하지않고 루트의 node_moduleshoisting 하여 모듈을 공유하게된다.

Dependency Module Hoisting 에러

can’t find module “B@2.0from project root “monorepo” (not able to follow symlink)
can’t find module “A@1.0frompackage-1 (unaware of the module tree above in “monorepo”)

yarn 공식 문서에 따르면, 아직 yarn workspace에서 Dependency Module Hoisting 관리는 불안정한 상태라고 한다. 프로젝트 내에서 build할 때 위와 같은 에러메세지가 나타나는데, 그 이유는 특정 모듈이 해당 로컬 node_modules만 참조하려하기 때문이다. 루트 node_moduls로 hoisting된 모듈은 로컬 node_modules에는 존재하지 않아서 can’t find 에러가 발생한다.

  • nohoist 옵션

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

"workspaces": {
  "nohoist": ["react-native", "react-native/**"]
}

nohoist 옵션을 사용하면 로컬 node_modules에 설치되어 can’t find 에러를 피할 수 있다.

  • 연결
//some-dep 전역환경 설치
npm install -g some-dep

//some-dep으로 이동해서 다음 명령어 실행하면, global symlink 생성
npm link

//my-app으로 이동해서 다음 명령어 실행하면, some-dep의 global symlink를 my-app에서 사용
npm link some-dep

이미지 내 출처 표시 - fhinkel

  • 연결 해제
//my-app으로 이동해서 다음 명령어 실행하면, 링크 제거 및 패키지 삭제. 두 개는 같은 명령어다.

npm unlink --no-save some-dep
//or
npm uninstall --no-save some-dep

//some-dep으로 이동해서 다음 명령어 실행하면, global symlink 삭제
npm uninstall

Dependency 관리

//root dependency 관리
yarn add <패키지명> -W (or --ignore-workspace-root-check)
yarn remove <패키지명> -W (or --ignore-workspace-root-check)

//package별 dependency 관리
yarn workspace <workspaceName> add <패키지명>
yarn workspace <workspaceName> remove <패키지명>

Versioning & Publishing

lerna publish

프로젝트가 git과 연결되어있는 경우, lerna 프로젝트의 패키지 버전관리 및 발행한다.

  • 명령어
lerna publish: 마지막 릴리즈 이후 변경된 패키지 게시
lerna publish from-git: 현재 커밋에 태그가 지정된 패키지를 명시적으로 게시
lerna publish from-package: 레지스트리에 최신버전이 없는 패키지를 명시적으로 게시

lerna version

변경사항이 존재하는 패키지의 version을 변경하고 changelog를 생성한다.

  • 명령어
lerna version

changelog custom

  • package.json
    • name: conventional-commit 설정에 대한 이름 지정
    • type 별로 커스텀 가능. hidden:true 하게 되면 해당 type은 changelog에 기록되지 않음
"command": {
    "version": {
      "allowBranch": "release/*",
      "conventionalCommits": true,
      "changelogPreset": {
        "name": "conventional-changelog-conventionalcommits",
        "types": [
          {
            "type": "feat",
            "section": ":rocket: New Features",
            "hidden": false
          },
          {
            "type": "fix",
            "section": ":bug: Bug Fix",
            "hidden": false
          },
          {
            "type": "docs",
            "section": ":memo: Documentation",
            "hidden": false
          },
          {
            "type": "style",
            "section": ":sparkles: Styling",
            "hidden": false
          },
          {
            "type": "refactor",
            "section": ":house: Code Refactoring",
            "hidden": false
          },
          {
            "type": "build",
            "section": ":hammer: Build System",
            "hidden": false
          },
          {
            "type": "chore",
            "section": ":mega: Other",
            "hidden": false
          }
        ]
      }
    }
  }
}
  • 명령어 실행 : conventional commits을 사용하여 changelog 생성
lerna version --conventional-commits
  • changelog

설정

  • lerna.json
    
    "publish": {
          "allowBranch": ["release/*"],
          "conventionalCommits": true,
          "message": "chore: publish",
    }
  • package.json
    //true면 배포 불가능, 보통 root package.json이 true 설정
    ❌
    "private": "true"
    
    //prefix에 package가 속한 github organization을 소문자 문자열로 설정
    ❌
    "name": "my-app1"
    
    ✅
    "name": "ongddree/my-app1"
    
    // 부가정보
    
    "license": "(MIT OR Apache-2.0)"
    // "UNLICENSED"는 타인에게 비공개 패키지의 사용 권한을 부여하지 않을 때 사용.
    "license": "UNLICENSED"
    
    "author": "ongddree"
    "description": "ongddree's my-app1"
    "directory": "packages/@my-app1"
    
    "repository": {
        "type": "git",
        "url": "https://github.com/ongddree/my-app1"
     },
    "publishConfig": {
        "registry": "https://npm.pkg.github.com/"
      },
  • npm token
    • Github Personal access token 발급
      https://github.com/settings/tokens
    • write:packages, read:package, delete:package 체크
    • 명령어 실행
      $ npm config set //npm.pkg.github.com/:_authToken {token}
    • .npmrc 파일 생성
      npm.pkg.github.com/:_authToken={PERSONAL_ACCESS_TOKEN}
      @ong:registry=https://npm.pkg.github.com
    • amplify 환경 변수에 NPM_TOKEN 입력
      //npm install 전에 preBuild 
      
      frontend:
        phases:
          preBuild:
            commands:
              - echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
              - npm install

결과

  • fixed 모드라 my-app1을 고쳐도 다른 패키지까지 업데이트하여 같은 버전으로 관리

  • 현재 npm token 설정을 하지 않았기 때문에, publish registry 등록 시 401 에러 발생

0개의 댓글