최근 HTTP/2 그리고 ESbuild, ESmodules 등 JavaScript 생태계의 많은 부분이 변화하고 있고 그에 따라 몇 가지 웹 애플리케이션 성능 향상 케이스를 살펴보았습니다. 현재 글에서 사용되는 프레젠테이션은 다음 레포지토리에서 확인이 가능합니다.

Seia-Soto/presentation-unbundled-code-splitting-strategy
Move to web forward. Contribute to Seia-Soto/presentation-unbundled-code-splitting-strategy development by creating an account on GitHub.

Webpack 등의 번들러로 '번들링'하는 것

기본적으로 현 세대 웹 사이트들에서 볼 수 있는 파일들은 개발자들이 작성한 코드가 아닌 대부분 번들러라는 것을 거쳐서 각각의 코드들이 최소화되고 최적화된 상태입니다. React.JS 등의 비교적 최신의 기술을 사용했을 때에도 이러한 번들러가 없다면 현재보다 웹 사이트가 몇 배 더 느려졌을 것입니다. 그 이유로는 여러가지가 있습니다.

  • 먼저 대표적으로 HTTP/1.1 프로토콜은 파일 하나 당 새로운 커넥션이 필요하기 때문에 많은 파일들 클라이언트에서 다운로드하는데에 적합하지 않습니다.
  • 웹 서버에서 Gzip이나 Brotli 등으로 압축을 하더라도 원본 코드가 최소화되는 것보다 효과가 크지 않습니다. 또한 이러한 코드들은 최적화에 꽤나 많은 자원과 시간이 소모되기 때문에 웹 서버에서 즉시 최적화시키기가 어렵습니다.

Webpack은 오랫동안 저희 주위에서 수많은 웹 사이트를 최적화했습니다. 그리고 실제로 이것이 성능이 좋지 않다는 것은 아닙니다. 여전히 괜찮은 대안 중 하나라고 생각합니다.

번들러에서의 Code-splitting

보통 저희가 Code-splitting이라고 하면 대부분 React.lazy나 Loadable component 등을 사용하면 컴포넌트가 비동기적으로 추가되어 번들러가 웹 사이트 번들을 생성할 때 여러 chunk로 나눕니다. 생각보다 쉽고 그렇게 어렵지 않은 방법입니다. 이렇게 하면 실제 웹 사이트에서 필요한 부분만 사용자에게 전달하여 트래픽과 로드 타임을 줄일 수 있습니다. 이러한 작업이 필요한 기본적인 이유는 최신 웹 앱들은 SPA 구조를 가지고 있기 때문입니다. (단, 이 글에서 왜 React나 Vue 등 최신 웹 앱들이 SPA 구조를 가지게 된 이유는 설명하지 않습니다)

변화하는 웹 환경

하지만 그동안 웹에는 수많은 역동적인 변경 사항이 있었습니다. HTTP/2 그리고 현재 상용은 아니지만 HTTP/3를 포함해서 개발 쪽에도 ESbuild와 같은 새로운 기술들이 생겨났고 이는 기존의 큰 문제점을 지속적으로 해결해나가기 시작하는 첫 걸음입니다.

번들러를 사용한 경우

기본적으로 기존 기술의 한계점을 채우기 위한 기술들은 한계점이 있습니다. 새로운 기술이 나왔을 때 기존 기술은 매우 비효율적으로 보이고 실제로 그런 경우가 많습니다. Webpack 등의 번들러가 실제로 기존 기술의 한계를 메꾸기 위한 도구가 아니었음에도 불구하고 실제로 HTTP/1.1에서의 여러 파일 전송이라는 기술적 한계를 메꾸기 위해 사용되었다고 봅니다. 그 외에도 기존의 번들링이라는 행위 자체는 많은 단점을 가지고 있습니다.

  • 느린 빌드 속도, 개발 속도에 영향을 크게 미칩니다.
  • 캐시 초기화 시의 비효율성, 하나의 청크는 매우 많은 디펜던시의 조합이기 때문입니다.

특히 위에서 Code-splitting으로 인해 생겨난 Chunk들은 상당히 비효율적인 면을 고수하고 있습니다. 번들러가 처리한 코드는 속도 상으로도 어느정도 최적화된 것이 맞지만 이후에 한 번 처리된 코드를 가지고 다른 작업을 하기에는 힘듭니다. 그렇기 때문에 파일 내용이 조금만 바뀌어도 번들의 내용은 그보다 몇 배 이상 바뀔 수 있음을 의미합니다. 그리고 웹 브라우저에서도 이 번들을 처리하기가 어려워집니다.

그렇게 되면 웹 브라우저에서는 캐시 문제가 발생할 확률이 높아집니다. 가장 큰 이유로는 웹 브라우저 자체에서 번들을 충분히 분석하지 못하는 것에 있습니다.

실제 번들을 가지고 테스트하기

그래서 실제 테스트를 구성해보기로 했습니다. 제가 아직 JavaScript에 부족한 면도 많고 Webpack 등 번들러는 매우 복잡한 스택으로 이루어져 있기 때문에 제가 설명하기에는 아직 한없이 부족하기 때문입니다. 저는 저의 개인 프로젝트인 Ohys-FE(Ohys-API의 프론트엔드 파트)를 테스트해보기로 했습니다. 사진에서 볼 수 있듯이 각각의 페이지는 Lazy loading됩니다. 사진에서 드래그된 한 줄의 유무로 2번 웹 사이트의 메인 페이지(/)에 접속하여 어느정도 캐싱이 성공하는지 결과를 확인해보겠습니다. 각각의 환경은 다음과 같이 짜여졌습니다.

