パフォーマンス無関心で作ったサイトのパフォーマンスチューニング④ 画像のアップロード圧縮

はじめに

こんにちわ!こんばんわ!ご観覧ありがとうございます。
定期的に雑色な記事を投稿します。(タイトルは記事のメインの内容にしてますが)
基本的には抱えている問題を改善し、改善方法を記事にしてアップしようと考えています。

お題と経緯

静的サイトで何が一番パフォーマンスに影響がでるかは、この記事を書いてきた経験上画像という認識です。
作成しているサイトにはアップロードされた画像を読み込む画面も存在しているため、圧縮が必要と考えました。

今回の内容

画像の圧縮方法を記録していきたいと思います!

使用した技術

Category     Technology Stack
Frontend Nextjs,React,Typescript
Backend Typescript,Node,CloudFunction
Database Firestore
Design   XD  

前回の内容

前回記事

パフォーマンス無関心で作ったサイトのパフォーマンスチューニング③ FirebaseでNextjsをhosting(ImageタグとSSG併用難しい) - zare926のブログ

NextjsのImageを使用してFirebaseへデプロイする方法でした。
バージョンが低い状態でしか成功していないので、いつかAppRouterについても挑戦してみようと思っています。

browser-image-compressionとsharpの紹介

browser-image-compressionはクライアントサイドで圧縮できるライブラリです。
sharpはnodejsでサーバーサイドで圧縮するライブラリです。

browser-image-compression ライブラリ

まずはインストールしましょう

$ npm install browser-image-compression
# または
$ yarn add browser-image-compression

使いたい場所でimport

import imageCompression from 'browser-image-compression';`

で実際圧縮したい場合の例

const options = {
  maxSizeMB: 1, // この場合1Mです
  maxWidthOrHeight: 1920, //最大の高さが1920
  useWebWorker: true, // Webワーカーを有効にしている
};


  const inputImage = async (e: React.ChangeEvent<HTMLInputElement>) => {
    if (e.target && e.target.files) {
      const file = e.target.files[0]
      const reader = new FileReader()
      const compressFile = await imageCompression(file, options)
      await imageUpload(compressFile)
      reader.readAsDataURL(compressFile)
    }
  }

optionに渡せる設定はドキュメント参照してください。

browser-image-compression - npm

とても簡単に圧縮できます!
クライアントサイドで特に重い処理などがなければ問題なく使えるライブラリだと思います。

sharp ライブラリ

前段としてNextのImageコンポーネントの画像圧縮に使われているライブラリです。
クライアントサイド側で圧縮することが似合わない場合はサーバーサイドで圧縮しましょう。
こちらもインストールから

npm install sharp
# または
yarn add sharp

モジュールじゃなくてrequire

const sharp = require('sharp');

圧縮の一例

// 入力画像のパス
const inputImagePath = 'input.jpg';

// 出力画像のパス
const outputImagePath = 'output.jpg';

// 圧縮設定
const compressionOptions = {
  quality: 70, // 画質の品質を指定 (0から100の範囲)
  progressive: true, // プログレッシブJPEGを有効にする
  optimizePalette: true, // PNGの場合、パレット最適化を有効にする
  fit: 'inside', // 画像のフィット方法を指定
  background: 'white', // 透明な領域の背景色を指定
};

sharp(inputImagePath)
  .resize(800, 600) // 画像のリサイズ
  .toFile(outputImagePath, (err, info) => {
    if (err) {
      console.error(err);
    } else {
      console.log(info);
    }
  });

Webpなども変換できるようですね。
以下記事を参考にしました。

Node.jsのライブラリsharpの出力形式について | Simple is Beautiful.

FirebaseであればCloudFuntions側圧縮するということができそうですね。

const functions = require('firebase-functions');
const { Storage } = require('@google-cloud/storage');
const os = require('os');
const path = require('path');
const sharp = require('sharp');

const gcs = new Storage();

exports.compressImage = functions.storage.object().onFinalize(async (object) => {
  const bucket = gcs.bucket(object.bucket);
  const filePath = object.name;
  const fileName = path.basename(filePath);

  if (!filePath.startsWith('compressed/')) {
    // This check prevents infinite recursion
    const tempFilePath = path.join(os.tmpdir(), fileName);

    // Download the image from Firebase Storage
    await bucket.file(filePath).download({ destination: tempFilePath });

    // Compress the image
    await sharp(tempFilePath).resize(800).toFile(tempFilePath);

    // Upload the compressed image back to Storage
    await bucket.upload(tempFilePath, {
      destination: `compressed/${fileName}`,
    });

    // Clean up the temporary file
    return fs.unlinkSync(tempFilePath);
  }

  return null;
});

(このコードはGPTに作ってもらったので、古いと思います。すいません。。。)

とは言ってもCloudFunctionsのv1は10MB、v2は32Mまでが受け取り可能なデータ量なので、なかなかないと思いますが、それ以上のデータを圧縮する場合は別の方法が必要ですね。

最後に

全然気にもしてなかったんですが、パフォーマンスの記事を書くようになってから画像に対する処理に敏感になりましたね。
いいこと!