kintoneでTailwindCSSを利用するための勘所

この記事は [kintone アドベントカレンダー 2022](https://qiita.com/advent-calendar/2022/kintone) の 11 日目の記事です。

# はじめに

[TailwindCSS](https://tailwindcss.com/)、いいですよね。
学習コストは大した事ないくせにけっこう劇的な効果があって、これまで CSS の命名ルールとか運用ルールとかで汲々としていた現場にそこそこドラスティックな変化をもたらしてくれていると感じています。

TailwindCSS 自体をあまり詳しく知らないと言う方は、ちょっと調べれば参考になるサイトはたくさん見つかるでしょう。
特に以下の記事はメリット・デメリット双方に言及しておりかなり有用です。

[tailwindcss を導入して感じたメリット・デメリットについて](https://tech.leverages.jp/entry/2021/10/29/180213)

この激オススメの CSS フレームワーク・TailwindCSS を kintone で利用するための勘所をお話ししようと言うのが今回の趣旨です。

# 早速作りましょう

仕上がりイメージはこんな感じ。
例によって kintone 新規アプリ作成の際に「おすすめのアプリ」としてラインナップされている「問い合わせ管理」をちょっといい感じに見せるやつです。
Trello などのような良くあるカンバン形式と少し趣向を変えて今回はカードを横に並べるやり方にしてみました。
![01-result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162138/85527044-64d1-441b-8bd1-7ca7a789f6af.png)

## プロジェクトを作る

今回は [Vite](https://ja.vitejs.dev/) + [Vue3](https://v3.ja.vuejs.org/) + [TypeScript](https://www.typescriptlang.org/) に TailwindCSS を組み合わせる流れです。
基本的には [Vite のスタートアップガイド](https://ja.vitejs.dev/guide/)や[TailwindCSS のガイド](https://tailwindcss.com/docs/installation/using-postcss)を参照すれば特段難しい事はないかと思います。

```bash
yarn create vite
? Project name: (任意のプロジェクト名)
? Select a framework: Vue
? Select a variant: TypeScript
```

できたプロジェクトフォルダを VS Code で開き、ターミナルを起動します。
ちなみに私はターミナルを Git Bash にしています。
TailwindCSS や関連ライブラリをインストールします。

```bash
yarn add -D tailwindcss postcss autoprefixer
npx tailwindcss init
```

`tailwind.config.cjs` と言うファイルができるかと思うので、拡張子は  `.js` にしておけばよいでしょう。

プロジェクト直下に `postcss.config.js` ファイルを作ります。

```javascript:postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  }
}
```

流れで作成された `tailwind.config.js` を修正します。

```diff:tailwind.config.js
-   content: [],
+   content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
```

この流れだと `package.json` に `"type": "module"` の記述があり、ビルドに差し支えます。
ここを修正します。

```diff:package.json
-   "type": "module",
```

ここまでやって `yarn dev` と実行して開発サーバを立ち上げて動作確認をしましょう。
![02-vite-vue.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162138/6c33578c-1ee2-3ab9-2898-17af4357a298.png)
うむ。まあ普通の Vite + Vue3 のスタートアップページですね。

さらに、デフォルトで作成されている `src/style.css` について、ガイドに従い TailwindCSS を読み込めるようにします。

```css:src/style.css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

## TypeScript 開発の準備

kintone で TypeScript 開発をするためには諸々準備が必要です。
まずは  [@kintone/dts-gen](https://www.npmjs.com/package/@kintone/dts-gen) をインストール。
ソース内で型参照をしたいので [@kintone/rest-api-client](https://www.npmjs.com/package/@kintone/rest-api-client) も合わせてインストールします。

```bash
yarn add -D @kintone/dts-gen
yarn add @kintone/rest-api-client
```

`src/types/kintone.d.ts` と言うファイルを以下の内容で作成します。
ただこれはもっと良いやり方がある気がする。

```typescript:src/types/kintone.d.ts
declare namespace kintone {
  interface Event {
    appId: number
    viewType: string
    viewId: number
    viewName: string
    records: Record[]
    offset: number
    size: number
    date: string
    type: string
  }
  interface Record {
    record: {
      $id: string
      [key?: string]: Field
    }
  }
  interface Field {
    type: string
    value: unknown
  }
}
```

この他、本来はアプリのレコードに関する  `d.ts` ファイルも作成する必要がありますが今回は省略。
詳しいやり方は [こちら](https://github.com/kintone/js-sdk/blob/master/packages/dts-gen/docs/how-to-use.md)  や [こちら](https://developer.cybozu.io/hc/ja/articles/900005568163) をご覧ください。
今回は  `src/types/Record.d.ts` と言うファイルを作った事にします。

あとは、`tsconfig.json` に `d.ts` ファイルの参照を追加してやります。
ついでにソースコード中のインポートで `@/` を  `src/` に置き換えるショートハンドも登録してやります。（好みの問題）

```diff:tsconfig.json
{
  "compilerOptions": {
    (省略)
+     "suppressImplicitAnyIndexErrors": true,
+     "paths": {
+       "@/*": ["src/*"]
+     }
  },
+   "files": [
+     "node_modules/@kintone/dts-gen/kintone.d.ts",
+     "src/types/kintone.d.ts",
+     "src/types/Record.d.ts"
+   ],
-   "include": [ "src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue" ],
+   "include": [
+     "src/**/*.ts",
+     "src/**/*.d.ts",
+     "src/**/*.tsx",
+     "src/**/*.vue",
+     "node_modules/@kintone/rest-api-client/**/*.d.ts"
  ],
  "references": [{ "path": "./tsconfig.node.json" }]
}
```

## がっしがっし作る

ここからは kintone の流儀。
`src/main.ts` をエントリポイントとして作り込んでいきます。
とりあえずカスタマイズビューに `<div id="container" />`  と言うコンテナを配置し、そこに UI をぶち込んでいく形で作りましょう。

```typescript:src/main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
createApp(App).mount('#app')
kintone.events.on('app.record.index.show', (event: kintone.Event) => {
  const mountTarget = document.getElementById('container')
  if (!mountTarget) return event
  createApp(App, { records: event.records }).mount(mountTarget)
})
```

あとはひたすら良い感じのビジュアルになるよう頑張ればよろしい。
表示要素単位でコンポーネントを作り込み、それに対しユーティリティクラスの積み上げでデザインを煮詰めていくのは積み木をやっているみたいな感覚に近いものがあります。
ユーティリティクラスと言う特性上、使いこなすには素の CSS の事は一通り知っておく必要があり、つまり TailwindCSS があれば CSS を知らなくても素敵なビジュアルを自由自在に組み上げられるぜと言う事にはなりません。
TailwindCSS は [Bulma](https://bulma.io/) や [Vuetify](https://vuetifyjs.com/ja/) のような UI ツールキットではないので、コンポーネント的な使い方をするなら割と自力で頑張るか [こちら](https://coliss.com/articles/build-websites/operation/css/tailwind-css-ui-components-libraries.html) で紹介されているようなところから程よいものを漁るとかになるでしょう。
自前で頑張ってもなんとかなると思いますけどね。

# 完成！

よしできたぜ！俺のこいつを見てくれ！！
![03-invalid-all.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162138/bbd7eeed-babe-4fe0-a1c8-22040e78f233.png)
ベネ。（良し）
良い感じにリストが並んで、その中にカードが配置されています。
カードをドラッグで別のリストに移動したりとかクリックでレコード詳細に遷移とかそう言うギミックが考えられますが、そう言うのは[そう言う製品](https://smartballoon.jp/kintone/plugins/kanban)に任せればいいと思うので（ステマ）、とりあえず何時間かでやれるぶんとしてはいい塩梅じゃないですか？

## ・・・と思いきや

・・・あれ？なんか・・・カスタマイズビューの中は意図通りのビジュアルだけど・・・
ヘッダとか、なんか、おかしくない・・・？
![04-invalid-header.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162138/aca7f76f-3591-a4ea-181f-b5be021331b3.png)
アプリのタイトルのところとか、その上の黒い帯のボタンの並び方とか、一覧切り替えの隣のフィルタや集計ボタンもおかしいよね？

どころか、カスタマイズビューでない普通の一覧でもおかしいですね・・・？
![05-invalid-list-view.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162138/64acbfff-2d29-9de2-a102-4c6a535754bf.png)


## なぜこんな事になっているのか

TailwindCSS で適用されるデフォルトの CSS はページ内の全要素に作用するようになっています。
デベロッパーツールで確認してみると、それぞれのタグに対して数多くのスタイルが設定されており、これが影響している事が分かります。
![06-tailwindcss-styles.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162138/345ad60c-a8a2-a069-a6e3-be86a6382fa7.png)


これはなんとかならんのか？

つまり、TailwindCSS のスタイルを全要素にではなくカスタマイズビューの枠内だけに収める事はできないのか？

ここまで長々と書いてきましたが、今回の記事の本題はまさにここから。

## もう少し原因を深堀りする

TailwindCSS のスタートアップガイドに従って、`src/style.css` に以下のように記述しましたね。

```css:src/style.css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

そして、カスタマイズのエントリポイントとなる `src/main.ts` にはファイルの冒頭に以下のように記述されています。

```typescript:src/main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
```

これは `kintone.events.on()` イベントハンドラの外側に配置されているため、kintone のイベント処理とは無関係に実行される。
さらに言えば、アプリの一覧画面、詳細画面、編集画面など、JS カスタマイズが作用するあらゆる場面で実行される事になる。

要するにこれがいけない。
`src/style.css` の影響を局所化したいわけです。

## 実はすごく簡単です

原因と目的が分かれば、対処法は容易です。
スタイルを局所化すると言えば Vue の界隈では [スコープ付き CSS](https://vue-loader-v14.vuejs.org/ja/features/scoped-css.html)と相場が決まっています。

なので、解決のためのアプローチとしては、`main.ts` でグローバルに インポートするのをやめ、個々のコンポーネントのスコープ付きの（`style scoped` な） CSS の中で `src/style.css` をインポートすると言う戦略となります。

```diff:src/main.ts
import { createApp } from 'vue'
- import './style.css'
import App from './App.vue'
```

```vue:src/App.vue
<style scoped>
@import url('@/style.css');
</style>
```

これによってグローバルな世界には影響を与えず、カスタマイズビュー（厳密には当該コンポーネント）の中だけに TailwindCSS のスタイルを適用できるようになるわけです。

注意点としては、スコープ付きスタイルは子コンポーネントには波及しないので、 **TailwindCSS を使うすべてのコンポーネントで `@import url('@/style.css');` する必要がある** と言う点です。

これで、
![07-valid-header.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162138/41e515b5-0857-2ee3-8e7e-f3cf8840b50f.png)
ヘッダ部のレイアウトも汝のあるべき姿に戻りました。

なお、一連のソース一式は [こちら](https://github.com/iShinkai/kintone-tailwindcss-test) で公開しています。

# まとめ

と言うわけで、複雑な命名規則や運用ルールを強いる事を余儀なくされる近年の  CSS 設計に一石を投じる  TailwindCSS を  kintone で利用するためのアプローチについて解説しました。
余談ながら今回取り上げた vite + Vue3 + TypeScript + TailwindCSS と言うスタックは現在絶賛開発中の弊社の新プラグインでも採用しているセットだったりします。
先日の Cybozu Circus 大阪でその一端をご覧いただいた方もいらっしゃいますかね！

kintone における JavaScript 開発は独特なお作法があったりして、新しい技術なんかは世の潮流から 1,2 歩遅れて入ってくる事がままあると感じます。
昨今は [ブラウザでもモジュールが使えるようになってきている](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Modules) 事ですし、kintone のカスタマイズも  `type="module"` として読み込めますよ、と言う風になると良いなと願っております。

以上、お読み頂きありがとうございました！