두개의 폴더에서 각각 개발하여 다른 포트를 가진
A.app과
B.app이 있다고 가정하자
A에서는 B의 결과물을 띄 울 수 있을까? 있음 셋팅을 그대로 쓸 것이니 나 혼자 알아볼 수 있으면 된다. 시작!
기본적인 파일 경로는 아래와 같다!
A.app
├hooks
│└useFederationComponent.js
└moduleFederation.config.js
B.app
└moduleFederation.config.js
A.app
├hooks
│├useDynamicScript.js
│└✔️useFederationComponent.js
└moduleFederation.config.js
간단설명 : federationRemoteModules 는 scope와 module을 전달받아 해당 경로에 있는 파일을 import하여 실행시키는 방식이다.
import useDynamicScript from '@/common/hooks/useDynamicScript';
import { lazy, useEffect, useState } from 'react';
const federationRemoteModules = {
rayBoard: {
Board: lazy(() => import('rayBoard/Board')),
BoardMgr: lazy(() => import('rayBoard/BoardMgr')),
},
rayManual: {
Manual: lazy(() => import('rayManual/Manual')),
RootCategoryMgr: lazy(() => import('rayManual/RootCategoryMgr')),
MyBizFlow: lazy(() => import('rayManual/MyBIzFlow')),
},
};
const componentCache = new Map();
const useFederatedComponent = ({ url, scope, module }) => {
const key = `${url}-${scope}-${module}`;
const [Component, setComponent] = useState(null);
useEffect(() => {
if (Component) setComponent(null);
}, [key]);
const { ready, errorLoading } = useDynamicScript(url);
useEffect(() => {
if (ready && !Component) {
const Comp = federationRemoteModules[scope][module];
componentCache.set(key, Comp);
setComponent(Comp);
}
}, [ready, Component, key]);
return { Component, errorLoading };
};
export default useFederatedComponent;
A.app
├hooks
│├✔️useDynamicScript.js
│└useFederationComponent.js
└moduleFederation.config.js
import { useEffect, useState } from 'react';
const urlCache = new Set();
const useDynamicScript = (url) => {
const [ready, setReady] = useState(false);
const [errorLoading, setErrorLoading] = useState(false);
useEffect(() => {
if (!url) {
setReady(false);
setErrorLoading(false);
return;
}
if (urlCache.has(url)) {
setReady(true);
setErrorLoading(false);
return;
}
setReady(false);
setErrorLoading(false);
const element = document.createElement('script');
element.src = url;
element.type = 'text/javascript';
element.async = true;
element.onload = () => {
urlCache.add(url);
setReady(true);
};
element.onerror = () => {
setReady(false);
setErrorLoading(true);
};
document.head.appendChild(element);
return () => {
urlCache.delete(url);
document.head.removeChild(element);
};
}, [url]);
return { errorLoading, ready };
};
export default useDynamicScript;
A.app
├hooks
│├useDynamicScript.js
│└useFederationComponent.js
└✔️moduleFederation.config.js
const deps = require('./package.json').dependencies;
module.exports = {
name: 'portal',
filename: 'remoteEntry.js',
remotes: {
rayBoard: `rayBoard@${process.env.REACT_APP_RAYBOARD_FEDERATION_REMOTE_URL}?v=${Date.now()}`,
rayManual: `rayManual@${process.env.REACT_APP_RAYMANUAL_FEDERATION_REMOTE_URL}?v=${Date.now()}`,
},
exposes: {
'./axios': '@/common/axios',
'./useAuth': '@/common/hooks/useAuth',
'./useUser': '@/common/hooks/useUser',
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps['react'],
shareScope: 'default',
},
'react-dom': {
singleton: true,
requiredVersion: deps['react-dom'],
},
},
};
B.app
└✔️moduleFederation.config.js
const deps = require('./package.json').dependencies;
module.exports = {
name: 'rayManual',
filename: 'remoteEntry.js',
remotes: {
portal: `portal@http://portal.rayful.com/remoteEntry.js?v=${Date.now()}`,
},
exposes: {
'./Manual': './src/app/App.js',
'./RootCategoryMgr': '/src/pages/admin/root-category-mgr',
'./MyBIzFlow': './src/pages/mybizflow',
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps['react'],
},
'react-dom': {
singleton: true,
requiredVersion: deps['react-dom'],
},
},
};