본문 바로가기

Vue 실습

모노레포 구성 정리

728x90

아래는 **host-maestro-monorepo**를 루트로 하는 pnpm 모노레포 구성부터,
HostremoteMaestro 두 패키지를 만들고,
Host에서 Remote 컴포넌트를 가져와서 사용하는 과정을 순서대로 정리한 내용입니다.


1. 폴더 구조 & 초기 설정

1) 모노레포 루트 폴더 만들기

 
mkdir host-maestro-monorepo
cd host-maestro-monorepo

2) pnpm init

루트에서 pnpm init을 통해 **루트의 package.json**을 생성합니다.

 
pnpm init

루트 package.json 예시

 
{
  "name": "host-maestro-monorepo",
  "private": true,
  "version": "1.0.0",
  "scripts": {
    // "install": "pnpm install", // ← (무한루프 방지를 위해 제거!)
    "dev:host": "pnpm --filter host dev",
    "dev:remote": "pnpm --filter remoteMaestro dev",
    "build:all": "pnpm build -r --filter './packages/*'"
  }
}
  • 주의: "scripts": { "install": "pnpm install" }는 넣지 않습니다(무한 루프 발생).

3) pnpm-workspace.yaml

 
packages: - "packages/*"
  • 모노레포로 인식할 경로를 지정: packages 폴더 하위의 모든 디렉터리를 워크스페이스 패키지로 삼음.

4) TypeScript / ESLint(옵션)

  • **루트(host-maestro-monorepo)**에 tsconfig.json, .eslintrc.js 등을 둬서 전체 규칙을 적용할 수 있습니다.
  • (옵션) 여기서는 간단히 생략하거나, 다음과 같은 최소 설정을 둘 수 있음:
 
// tsconfig.json
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "strict": true,
    "skipLibCheck": true
  },
  "exclude": [
    "node_modules"
  ]
}

2. 패키지 생성 (Host, remoteMaestro)

폴더 구조

host-maestro-monorepo
├── packages
│   ├── host
│   │   ├── package.json
│   │   ├── vite.config.ts
│   │   ├── index.html   ← Vite용 HTML
│   │   └── src
│   │       ├── App.vue
│   │       ├── main.ts
│   │       └── router.ts
│   └── remoteMaestro
│       ├── package.json
│       ├── vite.config.ts
│       ├── src
│       │   ├── HelloRemote.vue
│       │   ├── RemoteApp.vue
│       │   ├── main.ts
│       │   └── index.ts
├── package.json          ← 모노레포 루트
├── pnpm-workspace.yaml   ← 모노레포 설정
└── tsconfig.json         ← (옵션)
 

2-1) packages/host 설정

a) host/package.json

{
  "name": "host",
  "version": "1.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  },
  "dependencies": {
    "vue": "^3.3.0",
    "vue-router": "^4.0.0"
  },
  "devDependencies": {
    "vite": "^4.5.0",
    "@vitejs/plugin-vue": "^4.0.0",
    "typescript": "^4.5.0"
  }
}
 
  • "name": "host" 로 정의 (이 패키지의 이름)
  • Vue, Vite, TypeScript 등을 설치

b) host/vite.config.ts

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

export default defineConfig({
  plugins: [vue()],
  server: {
    port: 3000
  }
});
 
  • 호스트 앱을 3000 포트로 띄우는 설정 예시

c) host/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Host App</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>
 
  • Vite가 이 HTML을 기준으로 앱을 로드

d) host/src/main.ts

import { createApp } from 'vue';
import App from './App.vue';
import router from './router';

const app = createApp(App);
app.use(router);
app.mount('#app');

e) host/src/router.ts

import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue';

const routes = [
  {
    path: '/',
    name: 'Home',
    component: App
  }
];

export default createRouter({
  history: createWebHistory(),
  routes
});
 

f) host/src/App.vue

<template>
  <div>
    <h1>This is Host App</h1>
    <router-view />
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue';

const title = ref('Hello from Host App!');

onMounted(() => {
  console.log(title.value);
});
</script>

<style scoped>
h1 {
  color: steelblue;
}
</style>

2-2) packages/remoteMaestro 설정

a) remoteMaestro/package.json

{
  "name": "remoteMaestro",
  "version": "1.0.0",
  "main": "src/index.ts", // ← export할 엔트리 (중요)
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  },
  "dependencies": {
    "vue": "^3.3.0"
  },
  "devDependencies": {
    "vite": "^4.5.0",
    "@vitejs/plugin-vue": "^4.0.0",
    "typescript": "^4.5.0"
  }
}
 
  • name: "remoteMaestro"`
  • main: "src/index.ts"` 를 지정 → 이 파일에서 내보낸 것이 패키지의 기본 export가 됨.

b) remoteMaestro/vite.config.ts

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

export default defineConfig({
  plugins: [vue()],
  server: {
    port: 3001
  }
});
 
  • 리모트 앱을 3001 포트에서 실행

c) remoteMaestro/index.html (Vite용, Remote 단독 실행 시 필요)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Remote Maestro</title>
  </head>
  <body>
    <div id="remote-app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>
 

d) remoteMaestro/src/main.ts

import { createApp } from 'vue';
import RemoteApp from './RemoteApp.vue';

