网页
综合
更新时间 2026-11-14 16:18:41
Hot Module Replacement | Webpack
自动蜘蛛
webpack.kr
正文约 4709 字
이 가이드는 개발 가이드에 있는 코드 예제를 확장합니다.
Hot Module Replacement(또는 HMR)는 webpack에서 제공하는 가장 유용한 기능 중 하나입니다. 모든 종류의 모듈을 새로고침 할 필요 없이 런타임에 업데이트 할 수 있습니다. 이 페이지는 구현에 초점을 맞추고 개념 페이지는 작동 원리와 왜 유용한지에 대한 자세한 내용을 제공합니다.
HMR은 프로덕션용이 아니므로 개발용으로만 사용해야 합니다. 자세한 내용은 프로덕션 구축 가이드를 참고하세요.
Webpack은 output.html로 페이지를 직접 생성할 수 있습니다. Native HTML을 참고하세요. 이 가이드에서는 모든 엔트리의 script를 포함하는 단일 페이지 대신 엔트리 포인트별 페이지를 내보내야 동일한 결과가 되므로 HtmlWebpackPlugin을 계속 사용합니다.
이 기능은 생산성을 크게 높여줍니다. webpack-dev-server 설정을 업데이트하고 webpack의 내장 HMR plugin을 사용하면 됩니다. 이제 index.js 모듈이 print.js를 사용하므로 해당 엔트리 포인트도 제거합니다.
webpack-dev-server v4.0.0부터 Hot Module Replacement가 기본적으로 활성화되어 있습니다.
webpack-dev-server 대신 webpack-dev-middleware를 사용한다면 webpack-hot-middleware 패키지를 사용하여 커스텀 서버 또는 애플리케이션에서 HMR을 활성화하세요.
HMR에 대한 수동 엔트리포인트를 제공할 수도 있습니다.
CLI를 사용하여 webpack serve --hot-only 명령어로 webpack-dev-server 구성을 수정할 수 있습니다. T> output.htmlFilename은 페이지 자체 파일명에서 [name]을 리졸브하므로, 엔트리 이름에 관계없이 내보내는 문서는 index.html로 유지됩니다.
이제 index.js 파일을 업데이트하여 print.js 내부의 변경이 감지되면 webpack에서 업데이트된 모듈을 수락하도록 지시합니다.
print.js에서 console.log 문을 변경하면 브라우저 콘솔에 다음과 같은 출력이 표시됩니다. (당분간 button.onclick = printMe 출력에 대해 걱정하지 마세요. 나중에 해당 부분을 변경할 것입니다.)
Node.js API와 함께 Webpack Dev Server를 사용하는 경우 webpack 설정 객체에 dev 서버 옵션을 추가하지 마십시오. 대신 생성 시 두 번째 매개 변수로 전달하십시오. 예를 들어 보겠습니다.
new WebpackDevServer(options, compiler)
HMR을 활성화하려면 HMR 엔트리 포인트를 포함하도록 webpack 설정 객체도 수정해야 합니다. 다음은 그 모습에 대한 간단한 예시입니다.
webpack-dev-server Node.js API 전체 문서를 참고하세요.
webpack-dev-middleware 사용의 경우 webpack-hot-middleware 패키지를 사용하여 커스텀 개발 서버에서 HMR을 활성화합니다.
Hot Module Replacement는 까다로울 수 있습니다. 이를 보여주기 위해 작업 예제로 돌아갑시다. 계속해서 예제 페이지의 버튼을 클릭하면 콘솔이 이전 printMe 함수를 인쇄하고 있음을 알 수 있습니다.
이것은 버튼의 onclick 이벤트 핸들러가 여전히 원래의 printMe 함수에 바인딩 되어 있기 때문에 발생합니다.
HMR에서 이 작업을 수행하려면 module.hot.accept를 사용하여 새 printMe 함수에 대한 바인딩을 업데이트해야 합니다.
이것은 하나의 예시일 뿐이지만 사람들이 실수할 수 있는 상황이 많이 있습니다. 운 좋게도 Hot Module Replacement를 훨씬 쉽게 만들어주는 많은 로더가 있습니다. 그중 일부는 아래에 언급되었습니다.
대부분의 HMR 혼란은 하나의 잘못된 가정에서 비롯됩니다. 업데이트가 accept 호출을 작성한 모듈을 다시 실행한다는 것입니다. 그렇지 않습니다.
모듈이 변경되면 webpack은 해당 모듈을 가져온 모듈들을 따라 위로 올라가 변경을 수락한 모듈을 찾을 때까지 이동합니다. 변경된 모듈만 다시 실행됩니다. 이를 수락한 모듈은 계속 실행됩니다 — 동일한 인스턴스, 동일한 변수, 동일한 클로저 — 그리고 반응할 수 있도록 콜백이 호출됩니다. 따라서 accept('./dep.js', cb)는 "내가 ./dep.js의 변경을 처리하겠습니다"를 의미하며, "변경될 때 나를 다시 실행하세요"가 아닙니다.
이로 인해 두 가지 결과가 발생하며, 이것이 실제로 HMR의 전부입니다.
이 예제들은 import.meta.webpackHot을 사용하며, 모듈이 자동 감지되거나 strict ESM으로 처리되는 경우 모두 동작합니다. module.hot은 이와 동등하며 CommonJS에서 유일하게 사용할 수 있는 표현이지만, strict ESM 모듈에서는 사용할 수 없습니다. 둘 다 프로덕션 빌드에서 제거되므로 if 가드를 사용하면 코드가 번들에 포함되지 않습니다.
외부에 보이는 바인딩이 없는 모듈 — 실행될 때 사이드 이펙트만 발생시키는 모듈 — 은 자체 변경을 수락할 수 있습니다. webpack은 해당 모듈을 제자리에서 다시 실행하며, 업데이트는 엔트리 포인트까지 버블링되지 않고 거기서 멈춥니다.
이것이 스타일시트가 하는 방식이며, 등록된 라우트 테이블, 차트 정의 세트, <canvas> 렌더 루프 같은 것에 적합한 선택입니다. 다른 모듈이 이 모듈이 내보낸 것을 참조하고 있는 경우에는 잘못된 선택입니다. 해당 모듈들은 import 시점에 이전 값을 캡처했으며, 모듈을 다시 실행해도 그 복사본이 업데이트되지 않습니다. 대신 참조를 소유한 모듈에서 수락하세요.
모듈을 다시 실행하면 사이드 이펙트가 두 번째로 실행됩니다. 인터벌이 두 번 스케줄되고, 리스너가 두 번 등록되고, 소켓이 다시 열립니다 — 열 번의 수정 후에는 페이지에 모든 것이 열 개씩 있게 되어 webpack과 무관한 이유로 "느리거나" "화면이 깜빡이는" 것처럼 보입니다.
dispose는 이를 해제하는 밸브입니다. 새 버전이 실행되기 전에 실행되므로, 현재 버전이 점유한 것을 되돌리는 곳입니다.
경험 법칙: 모듈 자체 평가보다 더 오래 지속되는 모든 것 — 타이머, 리스너, 소켓, 옵저버, document에 추가된 DOM 노드, 전역 레지스트리에 추가된 항목 — 은 dispose에 대응하는 줄이 필요합니다.
dispose에 전달된 data 객체는 다음 버전의 모듈에 import.meta.webpackHot.data로 전달되며, 처음 실행 시에는 undefined입니다. 이 왕복 과정이 편집 시 유지해야 할 상태 — 스크롤 위치, 폼 초안, 게임 점수, 애니메이션의 프레임 카운터 — 를 보존하는 방법입니다.
이 모듈을 편집하면 카운터가 0에서 다시 시작하는 대신 멈춘 곳에서 계속됩니다 — dispose가 이전 인터벌을 멈추고 값을 저장한 다음, 새 버전이 data에서 값을 다시 읽어옵니다.
data는 직렬화되지 않고 동일한 객체로 전달되므로, 거기에 넣은 살아있는 핸들이 유지됩니다 — 하지만 그것이 살아있게 하는 모든 것도 마찬가지입니다. 전체 컴포넌트 트리를 저장하는 대신 필요한 값만 복사하세요.
스토어, 라우터, DI 컨테이너: 전체 애플리케이션이 참조를 보유하는 한 번만 생성되는 무언가입니다. 객체 자체를 교체하면 모든 보유자가 이전 객체에 묶이게 되므로, 대신 _내용물_을 교체하고 동일성을 유지합니다. Redux 스토어가 친숙한 예시이며 — replaceReducer는 정확히 이를 위해 존재합니다.
스토어는 상태와 구독자를 유지하고, 리듀서 함수만 변경됩니다. "구현을 교체하는" 메서드가 있는 모든 싱글톤 — 라우터의 라우트 테이블, 레지스트리의 항목, 이벤트 버스의 핸들러 — 은 동일한 패턴을 따릅니다.
체인의 어떤 모듈도 변경을 수락하지 않으면 업데이트가 중단됩니다. webpack-dev-server는 페이지를 리로드하는 방식으로 응답하므로, 개발 중 전체 리로드가 발생한다면 보통 "이 파일을 수락한 모듈이 없다"는 의미이며, 설정이 잘못된 것이 아닙니다.
의도적으로 이를 제어하는 두 가지 방법이 있습니다.
HMR은 브라우저 전용이 아닙니다 — target: 'node'에서도 동작하며, 리스닝 소켓이나 인메모리 상태를 끊지 않고 요청 핸들러를 교체하는 장기 실행 서버에서 사용할 수 있습니다. 여기에는 dev-server 클라이언트가 없으므로 번들이 직접 업데이트를 요청하며, 업데이트 파일을 작성하는 와처 컴파일러에 의해 구동됩니다.
check(true)는 업데이트를 다운로드하고 적용하며, 교체된 모듈로 리졸브됩니다 — 새로운 것이 없으면 null로 리졸브됩니다. 업데이트를 적용할 수 없는 경우 거부되며, 파일을 지정하는 메시지가 표시됩니다. 예: Aborted because ./handler.js is not accepted. 서버에서 이는 프로세스를 재시작해야 한다는 신호이며, 리로드할 페이지가 없기 때문입니다.
대응하는 dispose 없이 핫 모듈에 리스너를 등록하면 브라우저에서와 마찬가지로 서버에서도 누수가 발생합니다. 단, 프로세스가 정리를 위해 다시 로드되지 않으므로 핸들이 서버가 실행되는 동안 계속 누적됩니다.
CSS Hot Module Replacement는 실제로 style-loader의 도움으로 상당히 간단합니다. 이 로더는 CSS 의존성이 업데이트될 때 <style>태그를 패치하기 위해 백그라운드에서 module.hot.accept를 사용합니다.
本页为82搜索生成的网页快照,内容来自站内索引;演示数据的原文链接仅作示例。