ネットソリューション

build時にpublicフォルダ内の画像も軽量化する Astroを使ってオウンドメディアを作る-4

こんにちは。ネットソリューション事業部の山下です。
最近は実案件でもAstroを使い始めております。

弊メディアでは先週の更新から記事サムネイル画像・記事タイトル画像を追加しました。
これらはOGP画像と兼用しているため、サイズ・拡張子の関係でファイルサイズが大きくなりがちです。
また、Astroはpublicフォルダ内の画像に関しては圧縮・軽量化処理はせずそのままファイルを移動するだけのようです。
今まで画像軽量化は別の方法で行っておりましたが、今後の運用や実案件でのAstro活用を考えると、ビルド時に一括で画像軽量化処理を行えるようにしたいと思い、次のようなスクリプトを作成しました。

スペシャルサンクスはChatGPTさんです。

条件

  • 入力フォルダを指定できる。今回はpublic
  • 出力フォルダを指定できる。今回はdist/tecblog
  • 圧縮のクオリティを設定できる。
  • 画像のフォルダ構造は維持する。

コマンドライン上の圧縮では「画像のフォルダ構造は維持する。」という条件がクリアできなかったため、今回はJavaScriptファイルを作成し、処理を行う形としました。

追加パッケージのインストール

画像軽量化スクリプトに必要なパッケージをインストールします。

npm install fs-extra path imagemin imagemin-mozjpeg imagemin-pngquant imagemin-svgo

optimize-images.mjs

プロジェクトルートにoptimize-images.mjsを作成します。
今回はAstroの設定でベースディレクトリをtechblogにしているので出力フォルダ設定もdist/techblogにしています。
qualityの数値は実際に軽量化された画像を確認し適時調整していきます。

import fs from 'fs-extra';
import path from 'path';
import imagemin from 'imagemin';
import imageminMozjpeg from 'imagemin-mozjpeg';
import imageminPngquant from 'imagemin-pngquant';
import imageminSvgo from 'imagemin-svgo';

const inputDir = 'public';  // 入力フォルダ
const outputDir = 'dist/techblog';   // 出力フォルダ

// 画像圧縮の設定
const plugins = [
    imageminMozjpeg({ quality: 85 }),
    imageminPngquant({ quality: [0.75, 0.85] }),
    imageminSvgo()
];

// 画像ファイルの圧縮処理
async function optimizeImages() {
    try {
        // 入力フォルダの存在確認
        if (!await fs.pathExists(inputDir)) {
            throw new Error(`Input directory "${inputDir}" does not exist.`);
        }

        // 出力フォルダを作成
        await fs.ensureDir(outputDir);

        await processDirectory(inputDir, outputDir);

        console.log('Image optimization completed.');
    } catch (error) {
        console.error('Error optimizing images:', error);
    }
}

// ディレクトリ内のファイルを処理
async function processDirectory(inputDir, outputDir) {
    const files = await fs.readdir(inputDir);
    const tasks = files.map(async (file) => {
        const filePath = path.join(inputDir, file);
        const stats = await fs.stat(filePath);

        if (stats.isDirectory()) {
            // ディレクトリの場合、再帰的に処理
            const outputSubDir = path.join(outputDir, file);
            await fs.ensureDir(outputSubDir);
            await processDirectory(filePath, outputSubDir);
        } else if (isImageFile(file)) {
            // 画像ファイルの場合、圧縮処理
            const outputFilePath = path.join(outputDir, file);
            await fs.ensureDir(path.dirname(outputFilePath));
            await compressImage(filePath, outputFilePath);
        }
    });

    await Promise.all(tasks);
}

// 画像ファイルかどうかを判定
function isImageFile(file) {
    const ext = path.extname(file).toLowerCase();
    return ['.jpg', '.jpeg', '.png', '.svg'].includes(ext);
}

// 画像を圧縮
async function compressImage(inputFile, outputFile) {
    try {
        await imagemin([inputFile], {
            destination: path.dirname(outputFile),
            plugins
        });
        console.log(`Optimized ${inputFile} -> ${outputFile}`);
    } catch (error) {
        console.error(`Error optimizing ${inputFile}:`, error);
    }
}

// スクリプトの実行
optimizeImages();

上記スクリプトを以下のコマンドで実行します。

node optimize-images.mjs

buildプロセス後に画像軽量化を実行する設定

無事スクリプトが実行されたら次はnpm run build後にこのスクリプトが自動的に実行されるように設定します。
package.jsonに次のように追加します。

{
  "scripts": {
    "build": "astro check && astro build",
    "optimize-images": "node optimize-images.mjs",
    "postbuild": "npm run optimize-images"
  },
}

今回使用したこの記事のOGP画像は504KB⇒171KBに軽量化することができました。
昨今はモニタの高解像度化に伴い画像サイズも実サイズの2倍で書き出すことも珍しくなくなり、画像容量における割合も増えていきました。
今回ご紹介したようなスクリプトを使い、適切に画像を配置していきましょう。

Webの進化の速さにアップアップですがなんとか喰らいついていきたい。

山下 X@Frencel_ns

フロントエンドエンジニア
フレームワーク頑張りたい人
モンハンワイルズではキアヌ・リーブス似のイケオジでプレイしたい

好きなモンスターハンターの武器

双剣・狩猟笛

mail お問い合わせ

ご覧いただきありがとうございます。
当メディアへのご質問や各事業部へのお仕事のご相談がありましたら、お気軽にお問い合わせください。

article 過去記事