⚡ Yarn workspaces + Turborepo 를 이용한 프론트엔드 Monorepo 구축기 (Part 3)

Yoon Han
LBOX Team
Published in
35 min readMar 31, 2023

안녕하세요! 엘박스 프론트엔드 엔지니어 한 윤 입니다. 이전 편에서는 총 3개의 하위 workspace 를 구성해 보았습니다. 본편에서는 2편에서 구성해놓은 workspace 들간에 코드를 공유하여 사용하기 위한 세팅과 ESLint 와 Prettier 관련 세팅을 진행해 보도록 하겠습니다. 추가로, 모노레포를 구성하면서 겪었던 문제에 관한 Troubleshooting 내용을 다루면서 시리즈를 마무리 하도록 하겠습니다 👍

LBox 모노레포 예제 GitHub repository

🔁️ ’nextjs’ workspace 에서 ‘shared’ workspace 코드 import 하기

미리 생성해둔 monorepo-example/packages/shared/src/const.ts 파일을 import 해서 사용해 보겠습니다

monorepo-example/apps/nextjs/pages/index.tsx 파일을 열어줍니다. 그리고 파일 안의 내용을 모두 삭제해 줍니다. 모두 지우셨다면 다음과 같이 내용을 작성해 줍니다.

// monorepo-example/apps/nextjs/pages/index.tsx
export default function Home() {
return (
<div></div>
)
}

이제 이 파일에서 monorepo-exmaple/packages/shared/src/const.ts 파일 내에 있는 test 변수를 불러와 보겠습니다. import 구문을 넣어줍니다.

// monorepo-exmaple/packages/shared/src/const.ts
import { test } from '../../../packages/shared/src/const' // 추가된 부분

export default function Home() {
return (
<div></div>
)
}

하지만 코드가 잘 불러와졌다고 해서 설정이 완료된 것은 아닙니다.
뭔가 2% 부족한 느낌입니다.
test 변수를 JSX 영역에서 타이핑 했을 때, intellisense 가 동작했으면 좋겠고, import 경로도 relative path 형태라 가독성이 떨어져 보입니다.

이 부분을 해결해 보겠습니다.

📄 ’shared’ workspace package.json 파일 작성하기

monorepo-example/packages/shared 디렉터리에 package.json 파일을 생성해주고 그 안에 내용을 다음과 같이 기입해 줍니다.

// monorepo-example/packages/shared/package.json
{
"name": "shared",
"version": "0.1.0",
"private": true,
"devDependencies": {
"@types/node": "18.11.17",
"eslint": "8.30.0",
"typescript": "4.9.4"
}
}

그 후 yarn install 명령을 통해 shared workspace 의존성을 등록해 줍니다.

yarn install
monorepo-example/.pnp.cjs

이제 nextjs workspace 의 package.json 파일을 열어줍니다. dependencies 부분에 shared workspace 를 추가해줍니다.

// monorepo-example/apps/nextjs/package.json
{
...
"dependencies": {
"next": "13.1.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"shared": "workspace:^" <-- 추가
},
...
}

추가 후 yarn install 을 통해 의존성 트리를 업데이트 해줍니다.

yarn install
monorepo-example/.pnp.cjs

이제 monorepo-example/apps/nextjs/pages/index.tsx 파일의 import 문을 아래처럼 바꿔줍니다.

// monorepo-example/apps/nextjs/pages/index.tsx
import { test } from 'shared/src/consts'

바꾼 뒤 개발 서버를 재구동 시켜주면, 코드가 정상적으로 동작하는 것을 확인할 수 있습니다.

http://localhost:3000 URL 로 접속 했을 때의 브라우저 화면

하지만, 아직 test 변수에 관한 VS Code intellisense 가 제대로 동작하지는 않습니다.

무엇을 더 설정해주어야 할까요? 바로 tsconfig.json 파일 입니다.

📄 monorepo-example/tsconfig.base.json 파일 작성하기

모노레포 root 위치에 tsconfig.base.json 파일을 생성해 줍니다. 파일명에 base 키워드가 들어간 이유는 이 파일을 하위 workspace 의 tsconfig.json 파일들에서 extend 하여 사용할 것이기 때문입니다.

