본문 바로가기

프론트엔드

iframe으로 Vue to React Migration 준비하기

  • 키워드: vue to react migration, iframe, postMessage, contentWindow, window.parent
  • 상황
    1. Vue2로 작성된 프로젝트가 있다.
    2. 프로젝트의 복잡성, 인수인계, 유지보수 등을 고려하여 React로 migration하기로 결정했다.
    3. 한 번에 옮기기엔 거대한 프로젝트이므로, 기존 로직을 유지하면서 점진적으로 migration하고자 한다.
  • 해결 과정
    1. 기존 Vue 로직을 그대로 유지하면서 migration하는 방법은 iframe을 이용하는 것이다.
      React 프로젝트를 새로 만들어 특정한 기능을 만든 후, 해당 기능만 Vue에서 iframe으로 가져오면 된다.

    2. Vue와 React 프로젝트를 development mode로 실행할 경우, 각각 :8080(Vue) :3000(React) port를 사용한다.

    3. 먼저 부모 프로젝트인 Vue에서 iframe HTML을 작성한다. id는 'iframeExample'으로 하고, src는 자식 프로젝트인 localhost:3000으로 한다.
      iframe의 width는 100%로 설정했지만, height는 100%, 'auto'로 설정할 수 없으므로 일단 비워둔다.

        <!-- parent.vue -->
      <iframe
        id="iframeExample"
        width="100%"
        src="http://localhost:3000"
      />


    4. 자식 프로젝트인 React에서도 iframe 관련 로직을 작성한다.
        // child.tsx
      useEffect(() => {
        window.parent.postMessage(document.body.scrollHeight, 'http://localhost:8080');
      }, []);

      부모 프로젝트에서 iframe을 통해 자식이 load되었을 경우, 'window.parent.postMessage'로 부모 window 객체에 접근한 후 통신을 할 수 있다. 위의 코드에서는 부모에게 document.body.scrollHeight 정보를 전송한다.


    5. 부모 프로젝트인 Vue에서 해당 message를 수신한다. 그리고 3단계에서 설정하지 못한 height를 직접 설정한다.
        // parent.vue
      mounted() {
        window.addEventListener('message', function (e) {
          if (e.origin === 'http://localhost:3000') {
            console.log('child -> parent:', e.data);
            document.getElementById('iframeExample').style.height = e.data + 'px';
          }
        });
      }


    6. 부모 프로젝트에서도 마찬가지로 자식에게 message를 보낼 수 있다. 이 때는 contentWindow로 자식 window 객체에 접근한다.
        // parent.vue
      function sendChildMessage() {
        document.getElementById('iframeExample').contentWindow.postMessage(
          'sent message from parent', // 자식에게 필요한 정보(ex> access_token)들을 전송
          'http://localhost:3000'
        );
      }


    7. 부모에서 전송한 message를 자식이 수신한다.
        // child.tsx
      function receiveMessage(e: MessageEvent) {
        if (e.origin === 'http://localhost:8080') {
          console.log('parent -> child', e.data)
        }
      }
      
      window.addEventListener('message', receiveMessage);


    8. 지금까지 진행한 코드는 아래와 같다.
      Vue:
        // parent.vue(port 8080)
      mounted() {
        window.addEventListener('message', function (e) {
          if (e.origin === 'http://localhost:3000') {
            console.log('child -> parent:', e.data);
            document.getElementById('iframeExample').style.height = e.data + 'px';
          }
        });
      }
      
      ...
      
      function sendChildMessage() {
        document.getElementById('iframeExample').contentWindow.postMessage(
          'sent message from parent',
          'http://localhost:3000'
        );
      }
      
      ...
      
      <iframe
        id="iframeExample"
        width="100%"
        src="http://localhost:3000"
      />

      React:
        // child.tsx(port 3000)
      useEffect(() => {
        if (window.top?.length) { // NOTE: iframe 으로 사용하지 않는 경우 console error 방지
          window.parent.postMessage(document.body.scrollHeight, 'http://localhost:8080');
        }
      
        function receiveMessage(e: MessageEvent) {
          if (e.origin === 'http://localhost:8080') {
            console.log('parent -> child', e.data)
          }
        }
      
        window.addEventListener('message', receiveMessage);
        return () => {
          window.removeEventListener('message', receiveMessage);
        }
      }, []);


    9. 이제 Vue와 React가 서로 데이터를 전달할 수 있게 되었다! 기본 로직을 파악했으니 보안과 설계에 유의하며 마이그레이션을 진행해야겠다.