๐Ÿง‘โ€๐Ÿ’ป ์งง์€ํ˜ธํก/React

ํ”„๋ก ํŠธ์—”๋“œ ๋ชจ๋…ธ๋ ˆํฌ ๊ตฌ์ถ• ์‚ฝ์งˆ๊ธฐ (2) - ํ”„๋กœ์ ํŠธ ์„ธํŒ… with Next.js, Vite, storybook, emotion

ํ•œ๊ทœ์ง„ 2023. 1. 8. 21:57

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

yarn set version berry
yarn init
yarn add -D typescript @types/node @types/react

๋””๋ ‰ํ† ๋ฆฌ ํ•˜๋‚˜๋ฅผ ํŒŒ๊ณ  yarn ๋ฒ„์ „์„ berry๋กœ ์„ค์ •ํ•œ ํ›„์— package.json์„ ์ƒ์„ฑํ•ด์ค€๋‹ค. ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋„ ํ•จ๊ป˜ ์„ธํŒ…ํ•ด์ค€๋‹ค.

 

yarn ๊ด€๋ จ ์„ค์ •์€ ์ด์ „ ๊ธ€์—์„œ ์งค๋ง‰ํ•˜๊ฒŒ ์ ์–ด๋†“์•˜๋‹ค. ๋‹ค์‹œ ๊ฐ„๋žตํ•˜๊ฒŒ ์ ์ž๋ฉด,

 

// ./package.json
{
  "name": "dudoong-front",
  "packageManager": "yarn@3.3.0",
  "private": true,
  "workspaces": {
    "packages": [
      "apps/*",
      "shared/*"
    ]
  },
  // ...
}

๋ฃจํŠธ์˜ package.json์—์„œ workspaces๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค. workspaces์— ๊ฐ์ฒด๊ฐ€ ์•„๋‹ˆ๋ผ ๋ฐ”๋กœ ๋ฐฐ์—ด์„ ๋„ฃ์–ด์ค„ ์ˆ˜ ์žˆ๋Š”๋ฐ, ๋””ํดํŠธ๋กœ packages ์„ค์ •์œผ๋กœ ๋“ค์–ด๊ฐ€๊ฒŒ ๋œ๋‹ค. ๊ฐ์ฒด๋กœ ์ž‘์„ฑํ•ด์ค€๋‹ค๋ฉด "packages" ์™ธ์—๋„ "nohoist" ์„ค์ •์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ช…์‹œ๋œ ์˜์กด์„ฑ ํŒจํ‚ค์ง€๋Š” ๋ฃจํŠธ์˜ ๋…ธ๋“œ๋ชจ๋“ˆ๋กœ ํ˜ธ์ด์ŠคํŒ…๋˜์ง€ ์•Š๊ณ , ํ•ด๋‹น ์„œ๋น„์Šค์˜ ๋…ธ๋“œ๋ชจ๋“ˆ์— ์„ค์น˜๋œ๋‹ค.

 

eslint์™€ tsc ์„ค์ •์„ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค. ๋‘˜ ๋‹ค --init์œผ๋กœ ์ดˆ๊ธฐ์„ธํŒ…์„ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

.tsconfig

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "jsxImportSource": "@emotion/react"
  },
  "references": [
    {
      "path": "apps/ticket"
    },
    {
      "path": "apps/admin"
    },
    {
      "path": "shared/ui"
    },
    {
      "path": "shared/utils"
    }
  ],
  "include": [],
  "exclude": ["apps/**/dist/**"]
}

tsconfig์˜ ๊ฒฝ์šฐ references๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค. ๊ฐ ํ”„๋กœ์ ํŠธ์˜ tsconfig์— ๋ฃจํŠธ์˜ ์„ค์ •์„ ์˜ค๋ฒ„๋ผ์ด๋“œํ•  ์ˆ˜ ์žˆ๋‹ค.

 

2. ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

โ”œโ”€โ”€ apps
โ”‚   โ”œโ”€โ”€ admin # ์–ด๋“œ๋ฏผ ์„œ๋น„์Šค. react + vite
โ”‚   โ””โ”€โ”€ ticket # ํ‹ฐ์ผ“ ์„œ๋น„์Šค. nextjs
โ””โ”€โ”€ shared
    โ”œโ”€โ”€ ui # ๊ณต์œ  ์ปดํฌ๋„ŒํŠธ, theme. react + storybook
    โ””โ”€โ”€ utils # ์œ ํ‹ธ ํ•จ์ˆ˜, ๊ณต์šฉ ํ›…, ํƒ€์ž… ๋“ฑ. react