// monorepo-example/tsconfig.base.json
{
"$schema": "<https://json.schemastore.org/tsconfig>",
"include": [
"**/*.ts", <-- 모노레포 내의 모든 .ts 파일 포함
"**/*.tsx" <-- 모노레포 내의 모든 .tsx 파일 포함
]
}

include 필드에 TypeSciprt 의 컴파일 과정에 포함될 파일들을 glob pattern 으로 정의해 줍니다.

모노레포의 하위 workspace 들에 속해있는 .ts 또는 .tsx 확장자를 가진 파일 모두를 포함하도록 설정해 줍니다.

다음으로는 nextjs workspace 에 tsconfig.json 파일을 수정해 주겠습니다.

📄 monorepo-example/apps/nextjs/tsconfig.json 파일 수정하기

// monorepo-example/apps/nextjs/tsconfig.json
{
"extends": "../../tsconfig.base.json", <-- 추가*
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], <-- 삭제
"exclude": ["node_modules"]
}

기존 파일 내용에 include 필드가 이미 존재하고 있을텐데요, 이 부분을 삭제해 주겠습니다. include 필드는 모노레포 root 에 있는tsconfig.base.json 파일의 내용을 상속 받아서 사용할 것이기 때문입니다. 확장 받기 위해 extends 필드의 값을 위처럼 설정해 줍니다.

그런 다음 다시 monorepo-example/apps/nextjs/pages/index.tsx 파일을 열어서 intellisense 기능이 잘 동작하는지 테스트해 봅니다.

아래와 같이 잘 동작함을 확인할 수 있습니다.

shared workspace test 변수에 관한 intellisense 동작

import 경로도 더 이상 상대경로가 아닌 것 또한 확인할 수 있습니다.

️️️🔁 ’nextjs’ workspace 에서 ‘storybook’ workspace 코드 import 하기

그럼 이번에는 nextjs workspace 에서 storybook workspace 코드를 import 해보도록 하겠습니다. Storybook 초기 세팅을 과정을 통해 이미 생성되어 있는 Button 컴포넌트를 불러와 보겠습니다.

storybook workspace 에 있는 코드를 import 해오기 전에 진행해야 할 선행 작업이 있었죠?

네, 바로 nextjs workspace 에 storybook workspace 를 의존성으로 추가해주는 작업 입니다. 다음의 명령을 실행해 주세요.

yarn workspace nextjs add storybook

정상적으로 storybook workspace 의존성이 추가되었다면 다시 monorepo-example/apps/nextjs/pages/index.tsx 파일을 열어주세요. 그런 다음 JSX 부분에 StorybookButton 컴포넌트를 import 해서 넣어주겠습니다.

// monorepo-example/apps/nextjs/pages/index.tsx
import { test } from "shared/src/const";
import { Button } from "storybook/src/stories/Button";

export default function Home() {
return (
<div>
{test}
<Button label="테스트" />
</div>
)
}
nextjs workspace 에서 storybook workspace 의 버튼 컴포넌트를 import 할 때의 intellisense 동작

VS Code 에디터 상에서는 에러 없이 불러와 진 것처럼 보입니다.
하지만 개발 서버가 구동되고 있는 터미널 console 화면을 보면 다음과 같은 에러가 발생하고 있습니다.

위와 같은 에러가 발생하는 이유는, Next.js 에서 현재 프로젝트 디렉터리(nextjs workspace) 이외의 디렉터리에서 불러온 모듈은 컴파일 과정에 포함시키지 않기 때문인데요, 이 문제는 next-transpile-modules 라는 의존성을 추가해주고 next.config.js 파일을 조금 수정해주면 해결할 수 있습니다.

❗❗ `next-transpile-modules` 는 현재 deprecated 되었습니다. ❗❗
`Next.js` 13.1 버전부터 이 모듈의 기능이 내장되었기 때문인데요,
본 아티클은 `Next.js` 13.0 버전을 기준으로 작성되었기 때문에 이 모듈을 설치해주어야 합니다.

우선, 다음 명령어를 통해 next-transpile-modules 의존성을 설치해 줍니다.

yarn workspace nextjs add next-transpile-modules

그 다음 monorepo-example/apps/nextjs/next.config.js 파일을 열어서 내용을 다음과 같이 수정해 줍니다.

