続・Nuxt3の開発用テンプレート作ってみた(Docker/Vitest/Storybook対応)


## はじめに

2022/08にrc版になったNuxt3の開発用テンプレートを作成しました。それから１年が経ち、周辺ライブラリが整ってきたため改めてテンプレートを拡張しようと思います。

## 成果物

https://github.com/Gityosan/Nuxt3-template

https://github.com/Gityosan/Nuxt3-template-with-storybook

是非「Use this template」からリポジトリを作って使ってみてください。

## 要所解説

### **Version等(今回追加した箇所を中心に要所のみ抜粋)**

(nodeに関しては20系の最新版を使用しています)

```json:package.json
{
  <中略>
  "devDependencies": {
    "@nuxt/devtools": "^1.0.3",
    "@vitest/coverage-v8": "^0.34.6",
    "@vitest/ui": "^0.34.6",
    "@vue/test-utils": "^2.4.1",
    "jsdom": "^22.1.0",
    "nuxt": "3.8.2",
    "typescript": "^5.2.2",
    "unplugin-auto-import": "^0.16.6",
    "unplugin-vue-components": "^0.25.2",
    "vite-plugin-vuetify": "^1.0.2",
    "vitest": "^0.34.6",
    "vuetify": "3.4.2"
  }
  <中略>
}
```

### Docker対応

個人的にはdockerで立ち上げるのはバックエンドフレームワークだけでいいだろうと考えていましたが、いざ、dockerを構築してみると(たとえ本番環境へのデプロイには使わないとしても)やっぱり立ち上げが楽ですね。

まずはDockerfileから

```Dockerfile:Dockerfile
FROM node:20-alpine3.17

ENV TZ Asia/Tokyo

WORKDIR /frontend

COPY package.json ./
```

続いてcompose.yml

```yml:compose.yml
version: '3.9'

x-common-settings: &common-settings
  build: .
  volumes:
    - .:/frontend:cached
    - nuxt-node-modules:/frontend/node_modules:delegated
  tty: true

services:
  dev_nuxt:
    container_name: dev_nuxt
    <<: *common-settings
    ports:
      - '3000:3000'
    command: >
      sh -c `
      npm install &&
      npm run dev
      `
  prod_nuxt:
    container_name: prod_nuxt
    <<: *common-settings
    ports:
      - '3001:3001'
    command: >
      sh -c `
      npm install &&
      npm run build &&
      PORT=3001 node ./.output/server/index.mjs
      `
  storybook:
    container_name: storybook
    <<: *common-settings
    ports:
      - '6006:6006'
    command: >
      sh -c `
      npm install &&
      npm run storybook
      `
  vitest:
    container_name: vitest
    <<: *common-settings
    command: npm run test --exit

volumes:
  nuxt-node-modules:

```

下記設定の箇所で共通している設定をまとめています。各containerでは`<<: *common-settings`と書いて呼び出します。

```yml:compose.yml
x-common-settings: &common-settings
  build: .
  volumes:
    - .:/frontend:cached
    - nuxt-node-modules:/frontend/node_modules:delegated
  tty: true
```

まず、先述のDockerfileでは`RUN npm install`を行わず、compose.ymlでやっている理由ですが、Dockerfileでは`RUN npm install`を行う場合、ライブラリの更新があった際に下記のようなコマンドを打つ必要があります（あくまで一例になります。実際にはやり方は何通りかあります）

```bash
docker compose run --rm dev_nuxt npm install
docker compose run --rm prod_nuxt npm install
```

個人開発なら問題ないのですが、（会社等で）複数人で開発している場合、PRをレビューする際に上記コマンドの打ち忘れが発生し得るのです。その度に
**レビュワー**「このPR動かないよ」
**筆者**「あ、今回ライブラリの更新があったので更新コマンド打ってください！」
**レビュワー**「了解〜」
みたいな問答をするのも手間です。

