유동식 edited this page Oct 15, 2018 · 3 revisions

원문: https://hackernoon.com/the-100-correct-way-to-split-your-chunks-with-webpack-f8a9df5b7758

100% 완벽하게 웹팩으로 파일을 분리하는 방법

사용자에게 파일을 서비스하기 위해 가장 좋은 방법을 찾아내는 것은 어려운 일이다. 세상엔 매우 다른 환경과 다른 기술, 다른 용어가 많이 있다.

이 글에서는 여러분에게 아래와 같은 것을 할 때 필요한 모든 것을 알려줄 수 있길 바란다.

  1. 여러분의 사이트와 사용자에게 가장 좋은 파일 분리 전략을 알아내기
  2. 그 전략을 어떻게 수행하는지 알아내기

chop 쪼개기

웹팩 가이드에 따르면, 파일 분리에는 두 가지 다른 방법이 있다. 두 용어는 서로 비슷한 듯 들리지만, 분명히 다른 것이다.

번들 분리하기: 캐시를 더 잘 활용하기 위해 더 많은 파일을 더 작게 만든다(하지만 어쨌든 모든 파일이 네트워크 요청에 따라 로딩된다).

코드 분리하기: 코드를 동적으로 로딩한다. 사용자는 사이트에서 보고 있는 영역에 필요한 코드만 다운로드한다.

두 번째 것이 더 매력적으로 느껴지지 않는가? 그리고 사실, 이에 대해 쓴 많은 글에서 자바스크립트 파일을 더 작게 만드는 것만이 유용하다고 가정하고 있는 것 같다.

그러나 나는 많은 사이트에서 더욱 유용한 것은 바로 첫 번째 것임을 이야기하고자 한다. 그리고 모든 사이트에서 맨 먼저 해야 할 일이기도 하다.

같이 살펴보자.

번들 분리하기

번들 분리하기에 대한 아이디어는 아주 간단하다. 아주 큰 파일이 있고 코드에서 한 줄만 바꾼다고 할 경우, 사용자는 전체 파일을 다시 다운로드 받아야만 한다. 그러나 여러분이 파일을 두 개로 분리한다면, 사용자는 변경된 부분의 파일만 다운로드할 것이고 다른 파일 하나는 브라우저에 캐시에서 제공될 것이다.

알아둘 것은 번들 분리하기는 모두 캐시에 관한 것이기 때문에, 처음 방문한 사용자에게 번들 분리는 별 다른 점이 없다.

(매우 많은 성능 관련 이야기는 사이트를 처음으로 방문한 사용자에 대한 내용이 전부인 것 같다. 아마도 이 현상의 일부는 ‘첫인상 효과’와 그리고 그것이 측정하는 것이 수월하고 간단하기 때문인 것 같다.)

사용자가 사이트를 자주 방문할 때는 성능 개선 효과를 정량화하여 측정하는 것이 어렵지만, 정량적 측정은 반드시 해야 한다.

여기에서는 측정 도표가 필요할 것이고 각각의 캐시 전략을 테스트할 수 있도록 특정 조건으로 구성된 환경이 필요할 것이다.

방금 전에 이야기한 특정 시나리오는 이렇다.

  • 앨리스는 10주 동안 일주일에 한 번씩 사이트를 방문한다.
  • 우리는 일주일에 한 번씩 사이트를 업데이트한다.
  • 매주 ‘제품 목록’ 페이지를 업데이트한다.
  • ‘제품 상세’ 페이지가 있지만, 현재는 작업하지 않고 있다.
  • 5주차에 새로운 npm 패키지를 사이트에 추가한다.
  • 8주차에 기존 npm 패키지 중 하나를 업데이트한다.

나를 포함한 어떤 사람들은 시나리오를 가능한 현실과 비슷하게 만들려고 노력할 것이다. 그러나 그렇게 하지 말 것. 현실적인 시나리오는 중요하지 않은데 왜 그런지는 뒷부분에 나온다(오 서스펜스!).