// monorepo-example/apps/nextjs/next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
}

const withTranspileModules = require('next-transpile-modules')([ // 추가
'storybook',
])

module.exports = withTranspileModules

next-transpile-modules 의존성이 제공하는 함수에 우리가 포함시키고자 하는 의존성의 배열을 인자로 넣어줍니다.

위와 같이 수정하고 터미널 console 을 보면 next.config.js 파일에 수정이 일어났으니 개발 서버를 재기동 하라는 안내 문구가 나타납니다.

터미널 console 내용

터미널에 적혀있는 대로 next workspace 의 개발 서버를 재구동 해주면,

yarn workspace nextjs dev

개발 서버가 오류없이 정상 구동 됨을 확인할 수 있습니다.

http://localhost:3000 URL 로 접속했을 때 브라우저 화면

🔮 기타 도구 세팅하기

⚙️ lintstaged 설치 및 husky 설정하기

git 에서 staged 상태에 있는 파일들에 대해 특정 작업들을 수행해주는 lint-staged 관련 설정을 진행해 보겠습니다.

git add 명령어를 통해 staged 상태로 만들어준 파일들에 한해서만 원하는 작업들이 수행되도록 설정을 진행합니다. 또한, 해당 작업들이 git commit 명령어가 실행되기 전에(git pre-commit hook) 수행되어야 하기 때문에 husky 의존성 또한 설치해 주겠습니다.

다음 명령어를 통해 모노레포 root 에 lint-staged 의존성을 설치해 줍니다.

yarn add -D lint-staged

이어서 husky 의존성도 설치해 줍니다.

yarn add -D husky

설치된 husky 의 초기 구성을 진행합니다. (git hooks 활성화)

yarn husky install

monorepo-example/.husky 디렉터리 안에 pre-commit 이름을 가진 파일을 하나 생성합니다. 이 파일에 정의된 내용은 git commit 명령어 실행 바로 이전 단계에서 실행됩니다.

# monorepo-example/.husky/pre-commit

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

yarn lint-staged

여기까지만 세팅해둔 뒤 ESLintPrettier 설정을 이어서 진행한 다음 lint-staged 설정을 마무리 해주도록 하겠습니다.

⚙️ ESLint 설정하기

이전 섹션에서 이미 ESLint 를 모노레포 root 에 설치해주었습니다. 바로 설정 파일 작성 단계로 넘어가도록 하겠습니다.

다음으로 nextjs workspace 에서도 ESLint 설정 파일을 수정해줍니다.

create-next-app 을 통해 프로젝트를 생성할 때 자동으로 만들어진 monorepo-example/apps/nextjs/.eslintrc.json 파일의 이름을 .eslintrc.js 로 변경한 뒤에 다음 내용을 기입해줍니다.

// monorepo-example/apps/nextjs/.eslintrc.js
module.exports = {
extends: ['next'],
rules: {},
}

storybook workspace 에도 ESLint 를 설치하고 configuration 파일을 생성해 주어야 합니다.

yarn workspace storybook add -D eslint

Storybook 관련 workspace 이므로 eslint-plugin-storybook 이라는 ESLint 플러그인도 설치해 주도록 하겠습니다.

yarn workspace storybook add -D eslint-plugin-storybook

그 다음 monorepo-example/packages/storybook/.eslintrc.js 파일의 내용을 다음과 같이 기입해 줍니다.

// monorepo-example/packages/storybook/.eslintrc.js
module.exports = {
extends: ['plugin:storybook/recommended']
}

VS Code 의 ESLint Extension 이 정상적으로 동작하도록 하는 설정 또한 해주어야 하는데요,
VS Code command palette 를 열고(맥 환경 하에서Cmd ⌘ + Shift ⇧ + P) settings 키워드로 검색해 줍니다.

settings 검색 결과

검색 결과중에 Preferences: Open Workspace Settings (JSON) 를 선택해 주고, 열린 파일에 다음 내용을 추가해 줍니다.

{
...
"eslint.validate": [
"html",
"javascript",
"typescript",
"javascriptreact",
"typescriptreact"
],
...
}

그리고나서 monorepo-example/packages/storybook/src/stories/Sample.stories.tsx 파일을 생성해주면 빨간줄이 보이면서 다음처럼 ESLint 에러를 확인할 수 있습니다.

