Skip to content

Latest commit

ย 

History

History
727 lines (612 loc) ยท 21 KB

README.md

File metadata and controls

727 lines (612 loc) ยท 21 KB

๋ชจ๋…ธ๋ ˆํฌ: ๊ฐœ๋…๊ณผ ํ™œ์šฉ


์„ธ๋ฏธ๋‚˜ ์ž๋ฃŒ

https://bit.ly/3WI2zsC

์‹œ์ž‘ํ•˜๊ธฐ

$ git clone https://github.com/thepsyentist-public/learn-monorepo
$ yarn set version berry
$ yarn
$ yarn workspace @thepsyentist/client dev

๋ชจ๋…ธ๋ ˆํฌ ํ”„๋กœ์ ํŠธ ์„ค์ •ํ•˜๊ธฐ with Yarn berry

learn-monorepo ํ”„๋กœ์ ํŠธ๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  ์žˆ๋Š” ๋‚ด์šฉ์œผ๋กœ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

Step1. ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๋ฒ„์ „ ์„ค์ •

๋ชจ๋…ธ๋ ˆํฌ๋ฅผ ๊ตฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Node.js yarn ๋ฒ„์ „ ์„ค์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
https://yarnpkg.com/getting-started/install

Node.js ๋ฒ„์ „ ๋งค๋‹ˆ์ง•์ด ํ•„์š”ํ•˜๋‹ค๋ฉด nvm ์„ ์„ค์น˜ํ•ด์„œ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
https://jang8584.tistory.com/295

Node.js: 16.10.0 ๋ฒ„์ „ ์ด์ƒ
yarn: berry




Step2. yarn workspace ํ”„๋กœ์ ํŠธ ๋งŒ๋“ค๊ธฐ

ํ”„๋กœ์ ํŠธ ํด๋” ์ƒ์„ฑ: learn-monorepo ํด๋”๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

$ mkdir learn-monorepo
$ cd learn-monorepo

yarn ๋ฒ„์ „ ์„ค์ •: yarn berry ๋ฒ„์ „์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

$ yarn set version berry

workspace ํŒจํ‚ค์ง€ ์„ค์ •: ๋ชจ๋…ธ๋ ˆํฌ ๊ตฌ์„ฑ์„ ์œ„ํ•ด yarn workspace ํ”„๋กœ์ ํŠธ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

  • ์ถ”๊ฐ€์ ์ธ yarn berry cli๋ฅผ ํ™•์ธํ•ด๋ณด์„ธ์š”.
$ yarn init -w

{rootDirectory} package.json ํŒŒ์ผ ์ˆ˜์ •: workspaces ์†์„ฑ์—์„œ ๊ด€๋ฆฌ๋ฅผ ํ•  workspace ํด๋” ๊ฒฝ๋กœ๋ฅผ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

// package.json
{
  "name": "learn-monorepo",
  "packageManager": "[email protected]",
  "private": true,
  "workspaces": [
    "apps/*",       // ๐Ÿ‘ˆ ์ถ”๊ฐ€
    "packages/*"    // ๐Ÿ‘ˆ ์ถ”๊ฐ€
  ]
}

