DM Log

[AI 프로젝트 #0] 초기 세팅 #4: 공용 패키지(UI, 스타일, 유틸) 구조 설계 본문

PJT/AI PJT

[AI 프로젝트 #0] 초기 세팅 #4: 공용 패키지(UI, 스타일, 유틸) 구조 설계

Dev. Dong 2025. 10. 13. 23:17

서론

 

  • 모노레포(Monorepo) 환경의 핵심은 공용 패키지를 통한 중복 제거
  • AI 프로젝트가 확장될수록 PDF, Word, Excel 등 각 앱마다 공통 UI, 스타일, 유틸 함수가 반복되기 쉬운데, 이를 효율적으로 관리하기 위해 packages/ 디렉토리를 구성
  • packages/ui, packages/styles, packages/utils 구조 설계 정리
  • 각 앱에서 import하여 재사용하는 방법으로 사용

프로젝트 디렉토리 구조

frontend/
 ├─ apps/
 │   └─ pdf/
 │       ├─ src/
 │       │   ├─ App.tsx
 │       │   ├─ main.tsx
 │       │   ├─ components/
 │       │   │   └─ PdfUploader.tsx
 │       │   └─ pages/
 │       │       └─ Home.tsx
 │       ├─ index.html
 │       ├─ package.json
 │       ├─ tsconfig.json
 │       ├─ vite.config.ts
 │       └─ .env
 │
 ├─ packages/
 │   ├─ ui/
 │   │   ├─ src/
 │   │   │   ├─ Button.tsx
 │   │   │   └─ index.ts
 │   │   ├─ tsconfig.json
 │   │   └─ package.json
 │   │
 │   ├─ styles/
 │   │   ├─ src/
 │   │   │   ├─ theme.ts
 │   │   │   ├─ GlobalStyle.tsx
 │   │   │   └─ index.ts
 │   │   ├─ tsconfig.json
 │   │   └─ package.json
 │   │
 │   └─ utils/
 │       ├─ src/
 │       │   ├─ api.ts
 │       │   └─ index.ts
 │       ├─ tsconfig.json
 │       └─ package.json
 │
 ├─ turbo.json
 ├─ package.json
 ├─ tsconfig.base.json
 └─ .gitignore

UI 패키지 (packages/ui)

예시:  Button 컴포넌트

// packages/ui/src/Button.tsx
import React from "react";

interface ButtonProps {
  label: string;
  onClick?: () => void;
}

export const Button: React.FC<ButtonProps> = ({ label, onClick }) => {
  return (
    <button
      onClick={onClick}
      style={{
        background: "#2c2c2c",
        color: "#fff",
        padding: "8px 16px",
        borderRadius: "8px",
        border: "none",
        cursor: "pointer"
      }}
    >
      {label}
    </button>
  );
};

 

예시: index.ts

export * from "./Button";

Styles 패키지 (packages/styles)

  • Emotion  기반 글로벌 스타일과 테마 관리하여 프로젝트 설정
  • Emotion은 모든 앱과 패키지에서 공용으로 사용할 예정이므로 루트(frontend/) 기준에서 한 번만 설치
cd frontend
npm install @emotion/react @emotion/styled

 

예시: theme.ts

// packages/styles/src/theme.ts
export const theme = {
  colors: {
    primary: "#4a90e2",
    background: "#121212",
    text: "#ffffff"
  },
  font: {
    family: "Pretendard, sans-serif",
    size: {
      small: "12px",
      medium: "14px",
      large: "16px"
    }
  }
};

예시: GlobalStyle.tsx

// frontend/packages/styles/src/GlobalStyle.tsx
/** @jsxImportSource @emotion/react */
import { Global, css } from "@emotion/react";
import { theme } from "styles";

export const GlobalStyle = () => (
  <Global
    styles={css`
      body {
        background-color: ${theme.colors.background};
        color: ${theme.colors.text};
        font-family: ${theme.font.family};
      }
    `}
  />
);

Utils 패키지 (packages/utils)

  • 공용 함수 등을 정의
// frontend/packages/utils/src/api.ts
export const apiRequest = async (url: string, options?: RequestInit) => {
  const response = await fetch(url, options);
  if (!response.ok) throw new Error(`API Error: ${response.status}`);
  return response.json();
};

설정 예시 (.turbo/config.json):


Turborepo 의존 관계 자동 처리

  • 빌드 시 Turbo 각 패키지의 종속 관계를 인식하여 자동 순서 빌드
// frontend/turbo.json
{
  "tasks": {
    "dev": {
      "dependsOn": ["^dev"],
      "cache": false
    },
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    }
  }
}

Package의 package.json / tsconfig.json 생성

예시: package.json 

{
  "name": "styles",
  "version": "1.0.0",
  "main": "src/index.ts",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "private": true,
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": ""
}

 

예시: tsconfig.json

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "dist",
    "composite": true,
    "declaration": true
  },
  "include": ["src"]
}

 

 

예시: tsconfig.base.json

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "Node",
    "jsx": "react-jsx",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "baseUrl": "."
  }
}