기준 데이터 만들기

우리의 전체 자바스크립트 패키지는 다소 크게 400KB라고 하자. 우리는 main.js인 하나의 파일을 로딩하고 있다. 아래와 비슷한 웹팩 설정도 가지고 있다(관련 없는 설정 항목은 제외한다).

const path = require('path');

module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'),
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
  },
};

웹팩은 단일 엔트리인 경우 ‘main.js’를 생성한다.

(캐시를 더 잘되게 하게 위해 사용한 것이 있다. 내가 ‘main.js’라고 이야기할 때 실질적으로 의미하는 것은 main.xMePWxHo.js 와 같은 것인데, 저 이상한 글자들은 파일의 컨텐츠 해시이다. 애플리케이션의 코드가 변경된 경우 파일 이름이 달라지게 된다. 그래서 브라우저가 새로운 파일을 다운로드 받게 만든다.)

사이트에 새로운 변경 사항을 업데이트하는 주마다, 패키지의 contenthash는 바뀐다. 그러므로 앨리스는 우리 사이트를 방문하고 매주 새로운 400KB 파일을 다운로드 받아야만 한다.

우리가 이 10주간의 일정에 대해서 멋진 표를 만든다면, 이렇게 생겼을 것이다.

세상에서 가장 쓸모없는 합계를 보여주는 행

10주 동안 전부 4.12MB이다.

우리가 더 잘 만들어 보자.

라이브러리 패키지를 분리하기

패키지를 main.js와 vendor.js로 분리해보자.

아래처럼 쉽다.

const path = require('path');

module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'),
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
    },
  },
};

여러분이 번들을 어떻게 분리하고 싶은지 정확하게 이야기하지 않아도 웹팩4는 여러분을 위해 가장 좋은 결과를 제공하기 위해 노력한다.

이것이 사람들이 “웹팩 멋진데, 좋아"라고 하는 몇 가지 예이다.

그리고 이런 질문도 하게 된다. "웹팩이 도대체 내 번들 파일에 무슨 일을 하고 있는 거지?"

아무튼, optimization.splitChunks.chunks = 'all' 을 추가하는 것은 "node_modules 에 있는 모든 파일을 vendors~main.js에 집어 넣어"이런 의미이다.

이렇게 번들을 분리 하게되면, 앨리스는 여전히 사이트를 방문할 때마다 200KB의 main.js를 새로 다운로드를 받지만, 200KB vendors.js 파일은 1주, 8주,  5주(순서대로는 아님) 째에만 다운로드 받게 된다.

뜻밖이지만, 두 패키지는 정확하게 200KB로 되었다.

전체 용량은 2.64MB가 되었다.

크기가 36% 감소되었다. 설정 파일에 5줄 추가한 것 치고는 나쁘지 않다. 더 읽기 전에 지금 가서 실행해 보라. 웹팩 3에서 4로 업그레이드를 해야 한다해도 겁내지 마라. 어렵지 않다(그리고 계속 무료이다!).

이 성능 개선은 10주 동안에 걸쳐서 일어난 것이라 어떻게 보면 다소 와닿지 않게 느껴진다. 하지만 자주 방문하는 사용자에게 실질적으로 36%의 바이트를 감소시킨 것이므로 스스로를 뿌듯하게 여겨도 된다.

그러나 이보다 더 잘할 수 있다.

npm 패키지별로 분리하기

vendors.js파일은 최초에 main.js 파일이 그랬던 것처럼 같은 문제를 가지고 있다. 그 파일의 일부 변경은 전체를 다시 다운로드 받아야 하는 것을 의미한다.

그러면 각 npm 패키지를 별도의 파일로 나누는 것은 어떤가? 아주 쉽게 할 수 있다.

reactlodashreduxmoment 등의 패키지를 별도의 파일로 분리해 보자.

