Chrome拡張機能をTypescriptで作ってみた

# はじめに
皆さんは`マジカルミライ`をご存知でしょうか。

https://magicalmirai.com/

初音ミクのライブなわけですが、このチケットを応募する際に面倒な点がありました。

**個人情報を応募するたびに入力しなければいけない点です！！！**

マジカルミライ2023は大阪、東京合わせて12公演あるので、
もし、すべてに応募する場合、12回入力する必要が出てきます。

ということで、今回はこの個人情報を入力する部分を`Chrome拡張機能`を利用して、
自動入力してもらおうと思います。

ただChrome拡張機能作っても面白くないので、Typescriptで作成し、Github Actionsで自動Build&Releaseまで目指します。

今回作成した成果物

https://github.com/y-inoue15/AutoFormInput

<details><summary>念のため</summary>

自動入力を目的に作り始めますが、実際のソースの解説は今回しません。
フォームの内容をlocalstorage に記録し、次回以降localstorage　から呼び出せるようにする感じです。
どこかで別の記事にはするかもしれませんが…
また、botと同じ扱いとなる可能性があります。
今回作成した拡張機能の使用は自己責任でお願いします。
</details>



# Typescript でChrome 拡張機能の雛形作成
まずプロジェクトフォルダを作成して、npmの初期設定をしていきます。
コマンドラインでpackage.jsonを作成することができます。

## package.json の作成
```
npm init
```

package.jsonの中身はよしなに必要事項を書いてください。
この後buildコマンドをここに記載していきます。

## Typescript の導入
今回はTypescriptで作成するので、Typescriptを導入していきます。
Chromeの型定義も一緒に導入します。

```
npm install -D typescript @types/chrome
```

tsconfig.jsonを作成します。
下記コマンドで初期化できますが、ファイルを作成しても問題ありません。
```
npx tsc --init
```

ビルドツールを使用する予定なので、tscによるトランスパイルは行わない設定にしていきます。
ただ、型チェックは引き続き行いたいので、"outDir"は入れず、"noEmit"をオプションに追加します。

```tsconfig.json
{
  "compilerOptions": {
    "target": "ESNext",
    "lib": ["ESNext", "DOM", "DOM.Iterable"],
    "useDefineForClassFields": true,
    "module": "ESNext",
    "rootDir": "./src",
    "moduleResolution": "node",
    "baseUrl": "src",
    "resolveJsonModule": true,
    "allowJs": true,
    "checkJs": true,
    "removeComments": true,
    "esModuleInterop": true,
    "strict": true,
    "noImplicitAny": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "skipLibCheck": true,
    "noEmit": true,
    "isolatedModules": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist", "./tsconfig.json"]
}

```


## Vite の導入
Typescriptなので、ビルドを行い、Javascriptにしていく必要があります。
webpackも検討しましたが、思ったより簡単にできなかったので見送りました。
実行時間もViteのほうが早いようなので、今回は様々あるビルドツールの中でViteを採用していきます。

https://ja.vitejs.dev/

```
npm i -D vite
```

導入が終わったら設定ファイルを書いていきます。
rootディレクトリやoutput先、inputするファイルなどを設定します。

```vite.config.js
import { resolve } from 'node:path'
import { defineConfig } from 'vite'

export default defineConfig((opt) => {
  return {
    root: 'src',
    build: {
      outDir: '../dist',
      rollupOptions: {
        input: {
          index: resolve(__dirname, 'src/index.ts'),
        },
        output: {
          entryFileNames: '[name].js',
        },
      },
    },
  }
})

```

## manifest.jsonの作成
これで基本的な設定が整いました。
あとはChrome拡張に必要なmanifest.json とindex.ts を作成するだけです。
今回はva.pia.jp に適用できるよう設定していきます。
また、出力されるファイルはindex.js となるため、content_scripts にindex.jsを読み込むよう設定していきます。
manifest.json はsrc/public に作成していきます。

```src/public/manifest.json
{
  "manifest_version": 3,
  "name": "Auto Form Input",
  "description": "Base Level Extension",
  "version": "1.0",
  "content_scripts": [
    {
      "matches": ["http://va.pia.jp/*", "https://va.pia.jp/*"],
      "js": ["index.js"],
      "css": ["global.css"]
    }
  ]
}

```

