프론트엔드 개발 환경 공부 #18 핫 모듈 리플레이스먼트

핫 모듈 리플레이스먼트 개요

웹팩 개발 서버에서는 HTML이나 JS의 소스 코드를 변경했을 때, 모든 소스 코드를 다시 가져오면서 화면을 다시 그려주는 방식으로 우리에게 개발 편의성을 제공했다.

그런데 SPA(Single Page Application)에서는 빈 html인 index.html과 같은 파일에 app.js라는 하나의 js파일로 모든 것을 로드한다.

이와 같은 경우 리프레시 후에 모든 데이터가 초기화 되어 버린다. 다른 부분을 수정했는데 내가 의도하지 않은 부분까지 리프레시되어 개발하는데 불편할 수 있다.

전체를 갱신하지 않고, 변경한 모듈만 바꿔준다면 어떨까? 핫 모듈 리플레이스먼트는 이러한 불편함을 개선하는 목적을 가진 웹팩 개발서버의 한 기능이다.

핫 모듈 리플레이스먼트 설정 전 테스트

Alice라는 유저를 검색하는 것을 테스트하려고 위와 같이 세팅한 상태에서 아래쪽에 리스트에 ---라는 구분선이 넣고싶어졌다고 가정해보자.

위와 같이 소스코드를 바꾸고 저장하면?

모든게 새로고침되며 입력했던 Alice가 사라진다.

핫 모듈 리플레이스먼트가 적용되면 변경된 모듈만 새로고침되고 변경하지 않은 것은 새로고침 되지 않아야 한다.

핫 모듈 리플레이스먼트 설정하기

간단히 webpack.config.js에서 devServer에 관한 설정을 해주면 된다.

  devServer: {
    client: {
      overlay: true,
      logging: "info",
    },
    onBeforeSetupMiddleware: (devServer) => {
      devServer.app.use(apiMocker("/api", "mocks/api"));
    },
    hot: true,
  },

그런데 나는 이 부분에서 막혀서 한참을 헤맸다. 왜냐면, 내가 사용하던 프로젝트에서는 프로젝트 내부에 또 다른 프로젝트를 품는 형식으로 되어있는데, 뭐가 잘못된건지 계속해서 HMR이 적용되지 않았다. 결국 그냥 프로젝트 하나를 새로 만들어서 다시 적용하니 멀쩡히 잘 적용이 되었다.

핫 모듈 리플레이스먼트 제대로 설정됐나 확인하기

import form from "./form.js";
import result from "./result.js";

document.addEventListener("DOMContentLoaded", async () => {
  const formEl = document.createElement("div");
  formEl.innerHTML = form.render();
  document.body.appendChild(formEl);

  const resultEl = document.createElement("div");
  resultEl.innerHTML = await result.render();
  document.body.appendChild(resultEl);
});

if (module.hot) {
  console.log("핫모듈 켜짐");

  module.hot.accept("./result.js", () => {
    console.log("result 모듈 변경됨");
  });
}

위와 같이 module.hot에 조건문을 걸고, 핫 모듈이 켜졌을 때 제대로 로그를 찍는지, ./result.js가 변경되었을 때 제대로 로그를 찍는지를 살펴보자.

로그가 잘 뜨면 성공이다.

그렇다면 반면에 module.hot.accept()로 지정하지 않은 form.js가 수정되면 어떻게 될까?

전체 페이지가 새로고침되며 위와 같은 로그가 뜬다.

핫 모듈 리플레이스먼트 적용 확인하기

app.js

import form from "./form.js";
import result from "./result.js";

const resultEl = document.createElement("div");

document.addEventListener("DOMContentLoaded", async () => {
  const formEl = document.createElement("div");
  formEl.innerHTML = form.render();
  document.body.appendChild(formEl);

  resultEl.innerHTML = await result.render();
  document.body.appendChild(resultEl);
});

if (module.hot) {
  console.log("핫모듈 켜짐");

  module.hot.accept("./result.js", async () => {
    console.log("result 모듈 변경됨");
    resultEl.innerHTML = await result.render();
  });
}

resultEl을 밖으로 빼고, 전역화 시켰다. 그리고 module.hot.accept()의 콜백함수에도 await result.render();를 넣어주었다.

이제 result.js에 변경사항이 있을 때, 화면 전체가 갱신되지 않고, 딱 갱신한 부분만 갱신이 된다.

핫 모듈 리플레이스먼트를 지원하는 로더

모든 로더가 HMR을 지원하지 않는다. HMR 인터페이스를 구현해야지만 핫 로딩 리플레이스먼트를 지원한다. 웹팩 기본 편에서 보았던 style-loader가 그렇다.

그래서 app.css에서 무언가 스타일을 변경해도 HMR(Hot Module Replacement)이 잘 지원된다.

이미지와 같은 것들도 HMR을 지원하기 때문에, 이미지를 바꿔도 HMR이 잘 적용된다.

좋은 웹페이지 즐겨찾기