dev-server에서 HMR 문제 해결하기 with LocatorJs

황준·2025년 9월 6일
2

좋아하는 글

목록 보기
2/8
post-thumbnail

✅ 개요

개발자에게 큰 스트레스 중 하나는 원인을 알 수 없는 문제를 마주하는 것입니다.
특히 개발 환경 문제는 생산성에 영향을 미치기 때문에 답답합니다.

팀은 Rspack 1.0부터 도입하기 시작했고, 최신 Module Federation을 함께 사용해왔습니다.

하지만 이 과정에서 HMR(Hot Module Reload)이 제대로 작동하지 않는 문제가 지속적으로 발생해왔습니다.

CSS 클래스로 충돌로 인해 스타일이 덮어지거나, 개발 서버가 실행되지 않는 등 자잘한 문제들을 해결해가며 사용하고 있었습니다.

하지만 HMR 문제는 여전히 해결되지 않았습니다.

더 이상 임시방편으로는 한계가 있다고 판단하였고,
이번 기회에 HMR 문제의 원인을 파악하고 해결하기로 마음을 먹었습니다.


🤔 어디서부터 시작할까

해결하려고 하니 어디서부터 접근해야 할지 막막했습니다.
먼저 성공 케이스와 실패 케이스 로그를 비교했습니다.

실패 케이스의 로그:

[webpack-dev-server] App updated. Recompiling...

성공 케이스의 로그:

[webpack-dev-server] App updated. Recompiling...
[HMR] Checking for updates on the server...
[HMR] Updated modules:
[HMR] App is up to date.

실패 케이스에서는 HMR 로그가 출력되지 않았습니다.
MF와 Rspack의 호환성 문제로 인해 HMR 이슈가 발생한다는 가설을 세웠습니다.

  1. Rspack 업데이트하면 MF로 알림을 전송
  2. 이 과정에서 MF가 자체적으로 manifest.json를 재생성
  3. 컴파일이 반복적으로 발생
  4. 이러한 순환 참조로 인해 HMR 프로세스가 중단됨

manifest.json: Module Federation에서 사용하는 메타데이터 파일로, 각 마이크로 프론트엔드 모듈 간의 의존성과 구성 정보를 담고 있습니다.

이 가설을 검증하기 위해 webpack-dev-server의 watchOptions.ignored 설정을 사용하여 manifest.json 파일을 감시 대상에서 제외했습니다.

결과는 동일했고 문제가 해결되지 않았습니다.


👊 로깅 레벨 올리기

더 구체적인 정보를 얻기 위해 로깅 레벨을 상향하여 근본 원인을 찾아보기로 했습니다.

// dev-server.config.js
client: {
  logging: 'verbose', // 자세한 로그
  progress: true,     // 브라우저에서 진행률 표시
},

// module-federation.config.js
stats: {
  preset: 'verbose',
  progress: true
},

로그에서는 유의미한 정보를 찾기 어려웠습니다.
progress도 정상적으로 표시되었고, 성공했다는 메시지도 출력되었습니다.

Module Federation log에서는 CSS load 순서가 불규칙하다는 문제를 발견할 수 있었습니다.
CSS 순서 문제가 스타일에 영향을 줄 수 있습니다.

하지만 JavaScript만 변경했을 때 반영되지 않았기 때문에 이를 원인이라고 보지 않았습니다.
결국 로깅 레벨을 높여도 방향을 잡을 수 없었습니다.

👁‍🗨 콘솔로그와 함께 소스를 보자

실패 케이스의 로그:

[webpack-dev-server] App updated. Recompiling...

성공 케이스의 로그:

[webpack-dev-server] App updated. Recompiling...
[HMR] Checking for updates on the server...
[HMR] Updated modules:
[HMR] App is up to date.

처음으로 다시 돌아왔습니다.
이번에는 단순히 로그를 보는 것이 아니라, 이 로그가 출력되는 소스 코드를 직접 찾아가보기로 했습니다.


