T-CREATOR

Node.js で画像や PDF を操作する:sharp・pdf-lib 活用術

Node.js で画像や PDF を操作する:sharp・pdf-lib 活用術

現代の Web アプリケーション開発において、画像や PDF の操作は避けて通れない重要な要素となっています。ユーザーがアップロードした画像の最適化、レポートの自動生成、ドキュメントの動的作成など、これらの処理を効率的に行うことで、ユーザー体験を大きく向上させることができます。

Node.js は、その豊富なエコシステムと非同期処理の特性を活かして、画像や PDF の操作においても非常に強力なツールとなります。特にsharppdf-libという 2 つのライブラリは、それぞれの分野で最高レベルのパフォーマンスと使いやすさを提供しており、多くの開発者から高い評価を受けています。

この記事では、実際のプロジェクトで即座に活用できる実践的なテクニックを中心に、画像と PDF の操作について詳しく解説していきます。初心者の方でも理解しやすいよう、段階的に学習を進められる構成となっています。

画像操作の基礎知識

画像フォーマットの種類と特徴

画像処理を始める前に、まずは扱う画像フォーマットの特徴を理解することが重要です。それぞれのフォーマットには得意分野があり、用途に応じて適切な選択を行うことで、パフォーマンスと品質の両方を最適化できます。

JPEG(Joint Photographic Experts Group)

  • 写真やグラデーションに適した可逆圧縮形式
  • ファイルサイズが小さく、Web での配信に最適
  • 透明度をサポートしていない

PNG(Portable Network Graphics)

  • 可逆圧縮で画質劣化がない
  • 透明度(アルファチャンネル)をサポート
  • ファイルサイズが大きくなりがち

WebP(Web Picture)

  • Google が開発した次世代画像フォーマット
  • JPEG と PNG の両方の利点を併せ持つ
  • ブラウザサポートが年々向上している

AVIF(AV1 Image File Format)

  • 最新の高効率画像フォーマット
  • 非常に高い圧縮率を実現
  • まだブラウザサポートが限定的

Node.js での画像処理の必要性

Node.js で画像処理を行う理由は、Web アプリケーションの開発において非常に重要です。ユーザーがアップロードした画像を自動的に最適化したり、サムネイルを生成したりすることで、ストレージコストの削減とページ読み込み速度の向上を同時に実現できます。

また、Node.js の非同期処理の特性を活かすことで、大量の画像を並列処理することができ、バッチ処理の効率を大幅に向上させることができます。

sharp ライブラリの導入と基本設定

sharp の特徴と利点

sharpは、Node.js で最も人気のある画像処理ライブラリの一つです。libvips という高性能な画像処理ライブラリをベースにしており、他のライブラリと比較して圧倒的な速度とメモリ効率を実現しています。

主な特徴

  • 高速な画像処理(他のライブラリの 4-5 倍高速)
  • 低メモリ使用量
  • 豊富な画像フォーマット対応
  • 直感的な API 設計
  • アクティブな開発とコミュニティサポート

インストールと初期設定

まず、新しい Node.js プロジェクトを作成し、sharp ライブラリをインストールしましょう。

bash# プロジェクトディレクトリの作成
mkdir image-pdf-processing
cd image-pdf-processing

# package.jsonの初期化
yarn init -y

# sharpライブラリのインストール
yarn add sharp

インストールが完了したら、基本的な設定を行います。

javascript// sharpの基本インポート
const sharp = require('sharp');

// エラーハンドリングの設定
sharp.cache(false); // キャッシュを無効化(開発時)
sharp.concurrency(1); // 並行処理数を制限

基本的な画像読み込みと保存

sharp ライブラリの基本的な使い方を学びましょう。まずは画像の読み込みと保存から始めます。

javascript// 基本的な画像読み込みと保存
const processImage = async () => {
  try {
    // 画像の読み込みとリサイズ
    await sharp('input.jpg')
      .resize(800, 600)
      .jpeg({ quality: 80 })
      .toFile('output.jpg');

    console.log('画像処理が完了しました');
  } catch (error) {
    console.error('画像処理エラー:', error.message);
  }
};

processImage();

