- 발행일
JSTree & VTree 사전 조사 — 그리고 1년 뒤 무엇이 살아남았나
JSTree & VTree 사전 조사 — 그리고 1년 뒤 무엇이 살아남았나
표준데이터 관리 시스템에서 트리 구조(메타 클래스 계층, ASN.1, 개념 그래프)를 시각화해야 해서 두 라이브러리를 조사하고 붙였던 기록입니다. 당시의 조사 내용을 두고, 1년 넘게 운영한 뒤의 결말과 당시 코드에서 지금 보면 걸리는 지점을 뒤에 붙였습니다.
1. 두 라이브러리의 성격
| jsTree | VTree | |
|---|---|---|
| 기반 | jQuery 플러그인 | D3 래퍼 |
| 잘하는 것 | 깊은 뎁스의 목록형 탐색 — 접기/펼치기, 선택, 아이콘 | 관계의 시각적 표현 — 노드를 공간에 배치 |
| 데이터 | {text, children} JSON | 일반 JSON을 reader로 변환 |
2. jsTree 기본 사용
Jinja 템플릿에서 서버 데이터를 받아 초기화하는 형태로 썼습니다.
$(`#{{ jstree_id }}`).jstree({
core: {
data: jsonObj,
themes: { name: 'proton', responsive: true },
},
types: {
default: { icon: 'far fa-folder text-primary' },
file: { icon: 'far fa-file text-primary' },
},
plugins: ['types'],
});
// 노드 선택 → 해당 클래스의 속성 테이블을 AJAX로 조회
$('#classTree').on('select_node.jstree', (e, data) => {
showMetaClassTable(data.node.id, data.node.text);
});
자주 쓴 이벤트만 추리면:
| 이벤트 | 시점 | 용도 |
|---|---|---|
select_node.jstree | 노드 클릭/선택 | 상세 조회 트리거 — 가장 많이 씀 |
changed.jstree | 선택 집합 변경 | 다중 선택 감지 |
open_node.jstree / close_node.jstree | 펼침/접힘 | 지연 로딩, 상태 저장 |
loaded.jstree | 초기 로드 완료 | open_all, 첫 노드 자동 선택 |
refresh.jstree | .refresh() 호출 | 새로고침 버튼 연동 |
기본 사용법과 모달 연동은 별도 글로 정리했습니다: Jstree 기본 · 컨텍스트 메뉴와 모달
3. VTree 사용
const vt = new VTree(vtreeEl);
vt.width(width).height(height);
const reader = new VTree.reader.Object(); // 일반 JSON → VTree 구조로 변환
vt.data(reader.read(jsonData)).update();
브라우저 크기를 재서 영역을 잡고, reader로 JSON을 변환해 렌더하는 구조입니다. 조사 시점의 판단은 "jsTree는 깊은 뎁스, VTree는 관계 표현"이라는 분업이었습니다.
4. 1년 뒤 — 무엇이 살아남았나
지금 코드베이스를 검색하면 결말이 명확합니다.
- jsTree: 생존, 오히려 확대. 메타 클래스 트리, OID 트리, 인스턴스 트리 등 10개 이상의 화면에서 트리 탐색의 기본이 됐습니다. 인라인 스크립트였던 초기 코드는 ES 모듈(
metaClassTree.js+ 공용 유틸)로 재편됐고요. - VTree: 완전 제거.
new VTree는 코드베이스에 한 줄도 없습니다. 관계 시각화는 전부 D3 직접 구현으로 갔어요 — 관계도 연쇄 탐색, 선버스트, 데이터 맵, 워드클라우드.
갈린 기준을 지금 말로 정리하면 이렇습니다.
정형화된 상호작용(목록형 트리 탐색)은 라이브러리가 이기고, 표현 자체가 요구사항인 화면(관계 시각화)은 래퍼가 진다.
jsTree가 이긴 이유는 트리 탐색의 요구사항이 어느 프로젝트나 비슷해서입니다 — 접고 펼치고 선택하고. 반면 관계 시각화는 화면마다 요구가 달랐습니다. 연쇄 탐색은 노드 겹침을 힘 시뮬레이션으로 풀어야 했고, ASN.1 트리는 Graphviz에서 D3로 옮기며 렌더링을 처음부터 설계했어요. VTree 같은 래퍼는 이 요구들 앞에서 "래퍼가 지원하는 것까지만 가능"이라는 천장이 됐고, 어차피 D3를 배워야 한다면 래퍼를 끼울 이유가 없었습니다.
5. 당시 코드에서 지금 보면 걸리는 것
사전 조사 단계의 코드라 지금 기준으로는 손볼 게 많았고, 실제로 대부분 이후에 고쳐졌습니다. 뭐가 문제였는지 남겨둡니다.
- 노드의 표시 텍스트를 URL 식별자로 썼습니다.
location.href = `/…/concept_graph/${선택 노드의 text}/`— 이름이 중복되거나 특수문자가 들어가면 깨집니다. 화면에 보이는 문자열과 리소스 식별자는 다른 것인데 섞어 썼어요. 현재 코드는node.id기반으로 바뀌었습니다. - 서버 JSON을 템플릿 리터럴로 주입했습니다.
JSON.parse(`{{ data | safe }}`)— 데이터에 백틱이나${가 들어오는 순간 스크립트가 통째로 깨지고,| safe라 이스케이프도 없으니 XSS 표면이기도 합니다. Django에는 정확히 이 용도의json_script필터가 있고, 그게 정석입니다. - 초기화를 두 번 하는 줄에 "완전 초기화 마무리"라는 주석을 달아놨습니다.
.on(…).jstree()— 이미 초기화된 트리에 인자 없이 재호출하면 아무 일도 안 일어납니다. 동작을 이해 못 한 채로 "되니까 두는" 코드에 그럴듯한 주석까지 붙인 사례라, 지금 보면 제일 뜨끔한 부분이에요. - 이벤트 17종 전부에 console.log를 바인딩한 디버그 코드가 그대로 실려 있었고, AJAX 결과를 문자열 연결 innerHTML로 그려서 서버 텍스트가 이스케이프 없이 DOM에 들어갔습니다.
- 응답 필드명에
is_mandatary오타가 있었습니다(테이블에 undefined로 렌더될 자리). 현재 코드베이스에는 이 오타가 0건 — 고쳐졌습니다. - 원문의 VTree "공식 문서" 링크는 사실 d3-selection 이벤트 문서였습니다. 조사 글의 참고 링크가 잘못돼 있으면 조사 자체의 신뢰가 흔들리는데, VTree가 D3 래퍼다 보니 뒤섞어 적은 거였어요.
돌아보면
- 사전 조사의 답은 조사가 아니라 운영이 알려줬습니다. "jsTree는 뎁스, VTree는 관계"라는 당시 결론에서 앞쪽만 살아남았어요. 래퍼 라이브러리를 고를 때는 "지금 되는가"보다 **"요구가 래퍼의 천장을 넘으면 어떻게 되는가"**를 물었어야 했습니다.
- 한계 — jsTree도 무비용이 아닙니다. jQuery 의존이 그대로 남았고, 노드 수천 개 규모에서의 렌더 성능은 검증하지 않았습니다(현재 화면들은 수백 노드 수준). jQuery 없는 대안(가상 스크롤 트리 등)으로의 이전은 필요가 생기기 전까지는 하지 않기로 한 상태입니다.