여기에서 사용되는 각각의 스택은 다음의 역할을 하고 있습니다.

  • Snowpack, 프론트엔드 툴체인
  • ESbuild, 번들러 (실제 번들링은 현재 Experimental이므로 사용하지 않고 기본적인 최적화만 거침)

Webpack

  1. ESbuild가 소스코드를 전처리
  2. Snowpack에서 의존성을 ESM 형태로 설치
  3. Webpack을 통해 처리된 파일을 하나로 번들링
  4. Clean-Webpack-Plugin을 통해 더 이상 실제 소스코드에 필요없는 에셋을 모두 제거

Unbundled

  1. ESbuild가 소스코드를 전처리
  2. Snowpack에서 의존성을 ESM 형태로 설치

그리고 마지막으로 serve 패키지를 사용하여 로컬에 배포하는 과정까지 거치도록 하겠습니다. 모든 테스트는 HTTP/1.1에서 진행됩니다. 이 테스트는 캐시를 확인하는 과정이므로 HTTP/2가 아니기 때문에 발생하는 문제는 없습니다.

번들링된 경우

실제로 App.JS에 있는 코드를 한줄만 변경했음에도 불구하고 전체 번들이 변경되어서 모든 JavaScript 파일이 HTTP 200 상태 코드를 받으며 다시 다운로드된 것을 확인할 수 있습니다. 이는 실제로 웹 브라우저가 각각의 Chunk에서 어떤 부분이 어느 의존성에 대해 이루어져 있는 확실하게 파악이 불가능하며 크게 사용하는 의존성이 달라지지 않았음에도 불구하고 전체 스크립트 파일이 다운로드되어 비효율적이라고 볼 수 있습니다.

현재 테스트는 절대로 첫 번째 요청이 아닙니다. 그 이유는 styles 파일은 현재 304 Not modified 헤더를 받았기 때문입니다. 이는 이전에 동일한 파일에 액세스되었고 웹 서버에서 웹 브라우저의 요청이 같은 파일을 가리키는 것으로 확인되었기 때문입니다.

번들링되지 않은 경우

반대로 번들링이 되지 않은 경우에는 실제로 ESM으로 코드가 컴파일되어 웹 브라우저에서 어느 부분이 어느 의존성을 가리키는지 알 수 있게 되었습니다. 그리고 실제로 변경된 App.JS만 제외하고 나머지 코드가 모두 304 Not modified 헤더를 받으며 웹 브라우저의 캐시가 대신 사용되었습니다.

이를 통하여 웹 브라우저가 정확히 어느 지점에서 직접 코드 분할을 클라이언트 단에서도 도움을 줄 수 있다는 것을 알 수 있습니다.

마이크로 프론트엔드 환경에서의 트래픽 최소화

마이크로 프론트엔드는 마이크로 서비스의 프론트엔드 버전이라고 볼 수 있습니다. 깔끔한 용어죠. 기본적으로 각각의 프로젝트가 하나의 완벽한 스택을 사용하는 대신에 번들링을 하지 않고 또는 웹 브라우저가 인지 가능한 정도까지 의존성들을 분리할 수 있다면 트래픽을 더욱이 최소화할 수 있습니다. 대부분의 마이크로 서비스 간의 스택은 공통되는 경우가 많습니다. 그렇기 때문에 예를 들어서 React로 프로젝트를 완성한다 하는 경우에는 의도적으로 CDN 등을 사용하여 React 소스코드를 CDN에서만 불러올 수 있게 할 수 있습니다. 그 다음부터는 React는 웹 브라우저에 캐시되어 다른 마이크로 서비스를 사용하더라도 중복도니 요청이 발생하지 않을 것입니다.

이러한 것은 꼭 한 프로젝트의 여러 마이크로 프론트엔드가 아니더라도 하나의 거대한 CDN을 사용하여 완성될 수 있습니다. 같은 CDN을 사용하는 사용자가 많을수록 더 많은 웹 사이트에서 상당한 속도 향상을 기대해볼 수도 있다고 생각합니다. Pikadev의 Skypack은 이를 위해 ESM 모듈을 제공하고 있습니다. 또한 Unpkg에서도 ?module을 통해 어느정도 사용이 가능합니다.

Referrrals

Seia-Soto/snowpack-plugin-import-map
A snowpack plugin that maps your imports to Skypack or other sources. - Seia-Soto/snowpack-plugin-import-map
面向未来的中后台场景‘伪’微前端几点想法
也聊 Micro Frontends 的落地和实践。

다음 세대 프론트엔드를 위한 준비

이 글은 프론트엔드 자체가 이렇게 된다기보다는 새로운 방향성을 제공하기 위해 작성되었습니다. 프론트엔드 시장은 항상 빠르게 변화하고 있고 언제나 하나의 프레임워크 그리고 라이브러리에만 통하는 솔루션은 더 이상 생존하기가 힘들어질 것이라고 생각합니다. 또한 이번 글에서 제시한 방향은 최신 기술이 더 모든 사람들에게 사용이 가능해지는 것을 필요로 합니다.

HTTP/2와 같은 경우에는 HTTPS를 기본적으로 요구하고 있고 중국 등 일부 국가는 TLS 1.3을 지원하지 않는 것을 고려하거나 이미 지원을 임의로 차단했습니다. 또한 모바일 네트워크는 그렇기 믿을만하지 않습니다. 기본적으로 웹이 Stateless하지만 어느정도 모두가 필요로하는 부분은 있습니다.

Fine;