짧게 써보는 웹 프론트엔드의 역사

김민상·2021년 7월 18일
71
post-thumbnail

들어가며

웹 프론트엔드의 역사는 다른 기술들에 비하자면 많이 짧습니다. 하지만 그렇기에 오히려 간과되기 쉽다는 생각이 들었어요. 많은 새로운 기술들이 "우린 OOO 문제를 우아하게 해결했어!" 라며 등장하지만, 그 문제가 왜 문제인지는 잘 설명해주지 않는 경우가 많으니까요.

저는 모든 기술에 관한 지식이 반드시 코어부터 쌓아올려져야 한다거나 해당 기술의 역사를 모두 꿰고 있어야 한다고 믿는 근본주의자(?)는 아닙니다. 다만 그냥 한번 쓱 훑어보는 것으로 후에 밟을 지뢰를 피하는 데 조금의 도움이 된다면, 혹은 새 기술을 선택하는데 중요한 기준점을 세울 수 있다면 좋을 것 같다는 생각에 오랜만에 글을 쓰게 되었습니다.

뭐 안되면 개발자끼리 노닥거릴 때 한마디 보탤 지식으로 쓰시길 바라며- 😉

1. HTML

요즘 웹 배우는 분들에게는 놀라우실지 모르지만, 그냥 동적인 데이터가 없던 시절이에요. 게시판도 없고 리스트도 없는. 만약 데이터 변경이 발생한다면, 서버에 있는 HTML파일을 열어 수정하고 저장하면 반영 끝.

참 쉽죠?ㅋ

2. CSS

데이터 갱신은 됐고, 화면이라도 예쁘게 그리고 싶은데 HTML에다가 줄줄이 붙이는게 너무 지저분해 보였나 봐요. 그럼 모양 잡는건 따로 써넣자, 그게 CSS에요. 처음엔 HTML이 들어가 있는 파일에 <style> 태그를 열어서 썼는데, 이것도 지저분하다 해서 나중엔 별도의 파일로 작성하고 <link> 태그를 통해 임베드하는 방식으로 바뀝니다. (이 방식은 지금도 사용하는 방식인데, CSS-in-JS가 나오면서 조금 달라졌습니다)

3. PHP/ASP/JSP

자, 이제 페이지에 동적 데이터를 넣을 수는 있는데, 서버만 넣을 수 있어요. 이게 무슨 뜻이냐- 브라우져가 새로 필요한 정보가 있으면 페이지를 다시 읽어야 한다는 뜻입니다.

새로고침! 혹은 페이지 이동! 그래야 서버가 바뀌는 정보를 다 가져와서 그려줍니다.

반대로 유저가 서버에게 뭘 보낼 땐 어떻게 해야할까요? 서버는 (지금도 마찬가지지만) GET/POST 등의 http 프로토콜을 사용한 방식으로 데이터를 받을 수 있어요. HTML에서 <form> 태그를 이용하면 특정 서버 엔드포인트에 이 정보를 전송할 수가 있죠. 그럼 서버는 이 데이터를 받아다가 DB에 저장하고 다시 DB에서 꺼내와서 그려줍니다.

페이지가 변경될 때마다 브라우져가 반짝-반짝 하겠죠?ㅋㅋ

4. ajax(XMLHttpRequest)

브라우져에 ajax라는게 생겼어요. 간단히 말하면, 페이지 이동 없이 서버에게 뭔가를 보내고 받을 수 있는 기술이죠. 보내는 건 그렇다 치는데, 뭔가를 받았으면 화면을 갱신해야하죠? 그러면 (지금도 마찬가지지만) 브라우져에서 화면을 그리는 데이터 구조인 DOM을 수정해야해요.

문제는, 당시엔 IE/모질라(Nestcape/Firefox)/크롬이 모두 각자마다 그리는 방식이 약간씩 달랐던 시절이에요. (그래서 '웹 표준'이라는 용어가 등장합니다) 어쨌든 이 시기의 웹 개발자는 각 브라우져에서 모두 잘 동작하도록 따로따로 코딩해야했죠.

