테마
마이크로프론트엔드 선택적 배포 전략
마이크로앱이나 프래그먼트를 추가 개발한 후, 빌드타임 의존성과 런타임 의존성의 차이를 이해하고, 변경 유형에 따라 배포 범위를 최소화하는 전략을 학습한다.
학습 목표
- 마이크로앱 추가 개발 후 배포 범위를 정확히 결정할 수 있다
- UIKit 같은 공유 패키지 변경 시 영향 범위(의존하는 모든 앱)를 파악하고 대응할 수 있다
- 프래그먼트 추가 개발 후 독립 배포가 가능한 조건을 이해한다
- 빌드타임 의존성과 런타임 의존성의 차이에 따른 배포 범위 차이를 구분할 수 있다
1. 배포 범위를 결정하는 핵심 원리
마이크로프론트엔드에서 배포 범위는 "무엇이 변경되었는가"에 의해 결정된다. 변경의 종류에 따라 재빌드/재배포해야 하는 범위가 달라진다.
의존성의 두 가지 유형
| 구분 | 빌드타임 의존성 | 런타임 의존성 |
|---|---|---|
| 포함 시점 | 빌드 시 번들에 인라인 | 브라우저 실행 시 네트워크 로딩 |
| 변경 영향 | 의존하는 모든 앱 재빌드 필요 | 해당 서비스만 재배포 |
| 예시 | UIKit, Shell Router, 타입 정의 | remoteEntry.js, API 응답 |
| 배포 범위 | 넓음 (연쇄 배포) | 좁음 (독립 배포) |
2. 시나리오 1: 마이크로앱 내부 로직 변경
가장 단순한 경우다. 포스팅 앱 내부에서 새로운 기능을 추가하거나 버그를 수정한 경우, 포스팅 앱만 재빌드하고 재배포하면 된다.
변경 범위와 배포 범위
bash
# 변경된 파일
apps/posting/src/pages/PageHome.tsx # 새로운 UI 추가
apps/posting/src/apis.ts # API 호출 수정
# 빌드 및 배포
pnpm --filter posting build # posting만 빌드
# dist/ 를 CDN에 업로드 # posting만 배포다른 마이크로앱(edu, network, job)은 포스팅과 런타임에만 통합되므로 전혀 영향을 받지 않는다. Shell 역시 importRemote로 포스팅을 로딩하므로 재빌드가 필요 없다.
3. 시나리오 2: UIKit 공유 패키지 변경 (빌드타임 의존성)
UIKit에 새로운 컴포넌트(예: Profile)를 추가하거나, 기존 컴포넌트의 인터페이스를 변경하면, UIKit을 빌드타임에 참조하는 모든 앱이 영향을 받는다.
커리어업 프로젝트의 UIKit 변경 시나리오
Profile 컴포넌트를 UIKit으로 이동하는 경우를 살펴보자.
typescript
// packages/ui-kit/src/components/profile.tsx (신규)
import type { User } from "@earth0/sba.js";
interface UserType extends User {
connectionCount: number;
postCount: number;
}
interface ProfileProps {
user: UserType;
}
export const Profile: React.FC<ProfileProps> = ({ user }) => {
return (
<div className={styles.profileTop}>
<img src={user.profileImageUrl} className={styles.profileImage} />
<span className={styles.profileName}>{user.name}</span>
<span className={styles.profileEmail}>{user.email}</span>
</div>
);
};typescript
// packages/ui-kit/src/index.ts
export { Button } from "./components/button";
export { Icon } from "./components/icon";
export { Profile } from "./components/profile"; // 신규 추가UIKit 변경 시 영향 범위 분석
Shared Singleton과 ShareScope 문제
Module Federation에서 UIKit을 singleton: true로 설정한 경우, 모든 마이크로앱이 동일한 UIKit 인스턴스를 공유한다. UIKit에 새 컴포넌트를 추가한 후 일부 앱만 재빌드하면, 앱마다 참조하는 UIKit 버전이 달라져 런타임 에러가 발생한다.
javascript
// apps/edu/webpack.config.js
new ModuleFederationPlugin({
shared: {
"@career-up/ui-kit": {
singleton: true,
// shareScope 미지정 시 기본 "default" 스코프 사용
}
}
});해결 전략: ShareScope 분리
javascript
// apps/edu/webpack.config.js (수정 후)
new ModuleFederationPlugin({
shared: {
"@career-up/ui-kit": {
singleton: true,
shareScope: "v2" // 새 버전 스코프
}
}
});shareScope를 v2로 지정하면, 같은 스코프의 UIKit끼리만 공유한다. 아직 재빌드하지 않은 앱은 default 스코프의 구 버전 UIKit을 사용하므로 충돌이 발생하지 않는다.
추가로, 새 UIKit의 CSS가 올바르게 적용되도록 injector에서 CSS를 명시적으로 import한다.
typescript
// apps/edu/src/injector.tsx
import "@career-up/ui-kit/index.css"; // 추가4. 시나리오 3: 프래그먼트 독립 배포 (런타임 의존성)
프래그먼트는 런타임에 remoteEntry.js를 통해 로딩되므로, 프래그먼트 내부만 변경했다면 해당 프래그먼트만 재빌드/재배포하면 된다.
SWR 의존성 추가 시나리오
추천 1촌 프래그먼트에 useSWR 훅을 도입하는 경우를 살펴보자.
typescript
// fragments/fragment-recommend-connections/src/containers/RecommendConnectionsContainer.tsx
import useSWR from "swr";
const serverUrl = process.env.REACT_APP_SERVER_URL!;
const RecommendConnectionsContainer: React.FC = () => {
const { data, isLoading, error } = useSWR<ConnectionType[]>(
`/connections?limit=3`,
async (url: string) => {
const token = await getAccessToken();
const response = await fetch(`${serverUrl}${url}`, {
headers: { Authorization: `Bearer ${token}` }
});
return response.json();
}
);
return (
<RecommendConnections connections={data ?? []} />
);
};프래그먼트 독립 배포 흐름
SWR shared 설정 주의사항:
새로운 의존성(SWR)을 추가할 때, webpack의 shared 설정에 버전을 명시해야 경고가 발생하지 않는다.
javascript
// fragments/fragment-recommend-connections/webpack.config.js
new ModuleFederationPlugin({
shared: {
react: { singleton: true, requiredVersion: "^18.2.0" },
"react-dom": { singleton: true, requiredVersion: "^18.2.0" },
swr: { requiredVersion: "^2.2.0" } // 버전 명시
}
});5. 배포 범위 결정 의사결정 트리
변경이 발생했을 때, 아래 의사결정 트리를 따라 배포 범위를 결정한다.
6. 변경 유형별 배포 범위 요약
| 변경 유형 | 구체적 예시 | 배포 범위 | 난이도 |
|---|---|---|---|
| 마이크로앱 내부 로직 수정 | 포스팅 페이지 UI 변경 | 해당 앱만 | 낮음 |
| 프래그먼트 내부 로직 수정 | 추천 1촌 렌더링 변경 | 해당 프래그먼트만 | 낮음 |
| 프래그먼트에 새 의존성 추가 | SWR 도입 | 해당 프래그먼트만 (shared 버전 명시) | 낮음 |
| 공유 패키지에 컴포넌트 추가 | UIKit에 Profile 추가 | 패키지 + 사용 앱 (ShareScope 분리) | 중간 |
| 공유 패키지 인터페이스 변경 | Button Props 변경 | 패키지 + 의존하는 모든 앱 | 높음 |
| 공유 패키지 대규모 리팩토링 | UIKit 메이저 버전 업 | 전체 앱 재빌드 + 재배포 | 매우 높음 |
| Shell 라우팅 변경 | 새 경로 추가 | Shell + 해당 마이크로앱 | 중간 |
핵심 정리
- 빌드타임 의존성(UIKit, Shell Router) 변경은 해당 패키지를 참조하는 모든 앱의 재빌드가 필요하다. 반면 런타임 의존성(remoteEntry.js) 변경은 해당 서비스만 재배포하면 된다
- 공유 패키지에 새 컴포넌트를 추가한 경우,
shareScope를 분리하면 아직 재빌드하지 않은 앱과 충돌 없이 점진적 배포가 가능하다 - 프래그먼트에 새 런타임 의존성(SWR 등)을 추가할 때는 webpack shared 설정에 버전을 명시해야 경고 없이 배포된다
- 공유 패키지의 인터페이스를 변경(Props 변경, 삭제)하면 의존하는 모든 앱의 연쇄 재빌드가 불가피하다. 이를 최소화하려면 인터페이스를 단순하게 유지하고 옵셔널 확장을 선호해야 한다
- 배포 범위를 결정할 때는 "빌드타임인가 런타임인가"를 먼저 판단하고, 의사결정 트리를 따라 최소 범위를 도출하는 것이 핵심이다
다음 단계
- 03-배포-마무리와-전략.md: 선택적 배포의 중요성을 종합적으로 정리하고, 컴포넌트 인터페이스를 단순하게 유지해야 하는 이유, 배포 시나리오별 전략, 그리고 롤백 전략을 학습한다