eslint-plugin-storybook 플러그인에 의한 ESLint 에러 노출

마지막으로는 package.json 파일들의 scripts 필드를 건드려 주겠습니다. 이 부분을 수정하는 이유는, lint-staged 에서 ESLintPrettier 작업을 수행할 수 있게 만들어주기 위함입니다.

먼저 nextjs workspace 의 package.json 파일을 수정해 주겠습니다.

// monorepo-example/apps/nextjs/package.json
{
...
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint". <-- 수정
},
...
}

원래는 lint script 부분에 next lint 라고 적혀있지만, 저희는 next 가 자체적으로 제공하는 lint 기능 대신 직접 eslint 를 사용하도록 수정해 주겠습니다.

다음으로 storybook workspace 의 package.json 파일을 수정해 주겠습니다.

// monorepo-example/packages/storybook/package.json
{
...
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"lint": "eslint", <-- 추가
"storybook": "start-storybook -p 6006 -s public",
"build-storybook": "build-storybook -s public"
},
...
}

lint script 를 추가로 작성해 주었습니다.

위의 2가지 workspace 에 대한 lint script 를 모노레포 root 에 등록해주기 위해 monorepo-example/package.json 파일의 scripts 필드에 다음 내용을 추가해 줍니다.

// monorepo-example/package.json
{
...
"scripts": {
"dev:nextjs": "turbo run dev --filter=nextjs",
"lint:nextjs": "yarn workspace nextjs lint", <-- 추가
"start:storybook": "turbo run start --filter=storybook",
"storybook": "turbo run storybook --filter=storybook",
"lint:storybook": "yarn workspace storybook lint". <-- 추가
},
...
}

turbo run 명령어를 사용하는 대신 yarn workspace 명령을 사용한 것을 볼 수 있습니다.

이렇게 해준 이유는, git pre-commit hook 을 통해 staged 상태에 있는 파일들을 lint 명령의 인자로 전달해줘야 하는데 turbo run 명령어를 이용할 경우 file path 자체를 명령어의 일부로 인식해버리는 문제가 있기 때문입니다. 이 문제를 yarn workspace 명령을 이용하면 해결할 수 있습니다.

이제 마지막으로 아까 설정해 두었던 husky 의 pre-commit hook 과 lint-staged 를 결합해 commit 하기 이전에 자동으로 ESLint 가 수행되도록 설정해보도록 하겠습니다.

먼저 nextjs workspace 쪽 설정부터 시작합니다.
monorepo-example/apps/nextjs/.lintstagedrc.js 파일을 생성해주신 뒤 다음 내용을 기입해주세요.

// monorepo-example/apps/nextjs/.lintstagedrc.js
module.exports = {
'*.{js,jsx,ts,tsx}': [
'yarn lint:nextjs',
]
}

nextjs workspace 에 속한 파일들 중에 js, jsx, ts, tsx 확장자를 가진 파일이 commit 되기 전 ESLint 로 해당 파일들을 넘겨주어 linting 작업을 수행하도록 해주는 설정입니다.

lint-staged 는 현재 저희 구성에서 모노레포 root 를 실행 맥락으로 가져가기 때문에 실행시킬 Yarn 명령어 는 모노레포 root 에 존재하는 package script 로 지정해 주었습니다.

storybook workspace 에도 똑같은 파일을 생성하고 내용을 기입해 줍니다.

// monorepo-example/packages/storybook/.lintstagedrc.js
module.exports = {
'*.{js,jsx,ts,tsx}': [
'yarn lint:storybook',
]
}

이제 테스트를 위해 nextjs workspace 의 코드를 lint rule 에 어긋나도록 수정한 뒤 commit 을 한 번 해보도록 하겠습니다.

commit 을 진행하기 전에 한 가지 더 설정해주어야 하는 것이 남았는데요, monorepo-example/.husky 디렉터리 아래에 있는 pre-commit 파일에 대한 executable 권한을 수정해 주어야 합니다. 그래야만 lint-staged 가 해당 hook 을 실행할 수 있기 때문입니다.

모노레포 root 에서 다음 명령어를 실행해주세요.

