複数環境向けにnpm scriptで前後処理をする

※[みらい翻訳 Advent Calendar 2024](https://qiita.com/advent-calendar/2024/miraitranslate)向けの記事になります

https://qiita.com/advent-calendar/2024/miraitranslate

はじめまして、エンジニアのhideです。何気にAdvent calendarには初参加します。

普段は主に音声系の翻訳サービスをフルスタックで開発しています。
今回はフロントエンドの話題です。

## 概要
前提としては以下の実装のお話です。
- Next.js アプリ
- SPAとしてS3ホスティング向けにビルド
    -  `output: "export"`
-  同じアプリを複数のロゴを用いて環境ごとにリリースしたい(OEMのような形式)というリクエストがある
-  SVGをコンポーネント化して管理

## 今回やりたいこと
- ロゴ画像、faviconが2種類ある(仮にAブランド、Bブランドとします)
     - Aブランド画像とBブランド画像は同じサイズに調整している
     - 本番AにはAブランド画像でリリース、本番BにはBブランドでリリースするようにしたい
- ただし、mainブランチは分けないで(重要)、ビルドのプロセスの中でデプロイ環境ごとに出しわけをしたい

## 解決策
上記の要件で何ができるか調べてると、npm scripts内の機能でpre-, post-を用いて前後処理で画像リソースを差し替える方式でいけるのではないかと考えました。

https://docs.npmjs.com/cli/v8/using-npm/scripts


### npm script pre-, post- の使い方

```package.json
  "scripts": {
    "dev": "next dev",
    "predev": "事前処理",
    "postdev": "事後処理",    
    "build": "next build",
    "prebuild": "事前処理",
    "postbuild": "事後処理",
  },
```
のように書きます。試作したものとしては、

```package.json
  "scripts": {
    "dev": "next dev",
    "predev": "/bin/sh ./prebuild.sh",
    "postdev": "/bin/sh ./postbuild.sh",
    "build": "next build",
    "prebuild": "/bin/sh ./prebuild.sh",
    "postbuild": "/bin/sh ./postbuild.sh",
  },
```

```prebuild.sh

#!/bin/sh
# vim:sw=4:ts=4:et

set -e

if [ -n $NEXT_PUBLIC_BRAND ]; then
    if [ -s ./.env.local ]; then
        source ./.env.local
    else
        echo "no env file"
        exit
    fi
fi

if [ $NEXT_PUBLIC_BRAND ] && [ $NEXT_PUBLIC_BRAND = 'brandB' ]; then
    # brandA資材退避
    mv -f ./src/app/favicon.ico ./src/assets/icons/brandA/;
    mv -f ./src/assets/icons/IconServiceLogo*.tsx ./src/assets/icons/brandA/;
    # brandB資材取得
    cp -f ./src/assets/icons/brandB/favicon.ico ./src/app/;
    cp -f ./src/assets/icons/brandB/*.tsx ./src/assets/icons/;
fi
```


```postbuild.sh
#!/bin/sh
# vim:sw=4:ts=4:et
set -e

if [ -n $NEXT_PUBLIC_BRAND ]; then
    if [ -s ./.env.local ]; then
        source ./.env.local
    else
        echo "no env file"
        exit
    fi
fi
if [ $NEXT_PUBLIC_BRAND ] && [ $NEXT_PUBLIC_BRAND = 'brandB' ]; then
    # brandA退避資材復帰
    mv -f ./src/assets/icons/brandA/favicon.ico ./src/app/;
    mv -f ./src/assets/icons/brandA/IconServiceLogo*.tsx ./src/assets/icons/;
fi
```

こちらを実行します。(以下の実行ログはいろいろ編集してますので擬似的なものです)

```
NEXT_PUBLIC_BRAND=brandB
npm run dev                              13s 09:47:38

> realtime-translate@0.0.0 predev
> 

> realtime-translate@0.0.0 dev
> next dev

// 画像が差し代わって起動

  ▲ Next.js 14.2.18
  - Local:        http://localhost:3000
  - Environments: .env.local

 ✓ Starting...
 ✓ Ready in 1938ms
^C
realtime-translate@0.0.0 postdev

// 画像が元に戻る
```
### 最終的な実装
ここまでで、事前事後処理がnpm script内で実行できることが確認できました。
最終的にプルリクレビューでブラッシュアップすることで以下のようになりました。

```package.json
  "scripts": {
    "dev": "next dev",
    "predev": "node prebuild.js ${npm_config_brand}",
    "build": "next build",
    "prebuild": "node prebuild.js ${npm_config_brand}",
  },
```

prebuild.jsはチームメンバがPRでコメントして実装できたので、ここでは割愛します（笑）

改善点としては、
- shellスクリプトではなくjavascriptでファイル操作
- ブランド指定をパラメータで制御
- 切り替えるファイルはシンボリックリンクを用いてGit管理上から外す（誤コミット防止）
    - 管理から外したのでpost処理（元に戻す）が不要に

となり、環境別にブランドロゴを切り替えるという要件を無事満たすことができました！


## おわりに
今回はNext.jsについて最近学んだ技術を中心に書かせていただきました。何かの参考になれば幸いです。
普段はこのほかにもAWSインフラをやったりスクラムの改善活動をやってますので、またそのテーマでも記事が書ければと思います！