์œ„์™€ ๊ฐ™์ด ์„ค์ •ํ•˜๋ฉด apps/* packages/* ๋‚ด๋ถ€ ํด๋”๋Š” ์›Œํฌ์ŠคํŽ˜์ด์Šค๋กœ ํ™œ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

apps ํด๋”์— Nextjs ํ”„๋กœ์ ํŠธ ์ถ”๊ฐ€: Nextjs CRA๋ฅผ ํ†ตํ•ด ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

$ cd apps
$ yarn create next-app
next cra

client ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.
์ด ํ”„๋กœ์ ํŠธ์— ๋‹ค๋ฅธ ํŒจํ‚ค์ง€๋“ค์„ ์ถ”๊ฐ€ํ•˜๋Š” ํ˜•์‹์œผ๋กœ ์ง„ํ–‰ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

workspace ์ด๋ฆ„ ์„ค์ •: ํ”„๋กœ์ ํŠธ์˜ ์›Œํฌ์ŠคํŽ˜์ด์Šค ์ด๋ฆ„์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. apps/client/package.json ํŒŒ์ผ ๋‚ด name์„ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
workspace name

workspace ์ ์šฉ: ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋ฉด .yarnrc.yml์— ํ”„๋กœ์ ํŠธ์˜ ์ข…์†์„ฑ์„ ์ธ์ง€ํ•˜๋„๋ก ์ถ”๊ฐ€ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

// {rootDirector} ๋กœ ์ด๋™

$ ..cd
$ yarn

yarn ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด์„œ workspaces์— ๋“ฑ๋ก๋œ ํ”„๋กœ์ ํŠธ๋“ค์„ ์ธ๋ฑ์‹ฑํ•˜์—ฌ .yarnrc.yml์— ์ข…์†์„ฑ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
yarnrc


@thepsyentist/client ํ”„๋กœ์ ํŠธ ์‹คํ–‰: ์ง€๊ธˆ๊นŒ์ง€ ์„ค์ •ํ•œ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹คํ–‰ ํ•ด๋ด…๋‹ˆ๋‹ค.

$ yarn workspace @thepsyentist/client dev



Step3. ํ”„๋กœ์ ํŠธ์— ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์ ์šฉํ•˜๊ธฐ

์ง€๊ธˆ๊นŒ์ง€์˜ ์„ค์ •์œผ๋กœ๋Š” ./apps/client/pages/index.tsx ํŒŒ์ผ์˜ ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•˜๋ฉด typescript error๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ด์œ ๋Š”, yarn berry pnp๋Š” node_modules ๋ชจ๋“ˆ์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐฉ์‹์ด ๋‹ฌ๋ผ์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค.

typescript ์„ค์น˜: {rootDirectory}์— typescript ์˜์กด์„ฑ์„ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

$ yarn add -D typescript

// vscode์˜ ๊ฒฝ์šฐ๋Š” ์•„๋ž˜ cli๋„ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.
$ yarn dlx @yarnpkg/sdks vscode

Node.js ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ € ์„ค์ •: ์›น์Šคํ†ฐ์˜ ๊ฒฝ์šฐ settings โ†’ Nodejs ๊ฒ€์ƒ‰ โ†’ ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ €์—์„œ Yarn berry๋กœ ์ง€์ • ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ €

vscode extension ์„ค์น˜: vscode IDE๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, arcanis.vscode-zipfs extends๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

// .vscode/extensions.json

{
  "recommendations": ["arcanis.vscode-zipfs"]
}

.vscode/extensions.json ์— ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•˜๋ฉด, ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๊ฐ€ ํ”„๋กœ์ ํŠธ ์ด์šฉ์‹œ extensions๋ฅผ ์„ค์น˜๋ฅผ ์ถ”์ฒœํ•˜๋„๋ก ์„ค์ • ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.




Step4. ๊ณตํ†ต ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํŒจํ‚ค์ง€ ๋งŒ๋“ค๊ณ  ์ ์šฉํ•˜๊ธฐ

packages/lib ์— ๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉํ•  ํŒจํ‚ค์ง€๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ ์šฉํ•ด๋ด…๋‹ˆ๋‹ค.

packages/lib ์ƒ์„ฑ: lib ํŒจํ‚ค์ง€๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

$ cd packages/lib
$ yarn init
$ yarn add typescript // ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์˜์กด์„ฑ ์ถ”๊ฐ€

workspace ์ด๋ฆ„ ์„ค์ •: packages/lib/package.json ๋‚ด name์„ @thepsyentist/lib ๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

// packages/lib/package.json

{
  "name": "@thepsyentist/lib",
  "main": "./src/index.ts",
  "packageManager": "[email protected]",
  "dependencies": {
    "typescript": "^5.1.3"
  }
}

tsconfig.json ์ถ”๊ฐ€: lib ํŒจํ‚ค์ง€์— tsconfig.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"]
}

๊ณต์œ  ํ•จ์ˆ˜ ์ถ”๊ฐ€: packages/lib/src/index.js ํŒŒ์ผ ์ƒ์„ฑ ํ›„, ์•„๋ž˜ ์ฝ”๋“œ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

export const sayHello = () => {
   return "Hello World";
};

์›Œํฌ์ŠคํŽ˜์ด์Šค ๋“ฑ๋ก: {rootDirectory}๋กœ ์ด๋™ํ•ด์„œ ํ”„๋กœ์ ํŠธ์— ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

$ cd ..
$ cd ..

// ํ˜„์žฌ ๊ฒฝ๋กœ {rootDirectory}
$ yarn

์—ฌ๊ธฐ๊นŒ์ง€ packages/lib ์˜ ๊ณตํ†ต ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.




Step5. client ํ”„๋กœ์ ํŠธ์— ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํŒจํ‚ค์ง€ ์‚ฌ์šฉํ•˜๊ธฐ

์„ค์น˜: Step4์—์„œ ์ƒ์„ฑํ•œ packages/lib ๋ฅผ apps/client ํ”„๋กœ์ ํŠธ์— ์˜์กด์„ฑ์œผ๋กœ ์ถ”๊ฐ€ํ•ด์„œ ํ™œ์šฉํ•ด๋ด…๋‹ˆ๋‹ค.

$ yarn workspace @thepsyentist/client add @thepsyentist/lib
๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํŒจํ‚ค์ง€ ์‚ฌ์šฉ

Nextjs ์„ค์ •: Nextjs์—์„œ๋Š” ์™ธ๋ถ€ ํŒจํ‚ค์ง€๋ฅผ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•  ๊ฒฝ์šฐ, ํ•ด๋‹น ํŒจํ‚ค์ง€๋ฅผ ํŠธ๋žœ์ŠคํŒŒ์ผ ์˜ต์…˜์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// apps/client/next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  transpilePackages: ['@thepsyentist/lib'],
};

module.exports = nextConfig;

์ ์šฉ: apps/client/src/app/page.tsx ํŒŒ์ผ ๋‚ด sayHello ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.
ํ•จ์ˆ˜ํ˜ธ์ถœ

์‹คํ–‰: @thepsyentist/client ํ”„๋กœ์ ํŠธ๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ๋‚ด์šฉ์ด ๋ฐ˜์˜๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

$ yarn workspace @thepsyentist/client dev


๋ชจ๋…ธ๋ ˆํฌ ํŒจํ‚ค์ง€ ํ™œ์šฉํ•˜๊ธฐ

๋ชจ๋…ธ๋ ˆํฌ๋ฅผ ํ†ตํ•ด ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š”, ์„ค์ • ์ ์šฉ ๋ฐ ๊ณต์œ , ๊ณตํ†ต UI ํ™œ์šฉ, ์Šคํฌ๋ฆฝํŠธ ํ™œ์šฉ ๋‚ด์šฉ์„ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค.

Step1. ๊ณตํ†ต tsconfig ์ ์šฉํ•ด๋ณด๊ธฐ

tsconfig.base.json ์ƒ์„ฑ: ์›Œํฌ์ŠคํŽ˜์ด์Šค ๋งˆ๋‹ค ๋งค๋ฒˆ tsconfig ์„ค์ •์„ ํ•˜๋Š”๊ฒƒ์„ ๋ฒˆ๊ฑฐ๋กœ์šธ ์ž‘์—…์ž…๋‹ˆ๋‹ค. {rootDirectory}์— ์›Œํฌ์ŠคํŽ˜์ด์Šค์— ๊ณต์œ  ํ•  ์ˆ˜ ์žˆ๋Š” ํŒŒ์ผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

// tsconfig.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", "**/.*/"]
}