const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'),
  plugins: [
    new webpack.HashedModuleIdsPlugin(), // 파일 해시가 예기치 못하게 변경되지 않게 한다.
  ],
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
  },
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: Infinity,
      minSize: 0,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name(module) {
            // 이와 같은 이름을 얻기 node_modules/packageName/not/this/part.js
            // 혹은 node_modules/packageName
            const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];

            // npm 패키지 이름은 URL-safe이지만 어떤 서버는 @ 기호를 좋아하지 않는다.
            return `npm.${packageName.replace('@', '')}`;
          },
        },
      },
    },
  },
};

이 문서는 여기에 일어나는 모든 것을 잘 설명해 줄 것이다. 그러나 내가 이것을 제대로 하기 위해서 제법 시간이 걸렸기 때문에 중요한 부분에 대해서는 약간의 설명을 할 것이다.

  • 웹팩에는 그다지 똑똑하지 못한 기본 기능이 몇 가지 있다. 결과 파일을 분리할 때 최대 3개 파일만 되는 것이라든가, 최소파일 크기가 30KB 라든가(더 작은 파일은 모두 합쳐진다)가 그렇다. 그래서 나는 이것들을 재정의하였다.
  • cacheGroups는 웹팩이 청크를 결과 파일로 그룹짓는 방법을 정의한다. 나는 node_modules로부터 로딩되는 모듈이 사용될 일명 vendor 파일이 있다. 보통은, 결과 파일을 위해 name만 문자열로 정의하면 된다. 그러나 나는 name을 함수(파일이 파싱될 때마다 호출되는)로 정의하고 있다. 모듈의 경로에서 패키지의 이름을 리턴한다. 결과적으로, npm.react-dom.899sadfhj4.js와 같이 패키지별로 하나의 파일이 생성된다.
  • 배포를 하기 위해서 npm 패키지 이름은 반드시 URL-safe해야 한다. 그래서 packageNameencodeURI를 할 필요는 없다. 하지만, scoped 패키지의 이름에 @가 있는 파일을 .NET 서버에 처리할 경우 문제가 발생했다. 그래서 나는 코드에서 그것을 바꿔 버렸다.
  • 모든 설정은 설정하고 잊어버려도 되기 때문에 좋다. 유지보수가 필요없다. 어떤 패키지도 이름을 참조할 필요가 없게 되었다.

앨리스는 여전히 매주 200KB의 main.js파일을 다시 다운로드 할 것이고 첫 사이트 방문에서 200KB의 npm 패키지를 다운로드 할 것이다. 하지만 같은 패키지를 두 번 다운로드 받지는 않을 것이다.

각 npm 패키지는 정확히 20KB였다. 그럴 가능성이 얼마나 되겠냐마는!

이제 2.24MB이다.

기준 데이터에서 44%를 감소시켰다. 이 블로그로부터 복사/붙여넣기를 한 코드지만 꽤 멋지다.

나는 이것을 50%까지 줄일 수 있을지 궁금했다.

뭔가 더 있지 않을까?

애플리케이션 코드의 영역을 분리하기

불쌍한 앨리스가 계속해서 다운로드해야 하는 main.js파일을 살펴보자.

나는 앞서 이 사이트에 구분되는 두 영역이 있다고 이야기한 적이 있다. 제품 목록과 제품 상세 페이지이다. 각 페이지의 유니크 코드는 25KB이다(150KB는 공통 코드라고 하자).

'제품 상세' 페이지는 아주 완벽해서 최근에 별로 바뀌지 않고 있다. 그래서 페이지를 분리된 파일로 만들어 대부분 캐시로부터 서비스되도록 할 수 있다.

또한, 우리가 아이콘을 표시하기 위해 25KB 정도의 크기이고 거의 변경되지 않는 큰 인라인 SVG를 포함하고 있다는 것을 알고 있는가?

이에 대해 우리가 뭔가를 해야만 한다.

