장기 캐싱 활용

webpack이 애셋 캐싱에 도움이 되는 방식

다음으로 할 일은 앱 크기를 최적화한 후 앱 로드 시간이 캐싱되고 있음을 개선합니다. 이를 사용하여 앱의 일부를 매번 다시 다운로드하지 않아도 됩니다.

번들 버전 관리 및 캐시 헤더 사용

캐싱을 수행하는 일반적인 방법은 다음과 같습니다.

  1. 파일을 매우 오랫동안 (예: 1년) 캐시하도록 브라우저에 요청할 수 있습니다.

    # Server header
    Cache-Control: max-age=31536000
    

    Cache-Control의 기능에 익숙하지 않다면 Jake Archibald의 캐싱에 대한 우수한 게시물 관행

  2. 파일이 변경되면 파일을 다시 다운로드하도록 이름을 변경합니다.

    <!-- Before the change -->
    <script src="./index-v15.js"></script>
    
    <!-- After the change -->
    <script src="./index-v16.js"></script>
    

이 방법은 JS 파일을 다운로드하고 캐시한 후 저장된 사본입니다. 파일 이름이 변경된 경우에만 브라우저가 네트워크에 접속합니다. 1년이 지난 경우).

webpack에서도 동일한 작업을 수행하지만 버전 번호 대신 파일 해시 파일 이름에 해시를 포함하려면 다음을 사용합니다. [chunkhash]:

// webpack.config.js
module.exports = {
  entry: './index.js',
  output: {
    filename: 'bundle.[chunkhash].js' // → bundle.8e0d62a03.js
  }
};

필요한 경우 파일 이름에 전달하려면 HtmlWebpackPlugin 또는 WebpackManifestPlugin입니다.

HtmlWebpackPlugin는 단순하지만 유연성은 떨어집니다 컴파일 중에 이 플러그인은 컴파일된 모든 리소스가 포함된 HTML 파일입니다. 서버 로직이 복잡하다면 그것만으로도 충분합니다.

<!-- index.html -->
<!DOCTYPE html>
<!-- ... -->
<script src="bundle.8e0d62a03.js"></script>

WebpackManifestPlugin 드림 은 보다 유연한 접근 방식으로, 서버 부분이 복잡한 경우에 유용합니다. 빌드 중에 파일 이름 간의 매핑이 포함된 JSON 파일을 생성합니다. 해시가 없는 파일 이름, 해시가 있는 파일 이름 등입니다. 서버에서 이 JSON을 사용하여 다음 파일을 사용합니다.

// manifest.json
{
  "bundle.js": "bundle.8e0d62a03.js"
}

추가 자료

종속 항목과 런타임을 별도의 파일로 추출

종속 항목

앱 종속 항목은 실제 앱 코드보다 자주 변경되는 경우가 많습니다. 이사 시 별도의 파일로 내보내면 브라우저는 별도로 캐시할 수 있습니다. 앱 코드가 변경될 때마다 다시 다운로드하지 않습니다.

종속 항목을 별도의 청크로 추출하려면 다음 3단계를 수행합니다.

  1. 출력 파일 이름을 [name].[chunkname].js로 바꿉니다.

    // webpack.config.js
    module.exports = {
      output: {
        // Before
        filename: 'bundle.[chunkhash].js',
        // After
        filename: '[name].[chunkhash].js'
      }
    };
    

    webpack이 앱을 빌드할 때 [name]를 대체합니다. 이름을 지정합니다 [name] 부분을 추가하지 않으면 해시를 사용하여 청크를 구별하는 것은 꽤 어렵습니다.

  2. entry 필드를 객체로 변환합니다.

    // webpack.config.js
    module.exports = {
      // Before
      entry: './index.js',
      // After
      entry: {
        main: './index.js'
      }
    };
    

    이 스니펫에서 'main'은 청크의 이름입니다. 이 이름은 다음에서 대체됩니다. 자리를 [name] 유지했습니다.

    이제 앱을 빌드하면 이 청크에 전체 앱 코드가 포함될 것입니다. 이 단계를 수행하지 않은 것처럼 말입니다. 하지만 이것은 곧 바뀔 것입니다.

  3. Webpack 4에서 optimization.splitChunks.chunks: 'all' 옵션을 추가합니다. 웹팩 구성에 추가하겠습니다.

    // webpack.config.js (for webpack 4)
    module.exports = {
      optimization: {
        splitChunks: {
          chunks: 'all'
        }
      }
    };
    

    이 옵션은 스마트 코드 분할을 사용 설정합니다. 이를 통해 webpack은 압축 후 gzip을 하기 전 30KB보다 큽니다. 또한 공통 코드인 이는 빌드가 여러 번들 (예: 앱을 경로로 분할하는 경우).

    Webpack 3에서 CommonsChunkPlugin를 추가합니다.

    // webpack.config.js (for webpack 3)
    module.exports = {
      plugins: [
        new webpack.optimize.CommonsChunkPlugin({
        // A name of the chunk that will include the dependencies.
        // This name is substituted in place of [name] from step 1
        name: 'vendor',
    
        // A function that determines which modules to include into this chunk
        minChunks: module => module.context && module.context.includes('node_modules'),
        })
      ]
    };
    

    이 플러그인은 경로에 node_modules가 포함된 모든 모듈을 사용합니다. vendor.[chunkhash].js라는 별도의 파일로 이동합니다.

