SharpとElectronで画像圧縮ツールを作る

この投稿はアイスタイル [Advent Calendar 2024](https://qiita.com/advent-calendar/2024/istyle) の16日目の記事です。
## 導入背景
画像の品質を保ちながらファイルサイズを削減することは、多くの場面で重要です。特にWebサイトの高速化やストレージ容量の節約を考えると、近年では効率的な画像圧縮ツールが求められます。チーム内でもWebで提供されている画像圧縮ツールを使用していましたが、機密性の問題や複数ファイルを一括で圧縮できないといった制約があり、各ツールにはそれぞれメリットとデメリットが存在していました。そのため、理想の画像圧縮ツールを見つけることが難しく、自作をしようと思ったのがきっかけです。
本記事では、ElectronとSharpを活用し、画像圧縮アプリを作成して社内に配布するまでの手順を紹介します。
## 前提条件
Node.jsとnpmは既にインストール済みで使用可能な状態とします。またコマンドでの操作、HTML、CSS、JSの基本的な文法を理解しているものとします。
## 使用する環境・バージョン
OS : Windows10
Node.js : 20.10.0
npm : 10.2.3
Electron : 13.6.9
electron-builder : 25.1.8
Sharp : 0.29.3
### 1. Electron：デスクトップアプリケーション開発
[公式サイト](https://www.electronjs.org/ja/)
JavaScript、HTML、CSS によるデスクトップアプリケーションを構築するフレームワーク。Web技術を利用してクロスプラットフォームのデスクトップアプリケーションを開発が可能。
### 2. Sharp：高速画像処理の必須ライブラリ
[公式サイト](https://sharp.pixelplumbing.com/)
高速で柔軟な画像処理を可能にするNode.jsのライブラリ。画像のリサイズ、圧縮、フォーマット変換など、さまざまな画像操作を効率的に行うための機能を提供。
- JPEG、PNG、WebP、AVIF、GIFなど幅広いフォーマットをサポート
- 高速な処理を実現するためにlibvipsを使用

画像圧縮に特化した[imagemin](https://www.npmjs.com/package/imagemin)も有力な選択肢ですが、複数画像を一括で処理する場面ではパフォーマンスが重要となるため、高速かつ効率的に処理を行えるSharpを採用しました。 また、Sharpは直接的な画像変換機能（例: サイズ変更やフォーマット変更）にも対応しており、柔軟性の面でも優れています。
### 3. Github Actions：自動化されたCI/CDの実現
GitHub Actionsは、GitHubに統合されたCI/CDプラットフォームであり、ソフトウェア開発ワークフローを自動化する強力なツールです。本プロジェクトでは、GitHub Actionsを活用してアプリのビルドや配布プロセスを自動化します。
## 導入
必要なツールやライブラリのインストールします。
### Electronの導入
[ドキュメントのチュートリアル](https://www.electronjs.org/ja/docs/latest/tutorial/tutorial-prerequisites)どおりに構築を進める。
※ここではGithubのアカウントおよびリポジトリが必要になります。
### Sharpの導入
```
npm install sharp
```
Electronのチュートリアルで生成したmain.js内でSharpを呼び出す（以下は一例）
```
import sharp from 'sharp';
```
```
const sharp = require('sharp');
```
## 実装
### 画像圧縮ツールの最終目標（ツールで達成したいこと）
- 単一画像または複数画像の圧縮機能
- ドラッグアンドドロップまたはボタンで画像を選択可能
- 圧縮率を調整できる機能（クオリティスライダー）
- 選択した画像のクリア機能
- 複数画像圧縮でも高速に処理
- デスクトップアプリとして社内配布（今回はWindows限定での配布予定）
### ファイル配置
```
/image-compressor
  ├─index.html
  ├─package.json
  ├─package-lock.json
  ├─css
  │   └─style.css
  ├─img
  │   └─ #アプリアイコン等
  ├─js
  │   └─build.js #electron-builder用
  │   └─main.js
  │   └─preload.js
  │   └─renderer.js
  └─out #ビルドされたアプリの格納場所
```
実際に作成した画像圧縮ツールです。
![image-compressor.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3625328/5ccfddd8-d204-6c12-ba39-ccf54e9b9201.gif)
動画には映っていませんが【DONE】ボタンを押してロードが完了した際にアラートが出るようになっています。（エラーがある場合はErrorとなるようにしています）
![success.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3625328/cb1b6827-1f06-9bbf-5843-a45cad4445b6.png)
また、圧縮が完了した画像は、選択したフォルダ内に【compressed】フォルダを作成し保存されるように設定しています。
![folder.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3625328/dc2bb9a6-8d65-25b2-b480-4988870d4827.png)
画像サイズも圧縮されていることも確認できました。
![size.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3625328/cdfff815-3515-a15c-09ad-0cd67d55b59b.png)
## ビルドからリリースまで
Electron製アプリを配布可能な状態にする方法はいくつかありますが、electron-builderを使えば非常に簡単にアプリを配布できるため、今回のビルドにはelectron-builderを使用します。ただし、electron-builderを使っても、Windows PCでMac用インストーラーを作成することはできません。（その点については後述します。）
また、GitHub Actionsを使用して、新しいリリースを作成するためのワークフローを定義します。
### electron-builderのインストール
npmよりyarnが推奨されているのでyarnがなければyarnをインストールします。
npmを既にインストールしている場合、以下のコマンドで簡単にyarnをインストールできます。
```
npm install -g yarn
```
electron-builderを追加
```
yarn add electron-builder --dev
```
### electron-builderの設定
GitHub Actionsとスムーズに連携させるため、GitHub ActionsワークフローおよびCIの設定については、以下の記事を参考にしました。また、ワークフローファイルとしてrelease.ymlを参照し、設置を進めていきます。
[CI を利用したマルチプラットフォーム・ビルド (GitHub Actions, electron-builder)](https://zenn.dev/sprout2000/books/3691a679478de2/viewer/13600)
```
name: release

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    strategy:
      matrix:
        # クロスプラットフォーム・ビルド
        platform: [macos-latest, windows-latest]

    runs-on: ${{ matrix.platform }}
    steps:
      # Actions の基本設定
      - name: Check out Git repositry
        uses: actions/checkout@master

      # Node.js のセットアップ
      - name: Setup Node.js
        uses: actions/setup-node@master
        with:
          node-version: 18

      # 依存パッケージのインストール
      - name: Install dependencies
        run: npm install

      #### webpackなどによるビルドが必要なとき ####
      # - name: Build
        # run: npm run build

      # GitHub releases へデプロイ
      - name: Deploy
        env:
          # GITHUB_TOKEN は自動生成されるのでとくに設定は不要
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        # NPM スクリプト名は 'release' にすることが必須
        run: npm run release
```
### release.ymlにワークフローファイルを設置する前の準備
release.ymlを使用するにあたってActions secrets and variables（自動トークン認証）およびアクセストークンを設定します。手順は以下の通りです。
#### アクセストークンの作成
アクセストークンは、GitHub APIや外部サービスとの認証に使用されます。GitHubでアクセストークンを作成する方法は以下の通りです。
1\. GitHubアカウントにログイン
2\. GitHubにログインし、右上のプロフィールアイコンをクリックし、「Settings」を選択
3\. 左側のメニューから「Developer settings」を選択
4\. 「Personal access tokens (classic)」セクションを選択し、「Generate new token」をクリック
5\. repoとworkflowにチェックを入れる
6\. 「Generate token」をクリックし、作成されたトークンをコピーして保存します。（※このトークンは後で使用するので、紛失しないように注意してください。）
#### GitHub Secretsの設定
アクセストークンを作成した後、そのトークンをGitHub Secretsとしてリポジトリに追加します。SecretsはGitHub Actionsで安全に使用できる認証情報を格納するために使用します。
1\. GitHubリポジトリにアクセスし、リポジトリのページに移動し、右上の「Settings」をクリック
2\. 左側のメニューから「Secrets and variables」を選び、「Actions」をクリック
3\. 「New repository secret」をクリックし、名前（GITHUB_TOKEN）と値（先ほど作成したアクセストークン）を入力
4\. 「Add secret」をクリックして保存

以上の手順に従うことで、release.ymlでアクセストークンを使用する準備ができました。
### Github Actionsでのリリース検証
1\. プロジェクトのpackage.jsonファイルのバージョン（"version": "1.0.x"）を更新します
2\. 変更をコミットする（git commit -am v1.0.x）
3\. コミットにタグを付ける (git tag v1.0.x)
4\. 変更を GitHub にプッシュする (git push; git push --tags)

Actionsのタブでワークフローが実行されているか確認し、チェックマークがついたら実行成功です。
![workflow-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3625328/1eb85040-fe5c-c0c4-67cb-8f70814231b1.png)
実行ファイルも生成されています。
![workflow-02.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3625328/2f962c25-cee7-ca55-4a68-729de20f38c5.png)
## Mac用インストーラー配布の課題
Mac用のインストーラーを配布するには、コード署名に加え、Apple 社による「公証 (notary)」を受けている必要があります（有償）。これにより、インストーラーがマルウェアや不正なコンテンツを含んでいないことをAppleが保証します。このプロセスを経ていないインストーラーは、macOSのセキュリティ機能（Gatekeeper）によって警告が表示され、ユーザーにインストールを拒否される可能性があります。
[[macOS] Electron アプリの公証に成功する方法 2023](https://qiita.com/sprout2000/items/0fa5db89c43155f4f0c8)
## まとめ
- Sharpのバージョンによっては、画像圧縮書き出し時にエラーが発生することがあった。
- release.ymlでnpm installを実行しようとすると、非推奨パッケージに関する警告が多く表示され、npm installが失敗することがあります（例: @electron/packagerがelectron-packagerに変更されているなど）。そのため、package.jsonを見直しました。
- release.yml作成時にGitHub Secretsの設定をスキップしていたため、実行の失敗が頻発しました。
- Mac用インストーラーに関しては、設定する機会があれば改めてまとめる予定です。