client, lib์— ์ ์šฉ: tsconfig ์„ค์ • ์ค‘ extends ํ•ญ๋ชฉ์„ ํ†ตํ•ด tsconfig.base.json๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

// apps/client/tsconfig.json

{
  "$schema": "https://json.schemastore.org/tsconfig",
  "extends": "../../tsconfig.base.json",     // ๐Ÿ‘ˆ ์ถ”๊ฐ€
  "compilerOptions": {
    "baseUrl": "./src",
    "target": "esnext",
    "lib": ["dom", "dom.iterable", "esnext"],
    "module": "esnext",
    "jsx": "preserve",
    "incremental": true,
    "paths": {
      "@/*": ["./*"]
    }
  },
  "exclude": ["**/node_modules", "**/.*/"],
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.mts",
    "**/*.js",
    "**/*.cjs",
    "**/*.mjs",
    "**/*.jsx",
    "**/*.json"
  ]
}
// packages/lib/tsconfig.json

{
  "$schema": "https://json.schemastore.org/tsconfig",
  "extends": "../../tsconfig.base.json",     // ๐Ÿ‘ˆ ์ถ”๊ฐ€
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "node",
    "target": "ESNext",
    "lib": ["ESNext", "dom"],
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "baseUrl": "./src",
    "noEmit": false,
    "incremental": true,
    "resolveJsonModule": true
  },
  "exclude": ["**/node_modules", "**/.*/", "./dist", "./coverage"],
  "include": ["**/*.ts", "**/*.js", "**/.cjs", "**/*.mjs", "**/*.json"]
}

์œ„ ์™€ ๊ฐ™์ด extends์— tsconfig.base.json์„ ์ถ”๊ฐ€ํ•˜์—ฌ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์„ค์ •์„ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.




Step2. eslint, prettier ์ ์šฉํ•˜๊ธฐ

