webpackでビルド時に指定ディレクトリ配下を丸ごとS3にアップロードする

状況

  • パフォーマンス向上のため、画像など一部静的ファイルをCloudFront経由でS3から読み込むようにしている
  • cssやjsファイルはコンパイルなど一手間あるのでアプリケーションサーバの静的ファイル置き場(publicディレクトリ配下)から読み込んでいた
  • コンパイル含むビルドはwebpackを使用している(さらに言うとLaravelのmixというwebpackのラッパー)

やりたいこと

  • パフォーマンス向上のため、cssやjsファイルを含むアプリケーションサーバの静的ファイル置き場から読み込んでいるものを全てCloudFront経由でS3から読み込むようにしたい

やったこと

webpack-s3-pluginというプラグインで本番環境時のみS3にアップロードするようにする

webpack-contrib/s3-plugin-webpack: Uploads files to s3 after complete

webpack.mix.js
const { mix } = require('laravel-mix');
const S3Plugin = require('webpack-s3-plugin');

// 本番環境でのみ実行する処理
if (mix.inProduction()) {

  // webpackのカスタム設定
  mix.webpackConfig({
    plugins: [
      // public/asv配下をs3にアップロードする 
      new S3Plugin({
        // s3Options are required
        s3Options: {
          accessKeyId: process.env.MIX_AWS_ACCESS_KEY_ID,
          secretAccessKey: process.env.MIX_AWS_SECRET_ACCESS_KEY,
          region: 'ap-northeast-1',
        },
        s3UploadOptions: {
          Bucket: process.env.MIX_S3_BUCKET,
          CacheControl: 'max-age=864000', // 10日のブラウザキャッシュ
        },
        // s3のどのルート直下パスに置くか
        basePath: 'site1',
        // リポジトリ内の下記ディレクトリを丸ごとアップロードする
        directory: 'public',
        // アップロード時にCloudFrontのインバリデーションを行う
        cloudfrontInvalidateOptions: {
          DistributionId: process.env.MIX_CLOUDFRONT_DISTRIBUTION_ID,
          Items: ["/site1/*"]
        }
      })
    ]
  });
}

開発環境やステージング環境では取り回しの聞くアプリケーションサーバからの読み込みの方が便利なのでそのままにし、本番環境でのビルド時のみS3にアップロードするようにしています。
ステージング環境で上記作業を行うようにすると、ブランチ運用している場合にS3のアップロード先が別ブランチの作業で上書きされかねないので注意が必要です。
また、CloudFrontからの読み込みはキャッシュに気をつけないとデプロイしたのに変更がされないという事故にも繋がりかねないので、アップロードしたディレクトリのオブジェクトキャッシュを全てインバリデーションするようにしています。

ファイル読み込み関数を本番環境時とそれ以外の環境で読み込む場所を変更する

一例ですが、自分はLaravelのasset関数をオーバーライドして対応しています。

public function asset($path, $secure = null, $withQuery = true)
{
    // 本番ではCloudFrontを見る
    if (app()->environment('production')) {
        $path = trim($path, '/');
        $url = config('site.cloudFrontUrl') . '/site1/' . $path;

    // ローカル、ステージングの場合はpublicを見る
    } else {
        $root = $this->route('top');
        $root = rtrim($root, '/');
        $url = $root . '/' . $path;
    }

    if ($withQuery) {
        $url = $this->revision ? $url .'?'. $this->revision : $url;
    }

    return $url;
}

CloudFrontのBehaviorsのCompress Objects AutomaticallyをYesにする

cssやjsなどのgzip圧縮を有効にするオプションです。
おそらくCloudFront側のメモリを使用するから初期値Noになってそう?
pngやjpgの画像はYesでもgzip圧縮できないのでそのままにしていましたが、cssやjsなどのテキストファイルは圧縮可能なのでYesに変更。
なお、拡張子ごとなどに設定を変えたい場合はBehaviorを作成してパスパターンで分岐させます。

css、jsの連結をやめて複数ファイルに分ける

CloudFrontへのリクエストはHTTP2に対応しており、同一ドメインへの同時リクエストの制限がなくなるので、HTTP1.1で行っていたリクエスト数を減らすためのファイル連結を行う必要がなくなります。
むしろページごとに使用するcss、jsのコンポーネントがある程度異なるような大きさのプロジェクトでは、いくつかのファイルに分けて不要なコンポーネントは読み込ませないようにした方がパフォーマンスが上がります。
キャッシュのヒット率がなるべく上がるように意識してファイルを分割しました。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.