このコードでは、入力画像を 800x600 ピクセルにリサイズし、JPEG 品質 80%で保存しています。sharp の API は直感的で、メソッドチェーンを使って様々な処理を組み合わせることができます。

画像の基本操作

リサイズとクロップ

画像のサイズ変更は、Web アプリケーションで最も頻繁に行われる操作の一つです。sharp では、様々なリサイズオプションを提供しています。

javascript// 基本的なリサイズ操作
const resizeImage = async () => {
  try {
    // 幅を指定して高さを自動調整(アスペクト比保持)
    await sharp('input.jpg')
      .resize(800)
      .toFile('resized-width.jpg');

    // 高さを指定して幅を自動調整
    await sharp('input.jpg')
      .resize(null, 600)
      .toFile('resized-height.jpg');

    // 幅と高さを指定(アスペクト比を無視)
    await sharp('input.jpg')
      .resize(800, 600, { fit: 'fill' })
      .toFile('resized-fixed.jpg');

    console.log('リサイズ処理が完了しました');
  } catch (error) {
    console.error('リサイズエラー:', error.message);
  }
};

クロップ操作では、画像の特定部分を切り出すことができます。

javascript// クロップ操作
const cropImage = async () => {
  try {
    // 中央から300x300ピクセルを切り出し
    await sharp('input.jpg')
      .resize(800, 800, { fit: 'cover' })
      .extract({
        left: 250,
        top: 250,
        width: 300,
        height: 300,
      })
      .toFile('cropped.jpg');

    console.log('クロップ処理が完了しました');
  } catch (error) {
    console.error('クロップエラー:', error.message);
  }
};

フォーマット変換

画像フォーマットの変換は、用途に応じて最適な形式を選択する際に重要です。

javascript// フォーマット変換の例
const convertFormat = async () => {
  try {
    // JPEGからPNGへの変換
    await sharp('input.jpg').png().toFile('output.png');

    // PNGからWebPへの変換
    await sharp('input.png')
      .webp({ quality: 85 })
      .toFile('output.webp');

    // JPEGからAVIFへの変換
    await sharp('input.jpg')
      .avif({ quality: 80 })
      .toFile('output.avif');

    console.log('フォーマット変換が完了しました');
  } catch (error) {
    console.error('変換エラー:', error.message);
  }
};

画質調整と圧縮

画質とファイルサイズのバランスを取ることは、Web パフォーマンスにおいて非常に重要です。

javascript// 画質調整と圧縮
const optimizeImage = async () => {
  try {
    // JPEGの品質調整
    await sharp('input.jpg')
      .jpeg({
        quality: 75,
        progressive: true,
        mozjpeg: true,
      })
      .toFile('optimized.jpg');

    // PNGの圧縮
    await sharp('input.png')
      .png({
        compressionLevel: 9,
        adaptiveFiltering: true,
      })
      .toFile('optimized.png');

    console.log('最適化が完了しました');
  } catch (error) {
    console.error('最適化エラー:', error.message);
  }
};

画像の高度な加工

フィルターとエフェクト

sharp では、様々なフィルターとエフェクトを適用することができます。これらを活用することで、画像に魅力的な視覚効果を加えることができます。

javascript// フィルターとエフェクトの適用
const applyFilters = async () => {
  try {
    // ガウシアンブラー効果
    await sharp('input.jpg').blur(5).toFile('blurred.jpg');

    // シャープ化
    await sharp('input.jpg')
      .sharpen({ sigma: 1, flat: 1, jagged: 2 })
      .toFile('sharpened.jpg');

    // グレースケール変換
    await sharp('input.jpg')
      .grayscale()
      .toFile('grayscale.jpg');

    console.log('フィルター適用が完了しました');
  } catch (error) {
    console.error('フィルターエラー:', error.message);
  }
};

画像の合成とオーバーレイ

複数の画像を合成したり、ロゴやウォーターマークを追加したりすることができます。