몇 개의 엔트리 포인트를 직접 추가하는 것으로 웹팩이 각 페이지를 위한 파일을 만들도록 할 수 있다.

module.exports = {
  entry: {
    main: path.resolve(__dirname, 'src/index.js'),
    ProductList: path.resolve(__dirname, 'src/ProductList/ProductList.js'),
    ProductPage: path.resolve(__dirname, 'src/ProductPage/ProductPage.js'),
    Icon: path.resolve(__dirname, 'src/Icon/Icon.js'),
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash:8].js',
  },
  plugins: [
    new webpack.HashedModuleIdsPlugin(), // 파일 해시가 예기치 못하게 변경되지 않게 한다.
  ],
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: Infinity,
      minSize: 0,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name(module) {
            // 이와 같은 이름을 얻기 node_modules/packageName/not/this/part.js
            // 혹은 node_modules/packageName
            const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];

            // npm 패키지 이름은 URL-safe이지만 어떤 서버는 @ 기호를 좋아하지 않는다.
            return `npm.${packageName.replace('@', '')}`;
          },
        },
      },
    },
  },
};

좋다, 웹팩은 ProductListProductPage 사이에 서로 중복되는 코드 없이 번들 파일을 생성할 것이다.

이렇게 해서 앨리스는 대부분의 주에서 50KB의 다운로드를 줄이게 될 것이다.

6번째 주에 우리는 아이콘을 수정했다는 것을 기억하자.

이제 고작 1.815MB이다!

우리는 앨리스가 56%의 다운로드를 줄이도록 했고, 이러한 감소는 (우리가 가정한 시나리오)에서 계속 유지된다.

그리고 이 모든 것은 웹팩 설정을 바꾼 것으로 이루어진 것이다. 애플리케이션 코드는 전혀 바꾸지 않았다.

앞서 언급한 것처럼 테스트의 정확한 시나리오는 그다지 중요하지 않다. 여러분이 어떤 시나리오를 따라가든, 결론은 같을 것이기 때문이다. 여러분의 사용자가 더 적은 코드를 다운로드 하도록 애플리케이션을 더 의미있는 작은 파일로 분리하라.

...

지금부터는 코드 분리하기 에 대해 이야기하겠다. 이건 파일 나누기의 다른 종류인데, 나는 먼저 여러분이 지금 당장 떠오른 3가지 질문에 대답해 보고 싶다.

#1: 많은 네트워크 요청으로 더 느려지는 것 아닌가요?

대답은 "아니"라고 강하게 말할 수 있다.

HTTP/1.1 시대에는 그렇겠지만 HTTP/2에서는 그렇지 않다.

비록 2016년도의 글2015년도의 칸 아카데미의 글 모두 HTTP/2라는 결론에 도달했지만, 너무 많은 파일을 다운로드하는 것은 여전히 느리다. 그러나 이 글들에서 '너무 많은' 파일은 '몇 백 개'를 의미하는 것이었다. 그러므로 여러분이 수백 개의 파일을 가지고 있다면 동시 처리를 제한할 수도 있다.

여러분이 만일 궁금하다면, Windows 10의 IE11에서 HTTP/2에 대한 지원하라. 더 오래된 환경을 사용하는 모든 사용자에게 설문 조사를 완료했는데, 사용자들이 얼마나 빨리 웹 사이트가 로드되는지 신경쓰지 않는다고 만장일치로 답변하였다.

#2: 각 웹팩 번들 파일에 오버헤드로 중복되는 보일러플레이트 코드가 있지 않나요?

사실이다.

#3: 여러 개의 작은 파일 때문에 압축율 측면에서 손해보지 않을까요?

맞다, 그것도 사실이다.

음, 젠장.

  • 더 많은 파일 = 더 많은 웹팩 보일러플레이트
  • 더 많은 파일 = 낮은 압축율

정량적 측량을 통해 이에 대해서 얼마나 많이 두려워 하고 있는지 한 번 알아 보자.

