0w0

우리를 조여오는 Node.js의 저주 그리고 Re:우리를 조여오는 Node.js의 저주 - 혹은 왜 TypeScript 이 외에 적당한 선택이 없는가

이건 우리의 이야기이며 당신과 나의 이야기 일지도 모른다.

얼마 전, 친구가 말했다. "오랜만에 Rails 썼는데 Node.js보다 못하다."

이 말에 동의했지만, 한 편으로는 이질감을 느낀다.

언제부터 우리는 Node.js만 사용한걸까. 그렇게 많이 사용하던 Rails 같은 많은 Web 기술에 즐거움이 없어진걸까.

물론 사용해야 하기 때문에 이런 이유는 아니다. 오히려 지금 현역인 프론트엔드 엔지니어는 모두 "주인공인 백엔드의 조연으로 존재하는 프론트엔드"를 경험한다.

사용하려하면, 사용한다. 그러니 그 중에서 프론트엔드와 그 기술을 골랐다.

그러나 지금은 어떤가. 프론트엔드 엔지니어는 이제 "JavaScript를 다루는 소프트웨어 엔지니어"가 되었고, 일반적인 백엔드는 물론 Node.js가 일등국민이 되어 클라우드 영역까지 들어왔다.

거기서 멈추는 것이 아니라 Electron, Ionic 같이 데스크탑 애플리케이션이나 네이티브 애플리케이션에서도 활약해서 JavaScript를 가지고 먹고 자는 사람도 적지 않다.

어느새인가 우리는 "프론트엔드 엔지니어가"가 아니라 "JavaScript 엔지니어"가 되었다.

이는 우리의 사고정지에 해당되는가. 아니면 그럴싸한 이유가 있는 것일까.

프론트엔드. 여기에는 누구도 의문의 여지가 없을 것이다. 핵심 중의 핵심. "무엇인가 하나 만의 목표를 위해 선택해야한다" 이유라면 옳은 선택이다.

서버사이드. 여기도 이해된다. 프론트엔드와 TypeScript에 의한 익숙함은 물론, k8s 영역을 제외하는 서버리스 세계에서는 Node.js는 주인공이라 해도 맞다. 비지니스 로직이나 기능을 일반적인 애플리케이션 서버로 제공할지, FaaS 같이 서버리스를 취할지. 유연히 고를 수 있는 것이 강점이다. 언어 자체로는 TypeScript는 다른 경량한 언어보다 탄탄하다는평을 받고 있으며, 동시에 Go 같이 간결함은 없지만 그 만큼 언어와 에코시스템 모두 파워풀을 겸하고 있다. 물론 이 뿐만이 아니라 많은 속성을 갖고 있으면서, 각각 비교해서 중동무이하다 느끼는 사람도 있을 것이다.

데스크탑 애플리케이션. 이건 OS 기능에 접근하는 것이 아니라면 Web 애플리케이션으로 만들어도 된다는 선택지가 대부분이라 느낀다. 2022년에 Qt를 사용해볼까 한다면 No라 하는게 대부분이겠고, 이용자도 많은 접근 권한을 넘기고 싶지 않을테니. 결과적으로 특별한 사정이 없다면 Web으로 끝낼 수 있으며, 통상적인 프론트엔드, 서버사이드와 같은 기술이므로 편안하다. Electron은 껍데기와 그 외의 사정이 너무 많이 변하므로 할애가 필요하다.

모바일 애플리케이션. 여기에는 그다지 프론트엔드의 손길이 닿지 않았다 느낀다. 큰 은행 수준에서도 Ionic를 사용하는 사업자도 있지만, 다양한 제약의 결과 모바일 애플리케이션를 Web 기술로 완결짓는 사람은 많지 않다. 대신 React Native 같은 것은 폭 넓게 사용되고 있으며, 특히 적은 리소스로 많은 플랫폼을 대응해야 하는 스타트업은 많이 이용한다.

이렇게 보면, 소프트웨어 엔지니어가 해야하는 거의 모든 영역에 Node.js를 사용할 여지가 있고, 또 이렇게 해야하는 것이 합리적으로 느껴지기도 한다.

