1. 개발 배경
DOM 테이블의 한계
프론트엔드 개발을 하다 보면 테이블은 피할 수 없는 존재이다.
사내에서든, 사이드 프로젝트에서든, 데이터가 좀만 많아지면 DOM 기반 테이블은 한계를 드러낸다. 1만 행만 넘어가도 렌더링에 2초 이상 걸리고, 스크롤할 때마다 30~45fps로 뚝뚝 끊기고, 행 하나당 ~1KB의 DOM 노드가 메모리를 잡아먹는다.
물론 가상 스크롤을 쓰면 좀 나아지지만, 결국 React의 렌더 사이클을 타는 이상 근본적인 한계가 있다.
스크롤할 때마다 React가 re-render를 하는데, 이걸 어떻게 60fps로 만들어?
이 질문이 프로젝트의 시작이었다.
TanStack Table은 좋은데...
TanStack Table은 훌륭한 headless 테이블 라이브러리이다. API 설계가 깔끔하고, 자유도가 높고, 커뮤니티도 크다.
근데 headless라는 말은 결국 렌더링은 개발자 몫이라는 뜻이고, DOM으로 렌더링하는 이상 위에서 말한 성능 문제를 피할 수 없다.
TanStack Table의 자유도 + Canvas/WASM의 성능
이 두 개를 합치면 어떨까? 하는 생각이 들었다.
Canvas로 테이블을?
Canvas로 테이블을 그리겠다는 발상은 새로운 건 아니다. Google Sheets가 대표적이고, 상용 그리드 라이브러리 중에서도 Canvas 기반인 것들이 있다.
근데 대부분 독자적인 API를 쓰거나, React와의 통합이 매끄럽지 않거나, 코드가 비공개이다.
내가 원한 건 이런 거였다:
- React 컴포넌트처럼 쓸 수 있을 것 (JSX로 컬럼 정의)
- TanStack Table API와 호환되어 러닝 커브가 없을 것
- 스크롤 핫패스에서 React re-render가 전혀 없을 것
- 100만 행 이상에서도 60fps
그리고 레이아웃 계산은? 러스트로 하면 되지.
러스트 + WASM, 또 너야
hwpjs에서 러스트로 호들갑을 떨었고, 이번에도 러스트를 안 쓸 이유가 없었다.
특히 셀 레이아웃 계산은 CPU 집약적인 작업이라 WASM으로 돌리면 자바스크립트 대비 유의미한 성능 향상을 기대할 수 있었다. 게다가 Taffy라는 러스트 flexbox 레이아웃 엔진이 있다는 걸 알고 있었고, 이걸 셀 내부 레이아웃에 쓰면 Canvas 위에서도 flexbox를 쓸 수 있겠다 싶었다.
Canvas 안에서 flexbox를 쓴다고?
맞다. 미친 소리 같지만, Taffy가 계산한 레이아웃 좌표를 Canvas에 그리면 된다. DOM 없이.
13일의 광기
레포지토리를 만든 게 2월 24일이고, 마지막 커밋이 3월 10일이다. 13일 동안 636개의 커밋을 찍었다.
하루 평균 49커밋. 2월 28일에는 하루에 113커밋을 찍었다.
미친 짓인 건 알지만, 도파민이 멈추질 않았다. Canvas에 셀이 하나씩 그려질 때마다, WASM에서 레이아웃이 계산될 때마다, 스크롤이 60fps로 부드럽게 돌아갈 때마다 손을 멈출 수가 없었다.
DOM 테이블의 성능 한계를 Canvas + Rust WASM으로 돌파하고, TanStack Table의 DX를 유지하는 테이블 컴포넌트를 13일 만에 만들었다.