자, 내가 방금 테스트해 본 결과 190KB짜리 사이트가 19개의 파일로 분리된 경우 브라우저가 받는 전체 바이트의 약 2% 정도가 증가되었다.

그래서 … 첫 방문에 2%가 더 많은 것과 우주가 끝날 때까지의 모든 방문에 대해 60%를 절약하는 것이다.

두려워할 만한 용량은 전혀 아니다.

내가 1개의 파일와 19개의 파일을 비교 테스트 하는 동안, HTTP/1.1을 포함한 다른 네트워크에서 테스트할 수 있을 것이라고 생각했다.

여기 '더 많은 파일이 더 낫다'는 의견의 근거가 되는 표가 있다.

같은 190KB크기의 사이트를 두 가지 버전으로 로딩한 것이다(Firebase의 정적 호스팅이다).

3G와 4G에서 이 사이트는 19개의 파일인 경우 30% 더 빨리 로딩되었다.

어? 진짜?

이건 다소 설명이 필요한 데이터이다. 예를 들어, Run2의 4G의 경우 이 사이트는 646ms에 로딩되었고, 그 후 두 번의 실행에서 1,116m가 소요되었다. 변경사항도 없이 73%나 더 오래걸렸다. 그래서 HTTP/2가 '30% 더 빠르다'는 주장은 다소 이상하다.

(페이지 로드 시간의 차이를 시각화하도록 고안된 맞춤 차트가 곧 제공 될 예정이다.)

나는 HTTP/2가 만들어내는 차이가 무엇인가를 정량적으로 측정하기 위해 이 표를 만들었다. 하지만 내가 진짜 말할 수 있는 것은 "크게 중요한 차이는 없어 보인다"이다.

진짜 놀라운 것은 마지막 두 행이다. 오래된 Windows와 HTTP/1.1에서 훨씬 더 느려지는 것이다. 더 느린 인터넷이 필요한게 아닌가하고 추측했다.

...

시간이 다소 걸렸다! 나는 Windows 7 Microsoft 사이트에서 가상 머신을 다운로드 받아 테스트했다. IE8이 설치되어 있어서 IE9로 업데이트하고 싶었다.

그래서 Microsoft의 IE9 다운로드 페이지로 갔다 …

OMG. SMH. FML.

HTTP/2에 대해 마지막으로 할 말이 있다. 이제 Node에서도 지원된다는 것을 아는가? 여러분이 한번 해보고 싶을 것 같아서, gzip, brotli 그리고 여러분의 테스트 취향에 맞게 캐시를 응답하는 내가 작성한 100줄의 HTTP/2서버를 보라.

...

이것이 내가 번들 분리하기에 대해 말하고자 하는 전부이다. 이 방법의 유일한 단점은 많은 작은 파일을 로딩하는 것이 괜찮은 것이라고 사람들을 설득하는 것뿐이다.

...

이제, 또 다른 파일 분리하기에 대해 이야기해 보자…

코드 분리하기(필요하지 않는 코드는 로딩하지 않는다)

이러한 접근법은 특정 사이트에서만 의미가 있는데, 그건 바로 나라고 말해주면 좋겠다.

나는 내가 만든 20/20 법칙을 적용하길 좋아한다. 만일 여러분의 사이트에 중에 20%의 사용자만이 방문하는 부분이 있고 그것이 사이트의 자바스크립트의 20%보다 크다고 했을 때, 여러분은 요청이 있을 때만 코드를 로딩해야 한다.

입맛에 맞게 이 숫자들을 조정해 보라. 더 복잡한 시나리오가 분명히 있지만 요점은 균형이다. 여러분의 사이트에서 코드 분리하기가 의미가 없다고 판단되면 상관없다.

어떻게 판단할 수 있는가?

여러분이 쇼핑 사이트를 갖고 있고 방문자의 30%만이 사용하기 때문에 '결제'코드를 분리해야 하는지 알고 싶다고 하자.