Node.js는 늘 Better한 결과를 준다. 기술만으로 보면 Best는 프론트엔드, 그리고 서버리스 세계라 느끼지만.

리소스가 있고, 멤버 구성, 전문성, 에코시스템, 플랫폼. 다양한 요소를 고려할 때 상황에 따라 다른 영역에서 조차도 Best가 되기도 한다.

그리고 거기서 쌓이는 경험치가 많겠지. 이는 UI = f(state)일지도 모르며, 더 소박한 라이브러리를 알아서, 몰라서 일지도 모른다. 실용적으로 유연히 타입의 정의해야 한다 할지도 모르고, struct도, class도 없는 JavaScript Object가 다루기 쉬움 때문일지도 모른다

이는 모든 것이 우리의 무기가 된다. Node.js는 나쁘지 않은 수준으로 문제를 해결해준다. 컨텍스트 스위치도 없다. 어째서인가 어떤 분야에서도 사용한다. 어쩌면 이 기술은 만능일지도 모른다.

이런 환상보다도 명백한 하나의 대답.

편함이라는 사실

Node.js를 TypeScript를 사용하면 좋다. React는 우수하고 TypeScript를 사용하면 다소 질이 나쁜 코드라도 가독성은 유지된다. gyp로 고민할 기회도 없어져서 이제는 개발환경 구축도 스트레스없이 구축할 수 있으며 할 수 있는 영역도 넓어서 범용성이 넓다.

편하고 고품질. 이보다 안락한 기술이 어디 또 있을까. 아마 없겠지.

이런 것을 주절대며 우리는 Node.js에 침식된다.

이건 저주같다. 이 기술이 사라질지도 모른다는 위기감을 갖을 그 날까지 우리는 벗어날 수 없는.

아마 벗어나는 것은 합리적이지 않을지도 모른다. 적어도 지금 이 순간은.

겨울이 끝을 알리는 봄의 포근한 바람. 우리가 걸린 저주의 끝은 어디인가. 그리고 당신은 어떠한가.

온천 여행 귀가 길에...


답변 글

2010년경, python2으로 프로그래밍을 학습한 나에게 Node.js + CoffecScript는 Better Python이었다.

CoffecScript는 당시 JS(ES3~5)에 없던 기능을 보완해줬고, Python과 같이 공백 제어 오프사이드 규칙이 마음이 들었다. 겉보기는 Ruby 같아서 당시 전성기였던 Rails 엔지니어를 구하기도 유리했다.

Node.js 모듈시스템인 Commonjs는 Python과 같이 파일스코프가 명시적이면서, Ruby + Rails로 느낀 이름 공간의 암묵적 발산이 어렵다. 또 npm이 rubygems보다 좋다 느꼈다. 후발 주자의 강함을 발휘했다.

CoffeeScript는 Rails에서 사용되는 등 널리 보급 후에 사양이 경직되어, 그 후에 주류가 되지 못하고 퇴장했지만 거기서 나온 사상은 ES2015의 ESM와 TypeScript로 계승되었다.

JavaScript 주전장은 AST 파이프라인이다

이런 주장하는 Node/프론트엔드 엔지니어와 거기에 굳이 수고를 들이고 싶지 않은 JS로 문화가 크게 나눠졌다.


장기적으로 사용되는 범용 언어의 짐을 주시하는 것에서 Node도 예외는 아니다. Python2/3가 그랬으니

Node 12에서 단계적으로 npm의 ESM를 대응했지만, commonjs로 구축된 에코시스템의 약점에 따른 대응이 필요하기에 npm은 지금은 혼돈의 시대이다.

애초에 node.js는 개인 개발자가 만든 작은 미들웨어의 집약으로 큰 기능을 실현하는 것이었다. 그 대표격이 express으로 core라 할 만한 것이 거의 없고, 미들웨어 파이프라인이라 표현해야 하는 것이 옳다.

모범사례가 없던 시대는 그것이 유연성으로 유리하게 작동했다. 과거에는 npm과 프론트엔드가 분리되어있어서 webpack 같은 빌드스텝이나, Commonjs/ESM 양쪽 모두 해결같은 것이 필요없었다.