이러한 변경 후에는 각 빌드에서 파일이 하나가 아닌 두 개의 파일(main.[chunkhash].jsvendor.[chunkhash].js (Webpack 4의 경우 vendors~main.[chunkhash].js) webpack 4의 경우 종속 항목이 작으면 공급업체 번들이 생성되지 않을 수도 있지만 괜찮습니다.

$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
                        Asset      Size  Chunks             Chunk Names
 ./main.00bab6fd3100008a42b0.js   82 kB       0  [emitted]  main
./vendor.d9e134771799ecdf9483.js  47 kB       1  [emitted]  vendor

브라우저는 이러한 파일을 별도로 캐시하고 변경된 코드만 다시 다운로드합니다.

Webpack 런타임 코드

안타깝게도 공급업체 코드만 추출하는 것만으로는 충분하지 않습니다. 당신이 앱 코드에서 무언가를 변경할 수 있습니다.

// index.js
…
…

// E.g. add this:
console.log('Wat');

vendor 해시도 변경됩니다.

                           Asset   Size  Chunks             Chunk Names
./vendor.d9e134771799ecdf9483.js  47 kB       1  [emitted]  vendor

                            Asset   Size  Chunks             Chunk Names
./vendor.e6ea4504d61a1cc1c60b.js  47 kB       1  [emitted]  vendor

이는 모듈 코드와 별도로 webpack 번들에 런타임 – 작은 코드 조각 kube-apiserver와 통신하는 역할을 합니다 코드를 여러 파일로 분할하면 이 코드 조각은 청크 ID와 대상 데이터 사이의 매핑을 포함하여 해당하는 파일:

// vendor.e6ea4504d61a1cc1c60b.js
script.src = __webpack_require__.p + chunkId + "." + {
    "0": "2f2269c7f0a55a5c1871"
}[chunkId] + ".js";

Webpack은 이 런타임을 마지막으로 생성된 청크(vendor)에 포함합니다. 이 경우에는 청크가 변경될 때마다 이 코드도 변경됩니다. 전체 vendor 청크가 변경됩니다.

이 문제를 해결하기 위해 런타임을 별도의 파일로 이동하겠습니다. Webpack 4에서 다음은 다음과 같이 optimization.runtimeChunk 옵션을 사용 설정하면 됩니다.

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    runtimeChunk: true
  }
};

webpack 3에서는 CommonsChunkPlugin로 추가 빈 청크를 만들어 이를 수행합니다.

// webpack.config.js (for webpack 3)
module.exports = {
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: module => module.context && module.context.includes('node_modules')
    }),
    // This plugin must come after the vendor one (because webpack
    // includes runtime into the last chunk)
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime',
      // minChunks: Infinity means that no app modules
      // will be included into this chunk
      minChunks: Infinity
    })
  ]
};

이러한 변경 후에는 각 빌드에서 다음과 같은 3개의 파일이 생성됩니다.

$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
                            Asset     Size  Chunks             Chunk Names
   ./main.00bab6fd3100008a42b0.js    82 kB       0  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       1  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

이를 index.html에 역순으로 포함하면 완료됩니다.

<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
<script src="./vendor.26886caf15818fa82dfa.js"></script>
<script src="./main.00bab6fd3100008a42b0.js"></script>

추가 자료