javascript// 画像の合成とオーバーレイ
const compositeImages = async () => {
  try {
    // ロゴを右下に配置
    await sharp('background.jpg')
      .composite([
        {
          input: 'logo.png',
          top: 50,
          left: 50,
        },
      ])
      .toFile('composited.jpg');

    // 複数の画像を合成
    await sharp('base.jpg')
      .composite([
        { input: 'overlay1.png', top: 0, left: 0 },
        { input: 'overlay2.png', top: 100, left: 100 },
      ])
      .toFile('multi-composited.jpg');

    console.log('画像合成が完了しました');
  } catch (error) {
    console.error('合成エラー:', error.message);
  }
};

メタデータの操作

画像の EXIF データやその他のメタデータを読み取り、操作することができます。

javascript// メタデータの操作
const handleMetadata = async () => {
  try {
    // メタデータの読み取り
    const metadata = await sharp('input.jpg').metadata();
    console.log('画像情報:', {
      format: metadata.format,
      width: metadata.width,
      height: metadata.height,
      channels: metadata.channels,
      hasProfile: metadata.hasProfile,
      hasAlpha: metadata.hasAlpha,
    });

    // メタデータの削除(プライバシー保護)
    await sharp('input.jpg')
      .withMetadata(false)
      .toFile('no-metadata.jpg');

    console.log('メタデータ処理が完了しました');
  } catch (error) {
    console.error('メタデータエラー:', error.message);
  }
};

PDF 操作の基礎知識

PDF の構造と特徴

PDF(Portable Document Format)は、Adobe Systems が開発した文書フォーマットで、プラットフォームに依存しない文書の表示と印刷を可能にします。

PDF の主な特徴

  • プラットフォーム非依存
  • フォントの埋め込み
  • セキュリティ機能
  • ベクターグラフィックス対応
  • フォーム機能

PDF の内部構造

  • ヘッダー:ファイルの基本情報
  • オブジェクト:テキスト、画像、フォントなどの要素
  • クロスリファレンステーブル:オブジェクトの位置情報
  • トレーラー:ファイルの終端情報

Node.js での PDF 処理の課題

Node.js で PDF を処理する際には、いくつかの課題があります。

主な課題

  • PDF の複雑な構造
  • フォントの処理
  • メモリ使用量の制御
  • 非同期処理での制約

これらの課題を解決するために、適切なライブラリの選択と実装方法の理解が重要になります。

pdf-lib ライブラリの導入と基本設定

pdf-lib の特徴と利点

pdf-libは、Node.js で PDF を操作するための強力なライブラリです。TypeScript で書かれており、型安全性と使いやすさを両立しています。

主な特徴

  • 完全な TypeScript サポート
  • 豊富な PDF 操作機能
  • メモリ効率の良い処理
  • アクティブな開発
  • 詳細なドキュメント

インストールと初期設定

pdf-lib ライブラリをインストールし、基本的な設定を行います。

bash# pdf-libライブラリのインストール
yarn add pdf-lib

基本的なインポートと初期設定を行います。

javascript// pdf-libの基本インポート
const {
  PDFDocument,
  rgb,
  StandardFonts,
} = require('pdf-lib');

// 基本的なPDF操作の設定
const createBasicPDF = async () => {
  try {
    // 新しいPDFドキュメントを作成
    const pdfDoc = await PDFDocument.create();

    // ページを追加
    const page = pdfDoc.addPage([595.276, 841.89]); // A4サイズ

    console.log('PDFドキュメントが作成されました');
    return pdfDoc;
  } catch (error) {
    console.error('PDF作成エラー:', error.message);
    throw error;
  }
};

PDF の読み込みと作成

既存の PDF ファイルを読み込んだり、新しい PDF を作成したりする基本的な操作を学びましょう。

javascript// PDFの読み込みと作成
const loadAndCreatePDF = async () => {
  try {
    // 既存のPDFを読み込み
    const existingPdfBytes = await fs.readFile(
      'existing.pdf'
    );
    const pdfDoc = await PDFDocument.load(existingPdfBytes);

    // 新しいPDFを作成
    const newPdfDoc = await PDFDocument.create();
    const page = newPdfDoc.addPage();

    console.log('PDFの読み込みと作成が完了しました');
    return { existing: pdfDoc, new: newPdfDoc };
  } catch (error) {
    console.error('PDF操作エラー:', error.message);
    throw error;
  }
};