(그게 귀찮으면 아래와 같은 방법도 있었어요ㅋㅋ)

5. jQuery

이게 너무 귀찮던 차에 구원에 가까운 라이브러리가 나왔어요. CSS에서 사용하던 Selector 방식으로 DOM의 위치를 찾고, 찾은 DOM을 간편한 방식으로 변경할 수 있는 녀석. 브라우져별 대응은 jQuery에서 웬만큼 해줬기 때문에 개발자들은 이 문제를 조금 덜 신경 쓸 수 있었죠. (그렇다고 늘 되는건 아니었ㅠ)

이 때부터 웹 프론트엔드의 복잡도가 급속도로 올라가기 시작했고, (훨씬 더 높은 수준의 사용자 경험을 제공하는 사이트가 생겼다는 뜻) jQuery만으로 DOM을 조작하는 일의 난이도도 더욱 올라가기 시작했어요. 조금의 변화라도 생기면 직접 그 위치를 포인팅해서 값을 바꿔줘야 하는 일이니, 얼마나 귀찮은가요.

2008년의 페이스북을 찾아봤는데, 이 때도 엄청 복잡하죠?

예를 들자면, 5개의 아이템이 나열되어 있고, 그 중에 하나를 선택하는 UI가 있다 할 때 1번 아이템을 선택하면? 선택한 아이템을 빨간색으로 색칠해줬어요. 그러다 다시 2번 아이템을 선택하면, 2번 아이템을 빨간색으로 색칠하고 끝일까요? 아뇨 1번 아이템을 다시 원래의 색으로 색칠해줘야겠죠. 이런 일이 거의 모든 페이지에 수없이 일어났다고 보시면 됩니다.

6. angular.js

그 시점에 angular.js의 등장은 가히 혁명적이었어요. angular.js 에서는 데이터셋을 $scope에 저장하고, 'template'이라 불리는 특이하게 생긴 HTML-like 한 문서에다가 특이하게 생긴 문법으로 쓰기만 하면 자동으로 갱신을 해준답니다. 진짜 놀라운 건 약간의 문법(ng-model)만 사용하면 유저의 입력도 $scope에 넣어 준다는 것이었어요. (이런걸 양방향 바인딩- two-way binding / bidirectional binding 이라고 합니다)

이제부턴 웹 개발자가 DOM의 구조에 대해 모두 알고 있지 않아도 됩니다. 그려져야 할 모양을 우선 '선언'해두고, 각 필요 위치마다 데이터를 바꿔주면 되니까요. (이런걸 선언적 프로그래밍- declarative programming 이라고 합니다)

그럼 이 마법은 어떻게 일어나는걸까요? $scope의 데이터가 변경될 때 뷰를 다시 그리는데, $scope가 변하는지는 어떻게 알아내는걸까요? 여기서부터가 중요한데, 왜냐하면 이 방법의 변화가 그 이후 React/Vue, Angular, Svelte 간의 결정적 차이를 만들어내기 때문입니다.

angular.js 의 방법은 좀 막무가내입니다ㅋ Digest Loop/Cycle 이라는 멋진 이름을 붙여놨는데, 쉽게 말해 '주기적으로 계속 확인하는 것'이에요. 특정 주기로 계속해서 체크하고 변경을 감지하는 방법. 그렇기에 조금만 페이지의 규모가 커지고 '체크' 해야할 내용이 많아지면 엄청나게 느려집니다. 이를 방지하기 위해 여러 방법들이 나오긴 했는데, 본질적으로 쓸데없는 연산을 많이 할 수 밖에 없는 구조라 해결이 쉽지 않았습니다.

그래서 숙제가 생겼어요.

a. 무작정 감시하는 대신 데이터 변경을 좀 더 똑똑하게 감지할 수 있어야 한다.
b. 변경이 감지되었을 때 뷰를 더 빨리 효율적으로 다시 그릴 수 있어야 한다.
c. a/b가 효율적이기 위해 웹페이지의 구조를 더 작은 단위로 만들어야 한다.

