웹에서 WebAssembly를 1급 언어로 만들기
(hacks.mozilla.org)- WebAssembly는 2017년 첫 출시 이후 C/C++ 등 저수준 언어 실행을 지원하며 발전해 왔지만, 여전히 웹 플랫폼에서 2급 언어로 취급되고 있음
- JavaScript만이 웹 API와 직접 상호작용할 수 있고, WebAssembly는 이를 위해 복잡한 JS 바인딩 코드(glue code) 를 작성해야 함
- 이러한 구조는 로딩 절차의 복잡성, 성능 오버헤드, 언어별 툴체인 단절 등으로 이어져 개발자 경험을 저하시킴
- Mozilla는 이를 해결하기 위해 WebAssembly Component Model을 제안, JS 없이도 표준화된 방식으로 Web API 호출 및 모듈 로딩을 가능하게 함
- 이 모델이 정착되면 WebAssembly는 브라우저 내 1급 실행 환경으로 자리잡아, 일반 개발자도 쉽게 활용할 수 있는 기반이 마련될 전망임
WebAssembly가 2급 언어로 취급되는 이유
- WebAssembly는 JavaScript를 통해서만 웹 플랫폼 접근이 가능하며, 직접적인 Web API 호출 권한이 없음
- JavaScript는
<script>태그로 간단히 로드되지만, WebAssembly는 JS API를 통한 수동 로딩 과정이 필요 -
WebAssembly.instantiateStreaming()등 복잡한 API 호출을 거쳐야 하며, 개발자가 이를 암기하거나 툴로 자동화해야 함
- JavaScript는
-
esm-integration 제안은 JS 모듈 시스템을 통해
.wasm파일을 직접 import할 수 있게 하여 로딩 절차를 단순화함-
<script type="module" src="/module.wasm"></script>형태로 직접 로드 가능
-
Web API 접근의 제약
- JavaScript에서는
console.log("hello, world")한 줄로 가능한 작업이 WebAssembly에서는 JS 메모리 접근, 문자열 디코딩, 함수 래핑 등 복잡한 절차를 요구- WebAssembly는
console객체나 DOM 접근이 불가능해, JS에서 메모리 공유 및 함수 import/export를 통해 간접 호출해야 함
- WebAssembly는
- 이 과정에서 생성되는 바인딩 코드(glue code) 는 언어별로 다르며,
embind,wasm-bindgen같은 도구로 자동 생성됨- 그러나 빌드 복잡도 증가, 런타임 오버헤드, 언어별 비호환성 등의 문제가 발생
WebAssembly가 1급 언어가 되지 못한 기술적 원인
- 컴파일러 통합의 어려움: 각 언어의 컴파일러는 JS 및 웹 플랫폼 통합 코드를 별도로 생성해야 하며, 이는 재사용 불가능
-
표준 컴파일러의 비호환성:
rustc --target=wasm으로 생성한 파일은 브라우저에서 바로 실행되지 않음- 플랫폼 통합을 구현한 비공식 툴체인을 별도로 설치해야 함
- 문서 생태계의 편향: MDN 등 웹 문서는 대부분 JavaScript 중심으로 작성되어, 다른 언어 사용자에게 진입 장벽이 높음
-
성능 문제: JS 바인딩을 거친 DOM 호출은 직접 호출 대비 45% 성능 저하 발생
- Dodrio 프레임워크 실험에서 JS glue 제거 시 DOM 변경 적용 시간이 절반으로 감소
- JavaScript 의존성: WebAssembly를 실무에 활용하려면 결국 JS를 이해해야 하며, 이는 추상화 누수(leaky abstraction) 문제로 이어짐
WebAssembly Component Model의 등장
-
WebAssembly Component Model은 여러 언어와 런타임에서 공통으로 사용할 수 있는 표준화된 실행 단위를 정의
- Web API 접근, 모듈 로딩, 링크 과정을 JS 없이 직접 수행 가능
- 다양한 언어에서 생성 가능하며, 브라우저나 Wasmtime 등 여러 런타임에서 실행 지원
-
WIT(Interface Description Language) 를 통해 필요한 API를 선언하고, 이를 컴포넌트 내부에서 직접 호출 가능
- 예:
→ Rust 코드에서component { import std:web/console; }console::log("hello, world")호출 가능
- 예:
- 브라우저는
<script type="module" src="component.wasm"></script>로 컴포넌트를 직접 로드하고, JS 없이 Web API 바인딩 자동 처리
JavaScript와의 상호운용
- Component Model은 하이브리드 앱 구조도 지원
- 예: 이미지 디코더를 WebAssembly로 작성하고, JS에서
import { Image } from "image-lib.wasm";형태로 호출 가능 - JS는 WebAssembly 컴포넌트를 일반 모듈처럼 import/export하여 사용
- 예: 이미지 디코더를 WebAssembly로 작성하고, JS에서
향후 전망과 참여
Hacker News 의견들
- 이 모든 일은 반십 년 전에 이미 가능했을 것 같음
WebIDL을 WebAssembly에서 지원하려던 초기 목표를 버리고, 또 다른 IDL을 만들겠다고 하면서 DOM 접근 부재를 문제로 보지 않았던 게 아쉬움
물론 시장 현실을 이해하지만, 잃어버린 시간이 아깝다는 생각을 지울 수 없음
관련 참고 링크: commit 기록, stringref 회고, ACM 기사- 예전에 interface-types 제안서 작업에 참여했었음
이후 목표가 두 가지 추가되었는데, 하나는 비웹 API 지원, 다른 하나는 언어 간 상호운용성이었음
WebIDL은 JS와 Web API의 합집합이라 표현력은 높지만, 이런 목표와 충돌하는 개념이 많았음
그래서 component interface는 표현력은 줄었지만 훨씬 이식성 높은 교집합 접근법을 택했음
개인적으로 DOM 접근은 중요하다고 생각하지만, Wasm CG가 우선순위 높은 일들로 바빴음
이번 글을 쓴 이유는 아직도 이 문제를 기억하고 있고, 계속 작업할 계획이 있다는 걸 알리고 싶었음 - stringref가 다시 돌아오길 정말 바람
- 예전에 interface-types 제안서 작업에 참여했었음
- WASM의 진입 장벽은 실제로 큼
툴체인과 빌드 과정이 너무 복잡해서, 사용할 때마다 인지적 부담을 느끼고 있음
성능은 glue 없이 훨씬 좋지만, 그만큼 위험 요소도 큼
component 모델이 새로운 복잡성을 도입하지 않길 바라지만, 여러 언어 예시를 보면 이미 꽤 혼란스러움
특히 Go 예시를 보면 생성된 파일이 너무 많고, 개발자 입장에서는 툴링 단순화가 절실함
지금은 복잡성을 없애는 게 아니라 단지 이동시키는 것처럼 보임- 아직 툴링이 초기 단계임을 인정함
wasm component 사양이 계속 바뀌면서 churn이 많았음
목표는 웹 개발자가 직접 WIT를 작성하지 않고, Web API를 라이브러리처럼 쓸 수 있게 하는 것임
하지만 아직은 갈 길이 멀음
- 아직 툴링이 초기 단계임을 인정함
- WebAssembly component 발전은 멋지지만, 웹 API를 작은 표준 단위로 분리할 기회를 놓치고 있다고 느낌
예를 들어 텍스트 공유, 미디어 공유, 애플리케이션 공유 등으로 나누면 보안도 좋아지고, 소규모 팀이 브라우저 대안을 만들 수도 있음
하지만 거대한 웹 API와 CSS의 규모가 브라우저 독점을 지탱하는 요소라 이런 시도는 어려울 듯함
WebAssembly registry를 표준화해 컴포넌트를 쉽게 조합할 수 있게 하면 좋겠음
결국 웹은 분산 운영체제 정의를 만드는 과정임 - 최신 WebAssembly를 배우고 싶다면 Component Model Book을 추천함
개념부터 코드 예시까지 잘 정리되어 있음
JS 생태계에서는 StarlingMonkey, ComponentizeJS, jco 세 프로젝트가 핵심임
현재 가장 성숙한 툴체인은 Rust지만, LLVM 기반 언어(C/C++, Go, Python 등) 지원도 점점 좋아지고 있음
WebAssembly의 목표는 로컬 툴체인에 자연스럽게 녹아드는 컴파일 타깃이 되는 것임 - “first-class”라는 표현이 중요한 이유는 대부분의 개발자가 성능보다 마찰(friction) 때문에 플랫폼을 포기하기 때문임
여전히 언어별 glue 코드나 두 런타임 모델을 이해해야 한다면, WebAssembly는 여전히 “극단적 상황에서만 쓰는 도구”로 남을 것임
진짜 변화를 만들려면, 일반적인 빌드 경로를 단순화해야 함 - 웹은 동적 기능 감지 없이는 작동하지 않음
Component Model에서 이 부분이 어떻게 처리되는지 불분명함
DOM은 브라우저마다 다르고, 페이지 로드마다 기능이 달라짐
JS 브리지 계층에서는 polyfill을 쉽게 적용할 수 있지만, WIT 인터페이스에서는 런타임 메서드 감지나 polyfill이 어려움
성능 외에도 생태계의 유연성이 중요함 - 이 글은 “WebAssembly 벽”의 답답함을 잘 표현함
JS glue 코드를 직접 관리하거나 자동 생성 도구에 의존하는 건 큰 후퇴처럼 느껴짐
Dodrio 실험에서 glue를 생략해 45% 오버헤드 감소를 얻은 건 인상적임
다만 WebAssembly Component Model이 Web API와 직접 상호작용할 때 메모리 관리가 어떻게 되는지 궁금함
Wasm GC 제안이 DOM 참조를 유지하는 데 쓰이는지, 아니면 여전히 JS GC에 의존하는지 알고 싶음
Wasm이 진정한 1급 시민으로 자리 잡는 걸 기대함- 최근 v8과 Bun의 sendMessage 개선이 이 문제를 완화할 수 있을지 궁금함
하지만 현재 IPC는 여전히 비효율적이며, 메모리 페이지 단위 전송 같은 방식이 필요하다고 생각함 - 위 댓글들이 LLM이 작성한 것처럼 보인다며, HN 규칙 위반 가능성을 지적함 (가이드라인)
- 최근 v8과 Bun의 sendMessage 개선이 이 문제를 완화할 수 있을지 궁금함
- 웹은 흥미로운 실험의 연속이었음
아무나 복잡한 프로그램을 내 컴퓨터에서 실행하게 한다는 건 보안상 미친 아이디어였지만, 실제로 그렇게 해왔음
JS 덕분에 20년간 수많은 브라우저 보안 버그를 겪었지만, 이제는 설계 원칙과 완화책이 자리 잡고 있음
그런데 이제 와서 또 다른 위험한 실행 패러다임으로 교체하려는 게 아이러니하면서도 아름다움- 사실 이런 역할은 운영체제(OS) 가 해야 하는 일임
모바일 OS는 데스크톱보다 훨씬 잘 해내고 있음 - JS 자체가 아니라 브라우저 API가 보안 문제의 주범이었음
- “교체”되는 건 없고, 단지 확장되는 것뿐임
- WASM이 JS보다 위험한 이유가 궁금함
- WebUSB처럼 샌드박스를 깨는 기능이 문제이지, wasm 자체는 그렇지 않다고 생각함
- 사실 이런 역할은 운영체제(OS) 가 해야 하는 일임
- 요즘 새 표준들은 단순함보다 복잡한 JS 보일러플레이트를 우선시함
엔지니어 중심으로 설계되어 있고, 저자(author) 친화적인 기본 워크플로우가 없음
그래도 이런 문제를 신경 쓰는 사람들이 남아 있어서 다행임 - 개인적으로는 여전히 좋은 아이디어가 아니라고 생각함 (이전 논의)
DOM API를 1:1로 매핑하기 위해 2배 빠른 문자열 처리만을 위해 과도한 엔지니어링을 하는 느낌임
WebGL2, WebGPU, WebAudio 같은 API에서는 JS shim의 비용이 이미 미미함
진짜 문제는 GPU 버퍼 복사 같은 부분인데, component model이 거기엔 도움이 안 됨
WebGL2나 WebGPU에서 수만 번의 draw call을 테스트한 벤치마크를 보고 싶음- 일부 케이스에서는 큰 향상이 없겠지만, DOM 성능은 여전히 많은 앱의 병목임
성능 외에도 개발자 경험(DX) 개선이 중요함
지금은 시작하기 너무 어렵고, 모든 사람이 전문가가 되어야만 이점을 얻을 수 있음 - 단순히 2배 빠른 문자열 처리뿐 아니라, glue 코드가 필요 없다는 점이 가장 큰 이점임
- WebAssembly가 모바일 OS의 폐쇄성에 대한 대안이 될 수 있음
네이티브 앱 수준의 효율로 경쟁할 수 있다면, 웹의 미래를 위한 비전 있는 변화임 - DOM 접근만 빨라져도 충분히 큰 의미가 있음
- component model은 불필요한 자기 일거리 만들기처럼 보임
코딩 에이전트 시대에는 그들이 말하는 DX 개선이 더 이상 중요하지 않음
- 일부 케이스에서는 큰 향상이 없겠지만, DOM 성능은 여전히 많은 앱의 병목임