추가 HTTP 요청을 저장하는 인라인 webpack 런타임

더 나은 결과를 얻으려면 webpack 런타임을 HTML에 인라인 처리하세요. 있습니다. 예를 들면 다음과 같습니다.

<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>

수행:

<!-- index.html -->
<script>
!function(e){function n(r){if(t[r])return t[r].exports;…}} ([]);
</script>

런타임은 짧으며 인라인 처리하면 HTTP 요청을 저장할 수 있습니다. HTTP/1에서 중요하며 HTTP/2에서는 덜 중요하지만 효과).

방법은 여기를 참조하세요.

HTMLWebpackPlugin을 사용하여 HTML을 생성하는 경우

HtmlWebpackPlugin을 사용하여 HTML 파일을 사용하면 InlineSourcePlugin 만 있으면 됩니다.

const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineSourcePlugin = require('html-webpack-inline-source-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      inlineSource: 'runtime~.+\\.js',
    }),
    new InlineSourcePlugin()
  ]
};

맞춤 서버 로직을 사용하여 HTML을 생성하는 경우

Webpack 4 사용:

  1. WebpackManifestPlugin 드림 런타임 청크의 생성된 이름을 확인합니다.

    // webpack.config.js (for webpack 4)
    const ManifestPlugin = require('webpack-manifest-plugin');
    
    module.exports = {
      plugins: [
        new ManifestPlugin()
      ]
    };
    

    이 플러그인을 사용하여 빌드하면 다음과 같은 파일이 생성됩니다.

    // manifest.json
    {
      "runtime~main.js": "runtime~main.8e0d62a03.js"
    }
    
  2. 편리한 방법으로 런타임 청크의 콘텐츠를 인라인으로 추가합니다. 예: Node.js 및 Express에서 사용할 수 있습니다.

    // server.js
    const fs = require('fs');
    const manifest = require('./manifest.json');
    const runtimeContent = fs.readFileSync(manifest['runtime~main.js'], 'utf-8');
    
    app.get('/', (req, res) => {
      res.send(`
        …
        <script>${runtimeContent}</script>
        …
      `);
    });
    

또는 Webpack 3을 사용하는 경우:

  1. filename를 지정하여 런타임 이름을 정적으로 만듭니다.

    module.exports = {
      plugins: [
        new webpack.optimize.CommonsChunkPlugin({
          name: 'runtime',
          minChunks: Infinity,
          filename: 'runtime.js'
        })
      ]
    };
    
  2. 편리한 방법으로 runtime.js 콘텐츠를 인라인으로 배치합니다. 예: Node.js 및 Express에서 사용할 수 있습니다.

    // server.js
    const fs = require('fs');
    const runtimeContent = fs.readFileSync('./runtime.js', 'utf-8');
    
    app.get('/', (req, res) => {
      res.send(`
        …
        <script>${runtimeContent}</script>
        …
      `);
    });
    

지금은 필요하지 않은 코드 지연 로드

때로는 페이지에 다음과 같이 더 중요하거나 덜 중요한 부분이 있습니다.

  • YouTube에서 동영상 페이지를 로드할 때는 동영상보다 동영상을 더 중요하게 여깁니다. 있습니다. 여기서는 동영상이 댓글보다 더 중요합니다.
  • 뉴스 사이트에서 기사를 열면 기사보다는 여기에서는 텍스트가 광고보다 더 중요합니다.

이 경우 가장 중요한 항목을 먼저 로드한 다음 나머지 부분은 지연 로딩합니다 import() 함수code-splitting을 지원합니다.

// videoPlayer.js
export function renderVideoPlayer() { … }

// comments.js
export function renderComments() { … }

// index.js
import {renderVideoPlayer} from './videoPlayer';
renderVideoPlayer();

// …Custom event listener
onShowCommentsClick(() => {
  import('./comments').then((comments) => {
    comments.renderComments();
  });
});

import()는 특정 모듈을 동적으로 로드하도록 지정합니다. 날짜 webpack이 import('./module.js')를 확인하면 이 모듈을 별도의 chunk:

$ webpack
Hash: 39b2a53cb4e73f0dc5b2
Version: webpack 3.8.1
Time: 4273ms
                            Asset     Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./main.f7e53d8e13e9a2745d6d.js    60 kB       1  [emitted]  main
 ./vendor.4f14b6326a80f4752a98.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