PDF の基本操作

ページの追加・削除・並び替え

PDF のページ操作は、ドキュメントの構造を変更する際に重要です。

javascript// ページの基本操作
const manipulatePages = async () => {
  try {
    const pdfDoc = await PDFDocument.create();

    // 複数のページを追加
    const page1 = pdfDoc.addPage();
    const page2 = pdfDoc.addPage();
    const page3 = pdfDoc.addPage();

    // ページの削除(インデックス0から開始)
    pdfDoc.removePage(1); // 2番目のページを削除

    // ページの並び替え
    const pages = pdfDoc.getPages();
    if (pages.length >= 2) {
      pdfDoc.movePage(1, 0); // 2番目のページを最初に移動
    }

    console.log('ページ操作が完了しました');
    return pdfDoc;
  } catch (error) {
    console.error('ページ操作エラー:', error.message);
    throw error;
  }
};

テキストの追加と編集

PDF にテキストを追加し、スタイリングを適用することができます。

javascript// テキストの追加と編集
const addTextToPDF = async () => {
  try {
    const pdfDoc = await PDFDocument.create();
    const page = pdfDoc.addPage();

    // フォントを埋め込み
    const font = await pdfDoc.embedFont(
      StandardFonts.Helvetica
    );

    // テキストを追加
    page.drawText('Hello, PDF World!', {
      x: 50,
      y: page.getHeight() - 50,
      size: 24,
      font: font,
      color: rgb(0, 0, 0),
    });

    // 複数行のテキスト
    const longText =
      'This is a longer text that demonstrates how to add multiple lines of text to a PDF document.';
    page.drawText(longText, {
      x: 50,
      y: page.getHeight() - 100,
      size: 12,
      font: font,
      color: rgb(0.5, 0.5, 0.5),
      maxWidth: 400,
      lineHeight: 16,
    });

    console.log('テキスト追加が完了しました');
    return pdfDoc;
  } catch (error) {
    console.error('テキスト追加エラー:', error.message);
    throw error;
  }
};

画像の挿入

PDF に画像を挿入し、サイズや位置を調整することができます。

javascript// 画像の挿入
const insertImageToPDF = async () => {
  try {
    const pdfDoc = await PDFDocument.create();
    const page = pdfDoc.addPage();

    // 画像ファイルを読み込み
    const imageBytes = await fs.readFile('image.jpg');
    const image = await pdfDoc.embedJpg(imageBytes);

    // 画像のサイズを取得
    const { width, height } = image.scale(1);

    // 画像をページに配置
    page.drawImage(image, {
      x: 50,
      y: page.getHeight() - height - 50,
      width: 200,
      height: (height * 200) / width,
    });

    console.log('画像挿入が完了しました');
    return pdfDoc;
  } catch (error) {
    console.error('画像挿入エラー:', error.message);
    throw error;
  }
};

PDF の高度な操作

フォームフィールドの操作

PDF フォームのフィールドを操作し、動的に値を設定することができます。

javascript// フォームフィールドの操作
const handleFormFields = async () => {
  try {
    // フォーム付きPDFを読み込み
    const formPdfBytes = await fs.readFile('form.pdf');
    const pdfDoc = await PDFDocument.load(formPdfBytes);

    // フォームフィールドを取得
    const form = pdfDoc.getForm();
    const fields = form.getFields();

    // テキストフィールドに値を設定
    const textField = form.getTextField('name');
    textField.setText('John Doe');

    // チェックボックスを設定
    const checkbox = form.getCheckBox('agreement');
    checkbox.check();

    // ラジオボタンを設定
    const radioGroup = form.getRadioGroup('gender');
    radioGroup.select('male');

    console.log('フォームフィールド操作が完了しました');
    return pdfDoc;
  } catch (error) {
    console.error('フォーム操作エラー:', error.message);
    throw error;
  }
};

電子署名の追加

PDF に電子署名を追加し、ドキュメントの信頼性を向上させることができます。