์ด ๋„ค๊ฐœ์˜ ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ ๋‹ค. ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž๋ฅผ ์œ„ํ•œ ํ‹ฐ์ผ“ ์„œ๋น„์Šค์™€ ํ˜ธ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ์–ด๋“œ๋ฏผ ์„œ๋น„์Šค. ํ‹ฐ์ผ“ ์„œ๋น„์Šค๋Š” Nextjs๋กœ, ์–ด๋“œ๋ฏผ ์„œ๋น„์Šค๋Š” vite๋กœ ์ฒ˜์Œ ์„ธํŒ…ํ•ด์ฃผ์—ˆ๋‹ค. SEO์™€ ํ•จ๊ป˜ ๋น ๋ฅธ ๋กœ๋”ฉ ๋“ฑ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ๋” ์ค‘์š”ํ•ด์กŒ๊ธฐ ๋•Œ๋ฌธ์— Nextjs๋ฅผ ์„ ํƒํ–ˆ๋‹ค. ๊ฐ™์€ ํŒ€์› ์ค‘์—์„œ Nextjs๋ฅผ ์ƒˆ๋กœ ๊ณต๋ถ€ํ•˜๋ฉด์„œ ํ”„๋กœ์ ํŠธ๊ฐ€ ์–ด๋ ค์šด ์นœ๊ตฌ๋“ค์ด ์žˆ์–ด์„œ ์–ด๋“œ๋ฏผ ์„œ๋น„์Šค๋Š” ์›๋ž˜์™€ ๊ฐ™์ด ๋ฆฌ์•กํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์™œ Vite๋ฅผ ์„ ํƒํ–ˆ๋Š”์ง€๋Š” ์•„๋ž˜์—์„œ ์งค๋ง‰ํ•˜๊ฒŒ ์–ธ๊ธ‰ํ•  ๊ฒƒ ๊ฐ™๋‹ค.

 

shared ๋””๋ ‰ํ† ๋ฆฌ์— ์žˆ๋Š” ui์™€ utils ์„œ๋น„์Šค๋Š” ์ผ๋ฐ˜ ๋ฆฌ์•กํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ๋ณ„๋„์˜ ๋นŒ๋“œ๊ฐ€ ํ•„์š”์—†๋Š” ๊ฒƒ๋“ค์ด๊ณ , vite์—์„œ ์Šคํ† ๋ฆฌ๋ถ์„ ์„ธํŒ…ํ•˜๊ณ  ๋ฐฐํฌํ•˜๋Š”๊ฒŒ ์ •๋ณด๊ฐ€ ๋ณ„๋กœ ์—†์–ด์„œ ์ผ๋ฐ˜์ ์ธ ๋ฐฉ๋ฒ•์„ ์„ ํƒํ–ˆ๋‹ค. ํ•ด๋‹น ๋””๋ ‰ํ† ๋ฆฌ๋กœ ๊ฐ€์„œ ๊ฐ๊ฐ cra cna ๋“ฑ์˜ ํƒฌํ”Œ๋ฆฟ์œผ๋กœ ์ƒ์„ฑํ•ด์ค€๋‹ค.

 

//shared/utils/package.json
{
  "name": "@dudoong/utils",
  "main": "src/index.ts",
  "types": "src/index.ts",
  // ...
}