7. React

그리고 React가 나왔습니다. React는 이 숙제들을 해결하는 가장 좋은 방법이었어요.

우선 c 문제부터. 모든 코드는 '컴포넌트'라 불리는 단위로 쪼개지고, 아무리 커다란 페이지라도 이 컴포넌트들을 레고 블럭처럼 조합해서 만들자는 거죠. 이건 예전부터 있던 개념으로 'Component-based Development'라 불리던 내용이고, 웹 개발에 잘 맞아 떨어지는 개념이라고 할 수 있어요. 우리가 만드는 코드들을 이 단위로 쪼개서 사용하면 나중에 재사용할때도 (copy&paste 대신에) 이미 만들어둔 컴포넌트만 가져오면 되니까요. 이 개념은 React/Angular/Vue/Svelte 까지 모두 동일하게 사용됩니다.

a 문제의 해결도 여기서 출발합니다. 컴포넌트끼리 데이터 전달은 prop으로만 가능하고, 상위 컴포넌트가 하위 컴포넌트에게 '내려주는' 것만 가능하게 하는거죠. 컴포넌트 내부에서 스스로 변경하는 데이터는 state라 명명하고 이 prop/state 말고는 데이터 변경을 감지하지 않는 거에요.

그래서 React는 '감시(watch)'라는 개념을 아예 사용하지 않아요. 정말 명시적으로 '그려라(render)' 할 때 그리기를 시작하는 거에요. 그리고 이렇게 명시적으로 렌더해야 할 상황들을 prop이 변경되었을 때와 setState가 호출되었을 때-로 강제한 것입니다.

prop이 변경되면?

  • prop을 전달한 부모 컴포넌트가 자식 컴포넌트에게: "너 다시 그려야 할듯?" 을 알려주고,

state가 변경되면?

  • setState 메소드가 스스로: "나 다시 그려야 할듯?"을 알려주는 식.

그럼 b 문제를 어떻게 해결할까요? prop/state 가 바뀌었다고 언제나 꼭 뷰가 다시 그려져야 하는 건 아니지 않나요? 그리고 언제나 뷰를 통으로 다시 그리면 성능 문제는 없을까요? 그래서 Virtual DOM이 등장합니다.

Virtual DOM이란 DOM을 복사한 데이터 구조에요. 브라우져에서 인스펙터를 열어보시면 아시겠지만 DOM은 엄청 복잡한 구조의 무거운 녀석인데, 여기서 필요한 부분만 뽑아 js 의 object 구조로 변환해서 관리하자는 아이디어.

그래서 누군가 '그려라!' 했을 때 실제로 DOM을 조작하지 않고, 먼저 VDOM 방식으로 이 컴포넌트의 뷰 구조를 그려보는거에요. 그리고 저장되어 있는 '현재' VDOM과 비교하는거죠. 이 비교는 그냥 메모리상의 데이터 diff 이기 때문에 무지 빨라요. 그래서 다- 그대로인데 요 부분만 다르다- 라는 결과가 나오면 실제 그 변경사항만 재빠르게 DOM에 반영하는게 가능해진거죠. 와 똑똑하죠?ㅋ

8. Angular

이 시기 angular.js 를 만들었던 개발자들도 이 문제들을 해결하고 싶었어요. 그래서 결론은-

"아 기존꺼 그대로는 못 해결한다! 새로 짜자!"

  • 실제로 이렇게 말한 것은 아닙니다 😉

으잉?! 기존의 angular.js 를 사용하던 웹 개발자들에겐 좀 황당한 결론이었ㅠ 그래서 "angular.js 2" 후에 "Angular"라는 이름으로 명명된 후속버전은 1 버전과 전혀 호환이 되지 않아요. 개인적으로, 이 결정이 대세가 React로 기울어지게 만든 결정적인 계기가 아닐까 합니다.

다시 Angular 로 돌아와서, 여긴 어떤 아이디어로 angular.js의 구조적 문제를 해결했을까요?

