React federaction

£€€.T.$·2024년 1월 11일
post-thumbnail

📑 개요

두개의 폴더에서 각각 개발하여 다른 포트를 가진

A.app과
B.app이 있다고 가정하자

A에서는 B의 결과물을 띄 울 수 있을까? 있음 셋팅을 그대로 쓸 것이니 나 혼자 알아볼 수 있으면 된다. 시작!


⚙️ Node

기본적인 파일 경로는 아래와 같다!

A.app
├hooks
│└useFederationComponent.js
moduleFederation.config.js

B.app
moduleFederation.config.js


📂 A.app

A.app
├hooks
│├useDynamicScript.js
│└✔️useFederationComponent.js
└moduleFederation.config.js

useFederationComponent.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

useDynamicScript.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

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

B.app
└✔️moduleFederation.config.js

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'],
    },
  },
};
profile
Be {Nice} Be {Kind}

0개의 댓글