본문 바로가기

테스트 플렛폼

Monorepo 멀티 React 버전 개발환경 구성 가이드

반응형
enterprise-monorepo/
├── .github/
│   └── workflows/
│       ├── ci-cd-crm.yml
│       ├── ci-cd-wms.yml
│       └── ci-cd-edi.yml
├── apps/
│   ├── crm-system/          # React 17
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   ├── webpack.config.js
│   │   ├── src/
│   │   └── public/
│   ├── wms-system/          # React 18
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   ├── vite.config.ts
│   │   ├── src/
│   │   └── public/
│   └── edi-system/          # React 19
│       ├── package.json
│       ├── tsconfig.json
│       ├── vite.config.ts
│       ├── src/
│       └── public/
├── packages/
│   ├── shared-ui/           # 공통 UI 컴포넌트
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── src/
│   ├── shared-utils/        # 공통 유틸리티
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── src/
│   └── shared-types/        # 공통 타입 정의
│       ├── package.json
│       ├── tsconfig.json
│       └── src/
├── backend/
│   ├── crm-api/            # Spring Boot + iBATIS
│   │   ├── pom.xml
│   │   └── src/
│   ├── wms-api/
│   │   ├── pom.xml
│   │   └── src/
│   ├── edi-api/
│   │   ├── pom.xml
│   │   └── src/
│   └── shared/
│       ├── common/
│       └── database/
├── tools/
│   ├── build-scripts/
│   └── docker/
├── docs/
├── package.json             # Root package.json
├── pnpm-workspace.yaml
├── tsconfig.base.json
└── .env.example

 

1. 환경 설정

Node.js 버전 관리 (Windows)

powershell# Chocolatey 설치 (관리자 권한으로 PowerShell 실행)
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))

# Node.js 및 도구 설치
choco install nodejs-lts
choco install git
choco install openjdk17
choco install maven

# pnpm 설치 (권장)
npm install -g pnpm

# 또는 Yarn 설치
npm install -g yarn

# nvm-windows 설치 (선택사항)
choco install nvm

 

MariaDB 설치 및 설정

powershell# MariaDB 설치
choco install mariadb

# 서비스 시작
net start MySQL

# 데이터베이스 생성
mysql -u root -p

 

 

-- 데이터베이스 생성
CREATE DATABASE enterprise_crm CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE enterprise_wms CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE enterprise_edi CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 사용자 생성 및 권한 부여
CREATE USER 'enterprise_user'@'localhost' IDENTIFIED BY 'enterprise_password';
GRANT ALL PRIVILEGES ON enterprise_*.* TO 'enterprise_user'@'localhost';
FLUSH PRIVILEGES;

 

2. Root 설정 파일들

package.json (Root)

{
  "name": "enterprise-monorepo",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "pnpm run --parallel dev",
    "dev:crm": "pnpm --filter crm-system dev",
    "dev:wms": "pnpm --filter wms-system dev",
    "dev:edi": "pnpm --filter edi-system dev",
    "build": "pnpm run --filter './apps/*' build",
    "build:crm": "pnpm --filter crm-system build",
    "build:wms": "pnpm --filter wms-system build",
    "build:edi": "pnpm --filter edi-system build",
    "test": "pnpm run --recursive test",
    "lint": "pnpm run --recursive lint",
    "type-check": "pnpm run --recursive type-check",
    "clean": "pnpm run --recursive clean",
    "backend:dev": "concurrently \"cd backend/crm-api && mvn spring-boot:run\" \"cd backend/wms-api && mvn spring-boot:run\" \"cd backend/edi-api && mvn spring-boot:run\"",
    "backend:build": "cd backend && mvn clean install",
    "docker:build": "docker-compose build",
    "docker:up": "docker-compose up -d",
    "docker:down": "docker-compose down"
  },
  "devDependencies": {
    "concurrently": "^8.2.2",
    "cross-env": "^7.0.3",
    "husky": "^8.0.3",
    "lint-staged": "^15.2.0",
    "typescript": "^5.3.3"
  },
  "engines": {
    "node": ">=18.0.0",
    "pnpm": ">=8.0.0"
  }
}

 

pnpm-workspace.yaml

packages:
  - 'apps/*'
  - 'packages/*'
  - 'backend/*'

 

tsconfig.base.json

{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["dom", "dom.iterable", "ES6"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "baseUrl": ".",
    "paths": {
      "@shared/ui": ["packages/shared-ui/src"],
      "@shared/utils": ["packages/shared-utils/src"],
      "@shared/types": ["packages/shared-types/src"]
    }
  },
  "exclude": ["node_modules", "**/node_modules/**", "dist", "**/dist/**"]
}

 

3. 공통 패키지 설정

