MSW보다 먼저 fetch되는 React Router Dom loader?

김 주현·2024년 7월 2일

FADE 개발일지

목록 보기
1/3

상황

백엔드 쪽에서 API가 개발되기 전까지 프론트 쪽에서는 API Mocking을 하려고 환경을 설정하고 있던 도중, 이상한 이슈가 생겼습니다.

메인 파일에서 MSW을 시작하고 있었어요.

main.tsx

import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './index.css';

export async function enableMocking(enabled: boolean = true) {
  if (import.meta.env.PROD || !enabled) {
    return;
  }

  const { worker } = await import('./__mock__/instance.ts');

  return worker.start({
    onUnhandledRequest: 'bypass',
  });
}

enableMocking().then(() => {
  ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
});

worker의 start() 메서드가 resolve를 반환하면 그 이후에 React를 렌더링하는 코드입니다. 그러므로 이후에 있는 React Router Dom의 loader든 뭐든 당연히 캐치가 되어야겠죠?

하지만 제 예상과는 다르게 loader에서 호출하고 있는 /user에 먼저 접근을 한 뒤, 서비스 워커가 불러와지고 있는 이상한 .. 이슈를 겪었어요.

첫 번째 시도: 서비스 워커 활성화 대기

첫 번째로 의심했던 건 MSW의 start() 메소드였습니다. 서비스 워커를 등록하는 메소드이지만, 서비스 워커를 '활성화'하고 resolve를 하지 않는다는 의심을 했어요. 등록은 완료했지만 서비스 워커가 활성화되기까지는 시간이 걸리니, 그 사이에 리액트가 먼저 렌더를 진행해 loader가 호출되는 것 같았어요.

그래서 다음과 같이 기다려주는 코드를 작성했어요.

활성화될 때까지 기다리기

return new Promise((resolve, reject) => {
    worker.start({ onUnhandledRequest: 'bypass' }).then(() => {
      if ('serviceWorker' in navigator) {
        navigator.serviceWorker.ready.then(resolve)
      } else {
        reject()
      }
    })
  })

ready가 되면 solve를 호출하게끔 했으나,, 결과는 여전히 똑같았어요.

두 번째 시도: dynamic import App.tsx

두 번째로 의심한 것은 App.tsx였습니다. 정확하게는 import App from './App.tsx' 구문이었어요.

자바스크립트는 가져오는 모든 종속 모듈들을 평가한 이후 코드를 실행하는 Module Evaluation이라는 개념이 있어요. 자세한 정보는 전에 작성한 포스팅을 참고해주세요. 이걸 이렇게 써먹네

실제로 fetch를 실패한 부분이 아닌, 더 위의 요청을 보면 App이 가져오는 모든 모듈을 먼저 가져오는 걸 확인할 수 있었어요.

그래 이걸 먼저 알아차렸어야 했는데...

그래서 App 파일 역시 모킹이 완료된 후 동적으로 불러오게 변경했습니다.

main.tsx

import ReactDOM from 'react-dom/client';
import './index.css';

export async function enableMocking(enabled: boolean = true) {
  if (import.meta.env.PROD || !enabled) {
    return;
  }

  const { worker } = await import('./__mock__/instance.ts');

  return worker.start({
    onUnhandledRequest: 'bypass',
  });
}

enableMocking().then(() => {
  import('./App.tsx').then((App) => {
    ReactDOM.createRoot(document.getElementById('root')!).render(<App.default />);
  });
});

이후 정상적으로 loader에 있는 녀석도 모킹을 할 수 있게 되었어요. 짱이다!


main.tsx 같은 파일은 국룰로 그냥 App.tsx 박고 시작하는 거라 크게 신경을 안 썼고, 파일도 다 import한 뒤 최적화 단계에서 lazy import로 바꿔서 최적화를 했다!는 접근을 하려고 했다보니 놓친 부분이었어요.

그냥 처음부터 lazy를 고려해야겠다 싶기도 하네요... ^~^

profile
FE개발자 가보자고🥳

0개의 댓글