지금은 그런 시대가 아니다. 모범사례의 집약, 수검의 시대이고, 이는 TypeScript 퍼스트, Node/브라우저 호환을 전제로 한 유니버설 설계, Next.js 같은 스택 은닉, 마이크로서비스를 배경으로 한 모노레포에 의한 분할통치가 진행되고 있다.

앞으로는 정세불안에 의해 서플라인체인 공격의 현실적인 위험도 커졌고, 라이브러리 간의 의존성 감소 방향으로 발전이 진행되겠지.

Deno가 사용하기 편해보이는 것은 node의 유산을 버려서 이며, 보급될 경우 늦든 빠르든 같은 길을 걸을 걷니다. 또한 만약 TypeScript 이외의 현실적으로 언어 선택지가 등장한다면, Deno의 우위 사라진다.


과거에 만들어진 npm 라이브러리는 어떻게 봐야하는가.

Ready Player One의 모델이 된 Node의 OSS 스타 substack은 활약이 대폭 감소되었고 그가 만든 대량 미들웨어은 점점 갱신되지 않는다.

현재 모던한 JS 프로젝트에서 반드시 고전하는 것은 webpack, eslint, jest, typescript(tsconfig.json) 설정 조합이다. 이는 아마 프론트엔드 전문가의 개인의 프리셋을 가지고 있어(서로 배끼며) 경우에 따라 제대로 돌아가지 않는다.

개인적으로 이런 타이밍에 도구를 버리는 선택지를 취하고 있다. 최근은 이렇게 한다.

결국 이것도 간접적으로 Evan You와 Ryan Dahl 프리셋을 쓰고 있을 뿐이라 말할 수 있다.

이 상황에 불만을 느낀 Babel/yarn의 sebmck가 Rome이라는 대통일 도구체인을 개발을 하려했지만, 재설계 반복, 공동창업자 이직 등으로 전진에 불안을 느낀다.


근래 자주 듣는 것이 "WASM로 프론트엔드를 바꾸면 JS는 장래 사라질 것이다"는 이야기.

WASM에 대해 말하기 전에 생각해야 할 것이 8년 전 즈음, "AltJS로 JS를 구축한다" 말이 어떻게 되었는지부터 이야기하자.

각 컴파일러 JS 백엔드에서 JS를 바꾸는 구현이 일시적으로 유행이었다.

필자도 여러가지 조사해봤지만 그나마 쓸만했던 것은 TypeScript 뿐이었다. 이는 언어의 표현능력 이야기가 아니라, node/npm/브라우저의 에코시스템에 대한 그 언어의 "태도"가 문제였다 생각한다.

굳이 다른 언어로 적고 JS 타겟 컴파일한다. 이런 세계관은 기본적으로 JS 측 에코시스템을 존중하지 않은 경우가 많았다. 그 결과 commonjs/ESM가 아닌 곳에서 전역 변수를 멋대로 만들거나, 경우에 따라 표준조차 바꿔버린다.

JS에서 호출할 떄, 그 언어 런타임이 랩한 인터페이스를 "벗겨서" 사용할 필요가 있다. js_of_ocaml이나 Scala.js의 리스트 구조체에 JS로 접근하려면 커리화된 것을 몇 번이나 몇 번이나 벗겨야했다. 이게 번거로운 일이다.

반대로 AltJS에서 JS 객체에 타입을 붙이는 것은 곤란에 반해, 종종 있는 정적타입 언어에서 JS 바인딩 부분에 타입을 붙이지 않으면 돌아가지 않아 현실적으로 사용하지 못하는 경우가 많았다.

퍼포먼스면에서도 각 AltJS는 작은 코드에서도 그 실행환경 특유의 큰 런타임 코드를 담아야 하는 경우가 많았고, 라이브러리에서 사용하려면 몇 번이나 거쳐야한다.

그럼 어떤 수준의 퍼포먼스 저하를 허용하고 AltJS로 철두철미하게 적자. 이런 선택도 가능하지 않은가 하면...이런 이야기는 들리지 않는다. 이는 당연한 것으로 이것도 "node.js를 대하는 태도"와 같아서, JS 커뮤니티를 위해서는 그다지 효율적이지 않으므로 와 닿지 않는다.

