💡 2022.12.14일 main 브랜치를 기준으로 작성되었습니다.
토스의 라이브러리는 모노레포로 구성되어 있습니다. 모노레포 세팅을 살펴보겠습니다.
.yarnc
파일을 보니 얀베리를 사용하는 것을 알 수 있습니다.
yarn-path: .yarn/releases/yarn-3.2.3.cjs
.pnp.cjs
이 존재하는 것으로 플러그 앤 플레이를 하고 있으며 더하여 .yarn/cache
이 있는 것으로 보아 제로 인스톨 전략을 쓰는 것을 알 수 있습니다.
.lerna.json
: 여러 패키지 관리를 도와주는 Lerna
를 사용하고 있습니다.
크게 3가지로 분리하고 있습니다.
1. docs : 문서
2. packages : 패키지들 Node환경에서 사용할 수 있는 common과 리액트에서 사용하는 react로 나뉘어 있습니다.
3. configs : 설정
.circleci
: 소스코드를 통합하고 빌드, 배포를 자동화하는 파이프라인 구성 툴입니다.
토스에선 circleCI를 통해 린팅, 테스트, 타입 체크 등을 병렬적으로 수행하고 있습니다. 자세한 설정은 아래를 참고해주세요.
# circleCI를 사용하려는 버전을 나타냅니다.
version: 2.1
# 이 파일 내에서 사용할 수 있는 파라미터를 나타냅니다.
# 여기선 pipeline.parameters.pull_request를 선언하였으면 기본값은 false입니다.
parameters:
pull_request:
type: boolean
default: false
# 일종의 alias라고 보면 됩니다. 명령들을 재사용 할 수 있도록 모아둔 패키지입니다.
orbs:
slack: circleci/slack@4.5.0
# job안에서 진행될 명령들을 모아두는 곳입니다. 하나의 커맨드를 여러 job에서 사용할 수 있습니다.
commands:
# setup이란 커맨드
setup:
# 여러 개의 step으로 구성
steps:
# 아래를 실행하세요
- run:
# circle CI UI내에서 보일 step의 이름을 의미합니다.
name: Install yarn
# 쉘을 통해 수행될 코맨드
command: |
COREPACK_PATH=$HOME/.local/bin
mkdir -p $COREPACK_PATH
eval "$(echo PATH=$COREPACK_PATH:\$PATH | tee -a $BASH_ENV)"
corepack enable --install-directory $COREPACK_PATH
yarn install --immutable --immutable-cache
export_published_version:
description: Export Published Version Environment Variable
steps:
- run:
name: Export PUBLISHED_VERSION
command: echo "export PUBLISHED_VERSION=$(node -p "require('./lerna.json').version")" >> $BASH_ENV
export_commit_message:
description: Export Commit Message
steps:
- run:
name: Export COMMIT_MESSAGE
command: echo "export COMMIT_MESSAGE=\"$(printf "%s\n" "$(git log --format=%B -n 1 "$CIRCLE_SHA1" | head -n 1)")\"" >> $BASH_ENV
# job이란 step들의 모음을 의미하며 Job의 모든 step들은 하나의 머신에 동작합니다.
# 여기서 머신은 docker container가 될 수도 있고 실제 물리적 머신이 될 수도 있습니다.
# 같은 job끼리는 캐싱이 동작합니다.
jobs:
lint:
docker:
- image: cimg/node:16.17
steps:
- checkout
- setup
- run:
name: Lint
command: yarn eslint -c .eslintrc.js $(git diff --name-only --diff-filter=ACMRUXB origin/main | grep -E "(.js$|.ts$|.tsx$)")
typecheck:
parallelism: 2
docker:
- image: cimg/node:16.17
steps:
- checkout
- setup
- run:
name: Typecheck
command: |
WORKSPACES_TO_TEST=$(
yarn workspaces since list origin/main HEAD |
circleci tests split
)
echo $WORKSPACES_TO_TEST
INCLUDE=$(
echo $WORKSPACES_TO_TEST |
# 띄어쓰기로 연결된 문자열을 콤마로 연결
sed 's/ /,/g'
)
yarn workspaces since run "typecheck" remotes/origin/main --include="{$INCLUDE,}"
pre-pack:
parallelism: 2
docker:
- image: cimg/node:16.17
steps:
- checkout
- setup
- run:
name: Prepack
command: |
WORKSPACES_TO_TEST=$(
yarn workspaces since list origin/main HEAD |
circleci tests split
)
echo $WORKSPACES_TO_TEST
INCLUDE=$(
echo $WORKSPACES_TO_TEST |
# 띄어쓰기로 연결된 문자열을 콤마로 연결
sed 's/ /,/g'
)
yarn workspaces since run "prepack" remotes/origin/main --include="{$INCLUDE,}"
check-peer:
docker:
- image: cimg/node:16.17
steps:
- checkout
- setup
- run:
name: Check Peer Dependency
command: ./.scripts/check-peer.sh || (echo "Peer Dependency 오류가 발생했습니다."; exit -1)
test:
docker:
- image: cimg/node:16.17
parallelism: 2
steps:
- checkout
- setup
- run:
name: Jest
command: |
UPDATED_PACKAGES=$(yarn workspaces since list origin/main HEAD)
echo "updated packages:"
echo $UPDATED_PACKAGES
UPDATED_DIRS=$(echo $UPDATED_PACKAGES | tr ' ' ',')
if [[ -z $UPDATED_DIRS ]]; then
echo "업데이트된 패키지가 없습니다."
exit 0
fi
# 테스트 파일이 없는 경우 아래 오류 발생
# panic: runtime error: index out of range [0] with length 0
if [[ -z $(circleci tests glob "{$UPDATED_DIRS}/**/*.{test,spec}.{ts,tsx}") ]]; then
echo "업데이트된 패키지에 실행할 테스트 파일이 없습니다."
exit 0
fi
TESTS=$(
circleci tests glob "{$UPDATED_DIRS}/**/*.{test,spec}.{ts,tsx}" | \
awk '!/__manual__/' | \
circleci tests split --split-by=timings
)
echo "tests to run:"
echo $TESTS | xargs -n 1 echo
if [[ -z $TESTS ]]; then
echo "실행할 테스트가 없습니다."
exit 0
fi
yarn jest $TESTS --passWithNoTests --runInBand
environment:
YARN_ENABLE_IMMUTABLE_INSTALLS: 'false'
JEST_JUNIT_OUTPUT_DIR: ./.test-reports/junit/
- store_test_results:
path: ./.test-reports/junit/
- store_artifacts:
path: ./.test-reports/junit
test-all:
docker:
- image: cimg/node:16.17
parallelism: 4
steps:
- checkout
- setup
- run:
name: Jest
command: |
yarn jest --runInBand
environment:
JEST_JUNIT_OUTPUT_DIR: ./.test-reports/junit/
- store_test_results:
path: ./.test-reports/junit/
- store_artifacts:
path: ./.test-reports/junit
save-git-cache:
docker:
- image: cimg/node:16.17
steps:
- checkout
- setup
- save_checkout_cache
# 워크플로우에선 여러 job들의 집합과 job들간의 스케줄링을 이야기합니다.
workflows:
main:
jobs:
#워크 플로우에서 테스트, 린트오 같은 작업들은 비교적 비용이 많이 소모되지 않으므로 job들의 앞단에 위치하는 것이 좋습니다.
# 아래에 있는 jobe들이 parallel하게 동작합니다.
# 브랜치 상황에 따라 main을 제외하고 테스트하는 경우가 있고 main만 테스트 하는 경우가 있습니다.
- test:
filters:
branches:
ignore: main
- test-all:
context:
- npm-public
filters:
branches:
only: main
- lint:
filters:
branches:
ignore: main
- typecheck:
filters:
branches:
ignore: main
- pre-pack:
filters:
branches:
ignore: main
- check-peer:
filters:
branches:
ignore: main
tsconfig를 목적에 따라 다르게 사용합니다.
//tsconfig.build.json
{
"extends": "./tsconfig.json",
"exclude": ["**/jest.setup.ts", "**/*.test.*", "**/*.spec.*", "**/*.stories.*", "**/__storybook__/*"]
}
//tsconfig.esm.json
{
"extends": "./tsconfig.build.json",
"compilerOptions": {
"module": "esnext", //신버전 자바스크립트 사용 es2015등으로도 지정 가능
}
}
//tsconfig.eslint.json
{
"extends": "./tsconfig.json",
"include": ["./.eslintrc.js"]
}
토스의 경우 크게 3가지의 패키지로 이루어져있습니다.
1. docs
2. packages
3. cofigs
이 중 configs
는 rollup에 관한 설정이 있습니다. 이 rollup설정은 대부분의 패키지에서 참조하고 있습니다.
packages
는 노드 환경에서 쓸 수 있는 commons
와 리액트 환경에서 쓸 수 있는 react
로 나뉘어져 있습니다.
자세한 의존성이 궁금하신 분들은 아래 이미지를 참고해주세요
토스 패키지 의존성 이미지로 확인하기 점선으로 둘러싸인 사각형은 참조되는 패키지를 의미합니다. 전체적으로 설정 패키지가 대부분에 패키지에 의해 참조되는 모습을 볼 수 있습니다.토스 레포를 살펴보면서 인상 깊었던 점입니다.
1. 이미 있는 라이브러리(ex. react-query, recoil)를 한번 더 래핑해서 패키지로 사용하는 점
2. 다른 라이브러리 중엔 리액트 18을 지원하는 버전이 나오면서 리액트 17에 대한 버전을 더 이상 지원하지 않는 경우도 있었는데 토스의 경우 16-18버전을 지원한다는 점이 신기했습니다.
3. 변환하는 자바스크립트 버전을 esnext
로 한 점이 인상깊었습니다. 언제나 새로운 점을 도전해보는 것 같습니다.