javascript// 電子署名の追加
const addDigitalSignature = async () => {
  try {
    const pdfDoc = await PDFDocument.create();
    const page = pdfDoc.addPage();

    // 署名フィールドを作成
    const form = pdfDoc.getForm();
    const signatureField =
      form.createSignatureField('signature');

    // 署名フィールドをページに配置
    signatureField.addToPage(page, {
      x: 50,
      y: 50,
      width: 200,
      height: 100,
    });

    // 署名の外観をカスタマイズ
    signatureField.setAppearance({
      text: 'Digital Signature',
      fontSize: 12,
      color: rgb(0, 0, 0),
    });

    console.log('電子署名フィールドが追加されました');
    return pdfDoc;
  } catch (error) {
    console.error('署名追加エラー:', error.message);
    throw error;
  }
};

セキュリティ設定

PDF にパスワード保護や権限制限を設定することができます。

javascript// セキュリティ設定
const setSecurityOptions = async () => {
  try {
    const pdfDoc = await PDFDocument.create();

    // セキュリティオプションを設定
    pdfDoc.encrypt({
      userPassword: 'user123',
      ownerPassword: 'owner456',
      permissions: {
        printing: 'lowResolution',
        modifying: false,
        copying: false,
        annotating: false,
        fillingForms: true,
        contentAccessibility: true,
        documentAssembly: false,
      },
    });

    console.log('セキュリティ設定が完了しました');
    return pdfDoc;
  } catch (error) {
    console.error('セキュリティ設定エラー:', error.message);
    throw error;
  }
};

実践的な活用例

バッチ処理での画像最適化

大量の画像を一括で最適化する実践的な例を見てみましょう。

javascript// バッチ画像最適化
const batchImageOptimization = async (
  inputDir,
  outputDir
) => {
  try {
    const fs = require('fs').promises;
    const path = require('path');

    // 入力ディレクトリの画像ファイルを取得
    const files = await fs.readdir(inputDir);
    const imageFiles = files.filter((file) =>
      /\.(jpg|jpeg|png|webp)$/i.test(file)
    );

    console.log(
      `${imageFiles.length}個の画像ファイルを処理します`
    );

    // 並列処理で画像を最適化
    const optimizationPromises = imageFiles.map(
      async (file) => {
        const inputPath = path.join(inputDir, file);
        const outputPath = path.join(
          outputDir,
          `optimized_${file}`
        );

        try {
          await sharp(inputPath)
            .resize(1200, 1200, {
              fit: 'inside',
              withoutEnlargement: true,
            })
            .jpeg({ quality: 80, progressive: true })
            .toFile(outputPath);

          console.log(`${file} の最適化が完了しました`);
          return { success: true, file };
        } catch (error) {
          console.error(
            `${file} の処理でエラー:`,
            error.message
          );
          return {
            success: false,
            file,
            error: error.message,
          };
        }
      }
    );

    const results = await Promise.all(optimizationPromises);
    const successCount = results.filter(
      (r) => r.success
    ).length;

    console.log(
      `処理完了: ${successCount}/${imageFiles.length} 成功`
    );
    return results;
  } catch (error) {
    console.error('バッチ処理エラー:', error.message);
    throw error;
  }
};

PDF レポートの自動生成

データベースの情報を基に PDF レポートを自動生成する例です。

javascript// PDFレポート自動生成
const generatePDFReport = async (data) => {
  try {
    const pdfDoc = await PDFDocument.create();
    const page = pdfDoc.addPage([595.276, 841.89]); // A4

    // フォントを埋め込み
    const font = await pdfDoc.embedFont(
      StandardFonts.HelveticaBold
    );
    const regularFont = await pdfDoc.embedFont(
      StandardFonts.Helvetica
    );

    let yPosition = page.getHeight() - 50;

    // タイトル
    page.drawText('月次レポート', {
      x: 50,
      y: yPosition,
      size: 24,
      font: font,
      color: rgb(0, 0, 0),
    });

    yPosition -= 50;

    // データテーブル
    data.forEach((item, index) => {
      if (yPosition < 100) {
        // 新しいページを追加
        const newPage = pdfDoc.addPage([595.276, 841.89]);
        yPosition = newPage.getHeight() - 50;
      }

      // 項目名
      page.drawText(`${item.name}:`, {
        x: 50,
        y: yPosition,
        size: 12,
        font: regularFont,
        color: rgb(0, 0, 0),
      });

      //
      page.drawText(item.value.toString(), {
        x: 200,
        y: yPosition,
        size: 12,
        font: regularFont,
        color: rgb(0.5, 0.5, 0.5),
      });

      yPosition -= 20;
    });

    console.log('PDFレポート生成が完了しました');
    return pdfDoc;
  } catch (error) {
    console.error('レポート生成エラー:', error.message);
    throw error;
  }
};