packages/shared-types/package.json

{
  "name": "@enterprise/shared-types",
  "version": "1.0.0",
  "private": true,
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "scripts": {
    "build": "tsc --build",
    "clean": "rimraf dist",
    "type-check": "tsc --noEmit"
  },
  "devDependencies": {
    "rimraf": "^5.0.5",
    "typescript": "^5.3.3"
  },
  "peerDependencies": {
    "typescript": ">=5.0.0"
  }
}

 

packages/shared-ui/package.json

{
  "name": "@enterprise/shared-ui",
  "version": "1.0.0",
  "private": true,
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "scripts": {
    "build": "rollup -c",
    "dev": "rollup -c -w",
    "clean": "rimraf dist",
    "type-check": "tsc --noEmit",
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build"
  },
  "dependencies": {
    "@enterprise/shared-types": "workspace:*",
    "@enterprise/shared-utils": "workspace:*"
  },
  "devDependencies": {
    "@rollup/plugin-commonjs": "^25.0.7",
    "@rollup/plugin-node-resolve": "^15.2.3",
    "@rollup/plugin-typescript": "^11.1.5",
    "@storybook/addon-essentials": "^7.6.6",
    "@storybook/react": "^7.6.6",
    "@storybook/react-vite": "^7.6.6",
    "@types/react": "^18.2.45",
    "rimraf": "^5.0.5",
    "rollup": "^4.9.1",
    "storybook": "^7.6.6",
    "typescript": "^5.3.3"
  },
  "peerDependencies": {
    "react": ">=17.0.0"
  }
}

 

4. 각 앱별 설정

CRM 시스템 (React 17)

apps/crm-system/package.json

{
  "name": "crm-system",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "webpack serve --mode development",
    "build": "cross-env NODE_ENV=production webpack --mode production",
    "test": "jest",
    "lint": "eslint src --ext .ts,.tsx",
    "type-check": "tsc --noEmit",
    "clean": "rimraf dist"
  },
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-router-dom": "^6.2.1",
    "@enterprise/shared-ui": "workspace:*",
    "@enterprise/shared-utils": "workspace:*",
    "@enterprise/shared-types": "workspace:*",
    "axios": "^1.6.2",
    "antd": "^4.24.15"
  },
  "devDependencies": {
    "@types/react": "^17.0.52",
    "@types/react-dom": "^17.0.18",
    "typescript": "^5.3.3",
    "webpack": "^5.89.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.15.1",
    "html-webpack-plugin": "^5.6.0",
    "ts-loader": "^9.5.1",
    "css-loader": "^6.8.1",
    "style-loader": "^3.3.3",
    "cross-env": "^7.0.3",
    "rimraf": "^5.0.5"
  }
}

 

4. 각 앱별 설정

CRM 시스템 (React 17)

apps/crm-system/package.json

{
  "name": "crm-system",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "webpack serve --mode development",
    "build": "cross-env NODE_ENV=production webpack --mode production",
    "test": "jest",
    "lint": "eslint src --ext .ts,.tsx",
    "type-check": "tsc --noEmit",
    "clean": "rimraf dist"
  },
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-router-dom": "^6.2.1",
    "@enterprise/shared-ui": "workspace:*",
    "@enterprise/shared-utils": "workspace:*",
    "@enterprise/shared-types": "workspace:*",
    "axios": "^1.6.2",
    "antd": "^4.24.15"
  },
  "devDependencies": {
    "@types/react": "^17.0.52",
    "@types/react-dom": "^17.0.18",
    "typescript": "^5.3.3",
    "webpack": "^5.89.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.15.1",
    "html-webpack-plugin": "^5.6.0",
    "ts-loader": "^9.5.1",
    "css-loader": "^6.8.1",
    "style-loader": "^3.3.3",
    "cross-env": "^7.0.3",
    "rimraf": "^5.0.5"
  }
}

 

apps/crm-system/webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

module.exports = {
  entry: './src/index.tsx',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.[contenthash].js',
    clean: true,
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx'],
    alias: {
      '@shared/ui': path.resolve(__dirname, '../../packages/shared-ui/src'),
      '@shared/utils': path.resolve(__dirname, '../../packages/shared-utils/src'),
      '@shared/types': path.resolve(__dirname, '../../packages/shared-types/src'),
    },
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
  devServer: {
    port: 3001,
    open: true,
    historyApiFallback: true,
  },
};

 

WMS 시스템 (React 18)

apps/wms-system/package.json

