# Yarn Berry Workspace

Yarn Berry WorkSpace란?

> 모노레포 방식으로 여러 프로젝트(패키지)를 단일 레포지토리에서 관리하는 방식
>
> 의존성 관리를 한 곳에서 관리하기 때문에 공통된 의존성 모듈을 재사용하고, 중복되는 의존성 설치 문제를 피할 수 있다.

* **workspace**: 모노레포의 패키지를 의미

### Yarn Berry

> Yarn v2 에서 사용하는 패키지 매니저

* **기본적으로 명시적인 의존관계를 나타내야 사용 가능**
* `node_modules` 에 패키지를 저장하는 방식이 아닌 패키지를 압축하여 한개의 파일을 `.yarn/cache/` 폴더에 수평적으로 저장한다. 이러한 방식을 **Plug'n'Play(PnP)** 라 한다.&#x20;
  * 수평적으로 존재하여 빠르게 탐색 가능
  * 압축파일을 설치하기 때문에 파일 개수가 감소하여 설치가 빠름
  * **Zero Install**을 이용하여 저장소에서 함께 관리 가능
    * 저장소에 올라가기 때문에 저장소 자체가 매우 커지게 될 수 있음 (push, pull등 느리게 됨)
    * Zero Install이 아예 설치하지 않는것은 아님, 여러 환경에서 새로운 파일을 만들어내는 경우 때문에 설치 필요
  * **Phantom Dependency**가 발생하지 않음
* PnP 모드를 기본적으로 지원
  * PnP 방식은 IDE에서 직접 사용하는 많은 도구들을 **`SDK`** 를 통해 우회 호출할 수 있도록 추가적인 설정 필요
* `workspaces plugin` 지원

{% hint style="info" %}
**workspaces plugin**이란 yarn의 workspace를 좀 더 쉽게 관리하기 위해 제공되는 플러그인

`yarn workspace` 명령어를 사용해서 워크스페이스에 속한 패키지들에 대해 일괄적으로 작업을 수행할 수 있다.
{% endhint %}

### node\_modules 문제점

* 의존성 탐색 알고리즘 비효율
  * 모듈을 검색할 때 의존성 경로의 깊이 만큼 상위 `node_modules` 를 검색하게 된다.
* `node_modules` 폴더 아래에 수 많은 패키지가 저장되어 공간을 많이 차지하게 되고 그만큼 설치하는데 오래 걸린다.
* 유령 의존성이란 프로젝트에 실제로 사용하지 않지만 lock 파일에는 존재하는 의존성 패키지
  * 의존성 중복 방지를 위한 호이스팅 기법의 Side Effect
  * 불필요한 공간을 차지하고 느리게 만든다.