Angular에 도입된 재밌는 아이디어는 Zone.js 라는 개념인데, 이 개념을 (돌 맞을 각오하고) 엄청 간단하게 축약하면, '데이터가 변경될 가능성이 있는 모든 핸들러에 감시를 붙인다' 정도로 설명할 수 있을 것 같아요. 데이터가 변경되는 길목에 서있는다-는 개념은 React와 유사한데, React는 변경이 가능한 데이터(prop/state)를 강제해서 그 길목을 '좁혔다'면, Zone 은 가능한 모든 길목을 감시(hooking)한다고 볼 수 있어요.

유저가 클릭하면 / 키보드로 뭔가를 입력하면 / 데이터를 요청하거나 받아오면 / setTimeout, setInterval 이 호출되면 등등. 데이터가 변경 가능한 모든 길목마다 딱 버티고 서서, "데이터가 변경되었을지 모르니 확인해봐!" 라고 알려주는거죠.

그럼 b 문제(효율적인 DOM갱신)는 해결 안했을까요? 솔직히 저도 React로 떠나왔기 때문에 잘 모르고 있었는데, 이 글을 쓰기 위해 찾아보니 Angular 9 버전 부터 Angular Ivy라는 새로 만들어진 렌더링 파이프라인에서 Incremental DOM 이라는 방식을 적용했네요. 읽어보니 재밌는 아이디어긴 한데, 관심 있는 분은 찾아보시면 좋을 것 같아요.

9. Vue

React가 등장해서 주목을 받았을 때, React의 방식이 완전히는 마음에 들지 않는 사람들이 있었어요. 특히 저 같은 angular.js 에서 개발을 하던 사람들. a/b/c 문제를 해결하는 방법 자체는 좋은데, angular.js 에서 너무 편리하게 사용했던 '템플릿'이 사라졌거든요. React 는 한번 템플릿을 읽어다 interpreting 을 하는걸 건너뛰고 그냥 바로 뷰를 그리는 방법(JSX)을 강제했으니까요.

그래서 컴포넌트 베이스 구조에(c) prop/state 를 쓰면서(b) VDOM render를 사용하는(a) 물건 (사실상의 React)에다가 템플릿을 끼얹은 물건이 만들어졌어요. Vue입니다!

angular.js 의 문법에 익숙한 개발자라면, Vue에 끌리지 않을 수 없을거에요. 저 역시 그랬습니다ㅋ 그래서 지금도, "angular.js 개발 경험이 있는데, 다음에 뭐할까?" 묻는다면, Vue를 추천하지 않을 수 없어요. angular.js 보다는 Angular 와 비교하는 것이 옳을 만큼 진보한 Template Syntax를 제공하고, 성능도 좋고, 안 쓸 이유가 대체 뭐죠?

안 쓰고 있는 저의 개인 의견을 잠시 넣자면, "템플릿"이 이젠 별로이기 때문입니다. Typescript를 전체 백/프론트엔드 모두에 사용하고 있는 지금으로선 타입체킹이 반쪽짜리인 것이 가장 큰 이유이고, Template Syntax를 통해 데이터를 컨트롤하는 것보다 JSX 안에서 JS-native 함수들을 사용해 조작하는 것이 더 직관적이라고 생각합니다. 뭐 취향 차이니까 뭐가 옳다 그르다 할 순 없는 주제지요.

또한 이 비교들과는 번외로, 웹 프레임워크에서 출발한 하이브리드앱 개발 기술들 중 ReactNative가 다른 프레임워크 기반 기술들(Vue Native / NativeScript / Ionic 등)과 비교할 때 압도적 지분을 갖고 있다는 점도 큽니다. 그 이유가 React의 설계 철학과 맞닿아 있는 점도 좋아하는 이유 중 하나.

10. Svelte

