이번에는 지난 포스팅 글에서 React 컴포넌트를 동적으로 사용하는 것에 이어서 동적으로 지정하는 부분을 이어서 진행해보고자 한다.
main-app에서 다른 서버의 컴포넌트 동적으로 지정하기
먼저 App.tsx에서 dynamic 경로로 Button을 import하는 코드를 작성해보도록 하자.
// @ts-ignore
const Button = React.lazy(() => import("dynamic/Button"));
const App = () => {
const [button, setButton] = useState<{
url?: string;
scope?: string;
module?: string;
}>({});
function setButtonFromComponentApp1() {
setButton({
// url: "http://localhost:3001/remoteEntry.js",
url: "http://localhost:3001",
scope: "component_app1",
module: "./Button",
});
}
function setButtonFromComponentApp2() {
setButton({
url: "http://localhost:3002",
scope: "component_app2",
module: "./Button",
});
}
return (
<div className="container">
<div>Name: main-app</div>
<div>Framework: react-18</div>
<div>
<button onClick={setButtonFromComponentApp1}>
Load Component App 1 Button
</button>
<button onClick={setButtonFromComponentApp2}>
Load Component App 2 Button
</button>
</div>
<div>
<DynamicButton button={button} />
</div>
<div>
<Suspense fallback={<div>Loading Button</div>}>
<Button />
</Suspense>
</div>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById("app") as HTMLElement);
root.render(<App />);
아직까지 dynamic 경로에 대한 타입을 명확하게 지정할 수 없으므로 `@ts-ignore` 타입 오류를 처리해준다.
그 다음 module federation 설정에 dynamic과 관련된 경로를 추가해주도록 하자.
const deps = require("./package.json").dependencies;
export const mfConfig = {
name: "main_app",
filename: "remoteEntry.js",
remotes: {
dynamic: "component_app1@http://localhost:3001/remoteEntry.js",
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},
};
이렇게 설정한 후 main-app을 실행하면 다음과 같이 component-app1 버튼이 잘 나오게 된다.
api-server 앱 추가하기
먼저 apps 내에 api-server 디렉토리를 만들고 pnpm init 명령어로 프로젝트를 초기화해주도록 하자.
그리고 serve 패키지를 설치해주도록 한다.
pnpm --filter api-server add serve
그리고 public 디렉토리에 remote.json을 추가해주도록 하자.
{
"scope": "component_app1",
"remoteUrl": "http://localhost:3001/remoteEntry.js"
}
마지막으로 package.json에 dev 스크립트를 추가해주도록 한다.
"dev": "serve public -p 4000 --cors"
Promise Based Dynamic Remotes 설정하기
module federation 설정에서 remote를 동적으로 가져와서 지정할 수 있는 Promised Based Dynamic Remotes를 설정하도록 하자.
const deps = require("./package.json").dependencies;
export const mfConfig = {
name: "main_app",
filename: "remoteEntry.js",
remotes: {
dynamic: `promise new Promise(resolve => {
fetch("http://localhost:4000/remote.json")
.then((res) => res.json())
.then(({remoteUrl, scope}) => {
console.log(remoteUrl, scope);
const script = document.createElement('script')
script.src = remoteUrl;
script.onload = () => {
const proxy = {
get: (request) => window[scope].get(request),
init: (...arg) => {
try {
return window[scope].init(...arg)
} catch(e) {
console.log('remote container already initialized')
}
}
}
resolve(proxy)
}
document.head.appendChild(script);
});
})
`,
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},
};
이렇게 설정한 뒤 main-app을 실행해보면 다음과 같이 다른 서버의 컴포넌트를 받아올 수 있다.
그리고, api-server의 remote.json을 app2로 변경하더라도 잘 반영됨을 볼 수 있다.
{
"scope": "component_app2",
"remoteUrl": "http://localhost:3002/remoteEntry.js"
}
결론
- Promised Base Dynamic Routes 방식으로 클라이언트의 코드를 고정한 상태에서 대상 서버를 동적으로 변경할 수 있다.
- scope와 remoteUrl 값이 맞으면 규격에 맞는 컴포넌트를 로드할 수 있다.
- 특정 remoteUrl이 장애 상황일 경우 다른 서버로 접속하도록 하는 기능 등을 구현하기에 용이하다.
출처
- https://webpack.kr/concepts/module-federation/#promise-based-dynamic-remotes