그 결과 "그럴싸하다는 이야기도 없고, 라이브러리에도 사용할 수 없다"가 되므로, 노하우 축적으로 존재하는 Node/프론트엔ㄴ드 세계에서 존재하지 않는 것과 같은 소리이다.

결국, 점직적인 모습으로 단계적 도입하면서 JS 슈퍼셋인 TypeScript만 남는다. 다른 것이 있을지도 모르지만, 노하우가 합류하지 않으면 결과적으로 "레거시"가 되버리지는 않을까.


그리고 WASM은 어떤가하면, 개인적으로는 "프론트엔드를 WASM 메인으로 적는 선택지가 당연한 세계"는 "10년 후에 30% 정도"로 보고 있다. 현시점에서는 커리어를 모두 거는 것은 불가하다. 그 상세에 대해서 다음 글을 읽자.

WASM의 GC Proposal은 무엇인가 / 무엇을 하려 하는가

간단히 요약하면, 현재 명령 세트가 너무 저수준이라 빌드사이즈가 폭발하고, 복잡한 사양에는 시간이 걸린다. 정말 제대로 되는지도 모른다, 들어가고 나서 다른 언어 런타임이 이보다 경량해질지도 모른다. 하고 싶다면 Rust no_std가 능숙해야 한다. 이런 느낌이다.

WASM의 모듈 시스템은 ESM와 닮아 JS 에코시스템을 만들 수도 있으며, 실제 ESM Binding도 진행되지만 빌드 사이즈 문제로 Rust 이외의 에코시스템에 넣기는 어렵다. 프론트엔드 도구체인의 현 과제는 거대한 동적 페이지나 SPA를 구축하는 번들사이즈로 여기에 의존하는 수만큼 축적된다.

필자는 이 문제와 싸워왔다.

wasm-pack으로 regex를 사용할 때 빌즈사이즈와 퍼포먼스 검사


현실적으로 "TypeScript에 불만이 있는가" 하면 "다른 사람이 쓴 것을 사용하는 건 불만이지만, 자기가 쓰는 언어로는 그럭저럭 만족한다" 수준이다.

TypeScript가 이상적 언어인가하면 전혀 그렇지 않다. typeof null => object가 그대로이지, var, with가 사라진 것도 아니다. Error는 그대로지, null과 undefined 나눠 쓰는 것도 번거롭지, class는 이런 저런 언어의 최대공약수 같은 사양이지, 위험한 객체 접근에 제약을 걸 수 있는 것도 아니다. enum 제약이 많아 비표준, 복잡한 추론에 위한한 경우 에러 메세지도 그리 친절치 않다. ESM 대응도 별로, 본체는 namespace로 적혀져서 Treeshake 불가능. 언어상 불편은 불건전한 업캐스트로 해결. tsconfig.json 자체로 안전성이 변하는건지, 단편적으로 전달되는 코드인지 모른다.

어디까지나 타입이 있는 JavaScript이며, 런타임의 사용하는 책임을 갖는 것은 작성자이다. 필자는 훈련 결과 어느정도 높은 보증이 되고 다른 사람 코드도 타입 검사를 했으니 신뢰할 수 있는가 하면, 이것도 불가하다. 이런 언어라 생각한다. 숙련도가 그대로 안정성에 직결한다.

능수능란해지면 타입 선언 없이 추론만으로 자연히 파악할 수 있는 코드를 제공할 수 있다. 그러나 그 기술을 얻기 위해서는 여러 OSS 라이브러리 구조를 보거나 공부할 필요가 있다. 대부분의 프로그래머에게 이걸 기대할 수 없다.

그러니 잘 만들어진 TypeScript의 타입을 제공한 라이브러리를 모두가 사용하는 것이 가장 좋은 TS 사용 방법이지, 그것이 기능하는 것이 React 부근으로 기능하지 않은 것이 xxx 같은 느낌이 든다.


그러니 TypeScript가 좋고, npm가 혼란기며, Deno는 아직도 짐을 해결하지 못했고, WASM은 특정 유스케이스를 제외하면 쓸 곳이 없는 현 상황이다.