SPA 통합
Single Page Application
각각의 SPA를 하나로 통합하는 방식에는
Linked
와Unified
방식이 존재
각각의 마이크로 앱이 SPA로 구성될 때, 이것들을 통합해 하나의 앱처럼 보이게 하기 위한 방법으로 2가지 방식이 있다.
Linked SPA

다른 SPA 경계로 넘어갈 때
Hard Navgiation
이라고 해서 Client-side가 아닌 Server-side 라우팅이 변경되게끔 처리Server-side 라우팅 처리란 서버에 요청하여 페이지를 받아오는 것을 의미한다.
각각의 SPA 내부적으로는
Soft Navigatio
Client-side 라우팅 처리구현하기 훨씬 쉽고 앱간 결합도(커플링)이 약하다.
Unified SPA

SPA를 넘나들 때 에도
Soft Navigation
을 사용하는 방식App Shell
을 이용해서 껍데기로 감싸고 사용자의 라우팅 입력을 받아 어떤 SPA를 보여줄지 계산하고 렌더할지 결정한다.서로 다른 SPA 경계를 넘나들 때 마다 이전 SPA를 안보이게 하고 신규 SPA를 띄우는 등에 별도의 추가 작업들이 필요하기 떄문에 구현이 더 어렵다.
Soft Navigation
방식은 서버에 추가로 요청하는 통신이 없기 때문에 찐으로 하나의 SPA로 관리하는 것처럼 보여 사용자 경험이 더 좋을 수밖에 없다.
App Shell 설계

Module Federation은 하나의 앱에서 여러 코드들을 Code spliting을 한 다음 다른 서버로 옮기는 것을 의미
라우트 코드들을 Code spliting을 통해 관리해주어야 한다.
// App Shell
import React, { lazy, Suspense } from 'react';
import { Navigate, ReactObject } from 'react-router-dom';
import { Layout } from '@/components/layout'
import { app1RoutingPrefix, app2RoutingPrefix } from '@/constants';
import App1Lazy = lazy(() => import("../components/App1"));
import App2Lazy = lazy(() => import("../components/App2"));
export const routes: ReactObject[] => [
{
path: '/',
element: <Layout />,
children: [
{
index: true,
element: <Navigate to={`/${app1RoutingPrefix}`} />,
},
{
path: `${app1RoutingPrefix}/*`,
element: <Suspense fallback="Loading App1..."><App1Lazy /></Suspense>,
},
{
path: `${app2RoutingPrefix}/*`,
element: <Suspense fallback="Loading App2..."><App2Lazy /></Suspense>,
}
]
}
]
// App1.tsx
useEffect(() => {
if (!isFirstRunRef.current) return;
// module federation을 통해 가져온 'mount'라는 함수를 이용
unmountRef.current = mount({
mountPoint: wrapperRef.current!,
initialPathname: location.pathname.replace(app1Basename, ''),
});
isFirstRunRef.current = false;
}, [location])
useEffect(() => unmountRef.current, []);
return <div ref={wrapperRef} id="app1-mfe" />;
import React from 'react';
import { createRoot } from 'react-dom/client';
import { RouterProvider } from 'react-router-dom';
import { createRouter } from '@/routing/router-factory';
import { RoutingStrategy } from '@/routing/types';
interface Mount {
mountPoint: HTMLElement;
initialPathname?: string;
routingStrategy?: RoutingStrategy;
}
export const mount = ({
mountPoint,
initialPathname,
routingStrategy,
}: Mount) => {
// 마이크로 앱들은 또하나의 react-app 이기 때문에 react router가 독자적으로 구성됨
const router = createRouter({ strategy: routingStrategy, initialPathname });
const root = createRoot(mountPoint);
root.render(<RouterProvider router={router} />);
return () => queueMicrotask(() => root.unmount());
}
import React from 'react';
import { Outlet } from 'react-router-dom';
import { NavigationManager } from '@/components/navigation';
import { Page1, Page2 } from '@/pages';
export const routes = [
{
path: "/",
element: (
<NavigationManager>
<Outlet>
</NavigationManager>
),
children: [
{
index: true,
element: <Page1 />,
},
{
path: 'page-1,
element: <Page1 />,
},
{
path: 'page-2,
element: <Page2 />,
}
]
}
];
Last updated