React, Typescript 환경에서 하나의 프로젝트에서 여러 시스템을 사용하고 공유 컴퍼넌트를 사용하고 다른 시스템의 기능을 사용 할 수 있도록 구성
React, Typescript 환경에서 하나의 프로젝트에서 여러 시스템을 사용하고 공유 컴퍼넌트를 사용하고
다른 시스템의 기능을 사용 할 수 있도록 구성해 주세요.
프로젝트 구조 설계
/monorepo-root
/packages
/shared-components # 여러 시스템에서 사용하는 공통 컴포넌트
/system-a # 시스템 A
/system-b # 시스템 B
package.json
tsconfig.json
...
monorepo 구조로 여러 시스템과 공유 컴포넌트를 하나의 저장소에서 관리
Yarn Workspaces, pnpm workspace, 또는 Lerna 등으로 패키지 관리를 추천
Monorepo 및 패키지 관리 설정
Yarn Workspaces 예시
package.json 최상위에 다음 추가
{
"private": true,
"workspaces": [
"packages/*"
]
}
각 패키지는 독립적인 React + TypeScript 프로젝트
공통 컴포넌트 패키지 설정 (shared-components)
packages/shared-components에 React 컴포넌트 작성
package.json에 React, TypeScript 등 peerDependencies 설정
예: Button.tsx
import React from 'react';
interface ButtonProps {
label: string;
onClick?: () => void;
}
export const Button: React.FC<ButtonProps> = ({ label, onClick }) => (
<button onClick={onClick}>{label}</button>
);
각 시스템 패키지 설정 (system-a, system-b)
React 앱으로 구성
shared-components를 의존성으로 추가 (workspace로 연결)
예: system-a/package.jso
{
"name": "system-a",
"version": "1.0.0",
"dependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0",
"shared-components": "*"
}
}
system-a에서 shared-components 사용 가능
import React from 'react';
import { Button } from 'shared-components';
export const SystemAComponent = () => (
<div>
<h1>System A</h1>
<Button label="Click me" onClick={() => alert('Clicked!')} />
</div>
);
시스템 간 기능 공유 방법
1) 공통 유틸 함수, 서비스 공유
shared-utils 같은 별도 패키지로 분리
각 시스템에서 import하여 사용
2) 이벤트 버스 또는 상태 공유
zustand, redux 같은 글로벌 상태 관리 라이브러리를 공유 패키지로 설정 후 시스템 간 상태 공유
또는 React Context를 최상위에서 감싸기
3) 마이크로 프론트엔드 (Micro Frontends)
Webpack Module Federation 활용
각 시스템을 독립 배포하지만 런타임에 컴포넌트/모듈을 주입 가능
Webpack Module Federation 기본 예시
system-a의 webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
// ...
plugins: [
new ModuleFederationPlugin({
name: 'system_a',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button', // system-a에서 expose할 컴포넌트
},
shared: ['react', 'react-dom'],
}),
],
};
요약
Monorepo로 여러 시스템과 공유 컴포넌트 관리
Yarn Workspaces 등으로 패키지 의존성 관리
공통 컴포넌트는 별도 패키지로 분리해 재사용
시스템 간 기능 공유는 공통 패키지 또는 Module Federation 활용
상태 공유가 필요하면 글로벌 상태 관리 도구 사용
React + TypeScript 멀티 시스템 아키텍처 구성
하나의 프로젝트에서 여러 시스템을 운영하면서 공통 컴포넌트와 기능을 공유하는 아키텍처를 구성해보겠습니다.
1. 프로젝트 구조 제안
src/
├── apps/ # 각 시스템별 앱
│ ├── system-a/ # 시스템 A
│ ├── system-b/ # 시스템 B
│ └── system-c/ # 시스템 C
├── common/ # 공통 코드
│ ├── components/ # 공유 컴포넌트
│ ├── hooks/ # 공통 훅
│ ├── utils/ # 유틸리티 함수
│ ├── services/ # API 서비스
│ ├── types/ # 공통 타입 정의
│ └── contexts/ # 공통 컨텍스트
├── libs/ # 외부 라이브러리 확장/설정
└── routes/ # 라우팅 설정 (필요시)
2. 공통 컴포넌트 구현 예시
src/common/components/Button.tsx
import React from 'react';
type ButtonVariant = 'primary' | 'secondary' | 'danger';
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant;
isLoading?: boolean;
}
const Button: React.FC<ButtonProps> = ({
variant = 'primary',
isLoading = false,
children,
className = '',
...props
}) => {
const baseClasses = 'px-4 py-2 rounded-md font-medium transition-colors';
const variantClasses = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
danger: 'bg-red-600 text-white hover:bg-red-700',
};
return (
<button
className={`${baseClasses} ${variantClasses[variant]} ${className}`}
disabled={isLoading || props.disabled}
{...props}
>
{isLoading ? 'Loading...' : children}
</button>
);
};
export default Button;
3. 시스템 간 기능 공유을 위한 서비스 레이어
src/common/services/api.ts
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
class ApiService {
private static instance: AxiosInstance;
static getInstance(): AxiosInstance {
if (!ApiService.instance) {
ApiService.instance = axios.create({
baseURL: process.env.REACT_APP_API_BASE_URL,
timeout: 10000,
});
}
return ApiService.instance;
}
static async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
const response = await ApiService.getInstance().get<T>(url, config);
return response.data;
}
static async post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
const response = await ApiService.getInstance().post<T>(url, data, config);
return response.data;
}
// 다른 HTTP 메소드들도 추가 가능
}
export default ApiService;
4. 시스템 간 상태 공유를 위한 컨텍스트
src/common/contexts/AuthContext.tsx
import React, { createContext, useContext, useState, useEffect } from 'react';
interface AuthContextType {
isAuthenticated: boolean;
user: User | null;
login: (token: string, userData: User) => void;
logout: () => void;
}
interface User {
id: string;
name: string;
email: string;
roles: string[];
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
// 초기 인증 상태 확인 (예: localStorage에서 토큰 확인)
const token = localStorage.getItem('authToken');
const userData = localStorage.getItem('userData');
if (token && userData) {
setIsAuthenticated(true);
setUser(JSON.parse(userData));
}
}, []);
const login = (token: string, userData: User) => {
localStorage.setItem('authToken', token);
localStorage.setItem('userData', JSON.stringify(userData));
setIsAuthenticated(true);
setUser(userData);
};
const logout = () => {
localStorage.removeItem('authToken');
localStorage.removeItem('userData');
setIsAuthenticated(false);
setUser(null);
};
return (
<AuthContext.Provider value={{ isAuthenticated, user, login, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
5. 시스템별 앱 구성 예시
src/apps/system-a/App.tsx
import React from 'react';
import { AuthProvider } from '../../common/contexts/AuthContext';
import Button from '../../common/components/Button';
import SystemARoutes from './routes';
const SystemAApp: React.FC = () => {
return (
<AuthProvider>
<div className="system-a">
<header>
<h1>System A</h1>
<Button variant="primary" onClick={() => console.log('System A action')}>
System A Button
</Button>
</header>
<main>
<SystemARoutes />
</main>
</div>
</AuthProvider>
);
};
export default SystemAApp;
6. 모노레포 구성 (선택사항)
여러 시스템을 완전히 분리하고 싶다면 모노레포 구조를 고려할 수 있습니다.
package.json에 workspaces 설정
{
"name": "my-mono-repo",
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"start:a": "cd packages/system-a && npm start",
"start:b": "cd packages/system-b && npm start",
"build:common": "cd packages/common && npm run build",
"build:a": "npm run build:common && cd packages/system-a && npm run build",
"build:b": "npm run build:common && cd packages/system-b && npm run build"
}
}
7. 타입 공유를 위한 중앙 집중식 관리
src/common/types/index.ts
// 공통 응답 타입
export interface ApiResponse<T> {
data: T;
message?: string;
success: boolean;
}
// 사용자 타입
export interface User {
id: string;
name: string;
email: string;
roles: string[];
}
// 시스템 간 공유할 수 있는 다른 타입들...
8. 시스템 간 컴포넌트 확장 패턴
import React from 'react';
import BaseButton, { ButtonProps as BaseButtonProps } from '../../common/components/Button';
interface ExtendedButtonProps extends BaseButtonProps {
icon?: React.ReactNode;
}
const SystemAButton: React.FC<ExtendedButtonProps> = ({ icon, children, ...props }) => {
return (
<BaseButton {...props}>
{icon && <span className="mr-2">{icon}</span>}
{children}
</BaseButton>
);
};
export default SystemAButton;
9. 빌드 및 배포 전략
- 공통 코드: common 디렉토리는 별도로 빌드하여 패키지로 관리하거나 프로젝트 내에서 직접 참조
- 시스템별 앱: 각 시스템은 독립적으로 빌드 가능하도록 구성
- 환경 변수: .env 파일을 시스템별로 분리하여 관리
.env.system-a
.env.system-b
10. 주의사항
- 의존성 관리: 시스템 간에 너무 강한 결합이 생기지 않도록 주의
- 버전 관리: 공통 코드 변경 시 모든 시스템에 미치는 영향 고려
- 성능: 사용하지 않는 시스템의 코드가 번들에 포함되지 않도록 동적 임포트 고려
- 테스트: 공통 코드는 더 엄격한 테스트 필요
이 구조는 시스템 간 코드 공유를 최대화하면서도 각 시스템의 독립성을 유지할 수 있도록 도와줍니다. 프로젝트 규모와 요구사항에 따라 적절히 조정하여 사용하시면 됩니다.
1. 프로젝트 초기 설정
# 프로젝트 디렉토리 생성 및 이동
mkdir my-monorepo
cd my-monorepo
# Yarn Berry (Yarn 2) 설치
yarn set version berry
# 워크스페이스 초기화
yarn init -2
2. 워크스페이스 설정
{
"private": true,
"workspaces": [
"packages/*"
]
}
3. 패키지 생성
# 패키지 디렉토리 생성
mkdir -p packages/system-a
mkdir -p packages/system-b
mkdir -p packages/shared-components
# 각 패키지 초기화
cd packages/system-a
yarn init -y
cd ../system-b
yarn init -y
cd ../shared-components
yarn init -y
4. TypeScript 설정
# 패키지 디렉토리 생성
mkdir -p packages/system-a
mkdir -p packages/system-b
mkdir -p packages/shared-components
# 각 패키지 초기화
cd packages/system-a
yarn init -y
cd ../system-b
yarn init -y
cd ../shared-components
yarn init -y
5. React 설정
# React 및 타입 설치
yarn workspace system-a add react react-dom @types/react @types/react-dom
yarn workspace system-b add react react-dom @types/react @types/react-dom
yarn workspace shared-components add react react-dom @types/react @types/react-dom
6. 공유 컴포넌트 작성
# src 디렉토리 생성
mkdir -p packages/shared-components/src
# Button 컴포넌트 작성
echo 'import React from "react";
interface ButtonProps {
label: string;
onClick: () => void;
}
const Button: React.FC<ButtonProps> = ({ label, onClick }) => {
return <button onClick={onClick}>{label}</button>;
};
export default Button;' > packages/shared-components/src/Button.tsx
7. 시스템에서 공유 컴포넌트 사용
# src 디렉토리 생성
mkdir -p packages/system-a/src
mkdir -p packages/system-b/src
# App.tsx 파일 작성
echo 'import React from "react";
import Button from "@shared/Button";
const App: React.FC = () => {
return (
<div>
<h1>System A</h1>
<Button label="Click Me" onClick={() => alert("Button clicked!")} />
</div>
);
};
export default App;' > packages/system-a/src/App.tsx
cp packages/system-a/src/App.tsx packages/system-b/src/App.tsx
8. 시스템 빌드 및 실행
# Webpack 및 관련 패키지 설치
yarn workspace system-a add webpack webpack-cli ts-loader html-webpack-plugin
yarn workspace system-b add webpack webpack-cli ts-loader html-webpack-plugin
# Webpack 설정 파일 작성
echo 'const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/App.tsx",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist"),
},
resolve: {
extensions: [".ts", ".tsx", ".js"],
},
module: {
rules: [
{
test: /.tsx?$/,
use: "ts-loader",
exclude: /node_modules/,
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
],
};' > packages/system-a/webpack.config.js
cp packages/system-a/webpack.config.js packages/system-b/webpack.config.js
# index.html 파일 작성
echo '<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>System A</title>
</head>
<body>
<div id="root"></div>
</body>
</html>' > packages/system-a/src/index.html
cp packages/system-a/src/index.html packages/system-b/src/index.html
# 빌드 및 실행
yarn workspace system-a webpack serve
yarn workspace system-b webpack serve
1. 프로젝트 초기 설정
# 프로젝트 디렉토리 생성 및 이동
mkdir my-monorepo
cd my-monorepo
# Yarn 초기화
yarn init -y
# 워크스페이스 설정
echo '{
"private": true,
"workspaces": [
"packages/*"
]
}' > package.json
2. 패키지 생성
# 패키지 디렉토리 생성
mkdir -p packages/system-a
mkdir -p packages/system-b
mkdir -p packages/shared-components
# 각 패키지 초기화
cd packages/system-a
yarn init -y
cd ../system-b
yarn init -y
cd ../shared-components
yarn init -y
3. TypeScript 설정
# TypeScript 설치
yarn workspace system-a add typescript
yarn workspace system-b add typescript
yarn workspace shared-components add typescript
# tsconfig.json 파일 생성 및 설정
echo '{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"jsx": "react",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": "./",
"paths": {
"@shared/*": ["../shared-components/src/*"]
}
},
"include": ["src"]
}' > packages/system-a/tsconfig.json
cp packages/system-a/tsconfig.json packages/system-b/tsconfig.json
cp packages/system-a/tsconfig.json packages/shared-components/tsconfig.json
4. React 설정
# React 및 타입 설치
yarn workspace system-a add react@17.0.2 react-dom@17.0.2 @types/react@17.0.2 @types/react-dom@17.0.2
yarn workspace system-b add react@17.0.2 react-dom@17.0.2 @types/react@17.0.2 @types/react-dom@17.0.2
yarn workspace shared-components add react@17.0.2 react-dom@17.0.2 @types/react@17.0.2 @types/react-dom@17.0.2
5. 공유 컴포넌트 작성
# src 디렉토리 생성
mkdir -p packages/shared-components/src
# Button 컴포넌트 작성
echo 'import React from "react";
interface ButtonProps {
label: string;
onClick: () => void;
}
const Button: React.FC<ButtonProps> = ({ label, onClick }) => {
return <button onClick={onClick}>{label}</button>;
};
export default Button;' > packages/shared-components/src/Button.tsx
6. 시스템에서 공유 컴포넌트 사용
# src 디렉토리 생성
mkdir -p packages/system-a/src
mkdir -p packages/system-b/src
# App.tsx 파일 작성
echo 'import React from "react";
import Button from "@shared/Button";
const App: React.FC = () => {
return (
<div>
<h1>System A</h1>
<Button label="Click Me" onClick={() => alert("Button clicked!")} />
</div>
);
};
export default App;' > packages/system-a/src/App.tsx
cp packages/system-a/src/App.tsx packages/system-b/src/App.tsx
7. Webpack 설정
# Webpack 및 관련 패키지 설치
yarn workspace system-a add webpack webpack-cli ts-loader html-webpack-plugin webpack-dev-server
yarn workspace system-b add webpack webpack-cli ts-loader html-webpack-plugin webpack-dev-server
# Webpack 설정 파일 작성
echo 'const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/App.tsx",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist"),
},
resolve: {
extensions: [".ts", ".tsx", ".js"],
},
module: {
rules: [
{
test: /.tsx?$/,
use: "ts-loader",
exclude: /node_modules/,
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
],
devServer: {
contentBase: path.join(__dirname, "dist"),
compress: true,
port: 9000,
},
};' > packages/system-a/webpack.config.js
cp packages/system-a/webpack.config.js packages/system-b/webpack.config.js
# index.html 파일 작성
echo '<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>System A</title>
</head>
<body>
<div id="root"></div>
</body>
</html>' > packages/system-a/src/index.html
cp packages/system-a/src/index.html packages/system-b/src/index.html
8. 빌드 및 실행
# 빌드 및 실행
cd packages/system-a
yarn webpack serve
cd ../system-b
yarn webpack serve
이제 system-a와 system-b에서 shared-components의 Button 컴포넌트를 사용할 수 있습니다. 모노레포 구조를 사용하면 여러 시스템에서 공유 컴포넌트를 쉽게 관리하고 사용할 수 있습니다