chmod ug+x .husky/*

실행 후 파일 권한을 확인해보면 usergroup 에 executable 권한이 제대로 부여된 것을 확인할 수 있습니다.

드디어 아까 생성해둔
monorepo-example/packages/storybook/src/stories/Sample.stories.tsx 파일을 commit 해볼 차례입니다.

해당 파일이 없으시다면 새로 만드시고,
이미 commit 을 하셨다면 삭제하고 commit 을 하신 뒤 다시 생성해주세요!
그래야 git 의 pre-commit hook 이 동작하는지 확인할 수 있습니다.

스토리 파일에 대한 commit 을 아래처럼 진행해주세요.

Sample.stories.tsx 파일 commit 직전

그럼 아래와 같은 ESLint 에러 문구가 나타납니다.

git pre-commit hook script 실행 결과

/monorepo-example/packages/storybook/src/stories/Sample.stories.tsx 파일을 열었을 때 보았떤 ESLint 에러가 발생하는 것을 확인 했습니다.

테스트를 마쳤으니, 테스트용 파일인 Sample.stories.tsx 를 삭제해 주겠습니다.

⚙️ Prettier 설정하기

Prettier 또한 ESLint 와 함께 이전에 이미 설치해 주었었죠.
ESLint 와 마찬가지로 설정해주면 됩니다. 설정 방식은 거의 같으니 자세한 설명 없이 과정만 나열해보도록 하겠습니다.

monorepo-example/.prettierrc.js 파일을 생성해줍니다.
이 파일에는 하위 workspace 에서 공통적으로 사용될 Prettier rule 을 정의할 것입니다.

// monorepo-example/.prettierrc.js
module.exports = {
trailingComma: 'es5', // 배열의 마지막 요소 끝에 붙여주는 ',' 를 어떤 JS 버전에 맞출지 선택
tabWidth: 2, // tab 크기 정의
semi: false, // 줄 끝에 세미콜론 붙이지 않기
singleQuote: true, // 홑따옴표 사용
plugins: [],
}

nextjsstorybook workspace 에는 각각 다음 내용으로 Prettier configuration 파일을 생성해 줍니다.

// monorepo-example/apps/nextjs/.prettierrc.js
const basePrettierConfig = require('../../.prettierrc.js')

module.exports = {
...basePrettierConfig,
}
// monorepo-example/packages/storybook/.prettierrc.js
const basePrettierConfig = require('../../.prettierrc.js')

module.exports = {
...basePrettierConfig,
}

여기까지 설정해준 뒤에 VS Code 의 Settings 를 열어서(맥 환경 하에서, Cmd ⌘ + ,) Format On Save feature를 아래처럼 활성화해줍니다.

VS Code Settings

그 다음, monorepo-example/packages/storybook/src/App.tsx 파일을 열어서 저장을 해주면 코드가 formatting 되는 것을 확일할 수 있습니다.
(세미콜론이 사라지는 것을 유심히 봐주세요)

Prettier 에 의해 자동 저장시 세미콜론이 사라지는 모습

마지막으로 lint-stagedPrettier 를 추가해 보겠습니다.

각 workspace 에 package script 작성하고, ESLint 설정 시에 생성해 두었던 .lintstagedrc.js 파일에 해당 package script 를 추가 해주기만 하면 됩니다.

먼저 각 workspace 에 package script 를 아래처럼 추가해주고,

// monorepo-example/apps/nextjs/package.json
{
...
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint",
"prettier": "prettier --write" <-- 추가
...
}
// monorepo-example/packages/storybook/package.json
{
...
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"lint": "eslint",
"prettier": "prettier --write", <-- 추가
"storybook": "start-storybook -p 6006 -s public",
"build-storybook": "build-storybook -s public"
},
...
}

이후 .lintstagedrc.js 파일을 아래와 같이 수정해줍니다.

// monorepo-example/apps/nextjs/.lintstagedrc.js
module.exports = {
'*.{js,jsx,ts,tsx}': [
'yarn lint:nextjs',
'yarn prettier:nextjs',
]
}
// monorepo-example/packages/storybook/.lintstagedrc.js
module.exports = {
'*.{js,jsx,ts,tsx}': [
'yarn lint:storybook',
'yarn prettier:storybook',
]
}

이후 pre-commit hook 에서의 Prettier 동작을 확인하기 위해 코드 format 을 의도적으로 깨뜨려 보겠습니다.

Format 을 깨뜨리려면 Format On Save 기능을 무시하고 저장해야 하는데요, VS Code command palette 에서 Save Without Formatting 을 검색하여 선택하면 됩니다.

pre-commit hook 테스트 대상 파일은
monorepo-example/packages/storybook/src/App.tsx 입니다.

// monorepo-example/packages/storybook/src/App.tsx

import React from 'react'; // 줄 끝에 세미콜론이 붙어있음
import logo from './logo.svg'; //
import './App.css'; //

function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="<https://reactjs.org>"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
); //
}

export default App; //

코드 포맷팅을 실행하지 않고 저장한 뒤 commit 을 수행했을 때 Pretiter 가 잘 수행되는지 확인할 차례인데요,
우선 파일에 세미콜론을 모두 지운채로 commit 을 한 번 하고 다시 세미콜론을 붙여서 저장하여 git 이 변경사항을 추적할 수 있도록 만들어 주겠습니다.

세미콜론 없는 버전의 코드를 먼저 commit 한뒤 세미콜론을 다시 붙여서 auto-formatting 없이 저장을 하면 git 이 변경 사항을 추적합니다.

이 상태에서 다시 한 번 commit 을 시도해 봅시다.

세미콜론을 다시 붙인 후 commit 한 후의 콘솔 내용

그러면 위와 같은 메시지가 나타나는데요 메시지를 해석해보면,

🖥️ 변경 사항이 아무것도 없는데? lint-staged 는 변경사항이 없는 빈 commit 을 허용하지 않아.

가 됩니다.

왜 empty commit 이라고 하는걸까요?
저희는 분명 세미콜론을 강제로 추가해준 상태로 commit 을 했는데 말이죠.
바로 pre-commit hook 단계에서 lint-staged 에 의해 Prettier 가 코드 formatting 작업을 수행했기 때문입니다.
그래서 정작 git commit 명령어 실행 단계에서는 세미콜론이 지워진 상태(최근에 수행한 commit 의 내용과 동일한 상태)여서 변경 사항이 없다고 판단한 것이죠.

🚀 Troubleshooting

아래의 내용들은 저희가 모노레포 구축 과정 상에서 겪은 문제들을 Troubleshooting 한 내용입니다. 같은 문제를 겪지 않으신다면 아래의 내용은 가볍게 읽어보시기만 해도 충분합니다!

⚠️ eslint error(The keyword ‘import’ is reserved) 해결하기

ESLint 세팅을 마치고 monorepo-example/packages/storybook/src/stories/Page.stories.tsx 파일을 열어보면 발생하지 않던 오류가 나타납니다.

example/packages/storybook/src/stories/Page.stories.tsx

ESLint parser 에 문제가 생긴 것인데요, 아래와 같은 방법으로 해결해주면 됩니다.

  1. storybook workspace 에 @typescript-eslint/parser 의존성 추가
yarn workspace storybook add -D @typescript-eslint/parser

2. monorepo-example/packages/storybook/.eslintrc.js 파일 내용 수정

// monorepo-example/packages/storybook/.eslintrc.js

module.exports = {
env: {
browser: true,
es6: true,
},
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
ecmaVersion: 2015,
ecmaFeatures: {
jsx: true,
},
},
extends: ['plugin:storybook/recommended'],
rules: {},
}

⚠️ eslint error(storybook/no-uninstalled-addons) 해결하기

ESLint 세팅을 마치고 monorepo-example/packages/storybook/.storybook/main.js 파일을 열어보면 다음과 같은 lint error 가 발생하는 경우가 있었습니다.

🔴 The @storybook/addon-links is not installed in monorepo-example/. Did you forget to install it or is your package.json in a different location?

분명 monorepo-example/packages/storybook/package.json 파일에,

  • @storybook/addon-links
  • @storybook/addon-essentials
  • @storybook/addon-interactions
  • @storybook/preset-create-react-app

이라는 패키지들이 존재하고 monorepo-example/.yarn/cache 디렉터리에 잘 들어있는데도 이런 오류가 발생하고 있었습니다.

에러 메시지를 읽어보니 VS Code ESLint extension 에서 발생하는 오류였고
The @storybook/addon-links is not installed in monorepo-example/. 라는 문구를 보니 ’VS Code ESLint 가 실행 맥락을 모노레포의 root 디렉터리로 잡고 있구나’ 라고 추측할 수 있었습니다.

이 오류를 해결하려면 2가지 부분을 수정해야합니다.

  1. packagesJsonPath 옵션에 현재 workspace 의 package.json 파일을 바라보도록 수정.

eslint-plugin-storybook 이라는 ESLint plugin 문서를 보면 아래와 같은 내용이 있습니다.

eslint-plugin-storybook Docs

no-uninstalled-addons 라는 rule 은 package.json 파일이 프로젝트의 root 에 위치하고 있다고 가정한다는 설명이 나와있네요.

저희가 원하는 동작은 이 rule 이 monorepo-example/packages/storybook workspace 의 package.json 파일을 바라보도록 하는 것이었습니다. 따라서 다음과 같이 경로를 수정해서 해결해 주었습니다.

module.exports = {
rules: {
'storybook/no-uninstalled-addons': [
'error',
{
packageJsonLocation: './package.json',
},
],
},
}

그런데 이상합니다. 이렇게 설정을 해주어도 여전히 같은 오류가 발생하고 있었습니다. 여전히 오류가 발생하고 있던 이유는 VS Code ESLint Extension 이 workspace 별로 실행 맥락을 가져가지 않고, 모노레포 root 맥락 하나만 가지고 있었기 때문이었습니다.

따라서 오류를 완전히 해결하기 위해서는 한 가지 설정을 더 해주어야 합니다.

2. VS Code settings 에서 workspace 별로 ESLint 실행맥락 설정해주기

(맥 환경 하에서)Cmd ⌘ + Shift ⇧ + P 를 누르면, VS Code command palette 가 나타나는데요, 여기에 Preferences: Open Workspace Settings (JSON) 을 검색하여 열어주신 뒤 다음의 내용을 추가해주면 됩니다. 이렇게 설정해주면 VS Code ESLint Extension 의 실행 맥락은 각 workspace 마다 독립적으로 구성됩니다.

// monorepo-example/.vscode/settings.json
{
"eslint.workingDirectories": [
"apps/nextjs",
"packages/storybook",
"packages/shared"
]
}

⚠️ ’storybook’ workspace 외부의 .ts, .tsx 파일 컴파일 하기

저희는 storybook 프로젝트에서 shared 프로젝트 안에 있는 소스코드(.ts, .tsx 파일들)를 import 해서 사용하고 싶었는데요, 이를 위해 Storybook 의 기본 webpack 설정을 건드려줄 필요가 있었습니다.

다음의 monorepo-example/packages/storybook/.storybook/main.js 코드를 살펴봐 주세요.

// packages/storybook/.storybook/main.js

module.exports = {
...
webpackFinal: async (config) => {
...
// 현재 프로젝트(workspace) 외부의 ts, tsx 파일을 트랜스파일 하기 위한 옵션
config.module.rules.push({
test: /\\.(ts|tsx)$/,
use: [
{
loader: require.resolve('ts-loader'),
options: {
reportFiles: ['../../shared/src/*.{ts,tsx}'],
},
},
],
})
config.resolve.extensions.push('.ts', '.tsx') // 추가

return config
},
...
}

.ts, .tsx 확장자를 가진 파일들을 처리하는 ts-loadershared workspace 의 코드도 처리하도록 Storybook 기본 webpack 설정을 override 해주었습니다.

🏁 마치며

바닥에서부터 모노레포를 구축하는 과정을 최대한 상세히 기술하려고 하다보니 부득이하게 시리즈가 많아졌네요!

마지막 편까지 인내심을 발휘하여 읽어주신 분들께 감사의 말씀을 전합니다.

본 아티클에서 소개한 모노레포 구축 과정을 담은 레포지토리 또한 공개해 놓았으니 참고해 주세요.

모쪼록 이 글이 프론트엔드 모노레포를 구축하고자 하시는 분들께 조금이나마 도움이 되었으면 하는 바람입니다.

다음 글에서도 보다 유익한 내용으로 찾아뵙도록 하겠습니다😊

--

--