์˜์กด์„ฑ ์„ค์น˜: ์•„๋ž˜ CLI๋ฅผ ์‹คํ–‰ํ•˜์—ฌ eslint, prettier ๊ด€๋ จ ์˜์กด์„ฑ์„ {rootDirectory}์—์„œ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

$ 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

ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์น˜: IDE์˜ ํ”Œ๋Ÿฌ๊ทธ์ธ์—์„œ eslint, prettier๋ฅผ ๊ฒ€์ƒ‰ํ•˜์—ฌ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

eslint ์„ค์ • ํŒŒ์ผ ์ถ”๊ฐ€: {rootDirectory}์—์„œ .eslintrc.js ํŒŒ์ผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

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'] }],
  },
};

eslint ์„ค์ • ๋“ฑ๋ก: IDE์— eslint๊ฐ€ ํ™œ์„ฑํ™” ๋˜๋„๋ก ์„ค์ •์„ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

  • Webstorm ์€ settings โ†’ eslint โ†’ Automatic Eslint configuration ์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
eslint ์„ค์ •

- vscode ๋Š” .vscode/setting.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"]
}

eslint ํŒจํ‚ค์ง€๋กœ ๊ตฌ์„ฑํ•ด์„œ ๊ณต์œ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด?: https://tech.kakao.com/2019/12/05/make-better-use-of-eslint๋ฅผ ์ฐธ๊ณ ํ•ด์„œ ๊ตฌ์„ฑํ•ด๋ณผ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

.prettierrc ํŒŒ์ผ ์ƒ์„ฑ: {rootDirectory}์—์„œ .prettierrc ํŒŒ์ผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

{
  "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"
}

prettier ์„ค์ • ๋“ฑ๋ก: IDE์— prettier๊ฐ€ ํ™œ์„ฑํ™” ๋˜๋„๋ก ์„ค์ •์„ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

  • Webstorm์€ settings โ†’ prettier ๊ฒ€์ƒ‰ โ†’ Automatic Prettier configuration์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