Web アプリケーションでの活用

Express.js を使用した Web アプリケーションでの画像・PDF 処理の例です。

javascript// Express.jsでの画像・PDF処理
const express = require('express');
const multer = require('multer');
const app = express();

// ファイルアップロードの設定
const upload = multer({ dest: 'uploads/' });

// 画像アップロードと最適化
app.post(
  '/upload-image',
  upload.single('image'),
  async (req, res) => {
    try {
      if (!req.file) {
        return res
          .status(400)
          .json({ error: '画像ファイルが必要です' });
      }

      const inputPath = req.file.path;
      const outputPath = `processed/${Date.now()}_${
        req.file.originalname
      }`;

      // 画像を最適化
      await sharp(inputPath)
        .resize(800, 800, {
          fit: 'inside',
          withoutEnlargement: true,
        })
        .jpeg({ quality: 80 })
        .toFile(outputPath);

      res.json({
        success: true,
        originalSize: req.file.size,
        processedPath: outputPath,
      });
    } catch (error) {
      console.error('画像処理エラー:', error.message);
      res
        .status(500)
        .json({ error: '画像処理に失敗しました' });
    }
  }
);

// PDF生成エンドポイント
app.post('/generate-pdf', async (req, res) => {
  try {
    const { title, content } = req.body;

    const pdfDoc = await PDFDocument.create();
    const page = pdfDoc.addPage();
    const font = await pdfDoc.embedFont(
      StandardFonts.Helvetica
    );

    // タイトル
    page.drawText(title, {
      x: 50,
      y: page.getHeight() - 50,
      size: 18,
      font: font,
      color: rgb(0, 0, 0),
    });

    // コンテンツ
    page.drawText(content, {
      x: 50,
      y: page.getHeight() - 100,
      size: 12,
      font: font,
      color: rgb(0, 0, 0),
      maxWidth: 500,
    });

    const pdfBytes = await pdfDoc.save();

    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader(
      'Content-Disposition',
      'attachment; filename=generated.pdf'
    );
    res.send(Buffer.from(pdfBytes));
  } catch (error) {
    console.error('PDF生成エラー:', error.message);
    res
      .status(500)
      .json({ error: 'PDF生成に失敗しました' });
  }
});

app.listen(3000, () => {
  console.log('サーバーがポート3000で起動しました');
});

パフォーマンスとベストプラクティス

メモリ使用量の最適化

大量の画像や PDF を処理する際は、メモリ使用量の最適化が重要です。

javascript// メモリ効率的な画像処理
const memoryEfficientProcessing = async (
  inputPath,
  outputPath
) => {
  try {
    // ストリーミング処理でメモリ使用量を削減
    const pipeline = sharp(inputPath)
      .resize(800, 600)
      .jpeg({ quality: 80 })
      .pipe(fs.createWriteStream(outputPath));

    return new Promise((resolve, reject) => {
      pipeline.on('finish', () => {
        console.log('ストリーミング処理が完了しました');
        resolve();
      });
      pipeline.on('error', reject);
    });
  } catch (error) {
    console.error(
      'ストリーミング処理エラー:',
      error.message
    );
    throw error;
  }
};

// バッチ処理でのメモリ管理
const batchProcessingWithMemoryManagement = async (
  files
) => {
  try {
    const batchSize = 5; // 一度に処理するファイル数
    const results = [];

    for (let i = 0; i < files.length; i += batchSize) {
      const batch = files.slice(i, i + batchSize);

      // バッチ処理
      const batchResults = await Promise.all(
        batch.map((file) => processImage(file))
      );

      results.push(...batchResults);

      // ガベージコレクションを促す
      if (global.gc) {
        global.gc();
      }

      console.log(
        `バッチ ${Math.floor(i / batchSize) + 1} 完了`
      );
    }

    return results;
  } catch (error) {
    console.error('バッチ処理エラー:', error.message);
    throw error;
  }
};