실행이 import() 함수에 도달할 때만 다운로드됩니다.

이렇게 하면 main 번들이 작아져 초기 로드 시간이 개선됩니다. 게다가 캐싱도 개선됩니다. 메인 청크에서 코드를 변경하면 영향을 받지 않습니다.

추가 자료

코드를 경로 및 페이지로 분할

앱에 여러 개의 경로나 페이지가 있지만 (단일 main 청크), 추가 바이트를 제공하고 있을 가능성이 실행할 수 있습니다 예를 들어 사용자가 사이트의 홈페이지를 방문하는 경우:

WebFundamentals 홈페이지

다른 기사에 있는 기사를 렌더링하기 위해 코드를 페이지를 로드한다고 가정해 보겠습니다. 또한 사용자가 항상 집만 방문하는 경우 기사 코드를 변경하는 경우 webpack으로 인해 사용자는 전체 앱을 다시 다운로드해야 합니다.

앱을 페이지 (단일 페이지 앱의 경우 경로)로 분할하는 경우 관련된 코드만 다운로드합니다. 또한 브라우저에서 앱 코드를 캐시하여 홈페이지 코드를 변경하면 webpack이 기존의 해당 청크를 반환합니다.

단일 페이지 앱의 경우

단일 페이지 앱을 경로별로 분할하려면 import()을 사용하세요 (자세한 내용은 '지연 로드 코드' 사용하지 마세요." 섹션). 프레임워크를 사용하는 경우 이를 위한 기존 솔루션이 있을 수 있습니다.

기존 다중 페이지 앱의 경우

기존 앱을 페이지별로 분할하려면 webpack의 항목을 포인트가 있습니다. 앱에 홈페이지, 기사 페이지, 사용자 계정 페이지 등 3개의 항목이 있어야 합니다.

// webpack.config.js
module.exports = {
  entry: {
    home: './src/Home/index.js',
    article: './src/Article/index.js',
    profile: './src/Profile/index.js'
  }
};

각 항목 파일에 대해 webpack이 별도의 종속 항목 트리를 빌드하고 해당 항목에서 사용하는 모듈만 포함된 번들입니다.

$ webpack
Hash: 318d7b8490a7382bf23b
Version: webpack 3.8.1
Time: 4273ms
                            Asset     Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./home.91b9ed27366fe7e33d6a.js    18 kB       1  [emitted]  home
./article.87a128755b16ac3294fd.js    32 kB       2  [emitted]  article
./profile.de945dc02685f6166781.js    24 kB       3  [emitted]  profile
 ./vendor.4f14b6326a80f4752a98.js    46 kB       4  [emitted]  vendor
./runtime.318d7b8490a7382bf23b.js  1.45 kB       5  [emitted]  runtime

따라서 기사 페이지에서만 Lodash를 사용하는 경우 homeprofile 번들은 포함되어 있지 않으며, 사용자가 추가 콘텐츠를 업로드할 때 홈페이지 방문

하지만 별도의 종속 항목 트리에는 단점이 있습니다. 2개의 진입점이 Lodash를 설치했으며 종속 항목을 공급업체 번들로 이동하지 않음(둘 다 항목임) 포인트에 Lodash 사본이 포함됩니다. 이 문제를 해결하려면 Webpack 4에서 optimization.splitChunks.chunks: 'all' 옵션을 webpack 구성에 추가합니다.

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  }
};

이 옵션은 스마트 코드 분할을 사용 설정합니다. 이 옵션을 사용하면 webpack이 자동으로 공통 코드를 찾아 별도의 파일로 추출합니다.

또는 Webpack 3에서 CommonsChunkPlugin를 사용합니다. – 공통 종속 항목을 지정된 새 파일로 이동합니다.

module.exports = {
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'common',
      minChunks: 2    // 2 is the default value
    })
  ]
};

자유롭게 minChunks 값을 사용하여 가장 적합한 값을 찾아보세요. 일반적으로 작게 유지하되 청크 수가 증가하면 늘립니다. 대상 예를 들어 청크 3개의 경우 minChunks는 2일 수 있지만 청크 30개는 8일 수 있습니다. 왜냐하면 2로 유지하면 너무 많은 모듈이 공통 파일에 들어가기 때문입니다. 너무 많이 부풀려서는 안 됩니다.

추가 자료

모듈 ID 안정성 향상

