Vite + TypeScript + React で kintone のchrome拡張を開発する

## はじめに
本記事は [kintone Advent Calendar 2023](https://qiita.com/advent-calendar/2023/kintone) 19日目の記事です。

今回はkintoneカスタマイズをchrome拡張で作ったので、その環境構築編を書きます。

アドベントカレンダーの投稿は初なので、何か間違いを見つけたよ！こうしたら良いよ！というFBは随時コメントください。

##  対象者
この記事は下記のような人を対象にしています。

- kintoneカスタマイズをchrome拡張版で作りたい方
- Vite + typescript (+ react) で chrome拡張を作りたい方

## 目次
- [はじめに](#はじめに)
- [対象者](#対象者)
- [目次](#目次)
- [環境](#環境)
  - [Viteプロジェクトの作成](#viteプロジェクトの作成)
  - [パッケージのインストール](#パッケージのインストール)
    - [@crxjs/vite-plugin@beta](#crxjsvite-pluginbeta)
    - [@kintone/dts-gen](#kintonedts-gen)
    - [@types/chrome](#typeschrome)
  - [設定ファイルの編集](#設定ファイルの編集)
    - [tsconfig.json](#tsconfigjson)
    - [vite.config.ts](#viteconfigts)
  - [ディレクトリ構成の変更](#ディレクトリ構成の変更)
  - [ファイルの書き換え](#ファイルの書き換え)
  - [ビルド](#ビルド)
- [chrome拡張の適用](#chrome拡張の適用)
  - [自環境のchromeに拡張をアップロードする](#自環境のchromeに拡張をアップロードする)
  - [テスト](#テスト)
- [コーディング](#コーディング)
- [開発中の拡張機能チラ見せ](#開発中の拡張機能チラ見せ)
- [まとめ](#まとめ)
- [参考文献](#参考文献)

<!-- 各チャプター -->
<a id="#環境"></a>
## 環境
```shell-session
$ sw_vers
ProductName:		macOS
ProductVersion:		14.1
BuildVersion:		23B74

$ node -v
v18.14.2

$npm -v
9.5.0

# ここからは必要なし
$ bun -v
1.0.1
```
(今回の個人開発では bun.js というランタイムを使っていますが、npmで大丈夫です)

<a id="#Viteプロジェクトの作成"></a>
### Viteプロジェクトの作成
```shell-session
$ cd <任意のディレクトリ>

$ npm create vite@4.4.1
Need to install the following packages:
  create-vite@4.4.1
Ok to proceed? (y) y
✔ Project name: … <任意のプロジェクト名>
✔ Select a framework: › React
✔ Select a variant: › TypeScript

Scaffolding project in (指定したディレクトリ)...

Done. Now run:

  cd vite-project
  npm install
  npm run dev
```
※`vite@latest`で作成しない理由は後ほど説明します

作成できたら`npm install`を実行する

プロジェクト生成後のディレクトリ構造は以下の通りです。
```shell-session
$ tree 
.
├── README.md
├── index.html
├── node_modules
├── package-lock.json
├── package.json
├── public
│   └── vite.svg
├── src
│   ├── App.css
│   ├── App.tsx
│   ├── assets
│   ├── index.css
│   ├── main.tsx
│   └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
```

<a id="#パッケージのインストール"></a>
### パッケージのインストール
`@crxjs/vite-plugin@beta` と `@kintone/dts-gen` と `@types/chrome` をインストール

```shell-session
$ npm install --save-dev @crxjs/vite-plugin@beta @types/chrome @kintone/dts-gen
```

#### @crxjs/vite-plugin@beta
CRXJS Vite Plugin は、Vite を使ったchrome拡張開発に役立つパッケージです。迷わずインストールしましょう。

※プロジェクト作成において、`vite@latest`でなくてバージョンを指定しているのは、latestでインストールされるバージョンのメジャーが新しくなっていて(作成時点(2023年12月4日)ではv5.0.0)、CRXJSの対応が間に合っていないようです。chrome拡張の content_scripts を含めるとエラーが出てビルドできなかったです。メジャーが4の中でも`npm create vite@<version>`で作成できる最新のプロジェクトが4.4.1だったためバージョンを指定しています。

#### @kintone/dts-gen
TypeScriptを使用してkintoneカスタマイズを実装するにはあると便利。
詳しくはこちら([目指せ！JavaScript カスタマイズ中級者(5)〜TypeScript 編〜](https://cybozu.dev/ja/kintone/tips/development/customize/development-know-how/javascript-customize-middle-class-typescript/))

#### @types/chrome
Chrome の型を定義したパッケージchrome拡張を開発するときには欠かせない型定義。
オートコンプリートが効くようになるため、入れておきます。

<a id="#設定ファイルの編集"></a>
### 設定ファイルの編集
#### tsconfig.json
下記を追加
```tsconfig.json
"files": [
  "./node_modules/@kintone/dts-gen/kintone.d.ts"
]
```

#### vite.config.ts
```vite.config.ts
import { crx, defineManifest } from '@crxjs/vite-plugin';
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

const manifest = defineManifest({
  manifest_version: 3,
  description: 'example chrome extension',
  name: 'example',
  version: '0.1.0',
});

export default defineConfig({
  plugins: [react(), crx({ manifest })],
  server: {
    port: 5174,
    strictPort: true,
    hmr: {
      port: 5174,
    },
  },
});
```

<a id="#ディレクトリ構成の変更"></a>
### ディレクトリ構成の変更
chrome拡張は大きく分けて三つのjsファイルで構成されます。
そのためsrcフォルダにその三つのディレクトリを作成して管理するような構成します。

**追加**
```shell-session
$ mkdir src/popup
$ mkdir src/content
$ mkdir src/background
$ touch src/content/content.ts
$ touch src/background/background.ts

$ mkdir icons
```

iconsというディレクトリを作成しましたが、chrome拡張のアイコン用です。
特に絶対ではないのですが、今回はわかりやすくchrome拡張の設定のためのディレクトリとして独立させています。

今回はなんとなく歯車のアイコンをフリーでDLしてきましたが、なんでもOKです。

**移動**
```shell-session
$ mv src/assets src/popup/
$ mv src/App.css src/popup/
$ mv src/App.tsx src/popup/
$ mv src/index.css src/popup/
$ mv src/main.tsx src/popup/
$ mv index.html src/popup/
$ mv public/vite.svg src/popup/assets
```

**削除**
```shell-session
$ rm -rf public
```

今の構造はこんな感じです。

```shell-session
$ tree 
.
├── README.md
├── dist
├── icons
│   ├── icon.png
├── package-lock.json
├── package.json
├── src
│   ├── background
│   │   └── background.ts
│   ├── content
│   │   └── content.ts
│   ├── popup
│   │   ├── App.css
│   │   ├── App.tsx
│   │   ├── assets
│   │   ├── index.css
│   │   ├── index.html
│   │   └── main.tsx
│   └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
```

<a id="#ファイルの書き換え"></a>
### ファイルの書き換え
```vite.config.ts
const manifest = defineManifest({
  manifest_version: 3,
  description: 'example chrome extension',
  name: 'example',
  version: '0.1.0',
  + action: {
  +   default_icon: 'icons/icon.png',
  +   default_title: 'example',
  +   default_popup: 'src/popup/index.html',
  + },
});
```

アクションを設定することでchrome拡張のアイコンを押したらポップアップ画面(index.html)がでるようになります。

```diff_src/popup/App.tsx
- import viteLogo from '/vite.svg'
+ import viteLogo from './assets/vite.svg'
```
```diff_src/popup/index.html
- <link rel="icon" type="image/svg+xml" href="vite.svg" />
+ <link rel="icon" type="image/svg+xml" href="./assets/vite.svg" />

- <script type="module" src="src/main.tsx"></script>
+ <script type="module" src="main.tsx"></script>
```

<a id="#ビルド"></a>
### ビルド
```shell-session
$ npm run build
```

built in なんとか ms がでればOKです！
上記の処理で何かが抜けてたらエラーが出ますが、焦らず解決してください。

ビルドできると、プロジェクトのルートに dist というディレクトリが作成されます。
distの中身がchrome拡張の中身になっています。これを後にchromeに適用することでchromeを使えるようになります。

<a id="#chrome拡張の適用"></a>
## chrome拡張の適用
<a id="#自環境のchromeに拡張をアップロードする"></a>
### 自環境のchromeに拡張をアップロードする
今回は、作成されたdistをchromeにアップロードします。

自作のchrome拡張のアップロードの仕方は公式サイトを参考にしてみてください！

[カスタムの Chrome アプリと拡張機能を作成して公開する](https://support.google.com/chrome/a/answer/2714278?hl=ja#:~:text=%E6%89%8B%E9%A0%86%202%3A%20%E3%82%A2%E3%83%97%E3%83%AA%E3%82%84%E6%8B%A1%E5%BC%B5%E6%A9%9F%E8%83%BD%E3%82%92%E3%83%86%E3%82%B9%E3%83%88%E3%81%99%E3%82%8B)

<a id="#テスト"></a>
### テスト
動画のようにpopupで初期画面を出すことができました。
![test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949201/330ef6ae-879c-3a65-c48c-749a0abf6192.gif)


<a id="#コーディング"></a>
## コーディング
それでは次に kintone カスタマイズです。
今回は環境構築編なので REST API を動かすまでにしておきます。

chrome拡張で kintone カスタマイズを作る際には、やりたいことによって大きく構成が変わります。

重要なこととしては、content/background/popup の3つの中で content 以外は `chrome-extension://` のようなドメインになるため、**REST APIを実行したい時にCORSで引っかかります。**

REST APIを実行させたい場合は、 kintone を表示させたタブ(つまり同一ドメイン)で実行することが肝になります。そこで登場するのは content script です。

content.js は拡張機能が呼び出されたタブ、今回でいうと kintone の画面で呼び出すため、ドメインが同一ドメインとなり、REST API がCORSで引っかかることがなくなります。

content script をchrome拡張に組み込みたい場合はvite.config.tsに次の内容を加え、manifestに定義する必要があります。

```vite.config.ts
const manifest = defineManifest({
  manifest_version: 3,
  description: 'example chrome extension',
  name: 'example',
  version: '0.1.0',
  action: {
    default_icon: 'icons/icon.png',
    default_title: 'example',
    default_popup: 'src/popup/index.html',
  },
  + content_scripts: [
  +   {
  +     matches: ['https://*.cybozu.com/k/*'],
  +     js: ['src/content/content.ts'],
  +     run_at: 'document_end',
  +   },
  + ],
});
```

ちなみに matches は content_scripts に必須の項目で、content scripts が動作する対象を URL で指定します。ワイルドカードを使うと便利です。
run_at は script をいつ走らせるかという名前の通りの項目ですが、必須ではありません。

今回はcontent scripts に REST API CLIENT を使用して適当なアプリから一件レコードを取得してみます。

```src/content.ts
import { KintoneRestAPIClient } from '@kintone/rest-api-client';
{
  const resp = await new KintoneRestAPIClient({}).record
    .getRecord({
      app: <任意のアプリ>,
      id: <任意のレコードID>,
    })
    .then((resp) => resp)
    .catch((err) => {
      console.log(err);
    });
  console.log(resp);
}

export {};
```

このようにコンソール上で実行したレスポンスを確認できました！

![rest.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949201/97309db8-0666-6828-1617-beb9677aea8d.png)

実際の開発では、content script ⇄ popup　等でやりとりできるAPIがあるため、popup側に取得してきたデータを渡したい場合は chrome API でやりとりすることでpopup側で取得してきたデータを扱うことができます。

<a id="#開発中の拡張機能チラ見せ"></a>
## 開発中の拡張機能チラ見せ
今回は環境構築と簡単な REST API の実行だけでしたが、下記の動画のようなものを開発しています。
後日別記事にします。

内容は、めんどくさい kintone のレコードオブジェクトを簡単に作成する拡張機能です。
![kin-obj.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949201/d222c1d7-fc5b-a53d-3dde-3abddd9757ef.gif)


<a id="#まとめ"></a>
## まとめ
- Vite + TypeScript + React で kintone カスタマイズするchrome拡張開発環境を準備しました。
- 簡単な kintone REST API を実行できるまでの環境を作成できました。

<a id="#reference"></a>
## 参考文献
- [Chrome Extensions を Vite + TypeScript で開発する](https://blog.chick-p.work/blog/chrome-extention-with-vite/)
- [chrome extension Reference](https://developer.chrome.com/docs/extensions/reference?hl=ja)