非同期処理の活用

Node.js の非同期処理を最大限に活用して、パフォーマンスを向上させましょう。

javascript// 非同期処理の最適化
const optimizedAsyncProcessing = async (tasks) => {
  try {
    // 並行処理数の制限
    const concurrencyLimit = 3;
    const results = [];

    for (
      let i = 0;
      i < tasks.length;
      i += concurrencyLimit
    ) {
      const batch = tasks.slice(i, i + concurrencyLimit);

      const batchPromises = batch.map(async (task) => {
        try {
          return await task();
        } catch (error) {
          console.error('タスク実行エラー:', error.message);
          return { error: error.message };
        }
      });

      const batchResults = await Promise.all(batchPromises);
      results.push(...batchResults);
    }

    return results;
  } catch (error) {
    console.error('非同期処理エラー:', error.message);
    throw error;
  }
};

// プログレス追跡付きの非同期処理
const asyncProcessingWithProgress = async (
  tasks,
  onProgress
) => {
  try {
    const results = [];
    let completed = 0;

    const processTask = async (task) => {
      const result = await task();
      completed++;
      onProgress(completed, tasks.length);
      return result;
    };

    const promises = tasks.map(processTask);
    const allResults = await Promise.all(promises);

    return allResults;
  } catch (error) {
    console.error('プログレス追跡エラー:', error.message);
    throw error;
  }
};

エラーハンドリング

堅牢なエラーハンドリングを実装して、アプリケーションの安定性を向上させましょう。

javascript// 包括的なエラーハンドリング
const robustErrorHandling = async (operation) => {
  try {
    // 操作の実行
    const result = await operation();
    return { success: true, data: result };
  } catch (error) {
    // エラーの分類と処理
    if (error.code === 'ENOENT') {
      console.error(
        'ファイルが見つかりません:',
        error.message
      );
      return {
        success: false,
        error: 'ファイルが見つかりません',
      };
    } else if (
      error.message.includes('unsupported image format')
    ) {
      console.error(
        'サポートされていない画像形式:',
        error.message
      );
      return {
        success: false,
        error: 'サポートされていない画像形式です',
      };
    } else if (error.message.includes('PDF')) {
      console.error('PDF処理エラー:', error.message);
      return {
        success: false,
        error: 'PDF処理に失敗しました',
      };
    } else {
      console.error('予期しないエラー:', error.message);
      return {
        success: false,
        error: '予期しないエラーが発生しました',
      };
    }
  }
};

// リトライ機能付きエラーハンドリング
const retryOperation = async (
  operation,
  maxRetries = 3,
  delay = 1000
) => {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      console.error(
        `試行 ${attempt}/${maxRetries} 失敗:`,
        error.message
      );

      if (attempt === maxRetries) {
        throw error;
      }

      // 指数バックオフ
      await new Promise((resolve) =>
        setTimeout(resolve, delay * attempt)
      );
    }
  }
};

まとめ

Node.js での画像・PDF 操作は、現代の Web アプリケーション開発において非常に重要なスキルです。sharppdf-libという 2 つの強力なライブラリを活用することで、高性能で使いやすい画像・PDF 処理システムを構築することができます。

この記事で学んだ技術を活用することで、以下のような価値を創造できます:

ユーザー体験の向上

  • 画像の自動最適化によるページ読み込み速度の改善
  • カスタマイズされた PDF レポートの即座生成
  • 直感的なファイル操作インターフェース

運用コストの削減

  • ストレージ使用量の最適化
  • 手動作業の自動化
  • エラー処理の自動化

開発効率の向上

  • 再利用可能なコンポーネントの作成
  • 標準化された処理フロー
  • 包括的なエラーハンドリング

これらの技術を習得することで、より魅力的で実用的な Web アプリケーションを開発できるようになります。継続的な学習と実践を通じて、さらに高度な技術を身につけていきましょう。

関連リンク