Svelte가 낯선 분이시라면, 우선 공식 튜토리얼(https://svelte.dev/tutorial/basics)을 한번 보고 오시는걸 추천드립니다. 제가 본 튜토리얼 중 최고 rating을 줄 만큼 잘 만들어져 있어요. 이걸 한바퀴 둘러보고 오신다면, 아래의 설명을 이해하기가 좀 더 쉬우실 것 같네요.

Svelte는 지금까지 위에 설명한 React/Vue의 VDOM 방식도 사용하지 않고, Angular의 Zone 방식도 사용하지 않습니다. 그럼 어떻게 데이터 변경을 감지하고 어떻게 DOM을 갱신할까요?

우선 Svelte가 다른 웹 프레임워크와 가장 크게 다른 점은, 개발자가 js 코드를 쓰는게 아니라 Svelte 문법으로 작성된 .svelte 코드를 쓰고, 이걸 Svelte가 컴파일해서 HTML/JS/CSS 결과물을 만들어준다는 점입니다. 튜토리얼 진행하면, "오 이건 대체 무슨 흑마법이지?" 싶어요ㅋㅋ

친절하게도 js 결과물이 uglify 된 룬어가 아니라 충분한 가독성을 가지게 잘 만들어진 코드라서 오히려 개념을 이해하게 도와주는데요. (다시 돌 맞을 각오하고) 이 과정을 간단하게 축약한다면, "Svelte는 (jQuery처럼) DOM 조작하는 코드를 직접 짜주는 컴파일러"라는 것입니다.

저 위에 4/5번(ajax, jQuery) 시기의 개발 방식을 기억하시나요? 그 시절엔 데이터 변경을 감시하고 갱신의 효율을 따지는 일이 필요하지 않았잖아요. 개발자가 그 지점과 행동을 모두 알아야 코딩이 가능했으니까요. 바로 그 '지점'과 '행동'을 Svelte 컴파일러가 대신 해주는 거죠. 그래서 "DOM을 언제 갱신하지?" "DOM을 어떻게 효율적으로 갱신하지?" 라는 질문 자체가 필요없어지는 것입니다.

정말 신박한 방법이라고 생각하고 감탄했지만, 바로 이 지점이 "이걸 프로덕션에 써도 괜찮을까?"라는 의문에 꼬리를 남깁니다. 만약 프로덕션에서 컴파일러가 커버할 수 없는 문제가 발생한다면, 어떤 workaround가 가능할지 그림이 잘 안 그려진다고 할까요. 폭신한 비단길인지, 지뢰가 가득깔린 지뢰밭인지 지금으로선 도저히 모르겠다-가 제 의견입니다;

하지만 반대로 이런 과정을 거치지 않은 '신기술' 또한 없으니 용기 있으신 분들이 먼저 성큼성큼 걸어주신다면야 언제든 따라갈 준비는 되어 있어요. (ㅋㅋㅋㅋ)

맺음말

서두에 썼듯 누군가에게는 도움이 되셨기를 바라며-

profile
RedDays CTO

9개의 댓글

comment-user-thumbnail
2021년 7월 22일

좋은 글 감사합니다.
React에 입문하는 단계인데 React와 Angular, Vue의 차이를 이해하는데 큰 도움이 되었습니다. ㅎㅎ

1개의 답글
comment-user-thumbnail
2021년 7월 28일

긴 이야기를 지루하지 않게 잘 서술해주셔서 편하게 잘 읽었어요~~
중간 중간 농담도 너무 재밌어요ㅎㅎ

1개의 답글
comment-user-thumbnail
2021년 9월 7일

와,, 긴 역사를 이렇게 재밌고 이해하기 쉽게 써주시다니 감사합니다!!!

1개의 답글
comment-user-thumbnail
2021년 12월 5일

와~ 지난 10년의 세월이 이 한 글에 압축되어 있네요! ㅋㅋㅋ 좋은 글 감사합니다. :)

1개의 답글
comment-user-thumbnail
2022년 1월 18일

쏙쏙 이해 잘되고, 웹 프론트엔드 기술들이 어떻게 나오게 되었는지 한눈에 볼수있는 좋은 글이네요~~bbb

답글 달기