JavaScript 디블로팅

1 week ago 10
  • 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과 구분하기 어려운 언어를 위한 트랜스파일 문법을 사용할 수 있음
    • grammar를 수정해 원하는 스타일의 언어를 만들 수 있음
    • 그리스어로도 가능하며, 이미 그리스어 예시가 있음
  • LispE는 JavaScript와 싸우지 않고 JavaScript 함수와 DOM 기능을 활용하는 방식으로 협력함

JavaScript 호출: evaljs와 asyncjs

  • LispE 안에서 JavaScript 코드를 실행하려면 evaljsasyncjs 를 사용할 수 있음
    • 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)`);
Read Entire Article