そのため、`npm install`はcompose.ymlのcommandに設定しています。
下記記事を参考にさせていただきました。

https://qiita.com/shindex/items/1deb070222d55a7d645d

### Vitest対応

viteベースのvue環境だとvite.config.tsの中に書いたりもしますが、nuxtではnuxt.config.tsがあるのでtestに関する設定のみを書くvitest.config.tsを作成します。

happy-domを使うのも良かったんですが、安定性重視でjsdom採用としました。
エイリアスは必要に応じて追加してください。

あとはtest配下に.test.tsか.spec.tsのテストファイルを作成するだけです。

```ts:vitest.config.ts
// <reference types="vitest">
import { defineConfig } from 'vitest/config'
import Vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { Vuetify3Resolver } from 'unplugin-vue-components/resolvers'
import { fileURLToPath } from 'node:url'

export default defineConfig({
  plugins: [
    Vue(),
    AutoImport({
      dirs: ['utils', 'composables'],
      imports: ['vue', 'vue-router', 'vitest'],
      dts: 'test/auto-imports.d.ts'
    }),
    Components({
      dirs: ['components'],
      resolvers: [Vuetify3Resolver()],
      directoryAsNamespace: true,
      dts: 'test/components.d.ts'
    })
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./', import.meta.url)),
      '~': fileURLToPath(new URL('./', import.meta.url)),
    }
  },
  test: {
    root: '.',
    globals: true,
    environment: 'jsdom',
    coverage: {
      provider: 'v8',
      enabled: true
    }
  }
})
```

### Storybook対応

今回の更新作業の中で一番時間が掛かりました。。。
なぜ時間が掛かったのかの詳細は別記事を書くつもりなのでそちらに譲るとして、ここでは概要のみまとめます。

Storybookには基本的に公式から提供されている各フレームワーク用のライブラリがあります。
![storybook-why-page.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2769277/70cd3314-7774-ea29-21ba-a0a2685492cb.png)
しかし、Nuxt3は唯一主要webフレームワークの中でStorybook公式ライブラリが提供されていません。

そのため、自力で環境をセットアップする必要があります。

https://github.com/storybookjs/storybook-nuxt

↑こちらのライブラリを使うことにしました。

READMEを拝見するとStackbliz上で動くDemoも複数用意されています。（ありがたい）

そして入れてみてなんやかんやした結果、

1. Nuxtのversionは最新だけどStorybookは入っていない「Nuxt3-template」repository
2. Nuxtのversionは3.6.5止まりかつ、パッケージマネージャはpnpm限定だけどStorybookは動く「Nuxt3-template-with-storybook」repository

の二つに分けて管理することになりました。

以降「Nuxt3-template-with-storybook」においての話となります。

まずは設定ファイルからです。
元々vitestの設定と同じように`unplugin-auto-import`や`unplugin-vue-components`を使ってこねくり回していましたが`@storybook-vue/nuxt`を導入してからは必要なくなりました。

```typescript:main.ts
import type { StorybookConfig } from '@storybook-vue/nuxt'
const config: StorybookConfig = {
  stories: [
    '../stories/**/*.mdx',
    '../stories/**/*.stories.@(js|jsx|ts|tsx)',
    '../components/**/*.mdx',
    '../components/**/*.stories.@(js|jsx|ts|tsx)',
  ],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions'
  ],
  framework: {
    name: '@storybook-vue/nuxt',
    options: {}
  },
  docs: { autodocs: 'tag' }
}
export default config
```

```typescript:preview.ts
import { type Preview } from '@storybook/vue3'

const preview: Preview = {
  parameters: {
    actions: { argTypesRegex: '^on[A-Z].*' },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/
      }
    }
  }
}

export default preview
```

以上となります。
この記事で紹介した二つのテンプレートリポジトリは継続的にキャッチアップして更新していきますのでまた大きな変化があったら記事にまとめたいと思います。