Nuxt.js + TypeScript のプロジェクトに CoreUI を手動クリーンインストール【前編】

この記事では `npx create-nuxt-app` で作成してTypeScript環境を導入したNuxt.jsプロジェクトにCoreUIを組み込む。

なお、Nuxt.jsにCoreUIを組み込んだ[非公式のテンプレート](https://github.com/muhibbudins/nuxt-coreui)が存在するけれど、今回この非公式テンプレートも参考にしながらすべて手動でCoreUIを組み込みこんだ結果わかったのは、この非公式テンプレのどこにCoreUIの要素があるのか全然わからんということだった。非公式テンプレのことは最後にまたすこし書く。

# 公式テンプレート
https://github.com/coreui/coreui-free-vue-admin-template
↑このリポジトリから取得できるものを、以下、公式テンプレートと呼ぶことにする。基本的にはこれを参考にしながら、同じことをNuxtプロジェクトで実現するにはどうすればよいかを考えていく。

# とりあえずインストール

まずは次の2つのモジュールをインストールする。

```bash
npm i @coreui/vue @coreui/coreui
```

これらをインストールしろという指示は、[coreui-vue](https://github.com/coreui/coreui-vue)のREADMEにある。ついでに、公式テンプレートのpackage.jsonを覗いて依存関係を見てみると、次のような感じ。

```json
"dependencies": {
  "@coreui/coreui": "~3.0.0",
  "@coreui/icons": "~1.0.1",
  "@coreui/utils": "~1.2.2",
  "@coreui/vue": "~3.0.0",
  "@coreui/vue-chartjs": "~1.0.3",
  "vue": "~2.6.11",
  "vue-router": "~3.1.5",
  "vuex": "~3.1.2"
}
```

インストール手順に記載のない `@coreui/utils` と `@coreui/icons` は必須ではなさそう。でもiconsのほうは、名前のとおりCoreUIの独自アイコンが収められており、それらを利用するためにこの記事のあとの方でまたすこし触れる。

## CoreUIコンポーネントを使ってみる
CoreUIのコンポーネントをひとつ使ってみよう。公式テンプレートの `src/views/base/Cards.vue` を参考に、自分のプロジェクトの `pages/index.vue` に次のような要素を加えてみた。

```vue
<div>
  <CCard>
    <CCardHeader>
      カードのタイトル
      <div class="card-header-actions">
        <a
          href="https://coreui.io/vue/docs/components/card-components"
          class="card-header-action"
          rel="noreferrer noopener"
          target="_blank">
          <small class="text-muted">docs</small>
        </a>
      </div>
    </CCardHeader>
    <CCardBody>
      カードの内容
    </CCardBody>
  </CCard>
</div>
```

でも、CCardというコンポーネントがVueに登録されるような処理をどこにも書いていないので、このままでは当然エラーがでる。

```
[Vue warn]: Unknown custom element: <CCard> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
```

これを解決するには、CoreUIをVueにプラグインとして登録すればよい。以下では、それをNuxt.jsプラグインとして実現する。

次の内容で `plugins/coreui.ts` というファイルを作成し、

```typescript
import Vue from 'vue'
import CoreUI from '@coreui/vue'

Vue.use(CoreUI)
```

この（Nuxt.jsとしての）プラグインを読み込むよう `nuxt.config.ts` を編集。

```typescript
export default {
  // ...
  plugins: [
    '~/plugins/coreui'
  ],
  // ...
}
```

これで、コンポーネントが見つからないというエラーは消えるけど、まだスタイルが適用されていない。公式テンプレートでは `src/assets/scss` 下に共通のスタイル定義 `style.scss`, `_custom.scss`, `_variables.scss` が置かれているので、これらを自分のプロジェクトの `assets/scss` 下にコピーし、グローバルなスタイル定義として `nuxt.config.ts` に記載しておく。

```typescript
export default {
  // ...
  css: [
    '~/assets/scss/style.scss'
  ],
  // ...
}
```

これでカードがきれいに表示されるようになるはず。

# アイコンを使う
アイコン表示につかうコンポーネント `CIcon` は `@coreui/icons-vue` で定義されているが、これは `@coreui/vue` から依存されているので、すでにインストールされている。

```
$ npm ls
my-project@1.0.0 /Users/user/my-project
├── @coreui/coreui@3.0.0
├─┬ @coreui/vue@3.0.0
│ ├── @coreui/icons@1.0.1
│ ├─┬ @coreui/icons-vue@1.3.0
│ │ └── vue@2.6.11 deduped
```

たとえば `pages/index.vue` に先ほど追加したカードのヘッダにアイコンを表示するなら、次のように書けばよいようだけど、いまのまま `CIcon` コンポーネントを使っても、エラーは出ないけれどアイコンは表示されない。

```vue
<CCardHeader>
  カードのタイトル
  <div class="card-header-actions">
    <a
      href="https://coreui.io/vue/docs/components/card-components"
      class="card-header-action"
      rel="noreferrer noopener"
      target="_blank"
    >
      <CIcon name="cil-envelope-open" />    <!-- これを追加した！ -->
      <small class="text-muted">docs</small>
    </a>
  </div>
</CCardHeader>
```

`CIcon` コンポーネントがどのようにアイコンを参照しているかというと、公式テンプレートの `src/main.js` にある次の記述。この中でインポートされている `icons.js` は、 `@coreui/icons` モジュールで定義されているアイコンやプロジェクト固有のアイコン（テンプレではサンプルとしてロゴを読み込んでいる）をexportしており、それをルートコンポーネントのオプションとして設定している。ちなみに、 `icons.js` がエクスポートする各アイコンはアイコンサイズと表示用のHTMLタグを文字列で保持する長さ2のstring配列データ。

```js
// src/main.js
import { iconsSet as icons } from './assets/icons/icons.js'

new Vue({
  el: '#app',
  router,
  store,
  icons,
  template: '<App/>',
  components: {
    App
  }
})
```

Nuxt.jsプロジェクトでこの `main.js` と同じことをやるには、ルートとなっているコンポーネントの生成箇所を、ハックするか、生成直後に次のようなことができればよいのだろうけど、いくら調べてもそれができる適切な方法を見つけることができなかった。誰か教えてほしい。

```typescript
import { iconsSet as icons } from 'myicons.ts'
$root.$options.icons = icons
```

仕方がないので、ルートの直下に配置されるレイアウト用コンポーネントの作成タイミングにこのロジックを挿入することにしてみた。以下、その手順。

プロジェクトをTypeScript化しているので、まずは型定義を追加する。 `@types/coreui-icons.d.ts` ファイルを次の内容で作成し、それを型定義ファイルとして tsconfig.json に追加。

```typescript
// @types/coreui-icons.d.ts
import Vue from 'vue'

type IconsSet = { string: string[] }

declare module 'vue/types/options' {
  interface ComponentOptions<V extends Vue> {
    icons?: IconsSet
  }
}
```

```js
// tsconfig.json
"types": [
  "@types/node",
  "@nuxt/types",
  "./@types/coreui-icons" // <- これ
]
```

つぎに公式テンプレートの `src/assets/icons` ディレクトリ下のファイル `icons.js` と `logo.js` を、プロジェクトの `assets/icons` ディレクトリにコピーする。TypeScript化するので、 `icons.js` 改め `icons.ts` がエクスポートしてるiconsSetに型指定しておく。

```typescript
// assets/icons.ts
import { IconsSet } from '~/@types/coreui-icons'

export const iconsSet: IconsSet = Object.assign( // ...略
```

ちなみに、公式テンプレートの `icons.js` は `@coreui/icons` が提供しているすべてのアイコンをインポートしているわけではないので、必要なアイコンがあればあとで足していけばよいと思う。

最後に `layouts/default.vue` にコンポーネントを定義して `created` メソッドを実装する。

```vue
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator'
import { iconsSet as icons } from '~/assets/icons/icons'

@Component
export default class DefaultLayout extends Vue {
  created() {
    this.$root.$options.icons = icons
  }
}
</script>
```

これでアイコンが表示されるようになったはず。 `icons.ts` をうまく組めば `CIcon` コンポーネントでfont-awesomeなどサードパーティのアイコンも表示できると思う。

# レイアウト
次に、公式テンプレートと同じように、コンテンツのまわりにヘッダー・サイドメニュー・フッターを配置するレイアウトを作る。そこまでできればCoreUIを利用する準備が整ったと言えそう。けど、疲れたので続きは改めて。

CoreUIはサイドメニューの開閉ステータス管理にVuexを使っているので、レイアウトと合わせてVuexまわりの設定についても触れる。vue-routerまわりについては特に問題なくサイドメニューなどを作成できる。ただしCoreUIは当然 `<nuxt-link>` を使ってはいないので、そこにこだわる場合ツラいことになりそう。

→ 書いた。 [Nuxt.js + TypeScript のプロジェクトに CoreUI を手動クリーンインストール【後編】](https://qiita.com/xfan/items/5b096331958c1ae122b0)

# 非公式テンプレートについて
リンク再掲。
https://github.com/muhibbudins/nuxt-coreui

非公式テンプレートのpackage.jsonは次のとおりで、Nuxt関連のはさておくとして、bootstrap-vue 以下、公式テンプレートの依存関係に存在しないライブラリを用いている。一方、CoreUI関連モジュールへの依存はない（え？）。

```js
"dependencies": {
  "cross-env": "^5.2.0",
  "nuxt": "^2.0.0",
  "express": "^4.16.3",
  "@nuxtjs/axios": "^5.0.0",
  "@nuxtjs/style-resources": "^0.1.1",
  "bootstrap-vue": "^2.0.0-rc.1",
  "chart.js": "^2.7.1",
  "flag-icon-css": "^2.9.0",
  "font-awesome": "^4.7.0",
  "simple-line-icons": "^2.4.1",
  "vue-chartjs": "^3.1.1"
},
```

非公式テンプレートが実際に何のコンポーネントを使ってデモサイトを作っているかというと、 bootstrap-vueっぽい。アイコンも依存関係にあるとおり、CoreUIとは関係のない製品を組み合わせている様子。

こんな記事を書いてるけれど別にCoreUI推しというわけではないので、Nuxt.jsにUIフレームワークとしてbootstrap-vueなどを組み合わせたテンプレートとして、これはこれでいいと思うのだけど、なぜこれをNuxt + CoreUI projectと称しているのかが理解できず、頭にハテナが付いてる状態。