✍️

kintone の型定義全部書く

2021/12/20に公開1

こんにちは。
今回は、kintone の型定義全部書こうと思います。

どういうことかと言いますと、kintoneオブジェクトの TypeScript 型定義を書いてエディタ上での開発体験を最高にしてやろうって話ですね。

ちなみにこの記事は、kintone アドベントカレンダー 2021 その2の19日目の記事です。

更新状況

  • 12/20: 1日遅れで初回投稿、未完成

kintoneオブジェクトと型定義

kintone には JavaScript や CSS を用いて UI をカスタマイズする機能があることは皆さんご存知かと思われます。
そういったカスタマイズをサポートするための機能として kitone JavaScript API というものが用意されており、利用する際にはグローバル変数として存在する kintoneオブジェクト経由で各 API にアクセスします。
現代においては、TypeScript を用いてカスタマイズ/プラグイン開発をされる方も多いと思われますが、ここで重要なのが型定義の存在です。

npm で配布されているパッケージであれば、API の型定義が同梱されていたり、あるいは Definitely Typed(いわゆる@types)で配布されていたり、大抵の場合何かしらの手段で型定義を用意することができます。
npm パッケージになっていない場合、例えば実行環境のグローバルに存在する API を利用する際にも、Node.js なら@types/node、chrome ブラウザ拡張なら@types/chromeなどのように、Definitely Typed に型定義が存在し、それを利用することができる場合もあります。DOM の API とかだとそもそも TypeScript 自体が定義を持ってたりしますね。

さてkintoneは後者のパターンですが、@types/kintoneは存在していないので、何らかの手段で型定義を用意してやる必要があります。手書きとか。

詳しい方なら@kintone/dts-genというツールをご存知かと思われます。
kintoneオブジェクトの型定義 + アプリに合わせた型定義の生成をしてくれるツールで、カスタマイズを TypeScript を用いて書く時に重宝している方も多いんじゃないでしょうか。[1]

また、REST API のリクエストの送信用にkintone.apiという API も存在していますが、TypeScript で書かれたクライアントである@kintone/rest-api-clientを利用することでdts-genを利用するときよりもさらに詳細な型定義を利用することも出来ます。

さて、型定義があることの利点とは何でしょうか。
もちろん大元の目的は静的型検査によるコードの安全性の担保だと思いますが、恐らく型推論による補完が実現する圧倒的開発体験が楽しくて、皆様 TypeScript を利用されてることかと思います。

今回の僕の目的は、kintoneの型定義を1から全部書くことで、自らの TypeScript 力を高めつつ、型推論によりバシバシ補完が効く世界に没頭し、そして大いなる kintone の恵みへの感謝を高める、まあそういう感じです。(?)

デモ

案ずるより産むが易し、百聞は一見に如かず、とにかく VSCode 上での補完の様子を見ていただきましょう。話はそれから。

リポジトリはこちら
https://github.com/shintaroNagata/kypes

kintone & types ということで、名前は kypes。意図せず sk◯pe っぽくなってしまいました。

現状 npm パッケージとしては公開していないので、README.md の手順にしたがってセットアップします。
この先yarnを利用しますが、お手元の環境に合わせてnpm等に読み替えてください。

まずはgit cloneyarn link

$ cd path/to/your/workspace
$ git clone https://github.com/shintaroNagata/kypes.git
$ cd kypes
$ yarn build
$ yarn link

そうしたら、適当なディレクトリを作成して、yarn link kypesします。

$ cd ../
$ mkdir test-kypes
$ cd test-kypes
$ yarn init -y
$ yarn link kypes

VSCode が賢すぎるのか、補完の動作見るだけなら僕の手元だと TypeScript なしでも動いてるんですが、一応設定しておきます。
執筆時点の最新バージョン(v4.5.4)で動作を確認しています。

$ yarn add -D typescript
$ yarn tsc --init

tsconfig.json は、補完を見るだけならとりあえず生成されたまま用いて問題ないです。

さて、実際の補完の様子を見てみましょう。
先ほど作成したディレクトリと同じ場所に適当な.tsファイルを作成し、先頭でパッケージを importします

test.ts
import "kypes"

これでkintoneに関する型定義が global に読み込まれます。
tsconfig.jsonに特別な記述をする必要はありません。

おもむろにkintone.と入力してみましょう。
kintone namespace 以下の API が補完される

はい、補完されそうですね。
API の返り値にも、もちろん補完が効きます。
kintone.getLoginUser() の返り値のプロパティアクセスに補完が効いてる

