💭

Next.jsのビルド時にファイルの存在チェックを行う

2023/08/21に公開

Next.jsで勉強がてらBlogを作っていますが、現在はすべてSSGで静的ファイルを出力して使うようにしてます。
ブログ記事には、アイキャッチ画像を設定できますが、画像ファイルを入れ忘れることが考えられます。
そこでyarn buildで静的ファイルを出力する際に、ブログ記事のアイキャッチ画像が存在しているかのチェックするようにしました。

チェックにはWebPackのカスタムプラグインを作ります。

ブログサンプル

作っているBlogでは、markdownで記事を管理しており、imageにイメージ画像ファイル名、emojiに絵文字を設定できます。
emojiには、絵文字を設定できます。
image→emojiの順に設定されている画像を表示します。

---
title: "サンプルデータ7"
description: "Markdownでのリストの作成方法について解説します。"
createdAt: "2023-08-03"
updatedAt: "2023-08-04"
emoji: "😀"
---

# リストの作成方法

Markdownでリストを作成するには、アスタリスク (`*`)、プラス (`+`)、またはハイフン (`-`) を使用します。

これで次のようなリストが作成できます。

- アイテム1
- アイテム2
  - サブアイテム2-1
  - サブアイテム2-2
- アイテム3

WebPackカスタムプラグインの追加

まずは、カスタムプラグインを作成します。

このプラグインでは、イメージファイルの存在チェックを行います。
gray-matterでブログ記事ファイルを読み込み、画像のデータが存在するかチェックします。
絵文字は、設定されている絵文字の文字コードを文字列化してファイル名に使っています。
ファイルがない場合、エラーを発生させて、yarn buildを失敗させます。

check-image-existance.js
const fs = require("fs").promises;
const path = require("path");
const matter = require("gray-matter");

class CheckImageExistencePlugin {
  emojiToImageFileName(emoji) {
    const emojiCode = [...emoji][0].codePointAt(0).toString(16).toLowerCase();
    return `emoji_u${emojiCode}.svg`;
  }

  async apply(compiler) {
    compiler.hooks.emit.tapAsync("CheckImageExistencePlugin", async (compilation, callback) => {
      const postsDirectory = path.join(compiler.context, "/posts");
      const emojiDirectory = path.join(compiler.context, "/public/emoji");
      const imageDirectory = path.join(compiler.context, "/public/images");

      const mdFiles = await fs.readdir(postsDirectory);
      const filteredMdFiles = mdFiles.filter((file) => file.endsWith(".md"));

      for (const file of filteredMdFiles) {
        const content = await fs.readFile(path.join(postsDirectory, file), "utf-8");
        const metadata = matter(content);

        if (metadata.data.image) {
          const imagePath = path.join(imageDirectory, metadata.data.image);

          try {
            await fs.access(imagePath);
          } catch {
            compilation.errors.push(
              new Error(
                `Image ${metadata.data.image} does not exist. File ${file}, Image path ${imagePath}`,
              ),
            );
          }
        }
        if (metadata.data.emoji) {
          const imageName = this.emojiToImageFileName(metadata.data.emoji);
          const imagePath = path.join(emojiDirectory, imageName);

          try {
            await fs.access(imagePath);
          } catch {
            compilation.errors.push(
              new Error(
                `Emoji ${metadata.data.emoji} does not exist. File ${file}, Image path ${imagePath}`,
              ),
            );
          }
        }
      }

      callback();
    });
  }
}

module.exports = CheckImageExistencePlugin;


next.config.jsの変更

作成したCheckImageExistencePluginをビルド時に実行するようにnext.config.jsを変更します。

webpack(config)の設定を追加します。

next.config.js
const withExportImages = require("next-export-optimize-images");
const CheckImageExistencePlugin = require("./plugin/check-image-existance.js");

/** @type {import('next').NextConfig} */
const nextConfig = withExportImages({
  output: "export",
  experimental: {
    appDir: true,
  },
  webpack(config) {
    // 絵文字ファイルが存在するかチェックするカスタムプラグインの追加
    config.plugins.push(new CheckImageExistencePlugin());
    return config;
  },
});

module.exports = nextConfig;


エラー表示

yarn buildでエラーが発生すると以下のようなエラーが発生します。
なんか2回エラーが発生していますが。

Failed to compile.

Image eyecatch/sample2.png does not exist. File 6.md, Image path /workspace/next_app/public/images/eyecatch/sample2.png

Emoji 👨 does not exist. File 6.md, Image path /workspace/next_app/public/emoji/emoji_u1f468.svg

Image eyecatch/sample2.png does not exist. File 6.md, Image path /workspace/next_app/public/images/eyecatch/sample2.png

Emoji 👨 does not exist. File 6.md, Image path /workspace/next_app/public/emoji/emoji_u1f468.svg

Discussion