kintone の UI ライブラリ開発ってどんな風にやってるの？という話

[kintone Advent Calendar 2021](https://qiita.com/advent-calendar/2021/kintone) 23日目の記事です:xmas-tree:

今年は、**[kintone UI Component](https://github.com/kintone-labs/kintone-ui-component)** という「kintone ライクなパーツを提供する OSS ツール」の開発に取り組んでいるので、**技術スタック・開発やテストでの工夫ポイントなどの裏側**を少しお見せしたいなと思います！
実際に使っていただいているユーザーさんがいらしたら、開発の仕組みを知っていただくことで少しでも安心して使ってもらえるといいなとも思っています:relieved:

![logo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/91ee6e80-fc4b-be33-b72b-4aa3dfc71c61.png)


# どんなライブラリ？
これです...！笑

**Desktop ver.**
![desktop_components.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/ecf68fb2-b2cc-7084-a0df-e01e62d60416.png)

**Mobile ver.**
![mobile_components.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/98a08594-c301-db13-47ee-d88d674e30c8.png)

kintone アプリやプラグインカスタマイズをする際に、UI 周りのアレンジをしたいケースがあるかと思います。そんな時、**デザインも kintone テイストに**したくなりますよね！
「CSS を色々いじったら寄せることはできるけど面倒だし、さらに挙動まで kintone ライクにするのはなかなか大変」。
そこで、簡単に呼び出してちょちょいとオプション設定を入れて使っていただけるのがこのライブラリです。

kintone カスタマイズあるあるを踏まえて各コンポーネントの仕様を考えるようにしているので、最低限の要件を満たすものをサクッと作っていただけるかなと思います。
（一方、汎用的に使ってもらえるものを目指していて、複雑な UI や動きを作り込むことは現状あまり想定していませんが、自由度はある程度持たせていきたいなと思っています！）

例えば npm パッケージ 利用の場合、**Button コンポーネント**はこんな風に呼び出して使うことができます。UMD ファイル, CDN, npm パッケージの３パターンで提供しているので、お好みに合わせてお使いください。

```javascript:index.js
import { Button } from 'kintone-ui-component/lib/button';

(() => {
  'use strict';

  kintone.events.on('app.record.index.show', event => {
    const header = kintone.app.getHeaderMenuSpaceElement();

    const button = new Button({
      text: 'Submit',
      type: 'submit'
    });
    button.addEventListener('click', event => {
      console.log(event);
    });

    header.appendChild(button);
    return event;
  });
})();
```

v0 シリーズを経て、2021年3月に v1 シリーズをリリースしまして、**現在 Desktop と Mobile ver. 合わせて Button, Checkbox, Notification, Text など 17 コンポーネントを提供中**です。
直近は **DateTime シリーズや Dialog を作っていてもうすぐお披露目する予定**です:tada:
あと、**プラグイン設定画面などでよく使われる Table** も計画中。

詳細はぜひ[ドキュメントサイト](https://kintone-ui-component.netlify.app/)をご覧ください。サンプルコード付きで書いておりますー:bulb:
Quick Start は[こちら](https://kintone-ui-component.netlify.app/docs/ja/getting-started/quick-start)。

**では、ざっくり紹介が終わったところで、ここから仕組み的なところをお話ししていこうと思います:bangbang:**

# 技術スタックはどんな感じ？
以下のように技術スタックを選択して実装しています！

- **コンポーネント実装**：[Web Components](https://www.webcomponents.org/) の [Lit](https://lit.dev/), [TypeScript](https://www.typescriptlang.org/)
    - `Web Components` は、ウェブブラウザの API 群で、標準の HTMLElement から拡張して簡単にコンポーネントを作ることができます。`<kuc-button>` などの Custom Element を作るのも容易です。
    - `Lit` には `Web Components を実装するためのライブラリ`や `HTML テンプレートライブラリ`が含まれています。コードの可読性向上、ユーザー I/F の改善のために新しく使うことに決めました！
    - 型定義をしておくと、読みやすさアップや自動補完などが効くので、TS を導入しています。
    ![スクリーンショット 2021-12-23 1.55.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/febf1a88-54df-c49c-4fed-48bcf479f4c1.png)
- **挙動や UI チェック**:[Storybook](https://storybook.js.org/)
    - これはおすすめです:bulb:コンポーネント群をアプリケーションに組み込まず開発できます。localhost 起動で、開発時点で動作チェックしながら進めることができてとても便利です。
    ![スクリーンショット 2021-12-23 1.46.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/4499442b-1170-a4be-1fa9-3628c3c0bdde.png)
- **開発ルールチェック**：[ESLint Custom Rule](https://eslint.org/docs/developer-guide/working-with-rules)（以前は [Sider](https://sider.review) 利用）
    - private function の場合は「_」をプリセットとしてつけよう、クラス名は BEM メソッドで書こうなど開発する上でのルールがいくつかあります。元々 Sider というツールを使って正規表現を書いて Pull Request タイミングでルールチェックするような仕組みを作っていました。
    ![a9ee6386-0411-44e1-9eae-e88d1ada1a6f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/60ba4b49-94b9-f73a-59f4-5bf13219d043.png)
    - これは+αでスペルミスなどもサジェストしてくれたりと便利だったのですが、実装時点でのチェックができない不便さがあり、ESLint Custom Rule に最近移行しました。こうすると JavaScript を使うことができるので大体のルールは定義できます。
    ![スクリーンショット 2021-12-23 2.08.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/fc569dcc-d3a7-ca56-f047-3478d0f3cba2.png)
- **アクセシビリティチェック**：[Screen Reader](https://chrome.google.com/webstore/detail/screen-reader/kgejglhpjiefppelpmljglcjbhoiplfn)
    - 視覚的な障害がある方にとっては、キーボードで操作ができることや音声読み上げが正しくされることが重要です。内部的には[WAI-ARIA](https://developer.mozilla.org/ja/docs/Learn/Accessibility/WAI-ARIA_basics)という技術を使って HTML だけでは表すことのできない構造や状態などを明示して、スクリーンリーダーで読み上げがされるように工夫していたり、キーボード動作もサポートしていたりします。障害がない方にとっても使い方の選択肢が広がり操作性がアップすることにも繋がりますね！
    - 読み上げの確認には、スクリーンリーダーというツールを使っています。Chrome Extension もあったりするので比較的簡単にインストールして使うことができます。
- **ユニットテスト**：[Karma](http://karma-runner.github.io/latest/index.html)
    - コードロジックのテストには、当初 Web Components のプロジェクトでおすすめされていた Karma を使っていますが、関連ライブラリがメンテナンスモードのため長期的には別のを探した方が良さそうだなと思っているところです。
- **テスト自動化**：[WebdriverIO](https://webdriver.io/)
    - コンポーネントごとにテストケースを作っていますが、多いものだと 100 を超えます。また複数ブラウザやモバイル端末をサポートしているので、手動確認しているとかなり大変です。ということで、導入コストをかけて自動化することにしました。WebdriverIO は Node.js のブラウザ・モバイルテスト自動化のためのツールです。
    - テストスクリプトを確認アプリに自動で適用（customize-uploader 利用） > GitHub Actions でのテストワークフロー実行 > ブラウザやモバイル画面を自動で立ち上げてテストケース実行までの一連の流れを自動化しました。（詳細は後ほど）
    ![testing_app.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/06c25511-ecc0-600d-30e6-62250830bbc9.png)
- **ドキュメント**：[docusaurus](https://v1.docusaurus.io/)
    - Facebook 製の静的サイトジェネレーターです。デザインテンプレートが予め用意されているので、マークダウンファイルさえ作れば簡単にドキュメントサイトを作ることができる & 多言語対応も可能などライトに作るにはもってこいということで、使ってみることにしました。いい感じです。
    ![スクリーンショット 2021-12-23 3.00.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/02c0e099-aac0-db84-c109-b16692e5d582.png)
- **サイトホスティング**：[Netlify](https://www.netlify.com/)
    - docusaurus で作ったサイトをホスティングするのに使ってます。
- **CI**：[GitHub Actions](https://github.co.jp/features/actions)
    - GitHub 標準搭載の Actions を使っています。管理画面で実行結果なども確認できて良いです。
    ![スクリーンショット 2021-12-23 3.04.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/19ce3870-3ab6-751f-d3d4-f347441a1c85.png)
- **パッケージの脆弱性チェック**：[dependabot](https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/about-dependabot-version-updates), [Yamory](https://yamory.io/)
    - dependencies, peerDependencies, devDependencies などを網羅的にチェックできるように、ツールを2つ導入して運用しています。
    ![スクリーンショット 2021-12-23 3.07.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/1e5f179d-b26c-1860-25a6-570a4bf16028.png)
    ![スクリーンショット 2021-12-23 3.09.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/1a8612c2-5204-fe12-0d5a-a145038a5b8b.png)

# 工夫ポイント
普段は、スクラム形式で毎週バックログ（要件リスト）の検討・タスクプランニング・進捗報告 & 成果発表というサイクルで開発を進めています。新規コンポーネントについては、ユーザーヒアリングもしながら PM, PG, QA メンバーで仕様を検討します。
プランニングは、こんな感じに kintone で:thumbsup_tone2:
![スクリーンショット 2021-12-23 3.17.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/3a60f031-e3a1-394b-240b-cf448526baf7.png)

既に技術スタックの解説で触れたものも多いですが、フェーズごとに工夫していることをいくつかピックアップしてみました:pencil2:

## 開発フェーズ
### 共通クラスや関数を切り出し
メンテコスト削減のため、コンポーネント間で共通で使えるクラスや関数を切り出して、各コンポーネントスクリプトで呼び出すようにしています。（`extends KucBase` や `visiblePropConverter` などの部分）

```javascript:src/dropdown/index.ts
import { html, PropertyValues, svg } from "lit";
import { property, state, queryAll, query } from "lit/decorators.js";
import {
  KucBase,
  generateGUID,
  dispatchCustomEvent,
  CustomEventDetail
} from "../base/kuc-base";
import { visiblePropConverter } from "../base/converter";
import {
  validateProps,
  validateItems,
  validateValueString,
  validateSelectedIndexNumber
} from "../base/validator";

type Item = {
  label?: string;
  value?: string;
};
type DropdownProps = {
  className?: string;
  error?: string;
  id?: string;
  label?: string;
  value?: string;
  selectedIndex?: number;
  disabled?: boolean;
  requiredIcon?: boolean;
  visible?: boolean;
  items?: Item[];
};

export class Dropdown extends KucBase {
  @property({ type: String, reflect: true, attribute: "class" }) className = "";
  @property({ type: String }) error = "";
  @property({ type: String, reflect: true, attribute: "id" }) id = "";
  @property({ type: String }) label = "";
  @property({ type: String }) value = "";
  @property({ type: Number }) selectedIndex = -1;
  @property({ type: Boolean }) disabled = false;
  @property({ type: Boolean }) requiredIcon = false;
  @property({
    type: Boolean,
    attribute: "hidden",
    reflect: true,
    converter: visiblePropConverter
  })
  visible = true;
  ...
```

### コミットごとにユニットテスト
ロジックのテストがうまく通っているか、コミットごとにチェックする CI を設定しているので、Pull Request 画面でレビュアーが結果を確認して問題があったときに早めに気づけるようにしています。
![スクリーンショット 2021-12-23 3.58.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/ebcbf9e9-4471-db4b-8873-68c21aa80efe.png)


```yml:.github/workflows/commit_v1.yml
name: On commit v1

on:
  push:
jobs:
  Run_tests:
    name: Run unit test
    runs-on: ubuntu-18.04
    steps:
    - uses: actions/checkout@v2
    - name: Cache dependencies
      uses: actions/cache@v2
      with:
        path: ~/.npm
        key: npm-${{ hashFiles('package-lock.json') }}
        restore-keys: npm-
    - name: Install dependencies
      run: npm ci
    - name: Run ESLint
      run: npm run es-lint
    - name: Run unit test
      run: npm test
```

## テストフェーズ
### WebdriverIO や GitHub Actions self-hosted runner などでテスト自動化！
サポートしている提供方法（ESM/UMD）・ブラウザ（Safari, Google Chrome, Firefox, Edge）やモバイル端末（iOS, Android）が複数あって手動確認では埒があきません...
全てのテストケースをカバーできるわけではないですが、自動化の仕組みを入れることで大幅にコストカットできるようになりました。

1. テストスクリプトを確認アプリに自動で適用（customize-uploader 利用）
1. GitHub Actions でのテストワークフロー実行（複数環境を同時実行）
1. ブラウザやモバイル画面を自動で立ち上げてテストケース実行
1. テスト結果を出力

#### システム構成
仕組みについては大体こんな感じです:mega: ちょっと複雑なので簡単な紹介に留めておきます。

- 環境ごとの GitHub Actions ワークフローファイルを定義
- パラレルに実行されるように Job を設定
- GitHub Actions self-hosted runner を設置した Windows と Mac 端末にて Job を実行
- テスト環境を立ち上げてテスト実行
![KUC-SelfHostedRunnersArchitecture.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/ee121ebf-c358-c176-a2f6-5f03cfa4e10b.png)

#### 動き
QA や PG メンバーは以下のステップでテストして問題があるところを修正します。

1. GitHub Actions 実行画面にて、テストしたいバージョン, ブランチ, プラットフォーム, コンポーネントなどを選択し、ワークフローを実行する
  ![KUC-Tests-RunWorkflow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/57153fe7-9224-c48d-7968-2c95135fc339.png)

1. テスト環境が立ち上がり、テストスクリプトが実行される
![testing_app.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/65ff359b-ff6d-6229-1d16-68ccd7cd504e.png)

1. ワークフローの実行結果を確認する
![KUC-Tests-Jobs_Short.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/3578654a-fa2d-ac5b-001b-580ffb0e0dd6.png)

1. テスト結果を確認する（Allure Report を活用） 
![スクリーンショット 2021-12-23 5.02.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/f8fae296-4f9b-f31d-26aa-14639e82ba85.png)

## リリースフェーズ
毎度作業が面倒なところは、できるだけ CI で自動化です！

- リリースチェック（バージョン, ESLint, ユニットテストなど）
- Release tag へのアーカイブファイルのアップロード
- ドキュメントのステージングとプロダクションサイトのデプロイ

この中で、一つだけ取り上げてみます。

### ドキュメントサイトのステージングとプロダクションの使い分け
正式にリリースをする前にドキュメントだけオープンになっては困るので、タイミングによって Netlify 上の2つの環境を切り替えてデプロイし、内容確認をしています。

- master branch にマージされた場合は、ステージングサイトにデプロイ
![Screen Shot 2021-09-09 at 23.29.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/de17a7e1-c1e2-bf83-fa48-2eaea3b7f369.png)

https://github.com/kintone-labs/kintone-ui-component/blob/master/.github/workflows/netlify_document_staging.yml

- Pre-release チェックが終わって正式リリースされた場合は、プロダクションサイトにデプロイ
![Screen Shot 2021-09-09 at 23.29.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90265/bb81d4c0-f14e-eb8c-127a-193eeaf91749.png)

https://github.com/kintone-labs/kintone-ui-component/blob/master/.github/workflows/netlify_document_live.yml

# 最後に
各トピック少しずつしか書けなかったので、需要があればもうちょっと詳しい内容もどこかでご紹介できればと思います！

また、kintone UI Component は OSS として公開しているので、ぜひ一緒に KAIZEN していけると嬉しいです。リクエストやお気づきのことがあれば、[issue](https://github.com/kintone-labs/kintone-ui-component/issues/new/choose) や Pull Request 登録などコントリビュートもお待ちしておりますー:muscle_tone2:

それでは最後まで読んでいただきありがとうございました:heart_eyes: