kintone と EDITROOM の機能連携の実例解説： 自作プラグインを利用した連携３―Vite と TypeScriptの導入

# 目次

- [目次](#目次)
- [はじめに](#はじめに)
- [作業](#作業)
  - [本記事中の作業範囲](#本記事中の作業範囲)
    - [どうして Vite を導入できるのか](#どうして-vite-を導入できるのか)
    - [なぜビルドツールを導入したいのか](#なぜビルドツールを導入したいのか)
  - [開発](#開発)
    - [1. Vite の導入](#1-vite-の導入)
    - [2. TypeScript の導入](#2-typescript-の導入)
      - [(opt.) kintoneオブジェクトの型推論を有効化](#opt-kintoneオブジェクトの型推論を有効化)
- [所感](#所感)
- [おわりに](#おわりに)

# はじめに

アップデイティットの毛利です。

前回に引き続き、弊社の「[EDITROOM](https://editroom.updateit.co.jp/)」と kintone の自作プラグインを繋いで動作させるために作業を進めていきます。

前回はこちら：  

https://qiita.com/update_it_inc/items/0dfdb40bfce392e1a32a

# 作業

## 本記事中の作業範囲

ひとまず、前回で API 発火までの実装までは進められたので、本記事では開発環境を少しモダンに手直ししてみます。

1.  Vite による開発環境を整備。
    -   TypeScript で開発できるようにする。
2.  TS -> JS のトランスパイラを経由して、config.js / desktop.js を吐かせる。
3.  自作プラグインを更新して、動作確認を行う。

### どうして Vite を導入できるのか

主に初心者向けの解説になりますが、環境を改修するにあたって、kintone プラグインファイル(dist/plugin.zip)が作成されるまでの挙動を理解しておきましょう。

まず、初期状態の src ディレクトリの構成が以下の通りでした。

```ShellScript:Tree
src
│  manifest.json
│  
├─css
│      51-modern-default.css
│      config.css
│      desktop.css
│      
├─html
│      config.html
│      
├─image
│      icon.png
│      
└─js
        config.js
        desktop.js
```

そして、 package.json のビルドコマンドは以下の記述になっていました。

```JSON:package.json
"build": "kintone-plugin-packer --ppk private.ppk --out dist/plugin.zip src",
```

この末尾にある「src」は、先ほどの src ディレクトリを示しています。  
つまり、src ディレクトリをターゲットに、manifest.json の内容から各データを抽出して zip 化しているわけです。

――であれば、「kintone-plugin-packer の実行先」を「Vite のビルド結果」に指定して、実行することも可能となります。  
もちろん、 Vite は適切な構成のアウトプットを生成する必要があるため、いくつかの改修作業が必要です（後述の作業）。

### なぜビルドツールを導入したいのか

これに関しては、開発効率の向上を始めいろいろメリットは出てきますが、今回は「TypeScript を導入して安全性を高めたい」というモチベーションが理由になります。

現状、ブラウザは TypeScript をそのまま実行できませんので、JavaScript に変換（トランスパイル）する必要があり、そのために何かしらのビルドツールを入れる必要が出てきます。  
（「なぜ Vite でなければならないのか？」とか「この記述量で TypeScript は不要じゃないか？」という疑問については、記事の存在意義を失うので忘れてください。）

TypeScript 導入によるメリット等々については、[kintone公式のリファレンス](https://developer.cybozu.io/hc/ja/articles/360023293091-TypeScriptでkintoneカスタマイズ開発をしてみよう)が存在します。併せてご確認ください。  
――ただし、公式リファレンスでは Webpack を使った手法を解説しているため、今回の解説とは内容の異なる箇所があります。

## 開発

### 1. Vite の導入

2022 年 11 月時点で、一番ノリに乗っている(主観)ビルドツール Vite を導入します。

@[card](https://vitejs.dev/)

Vite はテンプレートプロジェクトを呼び出すことができます。
[公式](https://vitejs.dev/guide/)を参考に、テンプレートプロジェクトを呼び出して、どのライブラリが依存しているか確認してみます。

```ShellScript
> npm create vite@latest vite_tmp_prj -- --template vanilla-ts
```

上記結果の package.json をみると、typescript と vite が入ってました。  
これであれば、競合も起こさずに前回まで使っていたプロジェクトに移植できます。

前回まで利用していた作業プロジェクトの package.json に対して、次の操作を実施します。

```ShellScript
> npm i -D vite typescript
```

問題なくインストールできたら、次にディレクトリ構成を変更します。  
Vite のstatic なリソースをコピーさせる指示方法の都合で、開発対象のファイルとそれ以外のファイルを別居させる必要があります。

次のように別居させます。

```ShellScript:Tree
...(中略)
├─src
│      config.js
│      desktop.js
│
└─static
    │  manifest.json
    │
    ├─css
    │      51-modern-default.css
    │      config.css
    │      desktop.css
    │
    ├─html
    │      config.html
    │
    ├─image
    │      icon.png
    │
    └─js （開発対象外があれば、こっちに置く。）
```

![pic_01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953427/5167a37d-d9a5-241a-7bc6-386cbed4afdf.jpeg)


次に、package.json に手を入れます。  
Vite でビルドした結果を kintone-plugin-packer に繋げるよう、以下の命名にします。

```JSON:package.json
{
  ...(中略)
  "scripts": {
    "start": "node scripts/npm-start.js",
    "develop_vite": "vite build --watch",
    "build": "vite build && kintone-plugin-packer --ppk private.ppk --out dist/plugin.zip dist/out",
    "develop_pack": "npm run pack -- --watch",
    "pack": "kintone-plugin-packer --ppk private.ppk --out dist/plugin.zip dist/out",
    ...(中略)
  },
  ...(中略)
}
```

"build" -> "pack" に変える際の末尾 src を dist/out に変えるのを忘れないようにしてください。  

次に、package.json の修正に対応させるため、scripts ディレクトリに入っている npm-start.js も修正します。

```JavaScript:npm-start.js
...(中略)
runAll(['develop_vite', 'develop_pack', 'upload'], {
...(中略)
```

次に、Vite のビルドコンフィグファイルを作成します。ディレクトリ直下に「vite.config.ts」というファイルを作成し、次のように追記してください。  
また、ビルドターゲットは ES5 ではなく ES2015(ES6) とします。

```TypeScript:vite.config.ts
import { defineConfig } from 'vite';
import path from 'path';

const root = `${process.cwd()}`;

// https://vitejs.dev/config/
export default defineConfig({
  root: root,
  publicDir: 'static',
  build: {
    target: 'es2015',
    outDir: `${path.resolve(__dirname)}/dist/out`,
    emptyOutDir: true,
    sourcemap: false,
    minify: 'esbuild',
    chunkSizeWarningLimit: 1000,
    reportCompressedSize: true,

    rollupOptions: {
      input: {
        desktop: `${path.resolve(root, 'src/desktop.js')}/`,
        config: `${path.resolve(root, 'src/config.js')}/`,
      },
      output: {
        manualChunks: {},
        entryFileNames: 'js/[name].js',
        assetFileNames: 'js/[name][extname]',
      },
    },
  },
  esbuild: {
    drop: ['console', 'debugger'],
  },
  plugins: [],
});
```

次に、TypeScript 用のコンフィグファイルを作成します。ディレクトリ直下に「tsconfig.json」というファイルを作成し、次のように追記してください。

```JSON:tsconfig.json
{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": true,
    "skipLibCheck": false,
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "newLine": "LF"
  },
  "include": ["src"]
}
```

最後に、開発中の言語仕様は ES5 である必要が全くなくなったため、eslint の設定を見直します。

```JavaScript:.eslintrc.js
module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: ['eslint:recommended'],
};
```

この状態で、最後にビルドを行い、動作を見てみます。

```ShellScript
> npm run build
```

問題なければ、デバッグ開発用の start コマンドの動作も見てみましょう。

```ShellScript
> npm run start
```

これで、Vite の導入が完了しました。

### 2. TypeScript の導入

さて、Vite の導入は成功しましたが、肝心の TypeScript の恩恵を受けられる状態になっていません。  
――なので、更に改修します。

まず、 Vite のコンフィグを修正します。

```TypeScript:vite.config.ts
rollupOptions: {
  input: {
    desktop: `${path.resolve(root, "src/desktop.ts")}/`,
    config: `${path.resolve(root, "src/config.ts")}/`,
  },
...(中略)
},
```

上記と同様に、実際の２ファイルについても拡張子を .ts に直します。  

-   desktop.ts
-   config.ts

一応、このままでもビルドは通りますが、ES5 に準拠した記法に縛られる必要もなくなったため、ソースコードに少し手を入れてみます。

```TypeScript:config.ts
((PLUGIN_ID: string) => {
  const elem_submit_list = document.getElementsByClassName("js-submit-settings");
  const elem_submit = elem_submit_list.item(0) as HTMLFormElement | undefined;
  const elem_api_url = document.getElementById("api_url") as HTMLInputElement | undefined;
  const elem_api_key = document.getElementById("api_key") as HTMLInputElement | undefined;
  if (!elem_submit || !elem_api_url || !elem_api_key) {
    throw new Error("Required elements do not exist.");
  }
  //
  const config = kintone.plugin.app.getConfig(PLUGIN_ID);
  if (config.api_url) elem_api_url.value = config.api_url;
  if (config.api_key) elem_api_key.value = config.api_key;
  //
  elem_submit.addEventListener("submit", (e) => {
    e.preventDefault();
    //
    kintone.plugin.app.setConfig({ api_url: elem_api_url.value, api_key: elem_api_key.value }, () => {
      alert("The plug-in settings have been saved. Please update the app!");
      window.location.href = "../../flow?app=" + kintone.app.getId();
    });
  });
})(`${kintone.$PLUGIN_ID}`);
```

```TypeScript:desktop.ts
((PLUGIN_ID: string) => {
  kintone.events.on("app.record.index.show", () => {
    const spaceElement = kintone.app.getHeaderSpaceElement();
    if (!spaceElement) throw new Error("The header element is unavailable on this page");
    //
    const fragment = document.createDocumentFragment();
    const elem_p = document.createElement("p");
    elem_p.classList.add("kintoneplugin-row");
    const elem_button = document.createElement("button");
    elem_button.innerHTML = "REST API FIRE";
    elem_button.classList.add("kintoneplugin-button-dialog-ok");
    elem_p.appendChild(elem_button);
    fragment.appendChild(elem_p);
    spaceElement.appendChild(fragment);
    //
    const config = kintone.plugin.app.getConfig(PLUGIN_ID);
    if (!config.api_url || !config.api_key) {
      elem_button.innerHTML = "REST API FIRE(DISABLED)";
      elem_button.disabled = true;
      return;
    }
    elem_button.addEventListener("click", (e) => {
      e.preventDefault();
      elem_button.disabled = true;
      //
      const header = { Accept: "application/json", "X-EDITROOM-API-KEY": config.api_key, "Content-Type": "application/json; charset=UTF-8" };
      const body = JSON.stringify({ docflow_name: "docflow0001", export_type: "html" }, null, 0);
      kintone.proxy(
        config.api_url,
        "POST",
        header,
        body,
        (body, status, headers) => {
          // success
          console.log(status, JSON.parse(body), headers);
          alert("sent.");
          elem_button.disabled = false;
        },
        (error) => {
          // error
          console.log(error); // proxy APIのレスポンスボディ(文字列)を表示
          alert("send error.");
        }
      );
    });
  });
})(`${kintone.$PLUGIN_ID}`);
```

上記ソースコードに対して、ビルドしてアップロードして、動作に遜色なければ TypeScript の導入も完了です。

#### (opt.) kintoneオブジェクトの型推論を有効化

TypeScript の導入で非常に便利になった一方で、オブジェクト「kintone」がグローバル展開されている状態に対して Warning が付いていることに気が付くでしょう。  

> 'kintone' is not defined. 

のようなエラーを eslint が吐いています。

これを解決するには、 kintone オブジェクトが何者なのか、型を適用する必要があります。  
一番は、公式が提供している「[@kintone/dts-gen](https://www.npmjs.com/package/@kintone/dts-gen)」を使う方法です。  

なのですが、使い方を見ての通り生成作業が必要で、今回のようなケースで気軽に使うには躊躇してしまいます。

そこで、世の中には素晴らしい OSS があるもので、この記事では「kypes」という型ライブラリを利用してみます。

https://www.npmjs.com/package/@shin-chan/kypes

使い方の通りに、インストールして、ソースコードに import してみましょう。

```ShellScript
npm i -D @shin-chan/kypes
```

```TypeScript:config.ts
import "@shin-chan/kypes";

..(以下、変更なし)
```

IDE を使っている場合、kintone オブジェクトに対する Warning がなくなり、かつ型補完が有効になったはずです。素晴らしい！

dts-gen でも、kypes でもそうですが、あくまで型を付けるだけのため、これを追加したことでビルド結果に大きな影響を与えることはないです。  
開発時の UX が向上するので、ぜひ導入していきましょう。

# 所感

今回、デモとして進めているプロジェクトの開発コード量は、思ったより TypeScript の恩恵は感じ取れないかもしれませんが、  
開発途中から TypeScript を導入するのと、最初から TypeScript が導入されているのとでは導入難易度に雲泥の差があります。

kintone プラグイン開発に限らず、TypeScript を導入できるのであれば導入することをおすすめします。

また、 Vite を利用できるようになったことで、Vite で開発できる React や Svelte を利用しやすくなりました。  
現状では、どうしても config.html を触る必要があるのと、desktop.js で DOM 操作をゴリゴリ書かないとならないため、  
次回は、フロントエンドフレームワークを導入して楽ができないか模索してみたいと思います。

# おわりに

再度の紹介となりますが、先日、弊社から「[EDITROOM](https://editroom.updateit.co.jp/)」という BtoB 向けの文書作成クラウドサービスをリリースしました。  
特に、定期的に文書を作る人的コストや作業にかかる拘束時間の長さを改善するのに、このサービスが大きな一助になると自負しております。

もし、ご興味がありましたら、トライアルをご利用いただければ幸いです。

## 次回

それぞれフレームワークを導入してみます。

https://qiita.com/update_it_inc/items/037440cdac0ea00fa323

https://qiita.com/update_it_inc/items/7310fb589f8e0c8a07c0