const app = createApp(RemoteApp);
app.mount('#remote-app');
  • 리모트 앱 실행 시, #remote-app에 마운트

e) remoteMaestro/src/RemoteApp.vue

<template>
  <div>
    <h2>Remote App</h2>
    <HelloRemote />
  </div>
</template>

<script setup lang="ts">
import HelloRemote from './HelloRemote.vue';
</script>

<style scoped>
h2 {
  color: darkmagenta;
}
</style>

f) remoteMaestro/src/HelloRemote.vue

<template>
  <p>{{ message }}</p>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const message = ref('Hello from Remote Component!');
</script>

<style scoped>
p {
  font-size: 18px;
}
</style>

g) remoteMaestro/src/index.ts

// remoteMaestro의 "main" 엔트리
export { default as HelloRemote } from './HelloRemote.vue';
export { default as RemoteApp } from './RemoteApp.vue';
  • 이렇게 export 해줘야, Host 쪽에서 import { HelloRemote } from 'remoteMaestro'; 형태로 사용 가능

3. Host에서 Remote 컴포넌트를 가져오기

3-1) Host가 remoteMaestro를 의존성으로 추가

Host 패키지(packages/host/package.json) 안에서 remoteMaestro를 직접 명시해줄 수 있습니다.
단, Monorepo Workspace 의존성을 사용하기 위해서는 루트에서 아래처럼 해주는 것이 보통입니다:

a) Host의 package.json

{
  "name": "host",
  "version": "1.0.0",
  "scripts": { ... },
  "dependencies": {
    "vue": "^3.3.0",
    "vue-router": "^4.0.0",
    // "remoteMaestro": "workspace:*"  ← 추가
    "remoteMaestro": "workspace:*"
  },
  "devDependencies": {
    "vite": "^4.5.0",
    "@vitejs/plugin-vue": "^4.0.0",
    "typescript": "^4.5.0"
  }
}
  • 이렇게 remoteMaestro를 의존성으로 지정하면,
    pnpm이 node_modules/remoteMaestro를 모노레포 내부에서 심볼릭 링크해 줍니다.

b) pnpm install

루트(host-maestro-monorepo) 폴더에서:

pnpm install
  • 이제 host/node_modules/remoteMaestro가 연결됨.

c) Host에서 import

host/src/App.vue (또는 다른 컴포넌트)에서:

<template>
  <div>
    <h1>This is Host App</h1>
    <HelloRemote />
  </div>
</template>

<script setup lang="ts">
import { HelloRemote } from 'remoteMaestro';

</script>
  • 이렇게 하면, remoteMaestro/src/index.ts에서 export된 HelloRemote가 정상적으로 import됩니다.

4. 실행 & 확인

4-1) 의존성 설치

# 루트에서
cd host-maestro-monorepo
pnpm install
  • 모든 하위 패키지(Host, remoteMaestro)의 의존성을 한 번에 설치

4-2) Host 실행

pnpm dev:host
  • scripts에 "dev:host": "pnpm --filter host dev"를 넣어뒀다면, 이 명령어로 Host 앱이 켜짐
  • http://localhost:3000 에 접속 (만약 Vite 서버에서 port: 3000이 지정되어 있다면)

4-3) Remote 실행 (옵션)

pnpm dev:remote

5. 만약 404가 뜨거나 import ... from 'remoteMaestro'가 안 될 때

  1. index.html 누락: Vite는 index.html이 필수(Host, Remote 각각).
  2. **루트에서 "scripts.install"**이 무한 루프를 일으키는지 체크 (지워야 함).
  3. Workspace 의존성 설정이 제대로 되었는지:
    • Host package.json에 "remoteMaestro": "workspace:*" 추가했는지?
    • remoteMaestro/package.json에 "name": "remoteMaestro", "main": "src/index.ts" 명시했는지?
  4. pnpm install 후에 다시 pnpm dev:host를 실행해보는지?

이 과정을 정확히 지키면, Host에서 remoteMaestro의 컴포넌트를 원활히 가져올 수 있게 됩니다.


6. (참고) Module Federation 방식으로 “동적” 로드하기

위의 방식은 정적 import(빌드 시점)이며, 실제로는 Module Federation을 사용하여 런타임에 remoteEntry.js를 불러와서 모듈을 가져오는 시나리오도 있습니다.

  • 그때는 Webpack (또는 Vite) Module Federation Plugin을 설정하고,
  • Remote 쪽에 exposes, Host 쪽에 remotes를 명시해야 합니다.
  • 예) import('remoteMaestro/HelloRemote') 로 동적 import.
    이 부분은 MFE(Micro Frontend) 구현 시 별도의 설정 작업이 필요합니다.

결론

  1. 폴더 구조: host-maestro-monorepo 루트 / packages/host / packages/remoteMaestro.
  2. pnpm-workspace.yaml: packages/* 지정.
  3. HostremoteMaestro 각각 package.json, vite.config.ts, index.html 세팅.
  4. remoteMaestro는 "name": "remoteMaestro", "main": "src/index.ts"로 export 포인트 설정.
  5. Host에서 "remoteMaestro": "workspace:*" 의존성을 추가하여 가져오기.
  6. pnpm install 후 pnpm dev:host 실행 → App.vue 에서 import { HelloRemote } from 'remoteMaestro'; 테스트.

 

결과물

 

728x90