brotli を使用してネットワーク ペイロードを最小化、圧縮する

マイケル・ディブラシオ(Michael DiBlasio)氏
Michael DiBlasio 氏

この Codelab は、ネットワーク ペイロードの圧縮と圧縮に関する Codelab の拡張であり、圧縮の基本コンセプトを理解していることを前提としています。この Codelab では、gzip などの他の圧縮アルゴリズムと比較して、Brotli 圧縮によって圧縮率とアプリ全体のサイズをさらに下げる方法を説明します。

アプリのスクリーンショット

測定

最適化に取り掛かる前に、まずアプリケーションの現在の状態を分析することをおすすめします。

  1. [Remix to Edit] をクリックしてプロジェクトを編集可能にします。
  2. サイトをプレビューするには、[アプリを表示] を押してから、全画面表示 全画面表示 を押します。

以前のネットワーク ペイロードの最小化と圧縮の Codelab では、main.js のサイズを 225 KB から 61.6 KB に縮小しました。この Codelab では、Brotli 圧縮によってこのバンドルサイズをさらに削減する方法を確認します。

ブロトリ圧縮

Brotli は新しい圧縮アルゴリズムであり、gzip よりもさらに優れたテキスト圧縮結果が得られます。CertSimple によると、Brotli のパフォーマンスは次のとおりです。

  • JavaScript の場合は gzip よりも 14% 小さい
  • HTML の場合は gzip よりも 21% 小さい
  • CSS では gzip よりも 17% 小さい

Brotli を使用するには、サーバーが HTTPS をサポートしている必要があります。Brotli は、ほとんどのブラウザの最新バージョンでサポートされています。Brotli をサポートするブラウザは、Accept-Encoding ヘッダーに br を含めます。

Accept-Encoding: gzip, deflate, br

使用する圧縮アルゴリズムは、Chrome デベロッパー ツールの [ネットワーク] タブ(Command+Option+I または Ctrl+Alt+I)の Content-Encoding フィールドで確認できます。

ネットワーク パネル

Brotli を有効にする

動的圧縮

動的圧縮では、ブラウザからリクエストされると、その場でアセットを圧縮します。

長所

  • アセットの圧縮バージョンの作成と更新を行う必要はありません。
  • オンザフライの圧縮は、動的に生成されるウェブページで特に効果的です。

デメリット

  • 圧縮率を高めるためにファイルを高いレベルで圧縮すると、時間がかかります。この場合、サーバーによってアセットが送信される前に、ユーザーがアセットの圧縮を待つため、パフォーマンスが低下する可能性があります。

Node/Express による動的圧縮

server.js ファイルは、アプリケーションをホストするノードサーバーの設定を行います。

var express = require('express');

var app = express();

app.use(express.static('public'));

var listener = app.listen(process.env.PORT, function() {
  console.log('Your app is listening on port ' + listener.address().port);
});

現在行われている処理は、express をインポートし、express.static ミドルウェアを使用して、public/directory 内のすべての静的 HTML、JS、CSS ファイルを読み込むことだけです(これらのファイルは、ビルドのたびに webpack によって作成されます)。

リクエストされるたびに brotli を使用してすべてのアセットが圧縮されるようにするには、shrink-ray モジュールを使用します。まず、package.jsondevDependency として追加します。

"devDependencies": {
  //...
  "shrink-ray": "^0.1.3"
},

これをサーバー ファイル server.js にインポートします。

var express = require('express');
var shrinkRay = require('shrink-ray');

さらに、express.static をマウントする前にミドルウェアとして追加します。

//...
var app = express();

// compress all requests
app.use(shrinkRay());

app.use(express.static('public'));

アプリを再読み込みして、[Network] パネルでバンドルサイズを確認します。

動的 Brotli 圧縮によるバンドルサイズ

Content-Encoding ヘッダーの bz から brotli が適用されていることを確認できます。main.bundle.js225 KB から 53.1 KB に縮小されました。gzip(61.6 KB)と比較して、約 14% 小さくなります。

静的圧縮

静的圧縮の背後にある考え方は、事前にアセットを圧縮して保存することです。

長所

  • 圧縮レベルが高いことによるレイテンシはもはや問題ではありません。ファイルを直接取得できるようになったため、圧縮のためにオンザフライで行う必要はありません。

