- 최근 Svelte 5 버전으로 웹 애플리케이션을 업그레이드한 후 겪은 이슈들 정리
- 딥 리액티비티 기능과 변경된 라이프사이클로 인한 예기치 못한 동작이 발생함
- Svelte 3/4를 오래 즐겼지만, 앞으로 새로운 프로젝트에 Svelte를 선택하지는 않을 것 같음
빠른 속도의 필요성
- Svelte 팀은 deep reactivity를 통해 성능 최적화를 시도했고, 더 나은 성능을 이끌어냄
- 기존에도 컴파일 과정을 통해 빠른 성능을 제공했으며, 이는 다른 프레임워크와 차별화되는 강점이었음
- 이는 불투명한 프레임워크를 만들어 디버깅이 어려웠으나, 성능과 생산성 면에서 타협 가능한 트레이드 오프라고 느꼈음
빠른 속도의 필요성
- Svelte 팀이 Svelte 5에서 주력하는 주요 변화는 “딥 리액티비티(deep reactivity)”로, 더 세밀한 반응성을 통해 성능을 높이려는 것임
- 이전 Svelte 버전들에서는 주로 Svelte 컴파일러를 활용해 이 목표를 달성
- 개발자가 직접 새로운 개념을 배울 필요 없이 내부 로직을 재구성하기 좋았던 점이 Svelte의 독창성을 부각시켰음
- 동시에 이러한 컴파일 과정이 프레임워크를 불투명하게 만들어, 복잡한 문제 디버깅이 어려워졌음
- 컴파일러 자체 버그로 인해 원인을 파악하기 힘든 오류가 발생했으며, 때때로 문제 컴포넌트를 전면 리팩토링해야만 해결 가능했음
- 그래도 속도와 생산성 면에서 합리적인 타협이라고 느껴, 프로젝트를 주기적으로 초기화해야 하는 불편함도 감수해 왔음
Svelte는 Javascript가 아님
- Svelte 5는 이 트레이드오프를 2배로 늘림
- 핵심 차이점은 추상화·성능의 절충 지점이 컴파일 단계를 넘어 런타임 부분에까지 침투했다는 것임
- Deep reactivity를 지원하기 위한 프록시 사용
- 암묵적인(implicit) 컴포넌트 라이프사이클 상태
- 이 두가지 변경사항은 성능을 개선하고, 개발자 API가 더 멋지게(slicker) 보이게 만듦
- 싫어할게 있을까? 안타깝지만 이 2기능은 leaky abstraction의 전형적 사례로 꼽을 수 있음
- 결국 개발자가 다루기에 더 복잡한 환경을 초래함
Proxies는 객체가 아님
- Proxy 사용으로 Svelte 팀은 개발자에게 추가 작업을 요구하지 않고도 프레임워크 성능을 약간 더 끌어올릴 수 있었음
- React 같은 프레임워크에서는 여러 컴포넌트를 거치며 상태를 전달할 때 불필요한 리렌더링을 유발하기 쉬운 편인데, Svelte는 이를 줄이려는 목적으로 Proxy를 도입했음
- Svelte 컴파일러는 기존에도 가상 DOM 비교 과정에서 생길 수 있는 일부 문제를 피했지만, Proxy를 통해 성능을 좀 더 향상시킬 수 있다고 판단한 듯함
- Svelte 팀은 Proxy가 개발자 경험 향상에도 기여한다고 언급했으며, “효율성과 사용 편의성을 모두 극대화할 수 있음”이라는 주장을 내세웠음
- 문제는 Svelte 5가 겉보기에는 더 단순해 보이지만, 실제로는 더 많은 추상화를 추가한다는 점임
- 예를 들어 배열 메서드 감지를 위해 Proxy를 쓰면, Svelte 4에서
value = value같은 코드를 작성하지 않아도 되는 이점이 생김 - Svelte 4에서는 리액티비티를 트리거하기 위해 개발자가 컴파일러의 동작 원리를 어느 정도 이해해야 했음. 반면 Svelte 5에서는 “컴파일러를 잊어도 된다”는 인상을 주지만 실제로는 그렇지 않음
- 새로운 추상화를 도입해 얻은 편의성만큼, 컴파일러가 원하는 대로 동작하도록 하기 위해 개발자가 알아야 할 규칙이 늘어났음
- 예를 들어 배열 메서드 감지를 위해 Proxy를 쓰면, Svelte 4에서
- 오랜 시간 Svelte를 사용하면서, 개인적으로는 점차 Svelte store를 더 많이 이용하고, 리액티브 선언은 덜 쓰게 되었음
- Svelte store는 기본적으로 자바스크립트 개념에 가깝고,
update메서드를 호출하는 방식이 단순하며,$문법은 부가적인 이점 정도였음 - Proxy는 리액티브 선언과 마찬가지로 “보기에는 하나처럼 보여도, 실제 경계 지점에서 다르게 작동한다”는 문제를 유발함
- Svelte store는 기본적으로 자바스크립트 개념에 가깝고,
- Svelte 5를 처음 쓸 때는 모든 것이 잘 돌아갔지만, Proxy 상태를 IndexedDB에 저장하려 하자
DataCloneError가 발생했음- 더욱이 어떤 값이 Proxy인지 확실히 파악하기 위해서는 구조화된 복제를
try/catch로 시도해야 하는데, 이는 성능 비용이 큼 - 결국, 무엇이 Proxy인지 기억하고, 외부에서 Proxy를 인식하지 못하는 문맥에는
$state.snapshot을 매번 사용해야 하게 됨 - 이는 결과적으로 “추상화가 개발 편의를 높인다”는 당초 의도와 달리, 개발자에게 더 복잡한 규칙과 절차를 요구하게 되는 상황을 초래함
- 더욱이 어떤 값이 Proxy인지 확실히 파악하기 위해서는 구조화된 복제를
컴포넌트는 함수가 아님
- 2013년 무렵 가상 DOM이 인기를 얻은 이유는 애플리케이션을 함수 조합 형태로 모델링할 수 있었기 때문임
- Svelte는 가상 DOM 대신 컴파일러를 사용해 라이프사이클 함수를 간소화하고 성능을 높이는 방식을 유지해 왔음
- 그러나 Svelte 5에서는 라이프사이클 개념이 React Hooks와 비슷하게 다시 추가됨
- React에서 Hooks는 라이프사이클 메서드의 상태 관련 코드를 줄여주는 추상화 개념임
- 코드는 깔끔해지지만,
setTimeout에서 상태를 참조할 때처럼 개발자가 주의해야 할 부분이 많음 - Svelte 4에서도 컴포넌트 언마운트 시점에 비동기 코드가 DOM 요소에 접근하면 문제가 생길 수 있었음
- 이제 Svelte 5에서는 상태 변경과 이펙트를 조율하기 위해 컴포넌트 라이프사이클에 암묵적인 상태가 추가된 것으로 보임
- 코드는 깔끔해지지만,
- $effect에 대한 공식 문서에서는 다음과 같이 설명함:
“$effect는 어디든 배치할 수 있지만, 컴포넌트 초기화 중(또는 부모 이펙트가 활성화된 동안) 호출되어야 하며, 컴포넌트(또는 부모 이펙트)가 언마운트되면 사라짐”
- 이는 라이프사이클이 마운트/언마운트 두 단계만 존재한다는 설명과 달리, 상태 변화를 추적해야 하는 복잡한 이펙트 구조가 존재함을 시사함
- 공식 라이프사이클 문서에서는 “before update/after update가 없다”라고 말하지만,
$effect.pre와tick같은 새로운 개념이 등장함 - 이는 사실상 마운트/언마운트 외에도 상태 변화 시점에 대한 이해가 필요함을 뜻함
- 실제 사용 중 문제를 일으킨 부분은, Svelte와 무관한 함수에 넘겨진 상태 역시 컴포넌트 라이프사이클에 묶인다는 점임
- 예를 들어, 모달 창을 스토어로 관리하면서 콜백을 자식 컴포넌트에 전달하는 패턴을 사용했음
const { value } = $props() const callback = () => console.log(value) const openModal = () => pushModal(MyModal, { callback }) - 만약 이 코드가 모달 컴포넌트 내부에 있다면, 모달을 호출한 컴포넌트가 먼저 언마운트되고, 그 시점에 value가 undefined로 변경됨
- 이 리포지토리에 미니멀 재현 예시가 올라와 있음
- 즉, 컴포넌트 생명주기가 끝난 후에도 살아있는 콜백에서 참조하던 props가 갑자기 undefined가 됨
- 이는 기본 자바스크립트와 다른 동작 방식이며, Svelte가 자체적으로 가비지 컬렉션 같은 작업을 하는 것처럼 보임
- 엔지니어링적인 이유가 있겠지만, 예상치 못한 동작이라 놀라움
결론
- 쉬운 것은 분명 매력적이지만, Rich Hickey가 말했듯이 쉬움이 곧 단순함을 의미하지는 않음
- Joel Spolsky의 말처럼, 예기치 못한 동작이 일어나는 것을 달가워하지 않음
- Svelte는 그동안 많은 ‘마법’을 보여줬지만, 이번 버전에서는 그 마법을 사용하기 위해 암기해야 할 것이 많아져 이득보다 부담이 커졌음
- 이 글의 요점은 Svelte 팀을 비난하기 위함이 아니며, 오히려 Svelte 5(와 React Hooks)를 선호하는 사람들이 많다는 사실을 인지하고 있음
- 중요한 것은 사용자에게 편의를 제공하는 것과 사용자가 주도권을 가질 수 있는 것 사이의 균형임
- 진정한 좋은 소프트웨어는 ‘영리함’이 아니라 ‘이해’에 기반함
- AI 도구가 발전함에 따라, 자신이 무엇을 하고 있는지 모르게 만드는 툴보다 이미 축적한 지혜를 활용하고 깊은 이해를 돕는 툴을 선택하는 것이 중요함
- Rich Harris와 팀에게 그간의 즐거운 개발 경험에 감사함. 이 글이 부정확하지만은 않은 피드백이 되길 바람