코드를 빌드할 때 webpack은 각 모듈에 ID를 할당합니다. 나중에 이러한 ID는 번들 내의 require()에 사용됩니다. 일반적으로 빌드 출력에 ID가 표시됩니다. 모듈 경로 바로 앞에 추가하면 됩니다.

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.8ecaf182f5c85b7a8199.js  22.5 kB       0  [emitted]
   ./main.4e50a16675574df6a9e9.js    60 kB       1  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime

↓ 여기

[0] ./index.js 29 kB {1} [built]
[2] (webpack)/buildin/global.js 488 bytes {2} [built]
[3] (webpack)/buildin/module.js 495 bytes {2} [built]
[4] ./comments.js 58 kB {0} [built]
[5] ./ads.js 74 kB {1} [built]
+ 1 hidden module

기본적으로 ID는 카운터를 사용하여 계산됩니다 (예: 첫 번째 모듈의 ID는 0, 두 번째는 ID가 1인 식입니다. 이런 식으로 계속됨). 이 문제는 모듈 목록 중간에 표시되어 기존 모듈의 다음 모듈의 ID:

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.5c82c0f337fcb22672b5.js    22 kB       0  [emitted]
   ./main.0c8b617dfc40c2827ae3.js    82 kB       1  [emitted]  main
 ./vendor.26886caf15818fa82dfa.js    46 kB       2  [emitted]  vendor
./runtime.79f17c27b335abc7aaf4.js  1.45 kB       3  [emitted]  runtime
   [0] ./index.js 29 kB {1} [built]
   [2] (webpack)/buildin/global.js 488 bytes {2} [built]
   [3] (webpack)/buildin/module.js 495 bytes {2} [built]

↓ 새로운 모듈...

[4] ./webPlayer.js 24 kB {1} [built]

↓ 결과를 확인할 수 있습니다! 이제 comments.js의 ID는 4가 아닌 5입니다.

[5] ./comments.js 58 kB {0} [built]

ads.js의 ID는 이제 5가 아닌 6입니다.

[6] ./ads.js 74 kB {1} [built]
       + 1 hidden module

이렇게 하면 ID가 변경된 모듈을 포함하거나 이에 의존하는 모든 청크가 무효화됩니다. 실제 코드가 변경되지 않았더라도 말이죠 여기서는 0 청크 (청크)가 comments.js가 있는 청크) 및 main 청크 (다른 앱 코드가 있는 청크)가 main만 무효화되어야 합니다.

이 문제를 해결하려면 HashedModuleIdsPlugin 카운터 기반 ID를 모듈 경로의 해시로 대체합니다.

$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
                           Asset      Size  Chunks             Chunk Names
      ./0.6168aaac8461862eab7a.js  22.5 kB       0  [emitted]
   ./main.a2e49a279552980e3b91.js    60 kB       1  [emitted]  main
 ./vendor.ff9f7ea865884e6a84c8.js    46 kB       2  [emitted]  vendor
./runtime.25f5d0204e4f77fa57a1.js  1.45 kB       3  [emitted]  runtime

↓ 여기

[3IRH] ./index.js 29 kB {1} [built]
[DuR2] (webpack)/buildin/global.js 488 bytes {2} [built]
[JkW7] (webpack)/buildin/module.js 495 bytes {2} [built]
[LbCc] ./webPlayer.js 24 kB {1} [built]
[lebJ] ./comments.js 58 kB {0} [built]
[02Tr] ./ads.js 74 kB {1} [built]
    + 1 hidden module

이 방법을 사용하면 모듈의 ID는 해당 모듈의 이름을 바꾸거나 이동해야만 변경됩니다. 모듈을 마칩니다 새 모듈은 다른 모듈에 영향을 주지 않습니다. 있습니다.

플러그인을 사용 설정하려면 구성의 plugins 섹션에 추가합니다.

// webpack.config.js
module.exports = {
  plugins: [
    new webpack.HashedModuleIdsPlugin()
  ]
};

추가 자료

요약

  • 번들을 캐시하고 번들 이름을 변경하여 버전을 구분합니다.
  • 번들을 앱 코드, 공급업체 코드, 런타임으로 분할
  • 런타임을 인라인하여 HTTP 요청 저장
  • import로 중요하지 않은 코드 지연 로드
  • 불필요한 항목이 로드되지 않도록 경로/페이지별로 코드를 분할합니다.