자바스크립트 환경에 관심을 가져온 사람이라면 웹 애플리케이션 개발이 점점 단순화되는 추세임을 인지하고 있을 것이다. 이 추세의 한 측면은 최대한 많은 작업을 HTML, REST, HATEOAS(애플리케이션 상태 엔진으로서의 하이퍼미디어)를 활용해 처리하는 것이다. 여기서는 네트워크를 통해 움직이는 HTML을 사용해 단일 페이지 스타일의 애플리케이션을 구축하기 위한 툴 모음인 핫와이어(Hotwire)에 대해 살펴본다.
핫와이어는 프론트엔드 웹 개발을 위한 참신한 접근 방식이다. 인기도 상당해서, 이 기사를 작성하는 시점을 기준으로 깃허브 별점 3만 3,000개 이상이며 NPM 주간 다운로드 횟수는 49만 3,000회에 달한다.
핫와이어 : HTMX의 대안
핫와이어는 HTMX와 비슷한 원칙을 기반으로 구축됐으며, HTML을 통해 웹을 구동하는 대안적 접근 방식을 제공한다. 핫와이어와 HTMX 프로젝트 모두 불필요한 자바스크립트를 덜어내고 개발자가 단순한 마크업으로 더 많은 일을 할 수 있도록 하는 데 주력한다. 또한 둘 다 HATEOAS와 REST의 원형을 포용한다. 여기서 중요한 부분은 애플리케이션 마크업에 상태(또는 데이터)와 데이터 표시 방식을 정의하는 구조를 모두 담을 수 있다는 점이다. 즉, 양쪽에서 JSON을 처리해야 하는 불필요한 마샬링을 피할 수 있다.
새로운 개념은 아니다. 사실 이것이 표현 상태 전송(REST)의 본질이다. 서버에서 특수한 데이터 형식(JSON)으로 변환한 다음 이를 클라이언트로 보내고 클라이언트에서 다시 UI용(HTML)으로 변환하는 대신, 서버가 그냥 HTML을 보내도록 하는 것이다.
HTMX, 핫와이어와 같은 기술은 이 프로세스를 간소화해서 에이잭스(Ajax)에서 생성하는 끝없는 마이크로 상호작용에 익숙해진 개발자와 사용자가 쉽게 받아들일 수 있게 해준다.
핫와이어에는 다음과 같은 3가지 주요 자바스크립트 구성요소가 있다.
- - 터보(Turbo) : 페이지 업데이트를 세밀하게 제어할 수 있게 해준다.
- - 스티뮬러스(Stimulus) : 클라이언트 측 상호작용을 위한 간결한 라이브러리
- - 네이티브(Native) : 터보와 스티뮬러스를 기반으로 iOS 및 안드로이드 네이티브 앱을 만들기 위한 라이브러리
여기서는 터보와 스티뮬러스를 살펴본다. 우선 터보에는 HTML 기반 상호작용을 더욱 강력하게 만들어주는 다음과 같은 여러 구성요소가 있다.
- - 터보 드라이브(Turbo Drive) : 링크 클릭과 양식 제출 시 전체 페이지를 다시 로드할 필요가 없게 해준다.
- - 터보 프레임(Turbo Frame) : 독립적으로 로드할 수 있는 UI 영역을 정의할 수 있게 해준다(지연 로딩 포함).
- - 터보 스트림(Turbo Streams) : 웹소켓, 서버 측 이벤트 또는 양식 응답을 사용해 특정 페이지 구역을 임의로 업데이트할 수 있게 해준다.
터보 드라이브 : 페이지 로드가 아닌 병합
표준 HTML은 사용자가 페이지를 로드하면 기존 콘텐츠를 완전히 지우고 서버에서 도착하는 대로 모든 콘텐츠를 새로 그린다. 매우 비효율적인 데다 사용자 경험도 떨어진다. 터보 드라이브가 사용하는 다른 접근 방식은 자바스크립트 링크를 넣어 페이지를 다시 로드하지 않고 페이지 콘텐츠를 병합하는 방법이다.
병합을 이해하는 좋은 방법은 현재 페이지와 새로 받는 페이지의 차이를 비교한다고 생각하는 것이다. 헤더 정보 전부가 교체되는 것이 아니라 업데이트된다. 최신 터보는 과 요소까지 “모핑”해서 훨씬 부드러운 전환을 구현한다(당연히 이 방식은 페이지 다시 로드에 특히 효과적임).
페이지에 터보 스크립트를 포함하기만 하면 된다.
또한 뒤로 가기, 앞으로 가기, 다시 로드하기와 같은 브라우저 동작이 모두 정상적으로 작동한다는 점도 중요하다. 병합은 낮은 비용과 위험으로 웹 페이지의 탐색과 다시 로드하기를 개선하는 방식이다.
터보 프레임 : 세분화된 UI 개발
프레임의 기본 개념은 웹 페이지의 레이아웃을 요소로 분해한 다음 필요할 때만 이 프레임들을 부분적으로 업데이트하는 것이다. 전체적인 효과는 JSON 응답을 사용해 UI의 반응형 업데이트를 구동하는 것과 비슷하지만 여기서는 HTML 조각을 사용한다.
다음 페이지를 예로 들어 보자.
Links that change the entire page
Thimbleberry (Rubus parviflorus)
A delicate, native berry with large, soft leaves.
Edit this description
Found a large patch by the creek.
The berries are very fragile.
페이지 전체에 작용하는 링크가 있는 상단 내비게이션 창(터보 드라이브와 함께 사용 가능), 그리고 페이지 전체를 다시 로드하지 않고 그 자리에서 바로 수정할 수 있는 두 개의 내부 요소가 있다.
요소는 그 안에서 발생하는 이벤트를 포착한다. 즉, 필드 노트를 편집하기 위해 사용자가 링크를 클릭하면 서버는 청크로 응답해 편집 가능한 양식을 제공한다.
Field Notes
Found a large patch by the creek.
The berries are very fragile.
이 청크는 실시간 양식으로 렌더링된다. 사용자가 업데이트 작업을 하고 새 데이터를 제출하면 서버는 업데이트된 프레임이 포함된 새로운 조각으로 응답한다.
Field Notes
Found a large patch by the creek.
The berries are very fragile.
Just saw a bear!
터보는 도착하는 프레임 콘텐츠에서 ID를 취해서 이 프레임 콘텐츠가 페이지의 동일한 프레임을 대체하도록 한다(따라서 서버는 전송하는 조각에 반드시 정확한 ID를 넣어야 함). 터보는 충분히 똑똑해서, 서버에서 전체 페이지를 받는다 해도 관련된 조각만 추출해 적절한 위치에 배치할 수 있다.
터보 스트림 : 복합 업데이트
터보 드라이브는 기본적인 서버 상호작용을 처리하는 데 적합한 단순하고 효과적인 메커니즘이다. 그러나 페이지의 여러 부분과 상호작용하거나 서버 측에서 트리거되는 더 강력한 업데이트가 필요한 경우도 종종 있다. 이를 위한 터보 기능이 바로 스트림이다.
기본 개념은 서버가 조각 스트림을 전송하면서 변경될 UI 부분의 ID와 변경에 필요한 콘텐츠를 각 조각에 함께 포함하는 것이다. 예를 들어 야생 생태 일지에 대한 다음과 같은 업데이트 스트림을 보자.
Just saw a Fox!
4 Notes
여기서는 프레임 대신 스트림을 사용해 노트 업데이트를 처리한다. 즉, 새 노트와 노트 카운터, 실시간 양식 섹션 등 업데이트가 필요한 각 섹션이 콘텐츠를 스트림 항목으로 받는다는 개념이다. 각 스트림 항목에는 앞으로 발생할 일을 설명하는 “action”과 “target”이 들어 있다.
스트림은 targets(복수형에 주목)과 CSS 선택 도구를 사용해 여러 요소를 대상으로 지정할 수 있다.
터보는 요소 모음이 포함된 서버 응답(예를 들어 양식 응답)을 자동으로 처리해 UI에 올바르게 배치한다. 이 방법은 복수의 변경이 필요한 많은 요구사항을 처리할 수 있다. 여기서 또 하나 주목해야 할 부분은 스트림을 사용할 때는 을 사용할 필요가 없다는 점이다. 사실 이 둘을 섞어 쓰는 것은 좋지 않다. 경험적으로 보면 가능한 모든 경우 프레임을 사용해 단순함을 우선하고, 필요할 때만 스트림으로 업그레이드해야 한다.
재사용성
터보 프레임과 터보 스트림의 중요한 혜택 중 하나는 UI 요소를 처음 렌더링할 때와 업데이트할 때 모두 서버 측 템플릿을 재사용할 수 있다는 점이다. RoR 템플릿, 타임리프, 코틀린 DSL, 퍼그 등 어느 툴을 사용하든 서버 측 템플릿을 UI에 필요한 것과 같은 청크로 분해하기만 하면 된다. 그러면 이를 사용해서 해당 청크의 초기 상태와 이후 상태를 모두 렌더링할 수 있다.
예를 들어 다음과 같은 간단한 퍼그 템플릿을 보자. 이 템플릿은 전체 페이지의 일부로도 사용할 수 있고 업데이트 청크를 생성하는 데도 사용할 수 있다.
turbo-frame#field_notes
h2 Field Notes
//- 1. The List: Iterates over the 'notes' array
div#notes_list
each note in notes
div(id=`note_${note.id}`)= note.content
//-
2. The Form: On submission, this fragment is re-rendered
- by the server, which includes a fresh, empty form.
form(action="/berries/thimbleberry/notes", method="post")
div
label(for="note_content") Add a new note:
div
//- We just need the 'name' attribute for the server
textarea(id="note_content", name="content")
div
input(type="submit", value="Save note")
서버 푸시
다음과 같이 <turbo-stream-source> 요소를 사용하면 백그라운드 이벤트 스트림도 제공할 수 있다.
이 요소는 SSE 또는 웹소켓 업데이트를 위한 백엔드 API에 자동으로 연결된다. 이러한 브로드캐스트 업데이트의 구조도 앞에서 본 것과 동일하다.
Which will automatically connect to a back end API for SSE or WebSocket updates. These broadcast updates would have the same structure as before:
Also found Salmonberries here!
스티뮬러스를 사용한 클라이언트 측 기능
HTMX는 알파인JS(Alpine.js)와 함께 사용되는 경우가 종종 있다. 이 경우 Alpine.js는 아코디언, 드래그 앤 드롭과 같은 더 다채로운 프론트엔드 상호작용을 제공한다. 핫와이어에서는 스티뮬러스가 그 역할을 한다.
스티뮬러스에서는 HTML 속성을 사용해 요소를 자바스크립트 기능 청크인 “컨트롤러”에 연결한다. 예를 들어 클립보드 복사 버튼을 제공하려면 다음과 같이 할 수 있다.
Thimbleberry (Rubus parviflorus)
A delicate, native berry with large, soft leaves.
data-controller 속성을 보자. 이 속성은 요소를 클립보드 컨트롤러에 연결한다. 스티뮬러스는 파일명 규칙을 사용하는데, 이 경우 파일 이름은 clipboard_controller.js가 되고, 그 내용은 다음과 같다.
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
// Connects to data-clipboard-target="source"
// and data-clipboard-target="feedback"
static targets = [ "source", "feedback" ]
// Runs when data-action="click->clipboard#copy" is triggered
copy() {
// 1. Get text from the "source" target
const textToCopy = this.sourceTarget.textContent
// 2. Use the browser's clipboard API
navigator.clipboard.writeText(textToCopy)
// 3. Update the "feedback" target to tell the user
this.feedbackTarget.textContent = "Copied!"
// 4. (Optional) Reset the button after 2 seconds
setTimeout(() => {
this.feedbackTarget.textContent = "Copy Name"
}, 2000)
}
}
static target 멤버는 마크업의 data-clipboard-target 속성을 기반으로 이러한 요소를 컨트롤러에 제공한다. 그러면 컨트롤러는 간단한 자바스크립트를 사용해 클립보드 복사를 수행하고, UI에 일정 시간 동안 메시지를 표시한다.
기본적인 개념은 필요할 때 마크업에 연결되는 작은 여러 컨트롤러에 자바스크립트를 깔끔히 격리해 두는 것이다. 이렇게 하면 서버 측 작업을 보완하기 위한 부가적인 클라이언트 측 동작을 관리 가능한 방식으로 자유롭게 수행할 수 있다.
결론
핫와이어의 장점은 아주 작은 리소스만으로 필요한 작업을 대부분 수행한다는 것이다. 20%의 노력만으로 80%의 작업을 처리할 수 있다. 핫와이어는 리액트 같은 본격적인 프레임워크나 넥스트와 같은 풀스택 옵션에 비견할 만큼 방대한 기능을 제공하지는 않지만, 대부분의 개발 시나리오에서 필요한 기능을 거의 다 제공한다. 또한 핫와이어는 일반적인 기술을 사용하는 모든 백엔드와 함께 사용할 수 있다.
dl-itworldkorea@foundryco.com
Matthew Tyson editor@itworld.co.kr
저작권자 Foundry & ITWorld, 무단 전재 및 재배포 금지