ui์™€ util๊ฐ™์€ shared ๋ ˆํฌ์ง€ํ† ๋ฆฌ๋“ค์˜ package.json์—๋Š” main๊ณผ types ํ•„๋“œ๋ฅผ ์„ค์ •ํ•ด์ค€๋‹ค. ํŒจํ‚ค์ง€์— ๋Œ€ํ•œ ์—”ํŠธ๋ฆฌํฌ์ธํŠธ๋ฅผ ๋ฃจํŠธ๊ฐ€ ์•„๋‹Œ src/index.js๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค. ํŒจํ‚ค์ง€ ์ด๋ฆ„์„ @dudoong/์œผ๋กœ ์‹œ์ž‘ํ•˜๊ฒŒ ํ–ˆ๋Š”๋ฐ,

 

  // apps/ticket/package.json
  "dependencies": {
    "@dudoong/ui": "workspace:shared/ui",
    "@dudoong/utils": "workspace:shared/utils",

apps์—์„œ๋Š” dependencies์—์„œ ๋˜‘๊ฐ™์€ ์ด๋ฆ„์œผ๋กœ ๊ทธ ํŒจํ‚ค์ง€๋“ค์„ ๊ฐ€์ ธ๋‹ค ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ ˆ๋Œ€๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•  ๋•Œ @๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฒƒ๊ณผ ๋˜‘๊ฐ™์€ ํ˜•์‹์ด๋‹ค.

 

3. ๊ทธ๋ฆฌ๊ณ  ์—ฌ๋Ÿฌ๊ฐ€์ง€๋“ค

transpile

๋ชจ๋…ธ๋ ˆํฌ ๋‚ด๋ถ€์—์„œ ๋งŒ๋“  ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ€์ ธ์˜ค๋ ค๊ณ  ๋ณด๋‹ˆ unexpected token ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

Module parse failed: Unexpected token (1:21)
You may need an appropriate loader to handle this file type,
currently no loaders are configured to process this file.
See https://webpack.js.org/concepts#loaders

 

์ปดํฌ๋„ŒํŠธ๋“ค์„ Next ์•ˆ์œผ๋กœ ๊ฐ€์ ธ์™€์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋ณ„๋„์˜ ํŠธ๋žœ์ŠคํŒŒ์ผ๋ง์ด ํ•„์š”ํ•˜๋‹ค. yarn์—์„œ next-transpile-modules ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์„ค์น˜ํ•œ๋‹ค.

 

const withTM = require('next-transpile-modules')([
  '@dudoong/ui',
  '@dudoong/utils',
]);
module.exports = withTM({
  // Any additional config for next goes in here
});

next.config.js๋ฅผ ์ด๋ ‡๊ฒŒ ์ˆ˜์ •ํ•œ๋‹ค. ํ„ฐ๋ณด๋ ˆํฌ ๋ฌธ์„œ์—์„œ๋Š” transpilePackages๋ผ๋Š” ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๋ผ๊ณ  ๋‚˜์™€์žˆ๋‹ค. Next13.1๋ถ€ํ„ฐ ์ ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค. ์ฒ˜์Œ ๋ฌธ์„œ๋Š” ์ €๊ฒŒ ์•„๋‹ˆ๋ผ ๋‚ด๊ฐ€ ํ•œ ๋ฐฉ๋ฒ•๋Œ€๋กœ ์จ ์žˆ์—ˆ๋Š”๋ฐ ๊ทธ์ƒˆ ์—…๋ฐ์ดํŠธ ๋œ ๋“ฏ.

 

์ฒ˜์Œ ํ”„๋กœ์ ํŠธ๋ฅผ ๊ตฌ์ถ•ํ•  ๋•Œ ์ด ๋ฌธ์„œ์—์„œ vite๋Š” ๋ณ„๋„์˜ ํŠธ๋žœ์ŠคํŒŒ์ผ ์„ค์ •์ด ํ•„์š”์—†๋‹ค๋Š” ๊ฒƒ์„ ๋ณด์•˜๋‹ค. ๊ทธ๋ž˜์„œ admin ํŽ˜์ด์ง€๋Š” vite๋ฅผ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ, ์ƒ๊ฐํ•ด๋ณด๋‹ˆ๊นŒ ๊ทธ๋ƒฅ ๋ฆฌ์•กํŠธ๋ฅผ ์‚ฌ์šฉํ–ˆ์–ด๋„ ์ƒ๊ด€ ์—†์—ˆ์„ ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๋ž˜๋„ vite๋ฅผ ์ฒ˜์Œ ์‚ฌ์šฉํ•ด๋ดค๋Š”๋ฐ, ๋นŒ๋“œ๋„ ์—„์ฒญ ๋น ๋ฅด๊ณ  ๋ณ„๋„์˜ eject๋‚˜ craco ์—†์ด๋„ vite.config.ts์—์„œ ๋ชจ๋“ˆ ๊ด€๋ จ ์„ค์ •์ด ํŽธํ•˜๋‹ค๋Š” ์ ์ด ์ข‹๋‹ค.

 

emotion

yarn add @emotion/react @emotion/styled

 

์ด๋ชจ์…˜์„ ์„ค์น˜ํ•ด์ค€๋‹ค. (+ CSS Prop ๊ด€๋ จ ์˜ค๋ฅ˜, ๊ทธ๋ฆฌ๊ณ  babel-plugin ๋“ฑ์˜ ์„ค์ •์„ ๊ฐ™์ด ํ•ด์ค€๋‹ค.)

 

//emotion.d.ts

declare module '@emotion/react' {
  export interface Theme {
    palette: TypeOfPalette;
    typo: TypeOfTypo;
  }
}

ThemeProvider๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ emotion ํƒ€์ž… ๋ชจ๋“ˆ์„ ์ง์ ‘ ์„ ์–ธํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ–ˆ๋‹ค. theme๊ณผ global style ๊ด€๋ จ ํŒŒ์ผ๋“ค์€ shared/ui์—์„œ ์ž‘์—…ํ•˜๊ณ  apps๋กœ ์ž„ํฌํŠธํ•˜๊ณ  ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”๋ฐ, ui ๋ ˆํฌ์ง€ํ† ๋ฆฌ ์™ธ๋ถ€์—์„œ๋Š” emotion.d.ts์˜ ๊ฒฝ๋กœ๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ•ด ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ๋‹ค.

 

// tsconfig.json
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "../../**/*.d.ts"],