{
  "name": "wms-system",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "test": "vitest",
    "lint": "eslint src --ext .ts,.tsx",
    "type-check": "tsc --noEmit",
    "clean": "rimraf dist"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.8.0",
    "@enterprise/shared-ui": "workspace:*",
    "@enterprise/shared-utils": "workspace:*",
    "@enterprise/shared-types": "workspace:*",
    "axios": "^1.6.2",
    "@mui/material": "^5.15.1",
    "@emotion/react": "^11.11.1",
    "@emotion/styled": "^11.11.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.45",
    "@types/react-dom": "^18.2.18",
    "@vitejs/plugin-react": "^4.2.1",
    "typescript": "^5.3.3",
    "vite": "^5.0.10",
    "vitest": "^1.1.0",
    "cross-env": "^7.0.3",
    "rimraf": "^5.0.5"
  }
}

 

WMS 시스템 (React 18)

apps/wms-system/package.json

{
  "name": "wms-system",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "test": "vitest",
    "lint": "eslint src --ext .ts,.tsx",
    "type-check": "tsc --noEmit",
    "clean": "rimraf dist"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.8.0",
    "@enterprise/shared-ui": "workspace:*",
    "@enterprise/shared-utils": "workspace:*",
    "@enterprise/shared-types": "workspace:*",
    "axios": "^1.6.2",
    "@mui/material": "^5.15.1",
    "@emotion/react": "^11.11.1",
    "@emotion/styled": "^11.11.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.45",
    "@types/react-dom": "^18.2.18",
    "@vitejs/plugin-react": "^4.2.1",
    "typescript": "^5.3.3",
    "vite": "^5.0.10",
    "vitest": "^1.1.0",
    "cross-env": "^7.0.3",
    "rimraf": "^5.0.5"
  }
}

 

apps/wms-system/vite.config.ts

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@shared/ui': path.resolve(__dirname, '../../packages/shared-ui/src'),
      '@shared/utils': path.resolve(__dirname, '../../packages/shared-utils/src'),
      '@shared/types': path.resolve(__dirname, '../../packages/shared-types/src'),
    },
  },
  server: {
    port: 3002,
    open: true,
  },
  build: {
    outDir: 'dist',
  },
});

 

EDI 시스템 (React 19)

apps/edi-system/package.json

{
  "name": "edi-system",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "test": "vitest",
    "lint": "eslint src --ext .ts,.tsx",
    "type-check": "tsc --noEmit",
    "clean": "rimraf dist"
  },
  "dependencies": {
    "react": "^19.0.0-rc.0",
    "react-dom": "^19.0.0-rc.0",
    "react-router-dom": "^6.20.0",
    "@enterprise/shared-ui": "workspace:*",
    "@enterprise/shared-utils": "workspace:*",
    "@enterprise/shared-types": "workspace:*",
    "axios": "^1.6.2",
    "@chakra-ui/react": "^2.8.2",
    "@emotion/react": "^11.11.1",
    "@emotion/styled": "^11.11.0"
  },
  "devDependencies": {
    "@types/react": "npm:types-react@rc",
    "@types/react-dom": "npm:types-react-dom@rc",
    "@vitejs/plugin-react": "^4.2.1",
    "typescript": "^5.3.3",
    "vite": "^5.0.10",
    "vitest": "^1.1.0",
    "cross-env": "^7.0.3",
    "rimraf": "^5.0.5"
  },
  "overrides": {
    "@types/react": "npm:types-react@rc",
    "@types/react-dom": "npm:types-react-dom@rc"
  }
}

 

5. 백엔드 설정

backend/crm-api/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.1</version>
        <relativePath/>
    </parent>
    
    <groupId>com.enterprise</groupId>
    <artifactId>crm-api</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    
    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <mybatis.version>3.0.3</mybatis.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
        
        <dependency>
            <groupId>org.mariadb.jdbc</groupId>
            <artifactId>mariadb-java-client</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

 

backend/crm-api/src/main/resources/application.yml

server:
  port: 8081
  servlet:
    context-path: /api/crm

spring:
  datasource:
    driver-class-name: org.mariadb.jdbc.Driver
    url: jdbc:mariadb://localhost:3306/enterprise_crm
    username: enterprise_user
    password: enterprise_password
  
  security:
    cors:
      allowed-origins: "http://localhost:3001"
      allowed-methods: "*"
      allowed-headers: "*"
      allow-credentials: true

mybatis:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
    
logging:
  level:
    com.enterprise: DEBUG
    org.mybatis: DEBUG

 

backend/crm-api/src/main/resources/application.yml

server:
  port: 8081
  servlet:
    context-path: /api/crm

spring:
  datasource:
    driver-class-name: org.mariadb.jdbc.Driver
    url: jdbc:mariadb://localhost:3306/enterprise_crm
    username: enterprise_user
    password: enterprise_password
  
  security:
    cors:
      allowed-origins: "http://localhost:3001"
      allowed-methods: "*"
      allowed-headers: "*"
      allow-credentials: true