첫 번째로 할 것은 더 좋은 물건을 파는 것이다.

두 번째는 결제에서 얼마나 많은 코드가 완벽히 유니크한지 알아내는 것이다. '코드를 분리하기' 전에 '번들을 분리하기'를 항상 해야하므로, 여러분은 이미 코드에서 이 부분이 얼마나 큰지 알 것이다.

(생각보다 작을 수도 있다. 그러면 좋아하기 전에 합계를 내보자. 예를 들어 여러분의 사이트가 React라면, 스토어, 리듀서, 라우팅, 액션 등이 사이트 전체에서 공유될 것이다. 유니크한 부분은 대부분 컴포넌트와 헬퍼들일 것이다.)

그래서 여러분은 결제 페이지에 완벽히 유니크한 코드가 7KB라고 알게 된다. 사이트의 나머지는 300KB이다. 나는 이것을 알았지만 뭐, 몇 가지 이유에서 이것을 분리하기 위해 코드를 변경하진 않을 거다.

  • 프론트에 로딩하는 것은 더 느려지지 않는다. 이 파일들이 모두 병렬로 로딩된다는 것을 기억하라. 300KB와 307KB를 로딩하는 시간의 차이를 기록해서 보라.
  • 이 코드를 나중에 로딩한다면, 사용자는 여러분이 최소한의 시간으로 처리되길 바라는 '내 돈을 가져가시오'를 클릭한 이후에 파일 로딩을 기다려야 할 것이다.
  • 코드 분리하기는 애플리케이션 코드의 변경이 필요하다. 이전에는 동기 로직이면 되었는데 비동기 로직으로 변경해야 한다. 이건 로켓 과학이 아니지만, 인식할 수 있을 만한 사용자 경험에 대한 개선이 필요한 정도의 복잡성은 있다.

좋다, 이 모든 내용은 "이 흥미로은 기술이 당신에게 적용되지 않을 수도 있다"고 할 수도 있다.

코드 분리하기의 두 예제를 살펴보도록 하자…

폴리필

폴리필은 대부분의 사이트에 적용되어 있고 간단하게 소개할 수 있으므로 여기부터 시작하겠다

나는 사이트에 최신 기능을 많이 사용하고 있어서, 필요한 폴리필을 모두 포함하는 하나의 파일이 있다고 하자.

require('whatwg-fetch');
require('intl');
require('url-polyfill');
require('core-js/web/dom-collections');
require('core-js/es6/map');
require('core-js/es6/string');
require('core-js/es6/array');
require('core-js/es6/object');

나는 엔트리 포인트인 index.js의 제일 윗부분에 이 파일을 포함시킨다.

import './polyfills';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App/App';
import './index.css';

const render = () => {
  ReactDOM.render(<App />, document.getElementById('root'));
}

render(); // 지금은 그다지 의미가 없는 부분이다.

번들 분리하기편의 웹팩 설정으로, 내 폴리필은 네 개의 npm 패키지를 사용했기 때문에 자동으로 네 개의 다른 파일로 자동으로 분리될 것이다. 그것은 모두 약 25KB이고, 브라우저의 90%가 폴리필을 필요하지 않기 때문에 동적 로딩을 할만하다.

웹팩4와 import() 문법(import문법과 혼동하지 말자)으로, 폴리필을 조건에 따라 로딩하는 것은 매우 쉽다.

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App/App';
import './index.css';

const render = () => {
  ReactDOM.render(<App />, document.getElementById('root'));
}

if (
  'fetch' in window &&
  'Intl' in window &&
  'URL' in window &&
  'Map' in window &&
  'forEach' in NodeList.prototype &&
  'startsWith' in String.prototype &&
  'endsWith' in String.prototype &&
  'includes' in String.prototype &&
  'includes' in Array.prototype &&
  'assign' in Object &&
  'entries' in Object &&
  'keys' in Object
) {
  render();
} else {
  import('./polyfills').then(render);
}

