웹 플랫폼에 선언적 템플레이팅 API를 추가할 시기가 무르익었습니다. 그 이유와 시의성, 가능한 길을 논의합니다.
TL;DR: 저는 선언적 템플레이팅 API를 웹 플랫폼에 추가하자는 제안을 하고 싶습니다. 그 이유는 다음과 같습니다...
웹 플랫폼은 사상 가장 성공적인 애플리케이션 런타임입니다. 그 가장 큰 이유는 웹의 폭넓은 접근성에 있지만, 이는 주로 정적인 문서 뷰어를 매우 동적이고 표현력 있는 런타임으로 바꾸어주는 DOM API가 있었기에 가능했습니다.
DOM은 때때로 비판을 받기도 하지만 (그 중 일부는 타당하지만, 그렇지 않은 부분도 많습니다!) DOM은 부정할 수 없는 강력한 API입니다. 실제로 DOM이 얼마나 강력한지 보여주는 놀라운 동적 웹 애플리케이션들이 도처에 있습니다. 포토샵처럼 복잡한 앱도 웹 앱으로 제공되고 있으며, 그 전체 UI가 웹 컴포넌트로 구축되었습니다!
그럼에도 불구하고, DOM은 현대 웹 개발에서 가장 중요하고 대중적인 기능 중 하나인 템플레이팅을 제공하지 않습니다. 표준 DOM API로는 데이터를 기반으로 DOM 노드 집합을 만들고, 이벤트 리스너를 추가하고, 엘리먼트에 속성을 설정하며, XSS 공격에 안전하게 처리하고, 새 데이터로 효율적으로 갱신할 수 있는 손쉬운 방법이 없습니다.
이런 템플레이팅 방식은 현대의 거의 모든 웹 프레임워크 및 렌더링 라이브러리의 핵심입니다. React, Vue, Angular, Preact, Lit, Svelte, SolidJS, Stencil, Quik, Ember, FAST, Polymer, Marko, 그리고 사실상 모든 프레임워크가 선언적으로 마크업과 데이터를 결합할 수 있습니다.
선언적 템플레이팅이 이처럼 널리, 그리고 기초적으로 자리 잡은 이유는 명확하지만, 주요 장점들을 다시 한 번 정리해 보겠습니다:
이처럼 개발자 경험(DX)과 사용자 경험(UX), 보안성, 이식성 등 모든 측면에서 완전히 승리한 기법은 드뭅니다. 너무나 대중적이라 이제는 사실상 기본이 되었습니다.
템플레이팅이 이렇게 좋은데, 현재 상황에서 문제가 되는 점은 무엇일까요?
가장 큰 문제는, 거의 모든 웹 애플리케이션에 필요한 핵심적인 요구사항임에도 불구하고, 플랫폼이 이를 제공하지 않는다는 것입니다. 웹 플랫폼은 분명히 개발자 커뮤니티가 필요로 하는 기능을 더 잘 반영해야 하고, 템플레이팅만큼 명확한 필요성을 보여주는 기능에 대해선 더더욱 그렇습니다. 그 동안 국제화나 고급 날짜/시간 API가 플랫폼에 포함되어 왔고, 스케줄러, Sanitizer API 등의 도입도 논의되고 있습니다.
그러나 플랫폼 차원의 템플레이팅 부재는 다음과 같은 실제적 피해를 낳고 있습니다:
이용자: 앱 다운로드 시간이 늘어나고, 렌더링 오버헤드나 보안 취약점에 노출됩니다. 템플레이팅에 필요한 라이브러리도 다운받아야 하며, 이것이 수 kB~100kB 이상까지 다양합니다. "몇 kB 별거 아니지 않나?" 싶지만, 초기 인터랙티브 렌더링 목표를 생각하면 적지 않은 용량입니다.
개발자: 가장 기본적인 일도 라이브러리, 즉 npm이나 CDN을 거쳐야만 합니다. 이것은 "순정"(vanilla) 파일이나 개발 도구들의 활용도를 떨어트리고, 스택 간 이식이 어려우며, innerHTML 같은 native DOM API는 기본적으로 안전하지도 않습니다.
프레임워크: 템플레이팅 구현 부담이 큽니다. Ergonomic, 렌더+업데이트 성능, 안전성, 코드 크기의 복잡한 트레이드오프를 항상 고려해야 하기에 구축과 유지가 어렵습니다.
플랫폼: kB 단위에 신경 안 써도 되는 네이티브 플랫폼(Flutter, SwiftUI, Jetpack Compose 등)과 그만큼의 경쟁력을 갖기 어렵습니다. 비슷하게, 이들 플랫폼은 기본적으로 템플레이팅 시스템을 제공합니다.
과거에도 템플레이팅 유사 기능의 제안이 있었습니다. E4X는 Firefox, Flash에서 실제로 동작했고, E4H란 간소화 버전도 존재했으나 현재는 자료를 찾기 힘듭니다. 2012년 무렵엔 내장된 html
템플릿 리터럴 태그의 프로토타입도 있었습니다. 하지만 이들은 DOM 생성에는 도움이 되었으나, 갱신에는 불편해서 결국 도입되지 않았습니다.
하지만 이제는 논의를 다시 시작할 좋은 때라고 생각합니다. 왜냐하면, 프레임워크들이 닦아놓은 길이 견고하게 자리 잡았고, 커뮤니티가 사용한 템플릿 해법들이 점차 수렴하고 있기 때문입니다. ergonomic 템플릿 문법과 리액티브 모델에 대해 13년 전보다 훨씬 많이 이해하게 되었습니다.
게다가, 프레임워크를 쓰지 않는 개발자, "순정 자바스크립트" 개발자, 웹 컴포넌트 커뮤니티 등 다양한 영역에서 ergonomic DOM 조작 및 리액티브 API에 대한 수요가 쌓여 있습니다.
현재 진행 중인 DOM Parts와 같이, 프레임워크 구현을 위한 저수준 DOM 업데이트 primitive 제안도 있지만, 저는 고수준 선언적 템플레이팅 API가 도입된다면 더 많은 부담을 해소하고, 저수준 API의 완성도와 유용성 입증에도 큰 도움이 된다고 생각합니다.
현대의 유명 템플릿 시스템은 모두 바인딩을 포함한 마크업이라는 기본적으로 유사한 문법을 공유합니다. 표현식 분리자, 제어구조, 타입 명시 등 세세한 차이는 있지만, 대동소이합니다. 심지어 HTML 기반(Vue, Angular, Svelte)과 JS 기반(React, Lit, Solid) 간도 마찬가지입니다.
특히 JS 기반 API(즉, 앞으로 DOM API가 될 영역)에서는 템플릿이 보통 표현식이고, 중첩이나 참조를 통해 합성하며, 제어 흐름은 JavaScript로 구현됩니다.
의미론적 측면도 유사합니다. 대부분의 JS-API 기반 시스템은 템플릿 표현식이 DOM 설명(가상 트리 혹은 유사 객체)을 반환하고, 별도의 렌더 함수가 이 설명을 실제 DOM에 적용합니다. 또한 대다수는 같은 렌더 함수로 업데이트까지 처리합니다.
E4X, E4H 등 과거 제안은 이와 다릅니다. 마크업이 JavaScript 내에서 새 문법으로 추가되지만, DOM 설명이 아니라 실제 DOM fragment를 반환해서 초기 생성에는 좋으나, 갱신 시에는 여전히 명령형 접근이 필요했습니다.
이미 오래 전부터 자바스크립트에는 내장 DSL(예: HTML)을 위한 태그된 템플릿 리터럴(tagged template literals) 기능이 있습니다.
즉, 새로운 자바스크립트 기능 없이도 DOM API에서 템플릿을 명확히 기술할 수 있고, 실제 이미 많은 라이브러리가 이 접근법을 성공적으로 사용함을 증명했습니다. 이는 현실적으로 제안이 도입될 수 있는 큰 강점입니다.
태그드 템플릿 리터럴은 명백히 훌륭하지만, 사용자 정의 컴포넌트 시스템에서는 JSX에 비해 약간의 구문 오버헤드가 있습니다. 그리고 지난 수 년간 많은 사람이 JSX의 간편함에 익숙해졌습니다.
왜 그냥 JSX를 표준으로 넣지 않을까요?
JSX의 문제는 문법만 있을 뿐, 의미(semantics)가 없다는 데 있습니다. 표준화하려면 단순히 JavaScript에 새 문법만 더할 수 없고, 의미까지 정의해야 합니다. ambient createElement로 퉁칠 수 없고, 객체 트리를 만드는 것도 효율적이지 않으며, Records & Tuples 제안이 stalls된 상태라 바로 쓸 수도 없습니다.
의미를 확정하는 것도 어렵지만, 템플레이트용 의미 정의에는 역설적으로 "목표가 될 실제 템플레이팅 시스템"이 필요합니다. React가 이미 있지 않냐고 할 수 있지만, React의 템플레이팅은 다른 시스템 대비 강력함이 떨어지는 부분이 있습니다. 예를 들어, DOM 요소의 속성/이벤트 바인딩이나 엘리먼트에 적용되는 디렉티브 등은 React에서 명시적으로 제공하지 않습니다.
그럼에도 불구하고 JSX가 마크업 임베딩 방식의 대중성을 확인시켜 주었으나, 현실적으로는 오늘날의 JavaScript로도 당장 쓸 좋은 템플레이팅 시스템을 만들 수 있습니다. 만약 나중에 표준 JSX 도입 논의가 본격화된다면, 구체적인 시스템을 목표로 논의될 수 있겠죠. 현재 비표준 JSX가 오히려 유리한 점도 있습니다. 런타임 의미가 없으니, JSX->태그드 템플릿 리터럴로 변환하는 컴파일러를 만들 수 있습니다. 실제로 JSX->Lit 등의 컨버터가 존재합니다. 이와 같은 방식으로 새로운 템플릿 API가 컴파일 타겟이 되면, 개발자는 원하는 문법을 써서 런타임 오버헤드 없이 사용할 수 있습니다.
향후 표준 JSX 논의가 활발해진다면, 이미 검증된 구현체 위에서 논의를 이어갈 수 있을 것입니다.
많은 웹 개발자가 HTML 기반의 완전한 템플릿 시스템을 바라고 있습니다. 웹 컴포넌트, 프레임워크 사용자, "순정" JS 개발자 모두 마찬가지입니다. <template>
요소, 선언적 쉐도우 DOM, 웹 컴포넌트 자체에서 이런 기능을 기대하는 개발자가 많았고, 그렇게 되지 않은 것에 실망한 분도 많습니다. Vue, Angular, Polymer, Svelte 등 대다수 프레임워크도 HTML 기반 템플릿을 쓰고 있습니다.
그래서 JS 기반 API보다 먼저 HTML 기반 API를 추진하지 않는 이유는 무엇일까요?
HTML 기반 시스템은 훨씬 거대한 작업입니다. React, JSX, 혹은 Lit 등은 마크업과 바인딩에 집중하고 제어 흐름/표현식은 JS로 위임하지만, HTML 기반 템플릿은 바인딩 구문, 표현식 언어, 제어구조, 그리고 갱신 메커니즘까지 모두 새로 설계해야 합니다.
또한 HTML 기반 템플릿 정의를 불러오는 네이티브 표준도 없습니다. Web Components V0의 HTML Imports는 폐기되었고, HTML Modules도 아직 도입 전입니다. 그 결과 HTML 템플릿은 JS 모듈 안에 임베드될 수밖에 없고, 그렇다면 표현식이 JS의 렉시컬 스코프를 그대로 쓸 수 있다는 점에서 JS 기반 접근이 더 나은 경험을 줍니다. 실제로 Polymer 2에서 Polymer 3(그리고 Lit)로 넘어가며 HTML 템플릿을 JS로 옮긴 배경도 이것입니다.
그러므로 JS API는 HTML 기반 템플릿 API로 가는 도약대이며, 그 자체로도 큰 진전을 의미합니다. 한 번 검증된 JS API 위에서라면 HTML API는 바인딩, 표현식, 제어 흐름, 모듈 시스템만 추가 설계하면 됩니다(물론 여전히 큰 작업입니다).
초기 DOM 템플레이팅 제안에는 갱신(업데이트)까지 포함되지 않았지만, 오늘날의 다양한 구현들이 이미 넓은 지형을 탐험하며 더 좋은 모델과 구현법을 찾아냈습니다. 이제는 각 방식의 장점을 잘 조합한 시스템을 설계할 시점입니다.
대표적인 리액티비티 방식은 다음과 같습니다:
Diff 방식은 이해가 쉽고 데이터 형태에 제약이 없지만, 다른 방식에 비해 느리고 diff 알고리즘 설계에 따라 관찰 가능한 차이가 발생할 수 있습니다. 표준화에 부적합할 수 있습니다.
Template Identity 방식(같은 템플릿이면 DOM 갱신, 다르면 교체)은 빠르고 diff와 유사한 결과를 얻기 쉽습니다. 태그드 템플릿 리터럴, JSX 변환, HTML 템플릿 모두에 잘 어울립니다. 간단한 의미론으로써 명세화도 쉽습니다.
Signals(신호)는 개인적으로 아주 긍정적으로 평가합니다. 저는 자바스크립트 신호(신호 변수) 제안을 적극 지지하며, 새로운 DOM API(템플릿 포함)의 일부가 되면 아주 좋겠습니다.
아직 신호가 표준화된 것은 아니지만, 언젠가 도입될 가능성이 크고, 신호 기반 시스템은 모든 데이터가 신호로 감싸져 있을 때 가장 효율적입니다. 하지만 신호가 아닌 데이터나 신호를 생성하지 않는 API와 결합하려면 여전히 전통적인 "렌더 다시하기"가 필요합니다.
두 방식을 함께 지원하는 시스템을 만들기도 상대적으로 쉽습니다. 바인딩에 신호만 전달되는 템플릿, 즉 신호-순수 템플릿은 다시 렌더링하지 않고 신호값 변화만 감지하면 됩니다. 신호 외 데이터를 참조하는 템플릿만 리렌더가 필요하며, 이는 대개 컴포넌트 또는 프레임워크 계층이 담당합니다.
따라서 Template Identity + Signals 조합은 빠르고, 이해하기 쉽고, 명세화 가능한 최고의 선택이라고 생각합니다.
저는 최근 이 깃헙 이슈에서 네이티브 선언적 JS 템플레이팅 API를 언급했고, 여기 어렴풋이 정리한 내용이 vanilla 개발자, 웹 컴포넌트 개발자, 미래 프레임워크 방향 모두에 논리적이고 가치 있는 다음 단계라고 봅니다.
또한 이 작업은 DOM Parts 제안이 완성되어야 하고, 이 과정에서 유익성과 실용성 검증에 중요한 역할을 할 것입니다.
물론 쉬운 일은 아닙니다. API 표면과 문법은 작지만 동작 영역과 내재된 DOM Parts API, fine-grained 리액티비티에 필요한 DOM 스케줄러 등 해결할 것이 많습니다. 따라서 합의, 명세, 테스트 등 많은 협업이 필요합니다.
그러나 충분히 가능하고, 웹 개발자 및 이용자 모두에게 엄청난 가치를 가져다 줄 수 있습니다. 저 역시 가능한 범위 내에서 프로포절 작업을 계속할 계획이고, 더 많은 개발자와 플랫폼 엔지니어분들이 관심을 가져 주시길 바랍니다!
관심 있으시다면 webcomponents#1069에 코멘트 남겨주시거나, Bluesky로 연락 주세요!