mybatis:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
    
logging:
  level:
    com.enterprise: DEBUG
    org.mybatis: DEBUG

 

6. Docker 설정

docker-compose.yml

version: '3.8'

services:
  mariadb:
    image: mariadb:10.11
    container_name: enterprise-db
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: enterprise_crm
      MYSQL_USER: enterprise_user
      MYSQL_PASSWORD: enterprise_password
    ports:
      - "3306:3306"
    volumes:
      - mariadb_data:/var/lib/mysql
      - ./database/init:/docker-entrypoint-initdb.d
    networks:
      - enterprise-network

  crm-api:
    build:
      context: ./backend/crm-api
      dockerfile: Dockerfile
    container_name: crm-api
    ports:
      - "8081:8081"
    depends_on:
      - mariadb
    environment:
      SPRING_DATASOURCE_URL: jdbc:mariadb://mariadb:3306/enterprise_crm
      SPRING_DATASOURCE_USERNAME: enterprise_user
      SPRING_DATASOURCE_PASSWORD: enterprise_password
    networks:
      - enterprise-network

  wms-api:
    build:
      context: ./backend/wms-api
      dockerfile: Dockerfile
    container_name: wms-api
    ports:
      - "8082:8082"
    depends_on:
      - mariadb
    environment:
      SPRING_DATASOURCE_URL: jdbc:mariadb://mariadb:3306/enterprise_wms
    networks:
      - enterprise-network

  edi-api:
    build:
      context: ./backend/edi-api
      dockerfile: Dockerfile
    container_name: edi-api
    ports:
      - "8083:8083"
    depends_on:
      - mariadb
    environment:
      SPRING_DATASOURCE_URL: jdbc:mariadb://mariadb:3306/enterprise_edi
    networks:
      - enterprise-network

  nginx:
    image: nginx:alpine
    container_name: enterprise-nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/ssl:/etc/nginx/ssl
    depends_on:
      - crm-api
      - wms-api
      - edi-api
    networks:
      - enterprise-network

volumes:
  mariadb_data:

networks:
  enterprise-network:
    driver: bridge

 

7. 개발 환경 실행 명령어

전체 개발 환경 시작

# 1. 의존성 설치
pnpm install

# 2. 백엔드 시작 (별도 터미널)
pnpm run backend:dev

# 3. 프론트엔드 개발 서버 시작 (별도 터미널들)
pnpm run dev:crm    # http://localhost:3001
pnpm run dev:wms    # http://localhost:3002
pnpm run dev:edi    # http://localhost:3003

# 또는 모든 프론트엔드 동시 시작
pnpm run dev

 

개별 앱 개발

# CRM 시스템만 개발
cd apps/crm-system
pnpm dev

# WMS 시스템만 개발
cd apps/wms-system
pnpm dev

# EDI 시스템만 개발
cd apps/edi-system
pnpm dev

 

빌드 및 배포

# 전체 빌드
pnpm run build

# 개별 빌드
pnpm run build:crm
pnpm run build:wms
pnpm run build:edi

# Docker로 배포
pnpm run docker:build
pnpm run docker:up

 

8. 주요 특징 및 장점

React 버전 격리

  • 각 앱이 독립적인 package.json을 가져 서로 다른 React 버전 사용 가능
  • pnpm의 workspace 기능으로 의존성 충돌 방지
  • 공통 패키지는 peerDependencies로 React 버전에 유연하게 대응

코드 공유

  • @enterprise/shared-* 패키지를 통한 공통 코드 재사용
  • TypeScript 경로 매핑으로 편리한 임포트
  • 각 앱에서 필요한 공통 컴포넌트만 선택적 사용

독립적인 빌드 도구

  • CRM: Webpack (레거시 지원)
  • WMS: Vite (최신 개발 경험)
  • EDI: Vite with React 19 (최신 기능)

통합 백엔드 관리

  • Spring Boot 기반 마이크로서비스
  • iBATIS/MyBatis ORM 사용
  • 각 시스템별 독립적인 데이터베이스

9. 트러블슈팅

Node.js 버전 충돌

# .nvmrc 파일 생성
echo "18.19.0" > .nvmrc

# 프로젝트별 Node.js 버전 사용
nvm use

 

TypeScript 타입 충돌

// tsconfig.json에서 경로 매핑 확인
{
  "compilerOptions": {
    "skipLibCheck": true,
    "paths": {
      "@shared/*": ["packages/shared-*/src"]
    }
  }
}

 

Hot Reload 문제

// webpack.config.js 또는 vite.config.ts에서 설정
module.exports = {
  watchOptions: {
    poll: 1000,
    aggregateTimeout: 300,
  }
};
반응형