๊ฐ ๋ ˆํฌ์ง€ํ† ๋ฆฌ์˜ tsconfig์—์„œ ์ง์ ‘ ํ•ด๋‹น ๊ฒฝ๋กœ์˜ d.ts ํŒŒ์ผ์„ includeํ•˜๋„๋ก ์„ค์ •ํ•ด์ฃผ์–ด์„œ ํ•ด๊ฒฐํ–ˆ๋‹ค.

 

Storybook ์„ค์ •

yarn dlx sb init --builder webpack5

storybook์„ ๋‹ค์šด๋ฐ›๋Š”๋‹ค. webpack5๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„  ๋ณ„๋„์˜ ์„ค์ •์ด ํ•„์š”ํ•˜๋‹ค.

 

 //...
"resolutions": {
    "webpack": "5",
    "@storybook/core-common/webpack": "^5",
    "@storybook/core-server/webpack": "^5",
    "@storybook/react/webpack": "^5"
  },

๋ฃจํŠธ์˜ package.json์—์„œ resolution์— ์ถ”๊ฐ€๋ฅผ ํ•ด์ค€๋‹ค. ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์˜์กดํ•˜๊ณ  ์žˆ๋Š” ๋‹ค๋ฅธ ํŒจํ‚ค์ง€๋“ค์˜ ๋ฒ„์ „์„ ๊ณ ์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ทผ๋ฐ ์™œ webpack5๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๊ฑฐ์ง€... ์†”์งํ•˜๊ฒŒ ๋งํ•˜๋ฉด ์šฐ์•„ํ•œ ๋ธ”๋กœ๊ทธ์—์„œ 5๋ฅผ ์‚ฌ์šฉํ•˜๊ธธ๋ž˜...

 

code-workspace

ํ”„๋กœ์ ํŠธ์˜ ๋ฃจํŠธ๋กœ ๋“ค์–ด๊ฐ”์„๋•Œ ๊ฐ–๊ฐ€์ง€ ์„ค์ •ํŒŒ์ผ๋“ค์ด ๋งŽ์•„์ง€๊ณ , ๋ถˆํ•„์š”ํ•œ ๋””๋ ‰ํ† ๋ฆฌ๋“ค ๋•Œ๋ฌธ์— ์ž‘์—…ํ•˜๊ธฐ ๋ถˆํŽธํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. ํ˜„์žฌ ๋‚ด๊ฐ€ ์ž‘์—…ํ•˜๊ณ ์žˆ๋Š” ๋ถ€๋ถ„์€ admin์ธ๋ฐ๋„ ticket ๋ถ€๋ถ„๊นŒ์ง€ ํƒ์ƒ‰๊ธฐ์— ์ซ™ ์žˆ์„ ํ•„์š”๋Š” ์—†๋‹ค.

//set-admin.code-workspaces
{
  "folders": [
    {
      "path": "apps/admin"
    },
    {
      "path": "shared"
    }
  ]
}

 

์ด๋ ‡๊ฒŒ .code-workspaces ํŒŒ์ผ์„ ๋ฃจํŠธ์— ๋งŒ๋“ค์–ด์ฃผ๋ฉด, vscode์—์„œ ์ž‘์—…๊ณต๊ฐ„์„ ์„ค์ •ํ•ด ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋‹ค.

 

 

 


 

๋‹ค์Œ (3ํŽธ)์—์„œ๋Š” ๋ชจ๋…ธ๋ ˆํฌ ์„œ๋น„์Šค ๋ฐฐํฌ์™€ CICD ๊ตฌ์ถ• ์‚ฝ์งˆ๊ธฐ๋กœ!