현대 프론트엔드의 기반 기술. JS를 중심으로

서버에 딸린 '뷰' 에서 탈피한 모던 웹의 기반 기술에 대해 다루어 봅시다

Featured image

들어가며

유럽의 핵물리 연구소 CERN에서 내부 정보 공유를 위해서 시작되었던 world wide web의 역사에서 시작해서, 모바일 기기로 사용하는 웹이 너무나도 깊숙히 침투해온 지금의 시대까지. 웹은 참 많은 길을 걸어 왔습니다. 처음 CERN에서 태어났던 최초의 웹 페이지는, 스타일링도 뭣도 없는 말 그대로의 Hyper Text Markup Language로만 이루어져있던, 텍스트 문서 묶음이었지만, 지금의 웹은, 단순 웹 페이지에서 그치지 않고, 사실상 앱과 같은 경험을 제공하며, 오프라인 환경일때도 무언가를 하기도 하는 등, 점점 그 복잡성은 확장되어 갔습니다.

단순히 마크업과 UI 상호작용을 위한 JS 등으로만 구성되어, 서버에서 내려주는 정적 파일 덩어리 중 하나였던 웹 뷰는 어느새, ‘프론트엔드’ 라는 또 다른 멋진 이름표를 달고, 프론트엔드 개발자는, ‘웹 개발자’에 속하지만, 고전적인 백엔드와 웹 뷰를 모두 맡던 역할과는 사뭇 다른 역할을 수행하고 있습니다. 그리고 외부 의존성이 늘어나고, 웹 특유의 다양한 환경에 대응하기 위한 여러 도구들이 추가됨에 따라, UI를 중심으로 찍어내는 ‘퍼블리셔’의 역할만으로는 웹 프론트엔드 생태계를 온전히 이해하기는 어렵게 되었습니다.

추석 연휴를 맞아 여유가 난 지금, 추상적으로 떠돌아다니고만 있는 저의 지식을 정리하며, 기술의 역사와 흐름을 정리하며, ‘남에게 설명할 수 있는 지식’ 으로 만들 수 있도록 이번 글을 준비해 보았습니다.

물론 모든 기술을 다 정리 할 수는 없을겁니다. JS 자체의 역사만 하더라도 너무나도 방대하고, DOM, CSSOM, 렌더링 과정 등등 정리할것이 너무나도 많습니다. 이번 글에서는 모던 프론트엔드 환경에서 쓰이는 JS 와 직접적으로 맞닿아있는 도구를 중점적으로 정리를 해볼까 합니다.

번들러

옛날 이야기를 잠깐 해보죠. 태초의 JS는 모든 것이 전역이었습니다. 그래서 타 언어의 지역 모듈과 같은 개념을 사용하기 위해서는 IIFE, 클로저 등등의 직관적이라고는 말하기 어려운 패턴들을 사용하면서 그 지역성을 유지해야 했던 나름 옛 이야기가 있었습니다. 컴퓨터를 만지는 사람들은 예로부터 지금까지, 문제를 자동화 도구 등으로 해결하는 것에 대해서 이상할 만큼의 선호도를 가지고 있습니다.

programmer_auto_meme