prettier
- vscode๋Š” `.vscode/settings.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]
}



Step3. ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ ๊ณต์œ ํ•˜๊ธฐ

ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ: packages/ui ํด๋”๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ํ”„๋กœ์ ํŠธ๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

$ cd packages/ui
$ yarn init

์›Œํฌ์ŠคํŽ˜์ด์Šค ์ด๋ฆ„ ๋ณ€๊ฒฝ: packages/ui/package.json ํŒŒ์ผ ๋‚ด name์„ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

{
  "name": "@thepsyentist/ui",
  ...
}

๋ฆฌ์•กํŠธ ์˜์กด์„ฑ ์„ค์น˜: ์•„๋ž˜ cli๋ฅผ {rootDirectory}๋กœ ์ด๋™ํ•˜์—ฌ ์˜์กด์„ฑ์„ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

// rootํด๋”๋กœ ์ด๋™
$ cd ../../

// ๊ฐฑ์‹ 
$ yarn


// install
$ yarn workspace @wanted/ui add typescript react react-dom @types/node @types/react @types/react-dom -D

tsconfig ์ถ”๊ฐ€: packages/ui/tsconfig.js ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์—ฌ, ์•„๋ž˜ ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

{
  "$schema": "https://json.schemastore.org/tsconfig",
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "baseUrl": "./src",
    "target": "esnext",
    "lib": ["dom", "dom.iterable", "esnext"],
    "module": "esnext",
    "jsx": "react-jsx",
    "noEmit": false,
    "incremental": true
  },
  "exclude": ["**/node_modules", "**/.*/", "dist", "build"]
}

Button UI ์ถ”๊ฐ€: ๊ณต์œ  ํ•  ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

// packages/ui/src/Button.tsx

import { ButtonHTMLAttributes, MouseEventHandler, ReactNode } from 'react';

export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
  children: ReactNode,
  onClick?: MouseEventHandler<HTMLButtonElement>,
};

const Button = (props: ButtonProps) => {
  const { children, onClick, ...other } = props;

  return (
    <button type="button" onClick={onClick} {...other}>
      {children}
    </button>
  );
};

export default Button;
// packages/ui/src/index.ts

export { default as Button } from './Button';

main ๋“ฑ๋ก: package.json์— main ์†์„ฑ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. main์„ ์ง€์ •ํ•˜์—ฌ ๋‹ค๋ฅธ ์›Œํฌ์ŠคํŽ˜์ด์Šค์—์„œ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, ์ฐธ์กฐํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

{
  "name": "@thepsyentist/ui",
  "packageManager": "[email protected]",
  "main": "src/index.ts",    // ๐Ÿ‘ˆ ์ถ”๊ฐ€
  "devDependencies": {
    "@types/node": "^18.16.3",
    "@types/react": "^18.2.0",
    "@types/react-dom": "^18.2.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "typescript": "^5.0.4"
  }
}

client์—์„œ Button ํ™œ์šฉํ•˜๊ธฐ: {rootDirectory}์—์„œ pakcages/ui ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

$ cd ..
$ cd ..
$ yarn workspace @thepsyentist/client add @thepsyentist/ui

Next.js config ์ˆ˜์ •: transpilePackages์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  transpilePackages: ['@thepsyentist/lib', '@thepsyentist/ui'],
};

module.exports = nextConfig;

ํŽ˜์ด์ง€์— Button ์ถ”๊ฐ€ํ•˜๊ธฐ: apps/client/pages/index.tsx ํŒŒ์ผ์— ๋ฒ„ํŠผ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
๋ฒ„ํŠผ ์ถ”๊ฐ€

ํ”„๋กœ์ ํŠธ ์‹คํ–‰: ํŽ˜์ด์ง€์— ๋ฒ„ํŠผ์ด ์ถ”๊ฐ€๋˜์—ˆ๋Š”์ง€ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

$ yarn workspace @thepsyentist/client dev



Step4. type check ์Šคํฌ๋ฆฝํŠธ ๋ณ‘๋ ฌ ์ ์šฉํ•˜๊ธฐ

์—ฌ๋Ÿฌ ์›Œํฌ์ŠคํŽ˜์ด์Šค๋งˆ๋‹ค ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
๋“ฑ๋ก ๋œ ์›Œํฌ์ŠคํŽ˜์ด์Šค๋งˆ๋‹ค lint, type check ๋“ฑ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์ฒดํฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ๊ฐ ์›Œํฌ์ŠคํŽ˜์ด์Šค ๋””๋ ‰ํ† ๋ฆฌ์— ์ ‘๊ทผํ•˜์—ฌ ๋ช…๋ น์–ด๋กœ ์‹คํ–‰ ํ•  ์ˆ˜์žˆ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ์—ฌ๋Ÿฌ ์›Œํฌ์ŠคํŽ˜์ด์Šค๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ชจ๋…ธ๋ ˆํฌ ๊ตฌ์„ฑ์—์„œ๋Š” ๋งค์šฐ ๋น„ํšจ์œจ์ ์ธ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.
์ด๊ฒƒ์„ ํ™œ์šฉํ•˜๊ธฐ์œ„ํ•ด yarn plugin์„ ํ™œ์šฉํ•ด๋ด…๋‹ˆ๋‹ค.

workspace-tools plugin ์„ค์น˜: ์•„๋ž˜ CLI๋ฅผ ์ž…๋ ฅํ•˜์—ฌ workspace-tools๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

$ yarn plugin import workspace-tools

typecheck script ์ถ”๊ฐ€: ์•„๋ž˜ ๊ฐ ์›Œํฌ์ŠคํŽ˜์ด์Šค์— type check script๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

  • apps/client/package.json
  • packages/lib/package.json
  • packages/ui/package.json
"scripts": {
  "typecheck": "tsc --project ./tsconfig.json --noEmit"
},

g:typecheck ์Šคํฌ๋ฆฝํŠธ ์ถ”๊ฐ€: {rootDirectory}์—์„œ ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰์„ ์œ„ํ•ด package.json์— script๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

"scripts": {
  "g:typecheck": "yarn workspaces foreach -pv run typecheck"
},

yarn workspaces foreach๋ช…๋ น์–ด option ํ™•์ธ
p: ๋ณ‘๋ ฌ ์‹คํ–‰
v: workspace name ์ถœ๋ ฅ


ํƒ€์ž…์ฒดํฌ ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰: {rootDirectory}์—์„œ ์›Œํฌ์ŠคํŽ˜์ด์Šค์˜ ํƒ€์ž…์ฒดํฌ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
ํƒ€์ž…์ฒดํฌ

์œ„ ๋‚ด์šฉ์ฒ˜๋Ÿผ ๊ฐ ์›Œํฌ์ŠคํŽ˜์ด์Šค์—์„œ ์›ํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋“ฑ๋กํ•˜๊ณ ,
{rootDirectory} package.json์— ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋“ฑ๋กํ•˜์—ฌ ๋ชจ๋“  ์›Œํฌ์ŠคํŽ˜์ด์Šค๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.