はじめに
kintoneプログラマーの赤座です!
クリスマスイブにアドベントカレンダー書かせていただきます
思い起こせば3年前、2018年11月にkintone hack NIGHTに登壇して、kintoneカスタマイズ用の開発フレームワーク「Goqoo on kintone」を紹介したのでした。
順位はブービーでしたが、アスキーさんの記事でじっくり触れていただきました。
スライドはコチラ。
この時点でGoqooはまだまだ荒削りの超ベータ版で、プレゼンも「自分語り」が中心でした。
どんどん洗練させていく予定でいたのですが、翌年に三人目の娘が生まれてから、とにかく本業と子育てだけで必死で、Goqooの開発はずっと止まってしまっておりました。
その間に時は流れ、今やTypeScriptが当たり前の世界となっております。
三女が二歳になった今年、ようやく時間的にも余裕が戻ってきて、夏頃からコツコツとGoqooの改修を進めてきました。最後の一歩、自分を追い込むためにkintoneアドベントカレンダーに登録し、何とか間に合いました。
本日ここに、Goqoo on kintone v1.0、正式版をリリースさせていただきます!!!
Goqoo on kintoneとは?
kintoneを自由自在に乗りこなすためのツール、Goqoo(悟空)であります。
JS/TSプログラミング、コマンドライン/Node.js/Gitなどに精通している人のための開発フレームワークで、プログラミング初心者の利用は想定していません。
とにかく「自分がトコトン使いやすいように」というオレオレ視点でチューニングしてありますw
Getting Started
プロジェクト作成
まずはコマンド一発、これだけ叩いてみてください。
$ npx goqoo new プロジェクト名
プロンプトが出るので、Project name、descriptionを任意で設定します。
カスタマイズビューを作りたいときは、Frontend FrameworkにReact/Vueのどちらかを選ぶこともできます。
? Project name › プロジェクト名
? Project description ›
? Frontend Framework …
❯ (None)
React
Vue
自動的にフォルダが作成され、テンプレートのコピーと各種ライブラリのインストールが走ります。
ちなみに内部ではSAOというScaffoldライブラリを使っています。(0.x系で使っていたYeomanはやめました)
初回コミット
作成されたディレクトリに移動します。git init
は済んでいるので、一度git commit
をしておきます。
(一度もコミットをしていない状態ではGoqooは正しく動きません)
$ cd my-project
$ git add .
$ git commit -m 'Initial commit'
アプリのエントリ作成
$ npx goqoo generate app my-app
これでsrc/apps/my-app
のようにアプリ名フォルダが作成され、ビルド対象となります。
開発ビルド(webpack-dev-server)
Goqooによる開発は、webpack-dev-serverをローカルで立ち上げて、localhostでホスティングされたjsファイルをkintoneに読み込ませるのが基本になります。
$ 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として設定します。
「アプリを更新」すると、いかがでしょう・・・?
「オッス、オラ悟空!」と挨拶してくれますね!
これで初回のデプロイは成功です
これで、プログラムを修正するたびにkintoneが自動リロードして、サクサクと開発が進みます。
kintone側のJSを入れ替える必要はありません。
localhostなので、他のPCでkintoneにアクセスした場合は何も起きません。
逆に言うと、複数人でプログラムの共同開発はしやすくなります。
開発ビルド(ファイル)
$ npx goqoo build
development
モードでビルドしてdist
配下にファイルが作成されます。
一応機能としては用意していますが、そんなに使わないかもしれません。
本番ビルド
$ npx goqoo release
production
モードでビルドしてdist
配下にファイルが作成されます。
圧縮などされるので、本番リリース時にはこの方法でビルドしてください。
ビルド済みファイルをkintoneにアップロードすれば、デプロイ完了です。
ちなみに、Goqoo 0.x系ではkintoneに自動的にJS URLをデプロイする機能がありましたが、事故を招きやすい仕様なので一旦廃止しました。(既存のJSを全部消しちゃってたのです・・・)
安全かつ使いやすい仕組みが考えられたらまた復活するかもしれません。
サイボウズさん公式のkintone-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
直後はこうなってます。
import { goqoo } from 'goqoo'
goqoo('app', () => {
require('./hello')
})
goqoo()
という関数のコールバック内で、別モジュールをrequire()
しています。
こんな感じでhello.ts
と同列に色んなモジュールを作って、
すべてindex.ts
内でrequire()
するように書いてください。1
goqoo()
関数を挟む理由ですが、以下の2つの機能を提供するためです。
関数の内部でゴニョゴニョっとやっているわけですね。
- dev-server向けのlocalhost URLと、ビルドしたファイルを両方kintoneにアップしても動くようにする
-
window.__devinfo__
というオブジェクトに開発情報を入れて、デバッグコンソールから参照可能にする
どうです?超嬉しくないすか???
この嬉しさが分かるなら、kintoneプログラマーとして本物だと言えるでしょうw
便利アラート関数
ジェネレータで生成されたモジュール内で、
helloGoqoo
という関数をimportしています。
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をラップしたものです。2
設定ファイル
goqoo.config.js
という設定ファイルがプロジェクトルートにあり、
goqoo.config.types.ts
という型定義専用の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アプリストアの「営業支援パック」を想定した3種類のアプリ名を登録済み。
これは削除して、自分なりのアプリ名をつける。
- Context: 型定義は特に触らなくて良い
- Config: 型定義は特に触らなくて良い
これを踏まえて、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
を設定済みです。
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
に定義済みのアプリ名を入力補完してくれちゃいます。素晴らしい。
dtsファイルの自動生成
@kintone/dts-genという神ツールがあります。
これはまじで素晴らしいんですが、複数アプリの型定義ファイルを一括で作る機能がないんですよね。
なので、Goqooでラップしました!
使えるようにするために、いくつかステップがあります。
goqoo.config.js
先程のgoqoo.config.js
の解説追加。
アプリIDは定義済みの前提で、さらにdtsGen
プロパティに注目。
const config = {
bundlerType: 'default',
dtsGen: {
env: 'development',
// skip: ['customer'],
},
env
プロパティで、「どの環境のアプリをdtsファイル化するか」を1つだけ選びます。
ほとんどの場合はデフォルトのdevelopment
環境で良いでしょう。
skip
プロパティは、「アプリID定義はしたけど、dtsファイル化したくない」ものがあれば列挙します。
デフォルトはコメントアウトしてあるので、そのままでOK
.env
kintoneのログイン情報を.env
に書いておきます。3
$ cp .env.example .env
# .envを編集、GOQOO_USERNAMEとGOQOO_PASSWORDを設定
生成
設定ファイルを書いてしまえばあとは簡単!
$ npx goqoo generate dts
これで、dts
フォルダ配下にずらーっとmy-app-fields.d.ts
のようなファイルが出来ていきます。
使い方
src/types.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 */>) => {
+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
内に丸っとまとめてるよ
至れり尽くせり、個人的にとっても便利な機能満載でございます!
だって、自分のために自分で作ったんだからね!!!
おわりに
公式ドキュメント書くには英語とかもふくめて相当気を使うのですが、
Qiita記事であれば、これくらいの書きなぐり口語体でも許されるかなと思いまして、
とにかく勢いに任せてガーッと雑に書きました!
今回の記事は、記事そのものではなくて、
あくまで「Goqoo on kintone」というフレームワーク自体に価値があると思っております。
アドベントカレンダーに合わせてリリースするモチベーションにはなりましたが、
アドベントカレンダーのために作ったのではありません。
「自分が便利に使い倒している開発体験を、みんなで使ってよ!」という想いを詰め込んでおります!
感想、Issue、プルリク、「ガッツリ一緒に開発したいよ」のラブコールなど、
ドシドシお待ちしてますので、よろしくおねがいしまーす!
それでは皆さん、Goqooと共にメリークリスマス
-
Dynamic Importでなくてrequireなのは、苦肉の策です。。。Dynamic Importを使っちゃうと、本番ビルド時にファイルが複数に分かれちゃうので、kintoneにデプロイする場合には不向きなんですよね。 ↩
-
SweetAlert2ではなく、元祖SweetAlertです。Swal2の方はEasyだけどカスタマイズ性が悪いので好きではありません。 ↩
-
本当は、プレーンテキストの.envにクレデンシャル情報を書きたくない。OAuth対応そのうちやります。
.env
はGitコミットされないように設定済み。
サンプルファイルがあるので、それをコピーして編集が便利です。 ↩