1. 개발 배경
DOM 테이블의 한계
프론트엔드 개발을 하다 보면 테이블은 피할 수 없는 존재이다.
행과 셀이 많아지면 DOM 노드 수, 셀 구성, 업데이트 방식에 따라 렌더링과 메모리 비용이 커질 수 있다. 이 프로젝트는 그 비용을 줄이는 다른 구현을 실험하려고 시작했다.
가상 스크롤은 중요한 해결책이다. 여기에 스크롤 중 React 작업을 줄이고 레이아웃 계산을 별도 엔진으로 옮기는 구조를 함께 실험하고 싶었다.
스크롤할 때마다 React가 re-render를 하는데, 이걸 어떻게 60fps로 만들어?
이 질문이 프로젝트의 시작이었다.
TanStack Table은 좋은데...
TanStack Table은 훌륭한 headless 테이블 라이브러리이다. API 설계가 깔끔하고, 자유도가 높고, 커뮤니티도 크다.
headless라는 말은 렌더링 방식을 개발자가 고를 수 있다는 뜻이다. 같은 API 경험을 Canvas 렌더러에도 연결해 보고 싶었다.
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일 만에 만들었다.