開発フレームワーク「Goqoo on kintone」を正式リリースしました！

# はじめに

kintoneプログラマーの赤座です！
クリスマスイブにアドベントカレンダー書かせていただきます :santa: 

思い起こせば3年前、2018年11月にkintone hack NIGHTに登壇して、kintoneカスタマイズ用の開発フレームワーク「Goqoo on kintone」を紹介したのでした。

順位はブービーでしたが、アスキーさんの記事でじっくり触れていただきました。

https://ascii.jp/elem/000/001/775/1775872/3/

スライドはコチラ。

https://www.slideshare.net/HisakiAkaza/kintone-hack-2018-122292090

この時点でGoqooはまだまだ荒削りの超ベータ版で、プレゼンも「自分語り」が中心でした。
どんどん洗練させていく予定でいたのですが、翌年に三人目の娘が生まれてから、とにかく本業と子育てだけで必死で、Goqooの開発はずっと止まってしまっておりました。
その間に時は流れ、今やTypeScriptが当たり前の世界となっております。

三女が二歳になった今年、ようやく時間的にも余裕が戻ってきて、夏頃からコツコツとGoqooの改修を進めてきました。最後の一歩、自分を追い込むためにkintoneアドベントカレンダーに登録し、何とか間に合いました。

**本日ここに、Goqoo on kintone v1.0、正式版をリリースさせていただきます！！！**

https://github.com/goqoo-on-kintone/goqoo

# Goqoo on kintoneとは？

kintoneを自由自在に乗りこなすためのツール、Goqoo(悟空)であります。
JS/TSプログラミング、コマンドライン/Node.js/Gitなどに精通している人のための開発フレームワークで、プログラミング初心者の利用は想定していません。

とにかく「自分がトコトン使いやすいように」というオレオレ視点でチューニングしてありますw

# Getting Started

## プロジェクト作成

まずはコマンド一発、これだけ叩いてみてください。

```shell
$ npx goqoo new プロジェクト名
```

プロンプトが出るので、Project name、descriptionを任意で設定します。
カスタマイズビューを作りたいときは、Frontend FrameworkにReact/Vueのどちらかを選ぶこともできます。

```shell
? Project name › プロジェクト名
? Project description › 
? Frontend Framework … 
❯ (None)
  React
  Vue
```

自動的にフォルダが作成され、テンプレートのコピーと各種ライブラリのインストールが走ります。

ちなみに内部ではSAOというScaffoldライブラリを使っています。（0.x系で使っていたYeomanはやめました）

https://github.com/saojs/sao

## 初回コミット

作成されたディレクトリに移動します。`git init`は済んでいるので、一度`git commit`をしておきます。
（一度もコミットをしていない状態ではGoqooは正しく動きません）

```shell
$ cd my-project
$ git add .
$ git commit -m 'Initial commit'
```

## アプリのエントリ作成

```shell-session
$ npx goqoo generate app my-app
```

これで`src/apps/my-app`のようにアプリ名フォルダが作成され、ビルド対象となります。

## 開発ビルド（webpack-dev-server）

Goqooによる開発は、webpack-dev-serverをローカルで立ち上げて、localhostでホスティングされたjsファイルをkintoneに読み込ませるのが基本になります。

```shell
$ npx goqoo start

{ bundlerType: 'default', nodeEnv: 'development' }
{ mode: 'development' }
{ env: { WEBPACK_SERVE: true } }
{ 'my-app': 'https://localhost:59000/my-app.js' }
```

コンソールに表示されたURL（ここでは`https://localhost:59000/my-app.js`)を、kintoneアプリのJavaScriptカスタマイズURLとして設定します。 
「アプリを更新」すると、いかがでしょう・・・？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24333/39a6cf22-f416-1e82-a740-9a4cd1a49ec5.png)

「オッス、オラ悟空!」と挨拶してくれますね！
これで初回のデプロイは成功です :tada: 

これで、プログラムを修正するたびにkintoneが自動リロードして、サクサクと開発が進みます。
kintone側のJSを入れ替える必要はありません。

localhostなので、他のPCでkintoneにアクセスした場合は何も起きません。
逆に言うと、複数人でプログラムの共同開発はしやすくなります。

## 開発ビルド（ファイル）

```shell
$ npx goqoo build
```

`development`モードでビルドして`dist`配下にファイルが作成されます。
一応機能としては用意していますが、そんなに使わないかもしれません。

## 本番ビルド

```shell
$ npx goqoo release
```

