JavaScript 디블로팅
요약
JavaScript의 복잡한 문법과 과도한 라이브러리 의존으로 인한 코드 비대화 문제를 분석하고, WebAssembly와의 효율적인 협업 방안을 제시합니다. LispE는 단일 WASM 바이너리에 다양한 핵심 기능을 통합하여 외부 라이브러리 의존성을 줄이고 브라우저 자원을 효율적으로 사용하는 대안을 보여줍니다.
핵심 포인트
- JavaScript의 중첩된 문법과 콜백 구조는 코드의 복잡도를 높이고 유지보수를 어렵게 만듦
- 작은 기능을 구현하기 위해 과도한 외부 라이브러리를 로드하는 '코드 비대화' 현상이 발생함
- WebAssembly와 JavaScript 간의 비동기 통신(이벤트 루프 연결)에는 성능 비용이 발생함
- LispE는 3.3MB의 단일 WASM 바이너리로 문자열, 행렬, 정규표현식 등 450개 이상의 함수를 제공하여 비대화를 해결함
JavaScript 문법은 중첩된 괄호와 콜백으로 쉽게 복잡해지고, 작은 UI에도 많은 라이브러리를 끌어오는 비대화가 생김
WebAssembly는 브라우저에서 다른 언어를 실행할 길을 열지만, Pyodide처럼 JavaScript 이벤트 루프와의 비동기 연결 비용이 큼
- 브라우저 자원과 WebAssembly 메모리는 제한적이어서, JavaScript를 대체하려 하기보다
협상하는 접근이 필요함
LispE는 3.3MB WASM 바이너리 하나에 450개 이상 함수를 담고, 문자열·계산·행렬·정규표현식을 함께 제공함
evaljs
와 asyncjs
로 JavaScript 함수와 DOM을 활용하면서, 여러 외부 라이브러리 대신 감사 가능한 단일 바이너리로 코드 비대화를 줄임
JavaScript 비대화와 브라우저 제약
JavaScript 문법은 괄호, 중괄호, 대괄호가 겹치고 닫는 순서를 맞춰야 해서 코드가 쉽게 복잡해짐
forEach
콜백 안에서 여러 객체에 값을 넣고 조건부 캐시를 갱신하는 예시가 나옴
newNames.forEach((name, i) => {
allAgentContents[name] = contents[i];
agentModes[name] = modes[i];
if (compiled[i]) agentCompiledCache[name] = compiled[i];
agentViewingCompiled[name] = viewing[i];
});
- 기본 프로그래밍 언어라면 흔히 포함하는 처리를 위해 JavaScript에서는 많은
라이브러리를 내려받게 됨 - C나 C++도 작업을 하려면
include
가 필요하지만, JavaScript 라이브러리는 누가 구현했고 누가 유지보수하는지 알기 어려운 경우가 많음
-
작은 창에 _hello_를 표시하려고 “인터넷의 절반”을 로드하는 듯한 페이지도 생김
-
인터넷은 JavaScript에 의존하며, TypeScript로 감추더라도 브라우저에서 페이지를 열 때마다 거쳐야 하는 문지기로 남아 있음
-
브라우저가 궁극의 운영체제가 되어 Windows나 Mac OS를 불필요하게 만들 것이라는 꿈이 있었고, 그 꿈을 구현할 언어로 JavaScript가 선택됨
WebAssembly와 JavaScript의 관계
WebAssembly는 자체 기계어를 가진 가상 머신에 가까워, 브라우저 안에서 다른 방식으로 코딩할 가능성을 열어줌
- Pyodide는 브라우저에서 Python을 실행하는 인상적인 엔지니어링 성과지만, JavaScript 영역 안에서 움직이는 비용도 드러냄
- Python의
asyncio
와 JavaScript 이벤트 루프라는 두 비동기 세계가 서로 대화해야 함
- 두 세계 사이의 다리는 취약하며, 모든
await
가 어느 세계에 속하는지 기억해야 함
-
브라우저 자원은 제한적이어서, 초기 컴퓨터 과학 시절처럼 매 명령과 구조를 비트 단위로 살펴보는 사고방식이 필요함
-
1981년에 정확히
15772 bytes만 남은 컴퓨터에서 프로그래밍을 시작했다는 기준이 제시됨 -
현대 브라우저 메모리가 그 첫 컴퓨터만큼 제한적인 것은 아니지만, WebAssembly는 일반 프로그램처럼 메모리를 자유롭게 소유하지 못하고 제한된 방식으로 먼저 허락을 받아야 함
-
핵심 태도는 JavaScript와 싸우는 것이 아니라
협상하는 것임
LispE가 제시하는 대안
LispE는 단일 바이너리 안에 450개 이상의 함수를 제공함
-
WASM 바이너리 크기는
3.3 MB임 -
문자열, 계산, 행렬, 정규표현식 처리 기능이 한곳에 묶여 있음
-
WASM 바이너리는 binaries/wasm에 있음
-
작은 인터프리터지만 반환값으로 문자열,
Float64Array
, 정수, 실수, 문자열 배열을 다룰 수 있음
- LispE는 Lisp 기반이라
AST가 살아 있는 구조를 가짐 - Lisp 문법이 맞지 않으면 Python이나 Basic과 구분하기 어려운 언어를 위한 트랜스파일 문법을 사용할 수 있음
- LispE는 JavaScript와 싸우지 않고 JavaScript 함수와 DOM 기능을 활용하는 방식으로 협력함
JavaScript 호출: evaljs와 asyncjs
- LispE 안에서 JavaScript 코드를 실행하려면
evaljs
와 asyncjs
를 사용할 수 있음
evaljs
는 JavaScript 코드를 실행해 값을 받음
asyncjs
는 페이지에 정의된 사용자 JavaScript 함수와 비동기 콜백을 연결함
(setq a (evaljs "10 + 20 + 30")) ; execute some JS code
; call_llm is a user-defined JS function in the page
(asyncjs `call_llm("Implement a piece of code in Python to sort strings");` 'mycallback)
(defun mycallback(theresult) ...)
asyncjs
는 작업이 끝나면 돌아오겠다는 Promise로 동작함
코드 비대화 줄이기
- 1995년 Wirth 인용은 더 빠른 컴퓨터와 더 큰 모델이 문제를 해결하지 않으며, 코드를 날씬하게 유지해야 한다는 결론으로 이어짐
- LispE는 브라우저에 노출되는 450개 함수를 단일 감사 가능 바이너리로 제공함
- 각 기능을 따로 구현하려면 수치 계산용
mathjs
, 컬렉션용 lodash
, 문자열 조작용 voca
, 통계 분포용 simple-statistics
같은 라이브러리를 로드해야 함
-
이런 방식은 각자 다른 작성자, 버그, 유지보수 일정을 가진 수백 MB의 코드로 커질 수 있음
-
LispE는 하나의 유지보수되는 코드로 이 기능들을 제공하며, 오픈소스라 전체 코드 감사를 할 수 있음
Garbage Collector가 최악의 순간에 코드 성능을 무너뜨리지 않는다고 봄 -
JavaScript와는 단순 API 호출로 투명하게 통신하며, 미리 정의된 구조를 반환할 수 있음
// floats here is a Float64Array
const floats = callEvalLispEToFloats(0, `(normal_distribution 100)`);
AI 자동 생성 콘텐츠
본 콘텐츠는 GeekNews의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기