하지만 여기서 문제가 생겼습니다. 이 함수가 어디서 호출되는지 알기 어려웠습니다.

변수나 정보들을 보면 도움될 정보가 있는지 확인해보기 위해,
소스 탭에서 Debugger를 활용해서 화면을 멈추게 했습니다.

App updated, Recompiling을 검색하면 되겠다고 생각했는데, node_modules에서는 검색이 되지 않아 활용할 수 없었습니다.

직접 node_modules에서 찾아 보기로 하였습니다.
링크를 통해 해당 파일의 위치를 알 수 있습니다.

node_modules/.pnpm/webpack-dev-server@5.2.2/
node_modules/webpack-dev-server/client/modules/logger/index.js

이제 정확한 호출 경로를 파악하기 위해 console.trace를 사용했습니다.

console.trace란?

// 번들링된 코드 (bundle.js:1500:23)
function getUserInfo() {
  console.trace('여기서 호출');
}
// 브라우저 개발자 도구에서 보이는 것:
// Trace: 여기서 호출
//   at getUserInfo (src/user/userService.ts:42:5)  ← 원본 소스 위치!
//   at handleClick (src/components/UserProfile.tsx:18:12)

여기서도 이 방법을 활용했습니다.

    case LogType.info:
		console.trace('LogType.info');
        if (!debug && loglevel > LogLevel.info) return;
        console.info.apply(console, _toConsumableArray(labeledArgs()));
        break;

console.trace 덕분에 함수가 어디서 호출되는지 정확한 경로를 확인할 수 있었습니다. 이제 HMR이 중단되는 지점을 찾기 위한 단서를 얻었습니다.

드디어 문제의 핵심에 접근할 수 있는 실마리를 찾았습니다.

❗ 개발자 도구 검색기능 활용하기

호출스텍에서 알려준 파일들을 살펴보았습니다.
데브서버에서 웹소켓 이벤트 받는 곳까지 호출스텍을 타고 올라왔는데, 원인을 찾지 못했습니다.
이제 더 올라가서 이벤트를 보내는 곳을 찾아야 했습니다.

이 과정까지 오다보니 배운 사실이 있었습니다.

바로 개발자 도구 검색을 활용하면 검색 할 수 있습니다.
node_module에서 검색안되는 문제를 해결할 수 있습니다.


원하는 문자를 입력하면 됩니다.

혹은 폴더로 범위를 한정지어서 할 수도 있어서 node_modules에서 검색 안되는 문제를 해결할 수 있습니다.

이 방법을 통해 이벤트를 주는 곳을 쉽게 찾을 수 있었고, 원인을 알아냈습니다.

바로 beforeunload 이벤트였습니다.

beforeunload 이벤트란, 페이지를 떠나기 전에 발생하는 이벤트를 말합니다.

self.addEventListener("beforeunload", function (e) {
    status.isUnloading = true;
});

    if (currentStatus.isUnloading) {
        return;
    }

가설 검증: 로그 추가로 확인하기
이 로직이 정말 HMR을 막는 원인인지 확인하기 위해 직접 로그를 추가했습니다.

    if (currentStatus.isUnloading) {
        console.log("[rspack]isUnloading", currentStatus.isUnloading);
        return;
    }

코드를 수정하고 테스트해보니, 예상했던 대로 해당 로그가 출력되면서 HMR이 중단되는 것을 확인했습니다.

하지만 여전히 의문이 남았습니다. beforeunload는 사용자가 페이지를 떠날 때 발생하는 이벤트인데, 개발 중에는 페이지를 떠나지 않는데 왜 이 이벤트가 발생할까요?

원인을 확인해보니 놀라운 사실을 발견했습니다. 바로 locatorJs 도구 때문이었습니다.

locatorJs로 컴포넌트를 클릭해서 VSCode를 열 때마다 beforeunload 이벤트가 발생했습니다.
여기서 vscode를 열 때마다, beforeunload 이벤트가 발생되면서 HMR이 멈추는 것이었습니다.

😒 해결방법 고민하기

여기서 2가지를 생각해볼 수 있었습니다.

1️⃣ 즉시 해결: locatorJs 설정 변경

  • locatorJs에서 beforeunload 이벤트가 발생하지 않도록 설정
  • 빠른 해결이 가능하지만 근본적인 문제는 남아있음

2️⃣ 근본 해결: webpack-dev-server 개선

  • beforeunload 이벤트 처리 로직 자체를 개선
  • 시간이 오래 걸리지만 모든 개발자에게 도움이 됨

결론부터 말하자면 locatorJs 옵션을 수정하여 해결하였습니다.

이미 LocatorJs에선 옵션을 제공하고 있었고 이를 통해 해결할 수 있었습니다.

🛠 dev-server 개선을 시도했습니다.

여기서 멈추지 않고, 근본적인 문제 해결도 시도해보았습니다.

제가 사용하는 dev-server는 rspack/dev-server였는데, wepback dev-server를 기반으로 만들어진 것이었고, 동일하게 문제가 발생하고 있었습니다.

rspack/dev-server에서 문제를 제기해도, webpack-dev-server에 기반하기 때문에
여기에 이슈와 pr을 남겨도 반영되기 어려워보였습니다.

그래서 원조격인 webpack-dev-server에 이슈와 PR을 남겨 개선하고, 이후 반영하려고했습니다.

개선하기 위해 먼저 히스토리 조사를 했습니다.
Issue와 PR들을 찾아서 왜 추가했는지 검토했습니다.

관련 PR
을 찾아보니 특정 상황에서 필요한 로직이었습니다.
하지만 이미 이 로직을 제거하려는 시도들이 있었지만, 실패한 이력도 발견했습니다.

부작용 없이 문제를 해결할 방법을 찾아봤지만, 쉽지 않았습니다.
기존 로직을 건드리면 다른 케이스에서 문제가 발생할 수 있습니다.

또한 리뷰어에게 이 사실을 공유했지만, 새로고침이 최선이라는 답변을 받아 작업을 멈추었습니다.

근본적인 해결은 어려웠지만, 저와 같은 문제를 겪을 다른 개발자들을 위한 작은 기여는 할 수 있다고 생각했습니다.

// 개선 제안: HMR 중단 이유를 명확히 알려주는 로그 추가
if (currentStatus.isUnloading) {
    console.warn('[HMR] 업데이트가 중단되었습니다. 새로고침해주세요.');
    return;
}

이런 로그 한 줄만 추가해도 제가 겪었던 며칠간의 시행착오를 다른 개발자들이 겪지 않을 수 있습니다.

현재 이 로그 추가 PR을 webpack-dev-server에 제출한 상태이며, 기다리고 있습니다.


😎 이번에 배운 좋은 디버깅 방법들

이번 문제를 겪으면서 좋은 디버깅 방법들에 대해 배울 수 있었습니다.

1. 전역 소스 검색 기능

  • Ctrl + Shift + F (Windows) / Cmd + Option + F (Mac)
  • node_modules 포함 모든 로드 파일에서 검색 가능
  • 폴더별 범위 제한 검색으로 효율성 증대


혹은 폴더로 범위를 한정지어서 할 수도 있어서 node_modules에서 검색 안되는 문제를 해결할 수 있습니다.

2. 호출 스택 추적

  • debugger 문을 사용하면 호출 스택을 확인 가능

👊 끝으로

이번 경험을 통해 개발자로서 한층 성장했다고 느꼈습니다.

평소에는 node_modules 안을 들여다본다는 생각을 못했는데, 이제는 원인을 찾기 위해 파고들 수 있다는 생각이 들었습니다.

앞으로 이런 문제가 생긴다면 두려워하지 않고 적극적으로 도전하겠습니다.
또한 오픈소스 생태계에도 더 많이 기여하고 싶습니다.

저와 같은 문제를 겪었던 분들에게도 도움이 되었으면 좋겠습니다.

긴글 읽어주셔서 감사합니다.

profile
잘하고 싶은 사람

0개의 댓글