[React/Multi-Entry] 엔트리 포인트를 나눠보자

Jinjer·2021년 11월 12일
0
post-thumbnail

1. 프롤로그

 

🤔SPA인데 엔트리 포인트를 나눠? 왜!!?
라고 생각될 테지만...

실제로 규모가 큰 프로젝트를 SPA로 구축하다 보면
처음 계획과 달리 달갑지 않은 선택의 순간이 찾아오기 마련이다.

예를 들면 다음과 같은 상황이다.

(프로젝트 종료 후)
클라이언트 : 아 참 문의하기 기능도 필요한데..
기획자 : 문의 글을 볼 수 있는 관리자 화면도 필요하겠군요!
클라이언트 : 센스쟁이! 이메일 연동도 되는 거죠?
기획자 : 당연히 되죠 🤣🤣🤣
.
..
...
개발자 : tlqkf

실제로 이런 경험을 겪은 개발자들이 적지 않을 것이다

자 그럼 본론으로 돌아와서
관리자 화면도 일반화면과 동일하게 하나의 .html에서 라우팅 처리만 적용하면 되는 거 아닌가 하고 의문이 들 수 있다.

1-1. 해결과제

해결해야 할 과제
1. 늘어난 정적리소스를 어떻게 분기 처리할 것인가
2. 동일한 변수명과 리스너를 가진 솔루션을 호출해야 한다면...

SPA의 최대 장점이 무엇인가 묻는다면
최초 다운로드 한 번만으로 모든 화면에서 정적리소스를 사용할 수 있다는 것 아닐까??

정답이다. 그러나 관리자 페이지가 추가로 만들어진다는 경우를 가정하면 그에 필요한 스타일이나 스크립트도 추가되야 하기에 처음 로딩에 부담을 줄 수 밖에 없다

1-2. 해결방법1

🤨코드스플리팅 하면 되는데?

그렇다. 모든 리소스를 한 번에 다운로드 하지 않고 화면 구성에 필요한 부분만 잘라내어 청크파일로 불러오면 성능 개선이 가능하다.

ES11(2020)의 Dynamic import를 사용하면 아래와 같이 import 표현식을 사용하여 해당 경로의 컴포넌트가 포함된 청크 파일을 동적으로 불러온다.

document.getElementById("button")
.addEventListener("click", async () => {
    const { modal } = await import("./component/Modal.js");
    modal();
});

Promise로 반환되어 async/await을 활용해 동기식으로 사용할 수도 있다.
React 프로젝트에서는 필요에 따라 아래와 같은 방법도 지원한다.

👍 Dynamic import for React
1. React.lazy
2. React Roadable(SSR 지원)

React 자체에 내장되어 있는 React.lazy를 사용하면 다음과 같이 컴포넌트를 동적으로 불러올 수 있다. 이때, Suspense는 lazy 컴포넌트가 로드되길 기다리는 동안 로딩 화면과 같은 예비 콘텐츠를 보여줄 수 있다.

import React, { Suspense } from 'react';

const Modal = React.lazy(() => import('./Modal'));

function MainComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <Modal />
      </Suspense>
    </div>
  );
}

🙆‍♂️ 정적리소스 문제는 해결.

그럼 이제 게시글의 핵심 문제였던 멀티 엔트리 포인트를 다뤄보자
실무에서 벌어진 상황은 다음과 같다.

  1. 전자서명을 위한 공동인증서 발급 솔루션을 업체로부터 전달받았다
  2. 기능이 다른 A솔루션과 B솔루션을 서로 다른 페이지에 적용하면 된다
  3. 멀티 페이지였다면 각각 페이지에서 솔루션을 불러오면 되겠지만..
  4. 단일 페이지에서는 두 솔루션을 동시에 불러오면 변수와 리스너가 중복되어 오류를 발생시킨다😤

실제로 대다수의 인증 혹은 결제 관련 솔루션들이 비교적 최근 트렌드인 SPA를 고려하지 않고 MPA에서 개발된 경우가 많다.

어떻게 되었든 간에 해답을 찾아야 하기에 삽질 다양한 시도를 해보았다

👷‍♂️ 삽질1
단일 페이지 유지한 상태로 동적으로 스크립트 로드

  1. custom hook
  2. react-helmet

결과부터 말하자면 위의 두 방법 모두 실패
이유는 당연했다 자바스크립트는 선언된 값을 자동으로 메모리에 할당시켜 버린다
중복된 변수는 초기화 하고 이벤트는 리스너를 제거해 줘야 했지만 수백 개에 달하는 변수와 이벤트를 일일이 제어하기란 쉽지 않았다

👷‍ 삽질2
네임스페이스를 이용해서 중복 식별자 관리

네임스페이스 또한 수십 개의 스크립트 파일을 변경하는건 비효율적이고 이후 예상 못 한 오류가 발생하면 대처하기도 어려울 것이 불 보듯 뻔했다.

1-2. 해결방법2

🍕그렇다면 페이지를 나누자!!

어렵게 생각하지 않기로 했다.

2개의 템플릿으로 나누고 엔트리 포인트만 다르게 설정해서
각 페이지의 HEAD에 솔루션을 넣어주면 해결될 일이었다

public 경로의 .html 템플릿을 나누는 방법은 웹팩 config에서 HtmlwebpackPlugin를 통해 지정할 수 있고 멀티 엔트리 설정을 위해서는 추가적인 설정이 필요하다

만약 CRA라면 react-app-rewire-multiple-entry로 간단하게 설정이 가능하다
단, eject을 한 상태에서는 적용할 수 없다

const multipleEntry = require('react-app-rewire-multiple-entry')([
  {
    entry: 'src/admin.js',
    template: 'public/admin.html',
    outPath: '/admin'
  }
]);
 
module.exports = {
  webpack: function(config, env) {
    multipleEntry.addMultiEntry(config);
    return config;
  }
};

주소창에서 /admin 경로를 입력해보면
지정된 템플릿을 기반으로 리소스를 다시 가져온다

react-router-dom을 이용해 접근해야 한다면
forceRefresh로 강제로 페이지를 리셋 해야만 된다

<BrowserRoter forceRefresh={true}>
	<App />
</BrowserRoter>

2. 정리

✔ SPA로 서비스를 개발할 땐 여러가지 변수가 존재함
✔ CRA에서 eject 수행은 신중하게 결정하자
✔ 여러가지 방법을 시도해 보는것은 언제나 옳다

profile
프론트엔드개발자

0개의 댓글