Hot Module Replacement 기능 개발하기
최근 UI 프레임워크들은 필수로 이 기능이 들어가있습니다.
번들러(vite, webpack, rollup) 를 통해서 개발을 할 때 특정 컴포넌트 파일이 수정이 되면 화면을 무조건 새로 고침하는게 아니라 해당 렌더링 되는 컴포넌트만 바뀌도록 되어 있습니다.
제가 만들고 있는 sapa 도 이 기능을 제대로 동작시키기 위해서 계속 업데이트를 하고 있는데요.
기본적으로 컴파일러로 코드를 뒤집어서 재구성 해야하는 방식이기 때문에 쉽지가 않네요.
-----
이 기능의 기본 컨셉은 import() 함수를 사용한 모듈을 자동으로 가지고 오는 것입니다.
하지만 기본적으로 JS 는 import() 이후에 들어온 것들에 대해서 인지를 하지 못합니다. 정확히는 같은 패스로 불려왔다 치더라도 다른 컴포넌트가 됩니다. 이름만 같을 뿐 .
아래 예를 잠시 보시죠.
import A from "a";
function B () {
return A;
}
B 함수는 과거의 A 밖에 모릅니다. import 로 아무리 새로운 A 를 가지고 와도 B 는 과거의 A 만 알 수 있죠.
이말은 기존의 모듈을 읽어 들이는 방식을 바꿔야 한다는 것과 같습니다.
즉, B 는 아래와 같이 해야합니다.
function B () {
return getModule(A);
}
registerModule(uniqueId, { A });
uniqueId 어딘가에 A 를 등록해놓고 B 는 A 를 바로 호출하지 않고 새로운 A 를 찾을 수 있도록 함수로 다시 감싸야 합니다.
------
흠 그럼 개발 방식일 다 바꿔야 하나요? 라고 물어볼 수 있습니다.
아닙니다.
react 는 react-refresh 라는 라이브러리를 제공하고 있고 vuejs 도 마찬가지로 그런 것들이 있습니다.
그런데 재미난건 react-refresh 코드를 보면 babel 로 코드를 완전히 재구성하는 것을 볼 수 있습니다.
개발방식을 안 바꾼다면서요?
우리가 안 바꿀 뿐, 번들러가 알아서 해주는데, 결국은 번들러도 함수 형태로 모듈을 로드 해야하기 때문에 그런 형태로 바꿔야 합니다.
어떻게 어떻게 컴파일러로 구문 분석 해서 export 되는 애들만 다른 모듈 형태로 감싸고 리턴하게끔 구조를 바꾸는 것입니다.
-------
이게 단순히 여기서 그냥 바꾸면 될 줄 알았는데요. 좀 더 본질적인 문제가 있습니다.
실제 컴포넌트가 렌더링 될 때 이전에 렌더링 된 컴포넌트의 상태를 그대로 유지를 해야할지가 고민이 됩니다.
그 말은 [과거 A] 가 가지고 있던 몇가지 데이타를 [새로운 A] 에게 넘겨줄 수 있어야 하는데요. 여기서 진도가 잘 안나가네요.
어떻게 하면 잘 풀수 있을까?
-----
컴포넌트가 새롭게 로드되면 내부에 가지고 있던 훅 정보를 어떻게 유지를 해야할까요?
일단 기본적으로 Sapa 는 리액트에서 지원하는 몇가지 훅들을 비슷하게 지원합니다.
useState
useState 는 하나의 값으로 들어가 있기 때문에 값을 복사해서 그대로 유지 시켜 주면 됩니다.
다만 변경하는 함수가 문제인데요.
이건 해당 컴포넌트를 변경하는 함수 기준으로 작성하면 넘어갈 수 있습니다.
그럴려면 해당 함수에 component 의 instance 를 넣어줘야 하네요.
그 외 Hook 들
함수 기준으로 작동하기 때문에 모두 초기화 합니다.
초기 생성된 함수의 context 가 옛날 클래스의 context 로 동작할거기 때문에 새로 생성된 context 랑 안 맞을 거에요.
즉, 옛날 콜백을 부르겠죠. 그래서 모두 지워줍니다.
Hook을 지우고 재구성 할 때는 hook 의 순서를 맞춰줘야 하기 때문에 훅 배열에서 특정 부분만 지우면 됩니다.
----------
이로써 대략적인 초기화를 할 수 있게 되었습니다.