## package.json の修正
最後にビルドを行うために、package.json のscript に以下を追加します。
tsc を行った後、vite のbuildを行います。
vite のbuild を行う際に、outdirが空でない場合、エラーが発生しますが、emptyOutDirオプションをつけてあげることで、エラーを発生させないようにできます。
今回は空でなくてもビルドできるよう、オプションをつけていきます。

```package.json
"build": "tsc && vite build --emptyOutDir"
```

## Typescript でChrome 拡張機能の雛形完成

これで、Typescript でChrome 拡張機能を作成するひな形が完成しました。
あとはsrc フォルダ内にindex.ts ファイルを作成し、書き始めるだけです！

# Github リリースノートを自動作成する
皆さんご存じの通り、Github はGithub Actions が存在します。
このGithub Actions を利用して、Chrome 拡張機能をbuild して、Github のリリースノートにbuild した成果物を掲載したいと思います。

## release.yml を作成する
.github/workflows フォルダにymlファイルを作成します。
今回はリリースノートには特に記載せず、build を行った日時のzipファイルが生成されるように設定しました。
また、発火するタイミングはmain ブランチにpush されたタイミングとしました。
yml ファイルの全体像は以下の通りです。

```release.yml
name: build and release

on:
  push:
    branches: [main]

jobs:
  release:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.step_version.outputs.version }}
      upload_url: ${{ steps.step_upload_url.outputs.upload_url }}
    steps:
      - name: Generate release tag
        id: release_tag
        env:
          TZ: 'Asia/Tokyo'
        run: echo "nowDATE=$(date +'%Y-%m-%d_%H:%M:%S')" >> $GITHUB_ENV

      - uses: actions/checkout@v3
      - name: ci
        run: npm ci
      - name: Build
        run: npm run build
      - name: Zip output
        run: zip -r ${{ env.nowDATE }}.zip dist

      - name: Create Release
        id: create_release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: Release
          draft: false
          prerelease: false

      - name: Upload Release Asset
        id: upload-release-asset
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ./${{ env.nowDATE }}.zip
          asset_name: ${{ env.nowDATE }}.zip
          asset_content_type: application/zip

```

## 各yml ファイルの説明

少しだけですが、何を行っているか説明しようと思います。

```yml
      - name: Generate release tag
        id: release_tag
        env:
          TZ: 'Asia/Tokyo'
        run: echo "nowDATE=$(date +'%Y-%m-%d_%H:%M:%S')" >> $GITHUB_ENV
```

リリースファイルのための日時を作成しています。
シンプルにdateをnowDate としてGITHUB_ENV に残しています。

```yml
      - uses: actions/checkout@v3
      - name: ci
        run: npm ci
      - name: Build
        run: npm run build
      - name: Zip output
        run: zip -r ${{ env.nowDATE }}.zip dist
```

リリースノートを作成します。
各項目を記載しますが、今回はtag をブランチ名にし、そのほかは特に設定しないことにしました。

```yml
      - name: Create Release
        id: create_release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: Release
          draft: false
          prerelease: false
```

build された成果物をリリースノートにアップロードします。
パスと名前は日付にしているため、追記した環境変数から取得するようにしています。

```yml
      - name: Upload Release Asset
        id: upload-release-asset
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ./${{ env.nowDATE }}.zip
          asset_name: ${{ env.nowDATE }}.zip
          asset_content_type: application/zip
```

これで自動的にmain ブランチにpush されるとリリースされるようになりました！

# 感想
今回はChrome の拡張機能をTypescript で作成しましたが、実際ここまでする必要はなんだろうなぁという気持ちはあります。
ただ、React を今まで使ってこなかった&Next.js などはすでにTypescript のビルドが含まれていたりと、考慮することがなかったので良い経験になりました。
また、自動でリリースノートを作成するワークフローも作成してみたかったので、勉強になりました。
何かの参考になれば幸いです。

# 参考
https://note.com/touch_wkm/n/n86125777c004

https://tech.revcomm.co.jp/build-chrome-extension-with-typescript

https://zenn.dev/takiko/articles/827c182638eb3b

https://qiita.com/nanana08/items/297569fe7d704b5ac1c2