자동화에 미친 프로그래머 (https://www.reddit.com/r/ProgrammerHumor/comments/f0ag3i/automation/)

JS를 사용하는 사람들이 중국 춘추전국 시대마냥 군웅할거를 하면서 만들어 놓은, commonJS, ESModule 등등의 형태로 import 하는 파일들을 지정할 수 있다는 것을 활용하여, import 또는 require 문으로 불러오는 파일들을 모듈러만의 비법소스를 사용해서, 스코프를 침범하지 않는 형태로 가공하여, 스코프 관심사 분리를 성공적으로 한 하나의 JS 파일로 말아넣기 시작 하였습니다.

이러한 번들링은 여러 이점이 있었습니다. HTTP/1 명세를 사용해서 웹 브라우저가 파일을 가져오던 시절에는 여러 파일을 가져오는 것이, 번들링 된 파일 한개를 가져오는 것 대비, 불필요한 오버로드를 일으켰기 때문에, 로딩 속도를 줄이는 데에도 도움이 되었거든요.

이제 다시 시계를 돌려서, 현대의 웹 환경으로 돌아가 봅시다. 지금은 HTTP/2 사양이 등장하여, 멀티플렉싱 덕분에 로딩 속도 만을 위해서 파일을 한개로 말아야 하는 환경도 없어지고, ECMA 사양의 모듈 시스템이 정착함에 따라서, 단순히 스코프 분리만을 위해서 번들링을 할 필요는 없어졌지만, 번들링은 여전히 유효합니다. 현대적 웹 프론트엔드의 관점에서 번들링의 용도는 코드 스플리팅과 트리셰이킹 이라고 볼 수 있습니다.

코드 스플리팅

말 그대로 코드를 쪼개는 일입니다. 이전에는 하나의 JS를 만드는데에 초점을 맞추었다면, 이제는 합리적인 묶음으로 포커스가 바뀌었다고 대략적으로 설명할 수 있을 것 같습니다. 이 글의 독자층이 대부분 한번 정도는 사용했을 법한, ‘인스타그램’ 서비스로 예시를 들어보겠습니다.

인스타그램 서비스에는 여러 기능들이 있는데, 주변 사람들의 피드를 조회하는 기능, 사용자 설정 이 두가지 기능 두가지를 생각해 봅시다. 너무나도 당연한 이야기를 하나 해 봅시다. 사용자 설정 페이지에서, 주변 사람들의 피드를 조회하는 코드의 전부를 불러올 이유도 없고, 반대의 경우도 마찬가지 입니다 (vice versa).

현대적인 ‘웹 앱’ 환경에서는 전체의 코드 규모가 점점 커짐에 따라서, 쓸 이유가 없는 JS 코드를 불러옴으로서 생기는 불필요한 페이지 로딩 시간을 줄이는 것이 번들러의 훌륭한 의의가 된다고 볼 수 있습니다.

트리 셰이킹

비단 JS로 작성하는 웹 프론트엔드 환경이 아니더라도, 프로그래밍 언어로 간단한 프로시저라도 작성을 해 보았다면, 서비스 흐름을 모두 고려하더라도 실행되지 않는 ‘죽은 코드’가 생기는 것을 경험적으로 알 수 있습니다. 물론, 죽은 코드를 발생시키지 않는것이 최적의 선택지 이겠지만, 외부 라이브러리에서 특정한 기능만 뽑아 쓴다거나, 아니면 향후 서비스 확장을 위해서 예비해둔 코드 일 수 있기 때문에, 섣부르게, ‘죽은 코드를 죽여라’ 하고 돌을 던질 수는 없는 노릇이지요.

export문과 import문을 통해서 모듈 단위로 함수가 사용되는 것을 감지하여, 필요 없는 코드를 프로덕션 빌드에 포함시키지 않는 일 또한 현대 웹 FE 환경에서의 번들러의 역할이라고 볼 수 있습니다.

웹 프론트엔드 개발에 쓰이는 번들러들

앞서, 과거의 관점과 현재의 관점 모두에서 번들러는 나름의 이유를 가지고 사용되었다는것을 설명하였습니다. 그 다음 수순은 자연스럽게 실제로 사용된 번들러가 어떤 것이 있을지에 대한 이야기 일 것 입니다.

webpack

전통적으로 많이 쓰였던 번들러 입니다. node로 작성되어서 속도가 약간 아쉽다는 것이 단점이지만, 오랜 시간 동안 사실상 표준으로 사용되다 보니, import 문을 처리하는 여러 loader들이 잘 되어 있고, 많이 쓰였던 loader들은 현대적 번들러에도 게승이 되고 있는, 많은 번들러들의 정신적 고향이라고 할 수 있겠습니다.

하나 덧붙이자면, 프론트엔드 개발에 정말 중요하고 기반이 되는 구성요소 였던지라, 마치 한국인들의 밥상에서 거의 필수요소로 들어가는 ‘김치’에 비견될 정도였어서, 당시 인프런 강의 등에서도 ‘김치를 마트에서 사먹는 것이 아니고, 직접 김장을 할 수 있어야 노련한 사람이듯, 웹팩 설정도 직접 할 수 있어야지, 노련한 개발자라고 할 수 있다’ 라는 말이 꽤나 설득력이 있었던 기억이 납니다. 2024년 현재로서도, 기능적인 문제는 크게 없기에 빌드시간이 약간 더 걸리는 부수적 문제만 넘길 수 있다면 webpack에 대한 공부를 해서, 기초적인 번들링 파이프라인 지식을 쌓는것은 여전히 유효하다고 개인적으로 생각하고 있습니다.

ESbuild & Vite

React 초심자들이나, 여러 기능들이 필요하지 않은 웹 페이지를 작성하는 데에 많은 도움을 주었던 Create React App이 Deprecated 되고 나서 사실상 표준으로 쓰이는 Vite 의 번들러가 ESbuild라는 점을 소개하면서 이번 장을 시작하고자 합니다.

node로 작성되어 느리다라는 webpack의 태생적 한계를 극복하기 위해 golang으로 작성된 번들러로서, 여러 FE 환경에서 성공적으로 자리를 잡은 번들러 입니다. 프로젝트의 규모가 커지면 커질수록 정말로 ‘순식간에’ 빌드가 되는 기적을 체험할 수 있으며, Vite의 HMR(Hot Module Replacement)와 합쳐져서 FE 개발 시에 DX를 끌어올려주는 훌륭한 도구의 위용을 한번 정도는 느껴보는 것이 좋다고 생각합니다.

여러 이야기들을 덧붙이고 싶었으나, vite로 한단계 감싸진 ESbuild만 써본 작성자의 경험 부족 이슈로, 궁금한 사항은 공식 Docs를 참고해주시길 바랍니다…

트랜스파일러

또 옛날 이야기를 해봅시다. IE가 살아숨쉬던 시절, ES6 명세는 모든 브라우저가 지원하는 스펙은 아니었습니다. 하지만, ES6 명세들은 상당히 코드를 직관적으로 간단하게 적을 수 있는 좋은 명세들이 많았기 때문에, 이를 포기하는것은 좋은 선택이라고는 말하기 힘든 상황이었지요. 이 문제에 대해서도 문제 해결을 참 좋아하시던 선조들이 JS를 JS로 트랜스파일 하는 도구를 만들었습니다. 성경에 나오는 바벨탑 사건 이라는 에피소드가 전 세계의 언어가 여러개로 흩어진 경위를 다루는데, 이에 착안하여, 각 브라우저 라는 환경에 맞게끔 언어를 맞춰주는 역할의 도구의 이름이 된 것이지요.

현재의 프론트엔드 개발 환경에서는 ES6는 IE 10, 11을 비롯한 레거시 환경에도 너무나도 당연히 지원되는 스펙이 되었습니다. 하지만 현대 웹 개발 환경에서는 JS 내에서 DOM을 마크업처럼 작성하도록 도와주는 JSX라는 문법이나, JS의 지나치게 자유방임적인 타입 시스템을 억지로라도 해결해보고자 하는 Typescript를 사용하기 위해서 트랜스파일러가 여전히 중요한 요소로 남아 있습니다.

웹 프론트엔드 개발에 쓰이는 트랜스파일러

앞서 트랜스파일러의 사용용례에 대해서 언급 하였으니, 다음 순서는 자연스럽게 실제로 어떤 트랜스파일러가 사용되는지에 대한 예시겠지요.

Babel

앞서 설명했던 이전부터 많이 사용되었던 트랜스파일러 입니다. JS를 이용한 프론트엔드라는 조금 더 넓은 관점에서 본다면, React Native 쪽에도 현역으로 잘 뛰고 있습니다. 그리고 후술할 swc라는 새로운 솔루션보다 많은 시간을 사실상 표준으로서 함께해 왔기 때문에, 광활한 라이브러리 생태계, 그리고 안정성을 챙길 수 있는 아직도 유효한 선택지 입니다.

swc

Speedy Web Compiler라는 이름 답게, 정말로 빠릅니다. rust 언어로 개발되고, 상당히 많은 부분을 생략하거나 간소화 함으로서, 속도를 챙겼다고 합니다. 현재 유명한 사용예로는 NextJS의 내부 트랜스파일 시스템이 swc로 작동되고 있다고 합니다. babel에서 swc로의 마이그레이션에 관한 이슈사항 들이 구글에 검색해 봤을 때에 꽤나 많이 노출되는 것으로 보아서, 마이그레이션이나 관심 등이 활발하게 유지되고 있는 것으로 보이며, 메인 유지보수자인 강동윤씨가 열심히 깃헙에 관련 코드들을 커밋하면서 활동적으로 유지되는 라이브러리로 확인되어, 실험적인 프로젝트에서 사용해보기에 좋은 선택지 같습니다.

브라우저

웹 프론트엔드 개발의 정말로 큰 특징이 저는 ‘다양한 환경에서 실행되는 나의 코드’ 라고 생각합니다. 그리고 브라우저는 국제 표준 같은것이 존재하지 않고, 많은 사람들이 구글 크롬이나, 크로미움 기반 브라우저를 사용하지만, iOS나 macOS 에서 기본적으로 설치되어 있어 꽤나 많은 사용자층이 사용하는 safari 브라우저를 무시할 수는 없습니다. 그리고 가끔 브라우저의 구버전도 신경써야하고… 꽤나 머리가 아픈 일이 아닐 수 없습니다. 여러 환경에서 시험을 직접 해보는 것이 가장 좋겠지만, 여러 정보 검색 결과, browserstack등과 같은 도구를 사용하면 좋다고 하네요. 나중에 기회가 되면 한번 써보는것도 나쁘지 않을 듯 합니다.

마치며

웹 프론트엔드 라는 주제로 다룰 수 있는 주제는 정말로 무궁무진 합니다. DOM, CSSOM, JS의 옛 명세, 현재 새롭게 들어오고자 하는 TC39프로세스, npm, yarn, bun, node, deno… 그런것 하나 하나 다 열거하면 정말로 끝도 없기에, 이번 글에서는 정말로 JS에 밀접하게 붙어있는 도구들, 그리고 짧은 시일 내에 변하지 않을 것으로 생각되는 개념들을 정리해 보았습니다. 아마 다음에 또 글을 쓸 기회가 된다면, 웹을 작성하는데에 사실상 필수품이 된 CSS와 스타일링에 대한 여러 접근법에 대해서 논의해 보는 글을 작성해 보고자 합니다. 끝까지 읽어주셔서 감사드리며, 잘못된 내용이나 보충이 필요한 내용이 있다면, 주저없이 댓글 남겨주시면 감사 드리겠습니다.