눈치채셨는가? 모든 기능이 지원되는 경우 바로 페이지를 렌더링한다. 그렇지 않으면, 폴리필을 로딩한 후 페이지를 로딩한다. 브라우저에서 이 코드가 동작할 때 웹팩 런타임은 네 개의 npm 패키지의 로딩을 처리할 것이고 파일들은 다운로드되어 파싱된 후 render()가 호출된다. 이 모든 것이 계속 수행된다.

(그런데 import() 사용에는 Babel의 동적 로딩 플러그인이 필요하다. 또한, 웹팩 문서에 따르면 import\\\\\\\\(\\\\\\\\)Promise를 사용하기 때문에 다른 폴리필과 별도로 폴리필이 필요하다.)

쉽지 않은가?

여기 좀 더 까다로은 것이 있다…

라우트에 따른 동적 로딩(React 기반)

앨리스 예제로 돌아가서, 이제 사이트에 '관리자' 페이지가 있다고 하자. 제품 판매자 는 로그인하여 판매 상황에 대해 관리할 수 있다.

이 페이지는 멋진 기능들이 많이 들어가 있고, 많은 수의 차트와 큰 npm 차트 라이브러리가 포함되어 있다. 이전에 이미 번들을 분리하였기 때문에, 이것들이 모두 100KB 이상의 크기라는 것을 알 수 있었다.

현재는, 사용자가 URL/admin에 접근할 때 <AdminPage>를 렌더링하는 라우팅하는 설정이다. 웹팩이 모든 것을 번들로 만들었을 때 import AdminPage from './AdminPage.js' 를 만나게 되면 "웹팩, 초기 페이로드에 이것도 같이 포함시키도록 해줘"라고 하게 된다.

그러나 우리는 그렇게 하고 싶지 않다. import('./AdminPage.js')와 같은 동적 로딩 내부에서 관리자 페이지의 참조를 추가할 필요가 있고 웹팩은 그것을 동적으로 로딩해야 하는 함을 알고 있다.

멋지다, 아무 설정도 필요가 없다.

AdminPage를 바로 참조하는 것 대신, 사용자가 URL /admin로 이동할 때 렌더링될 또 다른 컴포넌트를 만들 수 있게 되었다. 코드는 다음과 같을 것이다.

import React from 'react';

class AdminPageLoader extends React.PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      AdminPage: null,
    }
  }

  componentDidMount() {
    import('./AdminPage').then(module => {
      this.setState({ AdminPage: module.default });
    });
  }

  render() {
    const { AdminPage } = this.state;

    return AdminPage
      ? <AdminPage {...this.props} />
      : <div>Loading...</div>;
  }
}

export default AdminPageLoader;

이 컨셉은 매우 직관적이지 않은가? 이 컴포넌트가 마운트(사용자가 URL /admin에 접근했을 경우를 말한다)되었을 때, ./AdminPage.js가 동적으로 로딩되고 컴포넌트의 참조를 상태에 저장하게 된다.

우리는 렌더 함수에서 <AdminPage>로딩하기 전에 <div>Loading...</div>를 렌더링거나 이미 상태값에 저장되고 로딩되어 있는 경우 <AdminPage>를 렌더링한다.

나는 이 테스트를 나의 재미를 위해 했지만, 현실 세계에서는 React에서 코드 분리하기 문서에 설명된 대로 그냥 react-loadable를 사용하면 된다.

...

좋다, 이제 다 된 것 같다. 내가 앞서 말한 것을 더 짧게 요약할 수가 있을까?

  • 사용자가 한 번 이상 여러분의 사이트에 방문한다면, 코드를 많은 수의 작은 파일들로 분리하라.
  • 대부분의 사용자가 방문하지 않는 사이트 중에 큰 부분이 있다면, 코드를 동적으로 로딩하라.

읽어 줘서 고맙다, 멋진 날 보내길!

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.