`production`モードでビルドして`dist`配下にファイルが作成されます。
圧縮などされるので、本番リリース時にはこの方法でビルドしてください。
ビルド済みファイルをkintoneにアップロードすれば、デプロイ完了です。

ちなみに、Goqoo 0.x系ではkintoneに自動的にJS URLをデプロイする機能がありましたが、事故を招きやすい仕様なので一旦廃止しました。（既存のJSを全部消しちゃってたのです・・・）
安全かつ使いやすい仕組みが考えられたらまた復活するかもしれません。

サイボウズさん公式の[kintone-customize-uploader
](https://github.com/kintone/js-sdk/tree/master/packages/customize-uploader)は、使いにくいので採用してません。やるなら自分で仕組み作る予定です。

# もう少し詳しく

## フォルダ構成

こんな感じ。

```
.
├── .env.example
├── .eslintrc.js
├── .git/
├── .gitignore
├── .prettierignore
├── .prettierrc.yaml
├── README.md
├── babel.config.js
├── dts/
├── goqoo.config.js
├── goqoo.config.types.ts
├── jest.config.js
├── node_modules/
├── package.json
├── src
│   ├── apps/
│   │   ├── ***
│   │   │   ├── ***.ts
│   │   │   └── index.ts
│   │   └── ***
│   │       ├── ***.ts
│   │       └── index.ts
│   ├── context.ts
│   ├── example.ts
│   └── types.ts
├── test
│   ├── ***.test.ts
│   ├── ***.test.ts
│   ├── example.test.ts
│   └── jest.ignore.js
└── tsconfig.json
```


一番メインは`src`配下。ここにkintoneカスタマイズ用のコードを書いていきます。

`goqoo generate app`すると`src/apps`に自動的にフォルダが作られましたが、
`src/apps/***/index.[ts/js]`が見つかったら全部ビルド対象になるCoCな仕様です。
ジェネレータを使わずに、手動でフォルダ・ファイルを作っても動きます。

## index.tsについて

`goqoo generate app`直後はこうなってます。

```ts:index.ts
import { goqoo } from 'goqoo'

goqoo('app', () => {
  require('./hello')
})
```

`goqoo()`という関数のコールバック内で、別モジュールを`require()`しています。
こんな感じで`hello.ts`と同列に色んなモジュールを作って、
すべて`index.ts`内で`require()`するように書いてください。[^1]

[^1]: Dynamic Importでなくてrequireなのは、苦肉の策です。。。Dynamic Importを使っちゃうと、本番ビルド時にファイルが複数に分かれちゃうので、kintoneにデプロイする場合には不向きなんですよね。

`goqoo()`関数を挟む理由ですが、以下の２つの機能を提供するためです。
関数の内部でゴニョゴニョっとやっているわけですね。

* dev-server向けのlocalhost URLと、ビルドしたファイルを両方kintoneにアップしても動くようにする
    * dev-serverが止まっていればファイルの方を使う
    * ![CleanShot 2021-12-24 at 01.57.28@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24333/1bea8676-e3a5-36ef-bb8a-5cf01c3a6294.png)
* `window.__devinfo__`というオブジェクトに開発情報を入れて、デバッグコンソールから参照可能にする
    * アプリ別に分けてデプロイすることもあるので、コミットが特定できるとメンテも安心！
    * ![CleanShot 2021-12-24 at 01.58.55@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24333/d91e0040-965f-7f5d-07e0-4f11ffd75608.png)


どうです？超嬉しくないすか？？？
この嬉しさが分かるなら、kintoneプログラマーとして本物だと言えるでしょうｗ


## 便利アラート関数

ジェネレータで生成されたモジュール内で、
`helloGoqoo`という関数をimportしています。

```ts:hello.ts
import { helloGoqoo } from 'goqoo'
import type { IndexEvent } from 'types'

kintone.events.on('app.record.index.show', async (event: IndexEvent<any /* kintone.types.SavedXxxxFields */>) => {
  await helloGoqoo()
  return event
})
```

コレ自体はどうでも良いサンプル関数なのですが、
他にも便利アラート関数がいくつかあるので紹介しておきます。

* `import { confirmDialog } from 'goqoo'`
    * 「よろしいですか？」のような確認ダイアログを表示
    * OKで確定すると、ダイアログが消えずにそのままSpinnerになる
    * 完了後に新しいアラートを出すと消える
* `import { successDialog } from 'goqoo'`
    * 「成功しました！」みたいなダイアログ
    * confirmDialog() → successDialog()の流れで使う
* `import { errorDialog } from 'goqoo'`
    * エラー表示用のダイアログ
    * confirmDialog() → errorDialog()の流れで使っても、他の場面で使ってもOK
    * エラーの中身をクリップボードにコピーする機能もある

引数や戻り地などは、TypeScriptで型定義を見てもらうのが早いと思います。
すべて[SweetAlert](https://sweetalert.js.org/guides/)をラップしたものです。[^2]

[^2]: [SweetAlert2](https://sweetalert2.github.io/)ではなく、元祖[SweetAlert](https://sweetalert.js.org/guides/)です。Swal2の方はEasyだけどカスタマイズ性が悪いので好きではありません。

## 設定ファイル

`goqoo.config.js`という設定ファイルがプロジェクトルートにあり、
`goqoo.config.types.ts`という型定義専用のTSファイルとセットになっています。

まずは型定義ファイルの編集から。

```ts:goqoo.config.types.ts
import type { Config as _Config } from 'goqoo'

export type Env = 'development' // | 'staging' | 'production'

export type AppId = {
  project: number // 案件管理
  customer: number // 顧客管理
  sales_activity: number // 活動履歴
}

export type Context = {
  env: Env
  host: string
  appId: AppId
}

export type Config = _Config<Env, Context>
```

* Env: 文字列Union型で、開発環境・ステージング環境・本番環境などの各種環境名を列挙
* AppId: アプリ名をキーにしたオブジェクト型を定義
    * 直接JSカスタマイズを仕込まないアプリでも、API経由でリクエストを投げる可能性があれば列挙しておく
    * デフォルトでは、サンプルとしてkintoneアプリストアの「営業支援パック」を想定した３種類のアプリ名を登録済み。
これは削除して、自分なりのアプリ名をつける。
* Context: 型定義は特に触らなくて良い
* Config: 型定義は特に触らなくて良い

これを踏まえて、`goqoo.config.js`を編集します。

```js:goqoo.config.js
// @ts-check

/**
 * @type {import('./goqoo.config.types').Config}
 */
const config = {
  bundlerType: 'default',
  dtsGen: {
    env: 'development',
    // skip: ['customer'],
  },
  environments: [
    {
      env: 'development',
      host: 'example.cybozu.com',
      appId: {
        project: 0,
        customer: 0,
        sales_activity: 0,
      },
    },
    // {
    //   env: 'staging',
    //   host: '...',
    //   appId: { ... },
    // },
    // {
    //   env: 'production',
    //   host: '...',
    //   appId: { ... },
    // },
  ],
}
module.exports = config
```

冒頭の`@type {import('./goqoo.config.types').Config}`がポイント。
この設定ファイルは色んなところから読み込ませやすいように`.js`にしてるんですが、
JSDocでゴリゴリ型定義を書くのは苦行でしか無いので、
隣に「型定義専用の`.ts`ファイル」を置いて、importだけしてやるのです。
このパターンは意外といろんなところで使えるのでオススメ！

`goqoo.config.js`を設定しておくと、大きく2つの恩恵があります。

* 開発・本番環境で、APIで扱うアプリIDを自動切り替えできる
* dtsファイルを自動生成できる

## APIで扱うアプリID

以前僕が書いたこの記事

kintoneの本番環境・開発環境で異なるアプリIDを自動判定する方法
https://qiita.com/the_red/items/b2832b5cd8325b97a989

この機能を、フレームワーク側に取り込みました。

`goqoo.config.js`で環境別にアプリIDを定義した上で、
`src/context.ts`というモジュールをインポートして使います。
ちなみに`src`直下を直接インポートできるように`tsconfig.json`を設定済みです。

```ts
import { KintoneRestAPIClient } from '@kintone/rest-api-client'
import { context } from 'context'

const client = new KintoneRestAPIClient()
kintone.events.on('app.record.create.submit.success', async (event) => {
  const 顧客名 = event.record.顧客名.value
  client.record.addRecord({
    app: context.appId.customer, // ❗❗❗注目❗❗❗
    record: {
      顧客名: { value: 顧客名 },
    },
  })
  return event
})
```

`context.ts`の中身は割愛しますが、この中で開発・本番環境の判別をしてくれるので、
使う側は`context.appId.xxx`のように書くだけで良いのです。
そしてそして、`appId.`までエディタで打ち込んだら、
`goqoo.config.js`に定義済みのアプリ名を入力補完してくれちゃいます。素晴らしい。
![CleanShot 2021-12-24 at 03.05.35@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24333/8de9acfa-dc02-8fae-7889-c49c38ef8a4a.png)



## dtsファイルの自動生成

[@kintone/dts-gen](https://github.com/kintone/js-sdk/tree/master/packages/dts-gen)という神ツールがあります。
これはまじで素晴らしいんですが、複数アプリの型定義ファイルを一括で作る機能がないんですよね。
なので、Goqooでラップしました！

使えるようにするために、いくつかステップがあります。

### goqoo.config.js

先程の`goqoo.config.js`の解説追加。
アプリIDは定義済みの前提で、さらに`dtsGen`プロパティに注目。

```js
const config = {
  bundlerType: 'default',
  dtsGen: {
    env: 'development',
    // skip: ['customer'],
  },
```

`env`プロパティで、「どの環境のアプリをdtsファイル化するか」を１つだけ選びます。
ほとんどの場合はデフォルトの`development`環境で良いでしょう。

`skip`プロパティは、「アプリID定義はしたけど、dtsファイル化したくない」ものがあれば列挙します。
デフォルトはコメントアウトしてあるので、そのままでOK

### .env

kintoneのログイン情報を`.env`に書いておきます。[^3]
[^3]: 本当は、プレーンテキストの.envにクレデンシャル情報を書きたくない。OAuth対応そのうちやります。
`.env`はGitコミットされないように設定済み。
サンプルファイルがあるので、それをコピーして編集が便利です。

```shell
$ cp .env.example .env
# .envを編集、GOQOO_USERNAMEとGOQOO_PASSWORDを設定
```

### 生成

設定ファイルを書いてしまえばあとは簡単！

```shell
$ npx goqoo generate dts
```

これで、`dts`フォルダ配下にずらーっと`my-app-fields.d.ts`のようなファイルが出来ていきます。

### 使い方

`src/types.ts`というファイル内に、便利な型関数を用意してあります。
`hello.ts`内で既に使われているので、そこを書き換えてみましょう。

```diff
 import { helloGoqoo } from 'goqoo'
 import type { IndexEvent } from 'types'
 
-kintone.events.on('app.record.index.show', async (event: IndexEvent<any /* kintone.types.SavedXxxxFields */>) => {
+kintone.events.on('app.record.index.show', async (event: IndexEvent<kintone.types.SavedMyAppFields>) => {
   await helloGoqoo()
   return event
 })
```

ここまでやっておくと、`event.record(s)`オブジェクトの中身に実際のアプリの型が反映されるので、
フィールドコードのtypoチェックとか、入力補完とか、いろんな恩恵が受けられます。

開発途中でkintone側のフィールド追加・フィールドコードを変更などしたら、
その都度`goqoo generate dts`を叩いて更新しましょう。
フィールドコードが変わっていたら、影響がある場所はエラー出してくれて、
kintoneアプリ側のリファクタも安心して行うことができます。

# ほかにも・・・

* Jestでテスト書くための準備も整ってるよ(example.test.ts)
* `goqoo generate`は`goqoo g`と省略できるよ
* `goqoo start`は`goqoo s`と省略できるよ
* `npm run generate` `yarn generate`みたいなscriptsも使えるよ
* .envにAWSのアクセスキーを書くと、`goqoo build:s3`でS3にJSを自動アップできるよ
* ESLintの設定めんどくさいので`eslint-config-goqoo`内に丸っとまとめてるよ

至れり尽くせり、個人的にとっても便利な機能満載でございます！
だって、自分のために自分で作ったんだからね！！！ :v: 

# おわりに

公式ドキュメント書くには英語とかもふくめて相当気を使うのですが、
Qiita記事であれば、これくらいの書きなぐり口語体でも許されるかなと思いまして、
とにかく勢いに任せてガーッと雑に書きました！

今回の記事は、記事そのものではなくて、
あくまで「Goqoo on kintone」というフレームワーク自体に価値があると思っております。

アドベントカレンダーに合わせてリリースするモチベーションにはなりましたが、
アドベントカレンダーのために作ったのではありません。
「自分が便利に使い倒している開発体験を、みんなで使ってよ！」という想いを詰め込んでおります！

感想、Issue、プルリク、「ガッツリ一緒に開発したいよ」のラブコールなど、
ドシドシお待ちしてますので、よろしくおねがいしまーす！

https://github.com/goqoo-on-kintone/goqoo

それでは皆さん、Goqooと共にメリークリスマス :santa: :christmas_tree: