목록으로

HTML 문서를 확장하다, htmx

낱글, 입문
2024. 6. 18. PM 7:18:50

htmx는 동적 웹 페이지를 만드는 JavaScript 라이브러리로, 작고 사용하기 쉬운 도구입니다. 웬지 HTML Extension을 htmx로 줄인 이름같지 않나요? 실 유래가 무엇이든, htmx의 큰 매력은 기존 HTML을 확장하여, JavaScript를 적게 사용하면서도 동적인 웹 페이지를 쉽게 만들 수 있다는 점이에요. 분명 JavaScript로 동작하는 건데, 마치 HTML 자체에 기능을 추가한 것처럼 HTML 문서를 작성하듯이 동적인 웹 페이지 기능을 구현할 수 있지요. 예를 들겠습니다.

어떤 동적인 기능을 할 것인지 주석으로 의사 코드(pseudo code)를 작성했습니다. 이제 각 의사 코드를 htmx로 구현해볼게요.

HTML 태그에 속성(attribute)을 지정하여 무엇을 어떻게 수행할 것인지 선언적으로(declarative) 구현했습니다. htmx는 이렇게 HTML에 선언된 htmx용 속성을 참조하여 JavaScript로 처리해줍니다.

목차 표시 기능 구현 사례

눈치채신 분이 계실 것 같은데, 푸딩캠프의 컨텐츠 상세보기 페이지에 있는 목차는 htmx를 활용해 표시합니다.

푸딩캠프는 자체 컨텐츠 관리 체계(Content Management System)를 구축했는데, 컨텐츠 구조는 목차같은 계층적(hierarchical) 구조보다는 양방향(bidirectional) 또는 그래프 구조에 조금 더 가까워요. 그렇다보니 목차라는 기능의 단순성에 비해 목차같은 계층을 표현하는 데 들어가는 연산 비용이 있는 편이지요.

허나 그건 푸딩캠프의 사정이고, 학습자는 봐야할 컨텐츠가 빨리 떠야 답답하지 않잖아요? 그래서 컨텐츠를 먼저 화면에 표시하고, 목차는 HTML로 부분 렌더링(partial rendering)하여 비동기적으로 서버에서 가져와 표시합니다. 실제 htmx 코드는 이렇습니다.

앞선 예제 코드와 거의 동일하죠? delay:500ms라는 부분만 다른데, 이 HTML 요소가 화면에 노출되면 0.5초 후에 작업을 수행하라는 지연 시간을 설정한 것입니다.

피드백 남기기 기능 구현 사례

푸딩캠프 알파 버전에 있던 피드백 기능 중 몇 가지에도 htmx를 활용했는데, 이 부분은 목차 가져오는 것보다 더 복잡한 절차를 거쳐 화면에 표시되지요. 이런 저런 과정을 거치기 때문에 컨텐츠 본문을 빨리 띄우는데 조금이라도 방해가 되지 않길 바랐습니다. 그래서 컨텐츠에 사용자가 남긴 피드백을 가져와 표시하는 동작은 컨텐츠 본문을 출력한 후에 수행하도록 구현했어요.

그런데 앞선 코드와 다소 다르지요? hx-swap도 그렇고, 동작을 일으킬(trigger) 이벤트도 load가 아니라 fetch고요.

차이점은 후술하기로 하고, 이 기능이 동작하는 과정부터 살펴볼게요.

  1. 서버에게 데이터 요청 (위 코드)

  2. 서버는 필요한 데이터를 JSON으로 가공하여 응답하면서 어떤 동작을 하라고 언급

  3. htmx가 서버로부터 받은 언급에 따라 JSON 데이터를 지정한 작업에 전달하며 수행하도록 함

fetch 이벤트는 웹 브라우저나 htmx에 없는 사용자 이벤트(Custom event)입니다. 그래서 위 코드만으로는 아무 동작도 일어나지 않습니다. fetch 이벤트가 발생(trigger)해야 하지요. 실은 코드 한 줄이 더 필요합니다.

htmx는 웹 페이지를 쭉 분석하여 hx- 접두사가 붙은 HTML 속성이 선언된 HTML 요소(element)를 파악해둡니다. htmx.trigger()와 같이 htmx에게 이벤트를 발생시키면, htmx는 자신이 파악한 HTML 요소 중 대상을 찾아내 이벤트를 일으킵니다.

load 이벤트로 하면 간단한데 굳이 이렇게 한 이유는 사용자가 피드백을 남기면 바로 현재 보는 화면에 반영해야 하는 상황이 있어서 그렇습니다. 사용자가 피드백을 남기고 나면 fetch 이벤트를 일으켜서 서버로부터 데이터를 새로 가져와 현 화면에 반영하도록 하는 것인데, 웹 페이지를 출력할 때, 그리고 피드백을 남긴 후, 이렇게 두 상황에서 피드백을 가져오는 동작을 하므로, 사용자 이벤트로 구현을 재사용한 거지요.

hx-swapnone으로 지정하면 값 이름 그대로 htmx는 HTML 요소 어느 것에도 서버로부터 받은 데이터를 반영하지(swap) 않습니다. htmx는 서버로부터 받은 데이터가 HTML 문서라고 전제해서 서버가 JSON 데이터를 보내도 JSON 자료형(application/json)으로 다루지 않고 HTML 문서형(text/html)으로 다루는데요. 제게 필요한 동작은 서버로부터 JSON 데이터를 받은 후 이 데이터를 JavaScript로 후처리하는 것입니다. HTML로 출력(rendering)하는 게 아니지요.

