Snowpack Import Map: 번들링없는 코드 분할 전략
번들링은 프론트엔드의 최적화 전략 중 하나입니다. 여러 파일을 하나의 번들로 묶는 과정을 통해 고성능의 컴퓨팅과 네트워크를 기대하기 힘든 모바일 환경에서 비교적 빠른 렌더링 속도를 가져오는데 일조합니다. 하지만 ES Module이 도입된 이후 번들링없이도 웹 브라우저에서 캐싱 전략을 보다 효과적으로 이용할 수 있게 되었습니다.
이 글은 2021년 9월 10일에 작성되어 2023년에 재구성되었습니다.
번들러의 역할
Webpack은 웹 사이트에서 사용되는 코드를 분석해 번들링을 통해 이를 묶고 필요없는 부분은 Tree Shaking을 통해 잘라 최적화된 배포 코드를 제공합니다.
이때 공통된 부분은 다시 묶어 하나의 부분인 Chunk로 분류되어 서로 다른 페이지에서 캐싱의 역할을 최대화하는데, 이는 HTTP/1.1 환경에서 하나의 파일을 불러올 때마다 새로운 연결을 수립해야 했기에 이를 통해 낮은 성능의 네트워크 환경에 대응할 수 있었습니다.
웹 애플리케이션 아키텍쳐의 변화
그 후 HTTP/2의 상용화와 더불어 ESBuild를 포함해 많은 Rust 기반 툴체인이 보편화되면서 보다 나은 개발자 경험과 캐싱 전략의 수립이 가능해졌습니다. 특히 HTTP/2는 한 번의 연결로 클라이언트에 2개 이상의 파일을 전달할 수 있었기에 기존 Chunking 방식의 중요도가 보다 낮아졌습니다.
실제 번들러를 사용하면 고비용의 연산 작업이 필요하기 때문에 배포 전후로 많은 시간과 자원이 소모되어 개발자 경험이 저하됩니다. 또 잦은 배포가 일어나는 상황에서는 조금의 코드 변경이 캐시된 Chunk를 무의미하게 만들기 쉬워집니다. 결국 이는 더 많은 네트워크 사용으로 이어지기 마련입니다.
실제 프로젝트에서 번들러의 영향을 확인하기
이는 실제 예제에서도 쉽게 확인할 수 있습니다. Snowpack 툴체인을 사용한 React 사이드 프로젝트였던 Ohys-FE에서 조금의 코드 변경이 Chunk에 어떤 영향을 미칠 수 있는지 확인하려 합니다. 아래의 배포 환경에서 한 줄의 <Redirect ... />
컴포넌트를 제거한 전후의 번들 용량을 확인하겠습니다.
/** @type {import("snowpack").SnowpackUserConfig } */
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
plugins: [
[
'@snowpack/plugin-webpack',
{
target: [
'web',
'es5'
],
sourceMap: false,
extendConfig: config => {
config.plugins.push(new CleanWebpackPlugin({
dry: false,
verbose: true,
cleanStaleWebpackAssets: true,
protectWebpackAssets: true
}))
return config
}
}
]
]
}
단 한 줄의 코드가 제거되었으나 생성된 번들 파일에 모든 의존성이 포함되어 있었고 제거된 의존성으로 인해 브라우저는 200kB나 되는 번들을 통째로 다시 다운로드해야 했습니다. 이 예시는 최소한의 변경으로 최대한의 변경이 일어날 수 있는 상황으로 의도된 것을 고려하더라도 최소한 이러한 상황이 가능하다는 것을 보여줍니다.
그렇다면 이번엔 빌드 환경에서 Webpack을 제외하고 <Redirect ... />
컴포넌트를 제거하는 과정을 거쳐보겠습니다. 실제 코드가 변경된 파일만 다시 다운로드되었고 이는 1 kB에 불과합니다. 이는 파일이 번들링되지 않고 ESModule 형태 그대로 배포되었기에 웹 브라우저가 모든 의존성 구조를 볼 수 있었기 때문입니다.
번들러가 모든 의존성을 뒤죽박죽 합쳐놓는 대신 따로따로 배포된 파일들이 웹 브라우저가 정확히 어디에서부터 어디까지만 사용되었고 변경되는지 직접 알 수 있습니다. HTTP/2의 상용화로 파일을 여러개 사용함에도 페이지 로드 속도의 저하가 없었고 자연스럽게 웹 브라우저는 본래 있던 캐싱 전략을 최대화할 수 있었습니다.
마이크로 프론트엔드에 전략 적용
마이크로 프론트엔드 개념을 여기에 적용해 효과적으로 웹 사이트 배포 비용을 절감할 수 있습니다. 하나의 웹 사이트의 여러 부분에 많은 라이브러리가 공유되는 상황을 생각해보세요. ESModules에서 웹 브라우저가 어떤 의존성이 필요한지 그대로 볼 수 있기에 공통 파일은 그 어떤 마이그레이션 비용없이 자연스레 그 캐싱 전략이 최적화됩니다.
Snowpack-Plugin-Import-Map에서는 zhoukekestar님이 개발한 본래의 플러그인에 회사의 CDN을 적용할 수 있도록 변경하여 마이그레이션의 비용을 최소화했습니다. 회사의 CDN을 사용하지 않더라도 Skypack과 같은 많은 벤더가 이미 ESModule을 지원하고 있습니다. 공용 CDN을 사용함으로써 더 많은 웹 사이트가 같은 구조를 사용할수록 단순한 구조의 도입만으로도 많은 웹 사이트에서 수없이 더 많은 트래픽 절감이 가능합니다.
아래는 플러그인의 예시 출력입니다.
// Before
import React from 'react';
import ReactDOM from 'react-dom';
// After
import React from "https://cdn.skypack.dev/react@^16.13.1";
import ReactDOM from "https://cdn.skypack.dev/react-dom@^16.13.1";
ReactDOM.render(
React.createElement("h1", null, "Hello world!"),
document.getElementById("root")
);
앞으로의 과제
여전히 HTTPS의 지원을 필수 요소로 하는 HTTP/2의 보급은 늦어지고 있고 모바일 네트워크는 불안정합니다. 또 Internet Explorer 사용자나 JavaScript를 비활성화해야만 하는 상황은 어떤가요? 아직까지 고려되어야 할 요소는 많습니다. 다만 HTTP/2와 ESModule이 차세대 기술로써 보편화된다면 새로운 구조로 캐싱 전략을 최대화할 수 있다 확신합니다.
감사합니다.