デメリット

  • アセットはビルドのたびに圧縮する必要があります。圧縮レベルを高くすると、ビルド時間が大幅に長くなる可能性があります。

Node/Express と Webpack による静的圧縮

静的圧縮では、事前にファイルを圧縮することになるため、ビルドステップの一環としてアセットを圧縮するように Webpack の設定を変更できます。これには brotli-webpack-plugin を使用できます。

まず、package.jsondevDependency として追加します。

"devDependencies": {
  //...
 "brotli-webpack-plugin": "^1.1.0"
},

他の webpack プラグインと同様に、構成ファイル webpack.config.js にインポートします。

var path = require("path");

//...
var BrotliPlugin = require('brotli-webpack-plugin');

そして、これらを plugins 配列に含めます。

module.exports = {
  // ...
  plugins: [
    // ...
    new BrotliPlugin({
      asset: '[file].br',
      test: /\.(js)$/
    })
  ]
},

プラグインの配列では、次の引数を使用します。

  • asset: ターゲット アセット名。
  • [file] は、元のアセット ファイル名に置き換えられます。
  • test: この RegExp に一致するすべてのアセット(つまり、末尾が .js の JavaScript アセット)が処理されます。

たとえば、main.js の名前は main.js.br に変更されます。

アプリが再読み込みされて再ビルドされると、メインバンドルの圧縮バージョンが作成されるようになりました。Glitch コンソールを開いて、ノードサーバーから提供される最終的な public/ ディレクトリの内容を確認します。

  1. [ツール] ボタンをクリックします。
  2. [Console](コンソール)ボタンをクリックします。
  3. コンソールで次のコマンドを実行して、public ディレクトリに移動し、ディレクトリ内のすべてのファイルを表示します。
cd public
ls -lh
静的 Brotli 圧縮によるバンドルサイズ

バンドルの brotli 圧縮バージョン main.bundle.js.br もここに保存され、main.bundle.js よりもサイズが約 76% 小さくなりました(225 KB 対 53 KB)。

次に、元の JS バージョンがリクエストされるたびに、これらの brotli 圧縮ファイルを送信するようサーバーに指示します。そのためには、ファイルが express.static で提供される前に、server.js で新しいルートを定義します。

var express = require('express');

var app = express();

app.get('*.js', (req, res, next) => {
  req.url = req.url + '.br';
  res.set('Content-Encoding', 'br');
  res.set('Content-Type', 'application/javascript; charset=UTF-8');
  next();
});

app.use(express.static('public'));

app.get は、特定のエンドポイントに対する GET リクエストに応答する方法をサーバーに伝えるために使用します。このリクエストの処理方法を定義するために、コールバック関数が使用されます。ルートは次のようになります。

  • 最初の引数として '*.js' を指定すると、JS ファイルを取得するために起動されるすべてのエンドポイントで、この引数が機能します。
  • コールバック内で、.br がリクエストの URL に接続され、Content-Encoding レスポンス ヘッダーが br に設定されます。
  • Content-Type ヘッダーは、MIME タイプを指定する application/javascript; charset=UTF-8 に設定されます。
  • 最後に、next() はシーケンスが次に来る可能性のあるコールバックに進むことを保証します。

一部のブラウザは brotli 圧縮をサポートしていない場合があるため、brotli 圧縮ファイルを返す前に、Accept-Encoding リクエスト ヘッダーに br が含まれているかどうかをチェックして、brotli がサポートされていることを確認します。

var express = require('express');

var app = express();

app.get('*.js', (req, res, next) => {
  if (req.header('Accept-Encoding').includes('br')) {
    req.url = req.url + '.br';
    console.log(req.header('Accept-Encoding'));
    res.set('Content-Encoding', 'br');
    res.set('Content-Type', 'application/javascript; charset=UTF-8');
  }
  next();
});

app.use(express.static('public'));

アプリが再読み込みされたら、もう一度 [Network] パネルを確認します。

バンドルサイズは 53.1 KB(225 KB から)

Success! Brotli 圧縮を使用してアセットをさらに圧縮しました。

まとめ

この Codelab では、brotli を使用してアプリの全体のサイズをさらに縮小する方法を説明しました。サポートされている場合、brotligzip よりも強力な圧縮アルゴリズムになります。