htmx는 이런 상황에 대응할 수 있도록 서버가 클라이언트에게, 즉 htmx에게 이벤트를 일으키도록 하는 기능을 제공합니다. HTTP Header에 HX-Trigger 키를 사용해 발생시킬(trigger) 이벤트명을 값으로 제공하면 htmx는 HTTP header에 언급된 이벤트를 일으킵니다. HX-Trigger: fetch라고 HTTP Header에 명시하면 htmx가 이를 받아 fetch 이벤트를 일으키는 것이지요.

그런데 이벤트를 받아줄 대상이 있어야 하잖아요. htmx는 HTML문서 본문의 시작점인 body, 즉 document.body에게 이벤트를 발생시킵니다. document.body는 HTML 문서에 반드시 하나 존재해야 하니 개발자는 document.body가 없을지도 모르는 상황을 염두에 두지 않아도 되죠. 그래서 서버가 보내온 이벤트를 감지하는 건 document.body 가 합니다.

서버가 이벤트에 담아 보내온 데이터(payload)는 이벤트 객체의 detail 속성에 담깁니다. 왜냐하면 웹 표준 기술에서 사용자 이벤트(CustomEvent)의 적재 데이터(payload)는 detail에 담도록 하기 때문이죠.

이벤트를 일으키는 것 말고도 유용한 기능이 더 있습니다. 지정한 URL로 이동시킨다든가(redirect) 웹브라우저 화면을 새로고침(refresh)할 수 있지요.

이 동작과 흐름이 흥미로운 점은, 웹 브라우저에 탑재된 표준 기술만으로 전역 공간(global state/storage)과 이벤트 시스템을 간편하게 활용하도록 한 점입니다. 즉, 웹을 학습하면 전역 공간이나 이벤트 시스템에 필요한 구현이나 도구를 추가로 학습할 필요가 없지요. 예를 들면, 이렇습니다.

폼(form)을 제출하면(submit) 서버로부터 받은 데이터를 #container HTML 요소에 채워넣습니다(hx-swap). 서버가 JSON 데이터로 응답을 하면 JSON 데이터가 날것으로(raw) 채워지는 것이지요. 서버는 여기에 더해 swap 동작을 마치고 나면 helloWorld 이벤트를 일으키라고 HTTP header에 언급합니다(HX-Trigger-After-Swap). helloWorld 이벤트에 대해선 JSON 날 것 데이터를 가져와 JavaScript 데이터로 변환하고(JSON.parse()), 필요한 동작을 수행합니다.

이런 코드와 동작은 문서가 선언적으로(declarative) 작성되고, 이벤트 시스템으로 필요한 동작을 일으키는 HTML과 웹에 잘 부합하며, 심지어 간결합니다.

작업 방식 변화

푸딩캠프가 htmx를 채택한 이유는 신경쓰고 관리할 요소를 적게 유지할 수 있어서 생산성 유지에 도움이 되기 때문입니다. 그래서 Front-end UI 개발에 React는 컨텐츠 저작도구(Authoring tool)에만 사용했고, 나머지는 서버측 구현으로 간소화하고 일원화하고 있습니다.

서버에서 렌더링은 템플릿 파일로 통일하고, 필요한 건 서버 구현으로 채웁니다. React 등 도구를 쓰지 않고 사실상 HTML, CSS만 다루는 셈이기 때문에 기술스택이 단순하고 문제가 발생하는 범위도 매우 한정적이죠. 예를 들어, 화면에 데이터가 제대로 안 나오는 경우, 클라이언트측 렌더링(Client Side Rendering) 기술을 사용하면 서버와 클라이언트 구현을 모두 살펴보며 문제를 찾아야 합니다. 하지만 현 기술스택은 서버 구현만 살펴보면 되지요.

표현과 데이터 구조, 구현 간 합을(interface) 얼추 맞추고 나면 비로소 사용자 인터페이스 구현을 하는데, htmx을 사용하면 기존 템플릿 파일에 몇 가지 선언만 하면 동적 웹 페이지에 필요한 대부분 기능은 충족됩니다. 아직 푸딩캠프 Front-end엔 복잡하게 UI 상태 관리가 필요한 요소가 없거든요. 작업 환경이 서로 다른 서버 구현 작업과 클라이언트 구현 작업 간 맥락 전환이 적어 집중력을 효율적으로 유지할 수 있어요.

조금 더 들어가면, 복잡한 서버측 렌더링(Server Side Rendering) 결과물을 캐쉬(Cache)로 다루려면 추가로 구현하고 관리할 요소가 발생하지만, 서버측 렌더링 결과물인 HTML을 HTTP API 단위로 분리하면 쉽고 간편한 API단 캐쉬 처리가 가능해집니다. 구현과 캐쉬 대상, API가 서로 응집되어 관리하기 편해지는 거지요.

그래서 React같은 도구로 이미 구현한 많은 부분을 제거하고 htmx로 단순화했습니다. 채택해 사용해보니 작은 개발팀이 작고 가볍고 빠르게 간단한 동적 웹 페이지를 구현해야 한다면 htmx는 아주 좋은 도구라 생각이 듭니다.

푸딩캠프 뉴스레터를 구독하면 학습과 성장, 기술에 관해 요약된 컨텐츠를 매주 편하게 받아보실 수 있습니다.