{% hint style="info" %}
패키지 매니저(npm, yarn)에서 중복되는 의존성 설치를 방지하기 위해 [호이스팅기법](https://classic.yarnpkg.com/blog/2018/02/15/nohoist/)을 사용한다.
{% endhint %}

### Plug & Play

> 패키지 의존성을 설치할 때,  `node_modules` 폴더를 생성하지 않고, 의존성 패키지들을 `.yarn/cache`에 수평적으로 압축파일로 설치하여 필요한 패키지를 빠르게 가져와서 사용하는 방식

* 여러 패키지에서 중복되는 의존성 패키지들을 전역으로 설치해서 재사용하는 방식
* 해당 모듈을 사용하기 전 메모리에서 ZipFS라는 도구를 사용해서 압축을 해제하고 필요한 부분만 추출하여 가져오는 방식으로 효율적으로 접근한다.
* 의존성이 `.yarn/cache`에 수평적으로 설치되므로 모든 패키지에 대한 접근 시간이 O(1)
* 호이스팅 방식을 사용하지 않기 떄문에 유령 의존성이 발생하지 않는다.
* 압축파일로 된 의존성을 git으로 관리하게 되면 설치 과정을 생략(zero-install)할 수 있다.

{% hint style="info" %}
PnP 모드를 지원하지 않는 라이브러리를 사용하게 된다면 의존성 관리 방식을 `node_modules` 방식으로 변경해서 사용해야 한다.

```json
// .yarnrc.yml
nodeLinker: "node_modeuls" // default "pnp"
```
{% endhint %}

## Monorepo 환경 세팅하기

### NVM

> **Node Version Manager,** 여러 노드 버전을 스위칭하여 관리할 수 있게 도와주는 도구

* 여러 프로젝트에서 서로 다른 Node 버전을 사용할 수 있게 도와준다.
* 각 프로젝트마다 Node를 관리할 필요가 없게 된다.

#### [설치하기](https://jang8584.tistory.com/295)

```bash
brew install nvm
```

#### 환경변수 설정하기

```bash
mkdir ~/.nvm

vi ~/.zshrc 
export NVM_DIR="$HOME/.nvm"
[ -s "/usr/local/opt/nvm/nvm.sh" ] && . "/usr/local/opt/nvm/nvm.sh"  # This loads nvm
[ -s "/usr/local/opt/nvm/etc/bash_completion" ] && . "/usr/local/opt/nvm/etc/bash_completion"  # This loads nvm bash_completion

source ~/.zshrc 
```

#### 설치된 Node version list 확인

```bash
nvm ls
```

![](https://1912740209-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F3GVYdZvmqQyBVq29ebqk%2Fuploads%2FqWZjKzPpZlVlAMyyCz9a%2F%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202023-05-06%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%203.26.58.png?alt=media\&token=92f41a8c-fdfa-4fb8-9cef-c77e310c0ac2)&#x20;

### '[abumalick.vscode-nvm](https://marketplace.visualstudio.com/items?itemName=abumalick.vscode-nvm)' vscode extension 추가

> vscode에서 nvm을 사용하기 쉽도록 도와주는 확장 프로그램

프로젝트 루트경로에 `.nvmrc` 설정 파일을 추가하여 사용할 노드 버전을 지정하면 `nvm use` 명령어를 실행하지 않고도 특정 노드 버전을 **자동으로 사용할 수 있도록 관리**해준다.

#### `.nvmrc`

```json
// galium 이란 별칭으로 사용되는 버전 16.20.0을 사용하겠다 선언
lts/gallium
```

해당 프로젝트의 vscode가 열리면 자동으로 nvm use 명령어가 실행되어 지정한 노드버전을 사용하도록 변경해준다.

`nvm use`는 로컬에 설치된 특정 노드 버전을 사용하도록 설정하는 명령어이다.

<figure><img src="https://1912740209-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F3GVYdZvmqQyBVq29ebqk%2Fuploads%2F355iSApu1lQiptoieyq6%2F%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202023-05-06%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%203.39.26.png?alt=media&#x26;token=fe24078b-cd1a-4476-9e20-9613b732147b" alt=""><figcaption></figcaption></figure>

#### `.vscode/extensions.json`에 추가

> `.vscode/extension.json` 에 추가하여 자동으로 해당 익스텐션을 설치할 수 있도록 설정하자.

```json
{
  "recommendations": ["abumalick.vscode-nvm"]
}
```

### [Yarn Berry](https://yarnpkg.com/getting-started/install) Workspace

> yarn이 기본적으로 설치되어 있다고 가정

#### yarn version 변경하기

```bash
// yarn 버전 확인
yarn -v 

// yarn berry 버전 활성화
yarn set version berry

// yarn 버전 확인
yarn -v  // 현 시점 4.0.2
```

#### Yarn workspace 생성하기

> 루트 워크스페이스는 모든 프로젝트에 공통적으로 사용되는 패키지 의존성을 한번에 관리할 수 있다.
>
> 즉, 하위 워크스페이스에서 `yarn install` 명령어를 통해 루트에 설치된 패키지들을 사용할 수 있다.

`yarn init -w` 명령어는 yarn을 이용해서 프로젝트를 생성하는 명령어이다.\
`-w` 옵션은 현재 디렉토리를 루트 워크스페이스 디렉토리로 설정 (package.json 생성됨)

[yarn berry cli 명령어 참고](https://yarnpkg.com/cli/init)

```bash
yarn init -2 -w
```

#### yarn cache 확인하기

```bash
yarn config enableGlobalCache
```

<figure><img src="https://1912740209-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F3GVYdZvmqQyBVq29ebqk%2Fuploads%2For4Mdlv58dSV012ObT1H%2F%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202023-11-20%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011.35.30.png?alt=media&#x26;token=42fc31d0-c6d4-4994-a722-ebf5cb8eca92" alt=""><figcaption></figcaption></figure>

```bash
yarn config set enableGlobalCache false
```

#### ![](https://1912740209-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F3GVYdZvmqQyBVq29ebqk%2Fuploads%2FRMJDUBYsBDswwPuogmG4%2F%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202023-11-20%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011.37.20.png?alt=media\&token=865819bd-2485-4a6c-bc07-f7990b221939)

* `enableGlobalCache` 설정은 `.yarnrc.yml` 파일 내부에 설정 되있는것을 확인해 볼 수 있다.

#### apps/ 디렉토리 생성

> 실제로 서비스할 어플리케이션들을 모아놓는 디렉토리

```bash
mkdir apps
```

{% hint style="info" %}
`packages/`는 `apps/`에서 사용할 공통 라이브러리 패키지를 모아놓는 디렉토리
{% endhint %}

#### package.json

> 추가한 apps 디렉토리를 워크스페이스 디렉토리의 package.json에 등록

```json
{ 
  "name": "mono-test",
  "packageManager": "yarn@3.5.1",
  "private": true, 
  // 워크스페이스에서 관리할 패키지 디렉토리 경로를 설정
  "workspaces": [
    "apps/*", // 생성한 apps/ 디렉토리를 추가
    "packages/*"
  ]
}
```

## 프로젝트 패키지 추가해보기

> `apps/` 디렉토리 내에서 프로젝트를 생성해보자.

```bash
yarn init
```

프로젝트 생성이 완료되었다면 해당 프로젝트의 `package.json` 을 수정

#### package.json

> 패키지의 범위를 설정하기 위해 프로젝트의 네임을 `@` 접두어를 붙힌 `@org/package` 형식으로 변경

```json
{
  "name": "@jtwjs/web",
  "version": "0.1.0",
  "private": true,
  //...//
}
```

#### 워크스페이스로 돌아가서 상태를 갱신

> 프로젝트가 추가되면 워크스페이스로 돌아가서 상태를 갱신 해주어야 한다. &#x20;
>
> 해당 정보는 `.pnp.cjs` 에서 관리 된다.

`yarn workspace [package name] [scripts]` 로 워크스페이스 특정 패키지의 명령어를 사용하여 간단하게 실행할 수 있다.

```bash
cd ../../

yarn // 상태 갱신

yarn workspace @jtwjs/web run dev // 실행 확인
```

{% hint style="info" %}
&#x20;**`.pnp.cjs`**&#xB780;?  yarn berry pnp 관련 정보를 모아놓은 캐시 파일로 모든 의존성의 메타 정보(zip 경로, 의존성)와 ZipFS에 대한 처리 코드가 들어있다
{% endhint %}

### Typescript 설정하기

> 타입스크립트 코드에 빨간줄이 생기면서 인식을 못하는 에러가 발생한다.

* yarn berry는 `node_modules` 대신 패키지 의존성을 `zip` 파일 형태로 `.yarn/cache`에 저장하여 관리하는 **`plugin & play`** 모드를 사용한다.
* 이런 방식의 차이 때문에 추가적인 설정이 필요하다.

```bash
yarn add -D typescript
yarn dlx @yarnpkg/sdks vscode
```

{% hint style="info" %}
**`yarn dlx @yarnpkg/sdks vscode`**

* `yarn dlx` 명령어를 통해 `@yarnpkg/sdks` 패키지를 vscode 환경에 설치
* yarn에서 제공하는 플러그인 중 하나인 yarn sdk는 yarn을 이용해서 설치한 프로그램을 실행할 수 있도록 도와준다.
* 즉, vscode가 zip 파일로 된 패키지들을 읽어올 수 있게끔 설정하는 것
* typescript, eslint, prettier 이 패키지들은 `.yarn/sdks` 내에  설치된다.
{% endhint %}

### '[arcanis.vscode-zipfs](https://marketplace.visualstudio.com/items?itemName=arcanis.vscode-zipfs)' vscode extension 추가

> Yarn SDK와 함께 사용하면 캐시에서 파일을 원활하게 열고 편집할 수 있다.

#### `.vscode/extensions.json`에 추가

```json
{
  "recommendations": [
    "abumalick.vscode-nvm",
    "arcanis.vscode-zipfs"
  ]
}
```

<details>

<summary>Next.js 모노레포 Typescript 이슈</summary>

다른 패키지(React.js)에서 내보내는 컴포넌트를(ts) 사용할 때 ts 해석을 하지 못한다.

1. &#x20;javascript로 변환시켜 주는 툴 설치

```javascript
// next-transpile-modules 설치
yarn workspace @jtwjs/web add next-transpile-modules
```



2. `next.config.json` 수정

```json
// @wanted/ui 패키지를 tranpile 시킨다.
const withTM = require('next-transpile-modules')(['@wanted/ui']);

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
};

module.exports = withTM(nextConfig);
```

</details>

## 공통 패키지를 만들어보기

> 여러 패키지에서 공유하여 사용할 유틸 함수나 라이브러리를 관리하기 위해 `packages/lib` 폴더를 생성![](https://1912740209-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F3GVYdZvmqQyBVq29ebqk%2Fuploads%2FBBh2atXK5R5KXLsVd8LJ%2F%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202023-05-06%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%205.46.54.png?alt=media\&token=a7de554c-5da5-46da-801b-95e42c08eeac)

```bash
// lib 폴더 이동
cd packages/lib

// pacakge.json 파일 생성
yarn init

// typescript 설치
yarn add typescript
```

### `package.json` name 수정

```json
{
  "name": "@jtwjs/lib",
  "main": "./src/index.ts",
}
```

### tsconfig.json 설정

```bash
touch tsconfig.json
```

```json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "compilerOptions": {
    "strict": true,
    "useUnknownInCatchVariables": true,
    "allowJs": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "isolatedModules": true,
    "newLine": "lf",
    "module": "ESNext",
    "moduleResolution": "node",
    "target": "ESNext",
    "lib": ["ESNext", "dom"],
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "baseUrl": "./src",
    "noEmit": false,
    "incremental": true,
    "resolveJsonModule": true,
    "paths": {}
  },
  "exclude": ["**/node_modules", "**/.*/", "./dist", "./coverage"],
  "include": ["**/*.ts", "**/*.js", "**/.cjs", "**/*.mjs", "**/*.json"]
}
```

{% hint style="info" %}
패키지를 추가했다면 워크스페이스로 돌아가서 `yarn` 명령어를 입력해 갱신해주자.
{% endhint %}

### 다른 패키지에 의존성 추가하기

> 루트 워크스페이스 경로로 돌아가서 A 패키지에 B 패키지 의존성을 추가해보자.

```bash
// @jtwjs/web 패키지에 @jtwjs/lib 패키지를 추가
yarn workspace @jtwjs/web add @jtwjs/lib
```

@jtwjs/web 패키지를 확인해보면 아래와 같이 의존성이 추가된걸 확인할 수 있다.

![](https://1912740209-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F3GVYdZvmqQyBVq29ebqk%2Fuploads%2FBBh2atXK5R5KXLsVd8LJ%2F%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202023-05-06%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%205.46.54.png?alt=media\&token=a7de554c-5da5-46da-801b-95e42c08eeac)

{% hint style="info" %}
```bash
// 워크스페이스 간의 의존관계를 확인하는 방법
yarn workspaces list
```
{% endhint %}

## 개발환경 설정 공유하기

### tsconfig 설정 공유하기

> 프로젝트에서 공통적으로 사용되는 타입스크립트 설정을 한 곳에서 관리하여 중복되는 설정을 최소화한다.

{% hint style="info" %}
```bash
// 루트 프로젝트에 의존성 추가
yarn add <PACKAGE_NAME> 
```
{% endhint %}

1. 워크스페이스 경로에 `tsconfig.base.json` 파일을 생성하여 공통적으로 사용할 설정
2.  각 프로젝트 패키지의 `tsconfig.json` 에서 extends 속성을 사용하여 재사용한다.

    ```json
    "extends": "../../tsconfig.base.json",
    ```

```json
// tscofnig.base.json
{
  "compilerOptions": {
    "strict": true,
    "useUnknownInCatchVariables": true,
    "allowJs": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "incremental": true,
    "newLine": "lf"
  },
  "exclude": ["**/node_modules", "**/.*/"]
}
```

### eslint & prettier 설정 공유하기

> 여러 패키지에서 공통적으로 사용되는 eslint & prettier 규칙을 공유하여 재사용하자.

```bash
yarn add prettier eslint eslint-config-prettier eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks eslint-import-resolver-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser -D

yarn dlx @yarnpkg/sdks
```

#### 기타 vscode 설정 추가

<details>

<summary>.vscode/settings.json</summary>

```json
{
  "search.exclude": {
    "**/.yarn": true,
    "**/.pnp.*": true
  },
  "typescript.tsdk": ".yarn/sdks/typescript/lib",
  "typescript.enablePromptUseWorkspaceTsdk": true,
  "eslint.nodePath": ".yarn/sdks",
  "prettier.prettierPath": ".yarn/sdks/prettier/index.js",

  // 기본 포맷터 prettier로 사용
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  // 파일 저장시 formatter 실행
  "editor.formatOnSave": true,
  "editor.rulers": [120],

  // 추가되는 내용
  "eslint.packageManager": "yarn",
  "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"]
}
```

</details>

<details>

<summary>.vscode/extensions.json</summary>

```json
{
  "recommendations": [
    "abumalick.vscode-nvm",
    "arcanis.vscode-zipfs",
    "esbenp.prettier-vscode",
    "dbaeumer.vscode-eslint"
  ]
}
```

</details>

#### Eslint & Prettier설정

[eslint 설정 공유하기](https://tech.kakao.com/2019/12/05/make-better-use-of-eslint/)

[eslint 잘 활용하기](https://www.daleseo.com/eslint-config/)

<details>

<summary>Eslint 설정 </summary>

가장 최상위 eslint 설정에 root: true 설정하고 커스텀을 위한 하위 eslint는 root: false 설정을 해주자.

```javascript
module.exports = {
  root: true,

  env: {
    es6: true,
    node: true,
    browser: true,
  },

  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: { jsx: true },
  },

  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    'prettier',
  ],
  plugins: ['@typescript-eslint', 'import', 'react', 'react-hooks'],
  settings: { 'import/resolver': { typescript: {} }, react: { version: 'detect' } },
  rules: {
    'no-implicit-coercion': 'error',
    'no-warning-comments': [
      'warn',
      {
        terms: ['TODO', 'FIXME', 'XXX', 'BUG'],
        location: 'anywhere',
      },
    ],
    curly: ['error', 'all'],
    eqeqeq: ['error', 'always', { null: 'ignore' }],

    // Hoisting을 전략적으로 사용한 경우가 많아서
    '@typescript-eslint/no-use-before-define': 'off',
    // 모델 정의 부분에서 class와 interface를 합치기 위해 사용하는 용법도 잡고 있어서
    '@typescript-eslint/no-empty-interface': 'off',
    // 모델 정의 부분에서 파라미터 프로퍼티를 잘 쓰고 있어서
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/no-parameter-properties': 'off',
    '@typescript-eslint/no-var-requires': 'warn',
    '@typescript-eslint/no-non-null-asserted-optional-chain': 'warn',
    '@typescript-eslint/no-inferrable-types': 'warn',
    '@typescript-eslint/no-empty-function': 'off',
    '@typescript-eslint/naming-convention': [
      'error',
      { format: ['camelCase', 'UPPER_CASE', 'PascalCase'], selector: 'variable', leadingUnderscore: 'allow' },
      { format: ['camelCase', 'PascalCase'], selector: 'function' },
      { format: ['PascalCase'], selector: 'interface' },
      { format: ['PascalCase'], selector: 'typeAlias' },
    ],
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    '@typescript-eslint/array-type': ['error', { default: 'array-simple' }],
    '@typescript-eslint/no-unused-vars': ['error', { ignoreRestSiblings: true }],
    '@typescript-eslint/member-ordering': [
      'error',
      {
        default: [
          'public-static-field',
          'private-static-field',
          'public-instance-field',
          'private-instance-field',
          'public-constructor',
          'private-constructor',
          'public-instance-method',
          'private-instance-method',
        ],
      },
    ],

    'import/order': [
      'error',
      {
        groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object'],
        alphabetize: { order: 'asc', caseInsensitive: true },
      },
    ],

    'react/prop-types': 'off',
    // React.memo, React.forwardRef에서 사용하는 경우도 막고 있어서
    'react/display-name': 'off',
    'react-hooks/exhaustive-deps': 'error',
    'react/react-in-jsx-scope': 'off',
    'react/no-unknown-property': ['error', { ignore: ['css'] }],
  },
};
```

</details>

<details>

<summary>Prettier 설정</summary>

```json
{
  "arrowParens": "avoid",
  "bracketSameLine": false,
  "bracketSpacing": true,
  "endOfLine": "lf",
  "jsxSingleQuote": false,
  "printWidth": 120,
  "proseWrap": "preserve",
  "quoteProps": "as-needed",
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5"
}
```



</details>

## 모든 프로젝트에 공통적으로 실행할 명령어

> [`workspace-tools`](https://yarnpkg.com/api/modules/plugin_workspace_tools.html) 플러그인 설치
>
> 각 패키지 마다 공통적으로 동일한 네이밍의 스크립트를 일괄적으로 실행 시킬 수 있다.

```bash
yarn plugin import workspace-tools
```

```json
"scripts": {
//g:는 global 관례이다.
  "g:typecheck": "yarn workspaces foreach -pv run typecheck"
},
```

{% hint style="info" %}
[yarn workspaces foreach](https://yarnpkg.com/cli/workspaces/foreach) 여러 패키지 대해 일괄적인 작업을 수행할 수 있는 명령어

* `-p`: 병렬 실행
* `-v`: workspace name 출력
{% endhint %}

### typecheck

```json
"scripts": {
  "typecheck": "tsc --project ./tsconfig.json --noEmit"
},
```

{% hint style="info" %}
#### 개별적으로 특정 스크립트 실행하려면

```bash
yarn workspace @jtwjs/web typecheck
```
{% endhint %}

## Deploy

### Github actions

* `.github/workflows` 디렉토리 내에서 워크플로우(`.yml`)를 생성하여 배포 작업을 정의한다.
* 모노레포의 경우 각 하위 프로젝트에 대해 배포 작업을 **개별적으로 정의**해야 한다.
* **배포하기 전 관련 의존성을 모두 설치하는 작업이 추가 되어야 한다.**
* 관련 의존성을 설치하는 작업은 각 배포 워크플로우에서 공통으로 사용되기 때문에 **composite actions**를 사용하여 액션을 재사용해보자.

<details>

<summary>Composite actions</summary>

....

* [읽어보기#1](https://blog.outsider.ne.kr/1592)
* [읽어보기#2](https://fe-developers.kakaoent.com/2022/220106-github-actions/)

#### 배포 하기 전 관련 의존성 설치하는 액션

```yaml
// .github/actions/yarn-install/action.yml
name: 'Monorepo install (yarn)'
description: 'Run yarn install'

runs:
  using: 'composite'

  steps:
    - name: Expose yarn config as "$GITHUB_OUTPUT"
      id: yarn-config
      shell: bash
      run: |
        echo "CACHE_FOLDER=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT

    - name: Restore yarn cache
      uses: actions/cache@v3
      id: yarn-download-cache
      with:
        path: ${{ steps.yarn-config.outputs.CACHE_FOLDER }}
        key: yarn-download-cache-${{ hashFiles('yarn.lock') }}
        restore-keys: |
          yarn-download-cache-

    - name: Restore yarn install state
      id: yarn-install-state-cache
      uses: actions/cache@v3
      with:
        path: .yarn/ci-cache/
        key: ${{ runner.os }}-yarn-install-state-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }}

    - name: Install dependencies
      shell: bash
      run: |
        yarn install --immutable --inline-builds
      env:
        YARN_ENABLE_GLOBAL_CACHE: 'false'
        YARN_INSTALL_STATE_PATH: .yarn/ci-cache/install-state.gz # Very small speedup when lock does not change

```

</details>

<details>

<summary>deploy.yml</summary>

```yaml
name: CI-admin-app

on:
  push:
    branches:
      - main
      
    paths:
      - 'apps/admin/**'

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16.x]
    steps:
      - uses: actions/checkout@v3

      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}

      - name: 📥 Monorepo install
        uses: ./.github/actions/yarn-install

      - name: Build web-app
        working-directory: apps/admin
        run: |
          yarn build
```

</details>

### Menual Deploy (수동으로 배포하기)

> **workflow dispatch**를 사용하여 깃헙 저장소에서 수동으로 workflow를 실행할 수 있다.
>
> 여러 워크스페이스를 관리하는 상황에서는 수동으로 배포할 워크스페이스를 선택하는 방식도 꽤 유용해 보인다.

<details>

<summary>workflow dispatch</summary>

```yaml
name: CI-deploy-manual

on:
  workflow_dispatch:
    inputs:
      service_name:
        description: '배포할 서비스명을 선택해주세요.'
        required: true
        default: 'wanted'
        type: choice
        options:
          - wanted
          - admin

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16.x]
    steps:
      - uses: actions/checkout@v3

      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}

      - name: 📥 Monorepo install
        uses: ./.github/actions/yarn-install

      - name: Build web-app
        working-directory: apps/${{ inputs.service_name }}
        run: |
          yarn build

```

</details>

<figure><img src="https://1912740209-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F3GVYdZvmqQyBVq29ebqk%2Fuploads%2FnIX8MMVKvSfJTC286NUs%2F%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202023-05-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2010.11.09.png?alt=media&#x26;token=d8f31a78-9743-4d01-b7ed-e421e2325cea" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
default branch에 적용되있어야만 작동한다.
{% endhint %}
