본문으로 바로가기

MicroFrontend

category software engineering/frontend 2022. 8. 25. 12:25
728x90

MicroFrontend를 도입하고 싶다고 생각하게 된 계기는 다음과 같다.

  • 큰 서비스 안에 내가 만들고 있는 서비스가 있다
    • 빌드와 배포가 오래걸린다
    • lint, prettier 등을 고려하지 않은 팀도 있기에 불편하게 생각할 수 있으니 우리팀만 설득해서 하고 싶다
    • 부분장애가 모든서비스로 전파되지않음
    • 자유로운 기술 스택
    • 코드 복잡성 제거
    • 신규 서비스 추가의 자유로움

시도

  • Single SPA
    • 여러가지 app을 따로 실행시켜 한 화면에 렌더링 O
    • 원하는 프로젝트를 소스로 가능 (vue, react, ts, vite, …)
    • html,js 혹은 ejs 위에 여러개 얹는식
 {
    "imports": {
      "navbar": "http://localhost:8080/js/app.js",
      "vms": "http://localhost:8081/js/app.js",
      "app2": "http://localhost:8082/js/app.js",
      "single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js",
      "vue": "https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js",
      "vue-router": "https://cdn.jsdelivr.net/npm/vue-router@3.0.7/dist/vue-router.min.js"
    }
  }
      
<script>
    (function() {
      Promise.all([System.import('single-spa'), System.import('vue'), System.import('vue-router')]).then(function (modules) {
        var singleSpa = modules[0];
        var Vue = modules[1];
        var VueRouter = modules[2];

        Vue.use(VueRouter)

        singleSpa.registerApplication(
          'navbar',
          () => System.import('navbar'),
          location => true
        );

        singleSpa.registerApplication(
          'vms',
          () => System.import('vms'),
          location => location.pathname.startsWith('/vms')
        )

        singleSpa.registerApplication(
          'app2',
          () => System.import('app2'),
          location => location.pathname.startsWith('/app2')
        )

        singleSpa.start();
      })
    })()
  </script>
  <!-- See https://github.com/joeldenning/import-map-overrides#user-interface  -->
  <import-map-overrides-full show-when-local-storage="overrides-ui"></import-map-overrides-full>
  • 각각 서비스 키고 root 서비스에서 확인하는 식
  • public-path 설정했지만 vms에서 /assets /translate 이미 설정되어 있는거 바꾸는법을 조사해야함 (i18n이 아니기 때문)
  • 전체 라우트 관리 하는법, layout(login, logout), build
  • 활발하지 않은 github commit 흔적들
  • 유튜브에 3년전~ 요즘에도 간간히 나온다

예제 코드를 통해 만들어 봤는데 이건 아니다는 느낌이었다. 다른 방법은 없나 다시 찾아봤다.

 

  • Webpack module federation

webpack, @vue/cli module, vuetify 버전 update, bootstrap 말고는 따로 할게 없었다
Route, Vuex는 마이크로 서비스에서 등록하더라도 shell app에서도 따로 등록 필요

import localRoutes from './routes';
import facebookRoutes from 'facebook/routes';

const routes = [...localRoutes, ...remoteRoutes];

확인 안해봄

  • hot-reload
  • different version (vue2+vue3)
    • adapter 필요
    • 이 부분이 내 생각과 달리 허들이 될거 같았다. 자유로운 기술스택을 쉽게 사용할줄 알았지만 React + Vue2 + Vue Cli2 + Vue3 + Angular 로 하기엔 한 곳으로 정리해야했고 Adapter가 필요했다.
# 이건 vue2 -> vue3
export function vue2ToVue3(WrapperComponent, wrapperId) {
  let vm;
  return {
    mounted() {
      const slots = bindSlotContext(this.$slots, this.__self);
      vm = new Vue2({
        render: createElement => {
          return createElement(
            WrapperComponent,
            {
              on: this.$attrs,
              attrs: this.$attrs,
              props: this.$props,
              scopedSlots: this.$scopedSlots,
            },
            slots,
          );
        },
      });
      vm.$mount(`#${wrapperId}`);
    },
    props: WrapperComponent.props,
    render() {
      vm && vm.$forceUpdate();
    },
  };
}

# 이건 react different version
class Adapter extends React.Component {
  constructor(props) {
    super(props);
    this.refHold;
  }
  init = hydrate => {
    (async () => {
      const ReactDOM = (await import('app2/newReactDOM')).default;
      const React = (await import('app2/newReact')).default;
      const RemoteComponent = await this.props.importer();
      const { importer, children, ...rest } = this.props;
      const renderMethod = hydrate ? ReactDOM.hydrate : ReactDOM.render;
      renderMethod(React.createElement(RemoteComponent.default, rest, children), this.refHold);
    })();
  };
  componentDidUpdate(prevProps, prevState, snapshot) {
    this.init(true);
  }

  componentDidMount() {
    this.init();
  }

  render() {
    return (
      <div
        style={{ border: '1px red solid', padding: '10px', margin: '20px 0' }}
        ref={ref => (this.refHold = ref)}
      />
    );
  }
}

export default Adapter;

우선 우리 서비스도 같은 버전을 이용한다면 큰 문제 없이 원하는 이점은 취할 수 있어보인다.

'software engineering > frontend' 카테고리의 다른 글

React State Management  (0) 2022.08.25
Unit Test  (0) 2022.08.25
Framer with React  (0) 2022.08.25
Motion UI (feat. Framer Motion)  (0) 2022.08.25
국제화 (i18n) 자동화  (0) 2022.08.25