この辺りまでは@kintone/dts-genでも実現できる部分なので、もう少し複雑な例を見てみようと思います。

イベント処理

カスタマイズでよく使う API といえば、やはりkintone.events.onでしょうか。
第一引数はイベント名を入力しますが、もちろん補完が効きます。
kintone.events.on() でイベント名が補完される

イベントの種類に合わせて、イベントオブジェクトのプロパティも補完されます。
イベントオブジェクトへのプロパティアクセスが、イベントの種類に応じて補完される

複数のイベントに対するハンドラの登録でも、ちゃんと補完が効きます。
複数イベント登録時の補完

上の例では、両方のイベントに共通のプロパティが補完候補に出てきています。
うっかり片方にしかないプロパティにアクセスすると、ちゃんと怒られますね。
存在しないプロパティへのアクセスがエラーになる

そういう時にはevent.typeの値で分岐してあげれば、ちゃんと絞り込み型推論が適用されます。
event,type による絞り込み

イベント名自体の入力補完は効きませんが、changeイベントにもしっかり対応しています。
changeイベントでも補完が効いている

フィールドとかも補完されそうな雰囲気です。(フィールド名と対応させたりはまだ未実装ですが……)
フィールドのプロパティも補完される

REST API の実行

もう1つ、kintone.apiの例を見ていきましょう。
API パス、リクエストパラメータ、レスポンスのプロパティの補完などに対応しています。
API パスの補完
リクエストパラメータの補完
レスポンスへのプロパティアクセスの補完

地味なこだわりポイントとして、↑のように$id$revisionへの補完が効いたりします。

URL 指定の場合にも(もちろんkintone.api.urlを利用した場合にも)対応しています。
URL 指定で補完が効いてる

bulkRequest.jsonのような、比較的複雑な API でもちゃんと補完が効いてます。
bulkRequestでパス名の補完が効く
bulkRequestでリクエストパラメータの補完が効く

その他、紹介したいところは色々あるのですが、記事上では限界があるのでこのくらいにしておきます。
気になる方は色々触って試してみてください。

技術的な話

本来はここの話が本題なのですが、既に投稿遅れてしまっているので後日追って書きます……

今後

色々と書きましたが、正直まだまだ色々と不完全です。
「型定義全部書く」とかタイトルに書きましたが、全然全部書けてません。
型定義の沼は深く、そして kintone は壮大でした。

続きでやりたいことリストとしては

  • JSDoc対応
    • 型定義とは直接関係ないのですが、エディタ上の補完と同じくらい API の Usage が出ることって重要だと思うんです。知らんけど。
    • ちなみに現状でも一部の API は対応してます。
  • ページごとの型定義
    • ページの種類ごとに利用できる API って変わるので、その辺りを上手く解決したいなーと検討中
  • イベントハンドラの返り値の制約
    • Promise に対応してるかとか、return eventしてるかとか
    • あんまり調査できてないので実装に至らず。多分そんなに複雑じゃないので(フラグ)、やればできるんだろうなと思いつつ、少し様子見です。
  • 型定義の洗練
    • 単なるリファクタ目的というよりは、複雑な型をつけている関係上、エラーが出た時の可読性が怪しい。パッケージの性質上、エディタ上の表示こそが「機能」たりうるので、上手くその辺りのバランスを取った記述にしたい。
  • アプリごとの型定義への対応
    • 生成までは出来なくても、form/fields.jsonの返り値をローカルのファイルに持っておくと、そこから自動的に型判定される、とかやってみたいですね。
    • とはいえ今の型定義だと独自のアプリ構造を差し込む口がないので、設計を練り直さないと上手く行かなそう。

辺りです。引き続き、型定義全部書くまで頑張ろうと思います。

脚注
  1. 自分は普段カスタマイズというよりは、プラグインとかブラウザ拡張を作ることが多いので、実はあんまり使ったことがないです。 ↩︎

Discussion

The RedThe Red

これ凄いですね!
試してみましたけど、イベントハンドラの種類別にプロパティが変わるのとか見事です!感動しました!!

現状event.record(s)は汎用的なレコードオブジェクトしか入らないようですね。プラグイン開発にはとても良さそうですが、アプリの個別カスタマイズを想定すると、@kintone/dts-genで作った型をジェネリクスで渡せるようになると超ありがたいです。

ひとまずこんな風にすれば何とか行けますね。

const record = event.record as unknown as kintone.types.SavedFooFields

これ実務で普通に使いたいレベルなので、ぜひNPM公開してくださいませ!
というか、もうこれを@types/kintoneにしちゃいましょうよw