本記事は 電通国際情報サービス Advent Calendar 2022 の8日目の記事です。 執筆者は X イノベーション 本部 AI トランスフォーメーションセンター所属の山田です。 この記事では先日(2022年11月中旬)に安定版がリリースされた Nuxt 3 について紹介します。 なお、執筆時点での私たちのチームでは、本番プロジェクトへの Nuxt 3 の導入には至っていません。 本記事は、あくまで技術調査をしながら簡単なアプリケーションを開発してみた内容をまとめたものになります。 本記事で紹介する ソースコード は以下の リポジトリ で公開しています。 github.com Nuxt 3 について 開発環境 Nuxt 3 のプロジェクト作成と TypeScript の設定 TypeScript の設定 ESLint の設定 簡単なアプリケーションを開発してみる コンポーネントの設計 pages/index.vueの作成 app.vue の削除 API 呼び出しでのデータ取得 動作確認 コンポーネントの作成 CatCard コンポーネントの作成 CatCardList コンポーネントの作成 pages/index.vueから作成したコンポーネントを呼びだす テスト 準備 コンポーネントレベルのテスト Vitest の VSCode プラグインとデバッグ まとめ Nuxt 3 について はじめに、簡単に Nuxt 3 について紹介しておきます。 Nuxt は Vue.js の フレームワーク です。 冒頭でも述べましたが、2022年11月中旬に Nuxt 3 の安定版がついにリリースされました。 Announcing Nuxt 3.0 stable、 https://nuxt.com/v3 Nuxt 3 のリリースで大きいのは Vue 3 の正式なサポートです。 Vue 3 は 2020年9月にリリースされていましたが、Nuxt 2 ではサポートされていませんでした。 Vue 3 から追加された Compositon API などを利用するために、Nuxt 2 のプロジェクトで Nuxt Composition API などのパッケージを利用している人も多かったのではないでしょうか。 今回リリースされた Nuxt 3 は Vue 3 も公式にサポートしていますし、サーバエンジンも Nitro に刷新され、パフォーマンスも向上しています。 開発環境 本記事での利用した開発環境の情報を記載します。 開発環境/ IDE : GitHub Codespaces リージョン:Southeast Asia スペック:CPU 4コア、メモリ 8 GB Node.js のバージョン管理ツール:nvm Node.js バージョン:v18.12.1 パッケージマネージャー・バージョン:Yarn / 1.22.19 ブラウザ: Google Chrome せっかくなので、 GitHub Codespaces を用いてみました。 Node.js のバージョン管理ソフトには GitHub Codespaces 上でデフォルトでインストールされていた nvm を利用しています。 Node.js のバージョンは LTS の最新バージョンである v18 系を、パッケージマネージャーには Yarn を使っています。 VSCode の 拡張機能 は以下のものを利用します。 Volar Vue Language Features TypeScript Vue Plugin ESLint Chrome の 拡張機能 は以下のものを利用します。 Vue.js devtools Nuxt 3 のプロジェクト作成と TypeScript の設定 Nuxt 3のプロジェクト作成は npx nuxi init コマンドで行います。 npx nuxi init ${ プロジェクト名 } プロジェクト作成段階でできる ディレクト リ構成は以下のようになります。 プロジェクト名 ├── README.md ├── app.vue ├── nuxt.config.ts ├── package.json └── tsconfig.json TypeScript の設定 Nuxt はデフォルトだと TypeScript の厳格な型チェックが有効になっていないので有効にしましょう。 まずは TypeScript と vue- tsc を devDependency に追加します。 yarn add -D typescript vue-tsc 次に nuxt.config.ts を以下のように編集します。 // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig ( { typescript: { shim: false , strict: true , typeCheck: true } , } ); shim: false としているのは、 VSCode で TypeScript Vue Plugin (Volar) を利用するためです。 strict: true にすることで、型チェックが厳格になります。 typeCheck: true としているのは、開発時から型チェックを有効にするためです。Nuxt ではビルドパフォーマンスを最適化するためにデフォルトでは型チェックが行われません。 このあたりの詳細については以下の公式ドキュメントを参照してください。 Nuxt - Installation、 https://nuxt.com/docs/getting-started/installation Nuxt - type-checking、 https://nuxt.com/docs/guide/concepts/typescript また Nuxt 3 からは CLI での型チェックが実行できます。 yarn nuxi typecheck もしも開発中のホットリロード時にビルドパフォーマンスを求めるならば、 typeCheck: false として CLI での手動でのチェックという選択肢もあります。 以下のように npm スクリプト に typecheck コマンドを登録しておいてもいいかもしれません。 { " scripts ": { " build ": " nuxt build ", " dev ": " nuxt dev ", " generate ": " nuxt generate ", " preview ": " nuxt preview ", " postinstall ": " nuxt prepare ", " typecheck ": " nuxt typecheck " } } ESLint の設定 次に ESLint の設定をしましょう。 Nuxt では VSCode で開発する際には ESLint プラグイン を利用することが推奨されています。 注意点として Prettier は無効にすることが勧められています。 Nuxt - use-eslint、 https://nuxt.com/docs/community/contribution#use-eslint ということで、まずは ESLint を devDependency に追加します。 yarn add -D @nuxtjs/eslint-config eslint そして ESLint の設定ファイル .eslintrc を作成します。 touch .eslintrc .eslintrc は以下のように記述します。 { " extends ": [ " @nuxtjs/eslint-config-typescript " ] } すでに Prettier を導入している開発者に配慮して、Prettier によるフォーマットがかからないようにしましょう。 .prettierignore を用意し、プロジェクト内のすべてのファイルをフォーマット対象外にしてしまいましょう。 以下が .prettierignore の記述内容になります。 # プロジェクト内のすべてのファイルをPrettierの対象外にする ** .vscode/settings.json を設定し、保存時に ESLint によるフォーマットがかかるようにしましょう。 { " editor.codeActionsOnSave ": { " source.fixAll ": false , " source.fixAll.eslint ": true } } 最後に npm スクリプト に lint コマンドを登録しておきましょう。 { " scripts ": { " build ": " nuxt build ", " dev ": " nuxt dev ", " generate ": " nuxt generate ", " preview ": " nuxt preview ", " postinstall ": " nuxt prepare ", " typecheck ": " nuxt typecheck ", " lint ": " eslint --ext .ts,.js,.vue . " , }, } これにより以下のコマンドで ESLint によるコードの静的解析、フォーマットを実施できます。 # 静的解析 yarn lint # フォーマット yarn lint --fix 簡単なアプリケーションを開発してみる 環境が整ったので、ここからは簡単なアプリケーションを開発しながら Nuxt 3 について触れていきましょう。 今回は The Cat API を利用した、猫を愛でることができるアプリケーションを作ります。 The Cat API - Cats as a Service. https://thecatapi.com/ The Cat API では API キーがなくても、10枚まではランダムに猫の画像が取得できます。 完成形のイメージとしては、取得した猫画像をグリッド形式で表示するアプリケーションとします。 コンポーネント の設計 はじめに コンポーネント を設計しましょう。 Vue.js は コンポーネント 指向な フレームワーク ですので、画面デザインから コンポーネント を設計しておくのが大事です。 画像出典: コンポーネント の基本 - コンポーネント の構成、 https://v3.ja.vuejs.org/guide/component-basics.html 今回は手始めに以下のような コンポーネント 構成を考えましょう。 index.vue … ページ コンポーネント 。 API 呼び出しはここで行う。 CatCardList.vue … 猫画像のリストを表示する コンポーネント CatCard.vue … 正方形の猫画像1枚を表示する コンポーネント ツリー形式で可視化すると以下のようになります。 pages/index.vue の作成 はじめに pages コンポーネント の index.vue の追加しましょう。 Nuxt 3 では nuxi add コマンドで簡単に pages コンポーネント を追加できます。 npx nuxi add page index 作成時点では以下のような形になっています。 <script lang="ts" setup></script> <template> <div> Page: foo </div> </template> <style scoped></style> Nuxt 2 までの単一ファイル コンポーネント 内では、 template script style という順番で記述されるのが一般的でしたが、Nuxt 3では script template style の順で記述するのが一般的なようです。 また script 部分は <script setup> 構文での記述がベースとなっています。 <script setup> 構文の詳細については Vue.js 3 の公式ドキュメントを参照してください。 Vue.js - SFC <script setup> 、 https://v3.ja.vuejs.org/api/sfc-script-setup.html app.vue の削除 Nuxt では pages 配下の Vue ファイルでアプリケーションのルーティングが生成されます。 そのため、プロジェクト作成時点でのエントリポイントである app.vue は pages コンポーネント を作成後は不要になるため削除します。 rm app.vue API 呼び出しでのデータ取得 pages/index.vue で The Cat API を呼び出して、データを取得する処理を記述します。 外部 API 呼び出しには Nuxt 組込みの useFetch を利用します。 useFetch、 https://nuxt.com/docs/api/composables/use-fetch 呼び出し前に取得するデータの型を定義しておきましょう。 今回、利用する The Cat API のレスポンスは以下のようになっています。 [ { " id ": " 5ni ", " url ": " https://cdn2.thecatapi.com/images/5ni.jpg ", " width ": 500 , " height ": 375 } ] https://api.thecatapi.com/v1/images/search この情報をもとに型情報を定義しましょう。 型定義は types ディレクト リ配下に記述することにします。 # typesディレクトリの作成 mkdir types touch types/index.ts interface でレスポンスの型を定義します。 export interface CatResponse { id: string ; url: string ; width: number ; height: number ; } そして定義した型情報をインポートしつつ、 useFetch での API 呼び出しのコードを記述します。 <script lang="ts" setup> import { CatResponse } from '~/types' const { pending, error, data } = useFetch<CatResponse[]>( 'https://api.thecatapi.com/v1/images/search?limit=10' ) </script> <template> <div> Page: foo </div> </template> <style scoped></style> useFetch を利用する際に、インポート文を書いていないことに違和感がある方もいるのではないでしょうか? Nuxt 3 では、Auto Imports 機能があり、Vue.js標準の ref や computed Nuxt 組込みの useFetch などの関数を明示的にインポートすることなく記述できます。 Nuxt - auto-imports、 https://nuxt.com/docs/guide/concepts/auto-imports 動作確認 ここまででアプリケーションを起動し、 Chrome の Vue.js devtoolsを確認しましょう。 # 開発サーバでアプリケーションを起動 yarn dev Chrome の Vue.js devtoolsを確認すると、きちんとデータ取得できていることがわかります。 コンポーネント の作成 続いて コンポーネント を作成していきましょう。 コンポーネント も nuxi add コマンドで簡単に追加できます。 # CatCardListコンポーネントの作成 npx nuxi add component CatCardList # CatCardコンポーネントの作成 npx nuxi add component CatCard CatCard コンポーネント の作成 まずはより小さい階層の CatCard コンポーネント を作成します。 CatCard コンポーネント では、 props を介して1つの CatResponse オブジェクトを受け取ります。 そして画像のURLを <img> タグの src に渡します。 以下が CatCard コンポーネント のコードになります。 <script lang="ts" setup> import { CatResponse } from '~/types' interface Props { catData: CatResponse; } defineProps<Props>() </script> <template> <div> <img class="card-img" :src="catData.url" alt="cute cat" :data-testid="catData.id" > </div> </template> <style scoped> .card-img { border-radius: 8px; width: 320px; height: 320px; object-fit: cover; } </style> CatCardList コンポーネント の作成 次に CatCardList コンポーネント を作成します。 CatCardList コンポーネント では、 props を介して CatResponse オブジェクトの配列受け取ります。 そして v-for 構文で配列を展開し、 CatCard コンポーネント を複数描画します。 以下が CatCardList コンポーネント のコードになります。 <script lang="ts" setup> import { CatResponse } from '~/types' interface Props { catList: CatResponse[]; } defineProps<Props>() </script> <template> <div class="card-list" data-testid="cat-list"> <cat-card v-for="(cat, i) in catList" :key="i" :cat-data="cat" /> </div> </template> <style scoped> .card-list { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; } </style> pages/index.vue から作成した コンポーネント を呼びだす 作成した CatCardList コンポーネント を index.vue から呼び出します。 コンポーネント についても Nuxt 側での Auto Imports が有効になっているため、明示的にインポートする必要はありません。 <script lang="ts" setup> import { CatResponse } from '~/types' const { pending, error, data } = useFetch<CatResponse[]>( 'https://api.thecatapi.com/v1/images/search?limit=10' ) </script> <template> <div v-if="!pending && data" class="content"> <cat-card-list :cat-list="data" /> </div> </template> <style scoped> .content { margin: auto; } </style> CatCardList は data の値をプロパティにわたす必要があるため v-if で条件付き レンダリング にします。 あとは、少しレイアウトを整えれば以下のようなアプリケーションが完成します。 isid.github.io テスト 最後にテストについても触れましょう。 Vue.js 3 系からはテストツールとして Vitest が推奨されています。 Vue.js - テスト #推奨事項、 https://ja.vuejs.org/guide/scaling-up/testing.html#recommendation Nuxt 3 の公式ドキュメントではテスト用ツールに @nuxt/test-utils-edge が紹介されていますが、こちらは開発中で不安定です。 なので今回は コンポーネント レベルのテストだけを実施する Vue Testing Library(@testing-library/vue) を用いる方法を紹介します。 なお Vue.js のテストツールとしては、 Vue Test Utils もありますが、 コンポーネント レベルのテストでは @testing-library/vue の利用が推奨されています。 Vue.js - テスト コンポーネント のテスト#推奨事項、 https://ja.vuejs.org/guide/scaling-up/testing.html#recommendation-1 準備 まずは必要なパッケージをインストールします。 テスト時に コンポーネント を描画するDOM環境には happy-dom を使用します。 yarn add -D vitest @testing-library/vue happy-dom Vitest の設定ファイル vitest.config.ts を作成します。 /// <reference types="vitest" /> import { defineConfig } from 'vitest/config' import Vue from '@vitejs/plugin-vue' export default defineConfig ( { plugins: [ Vue () ] , resolve: { alias: { '~' : ` ${__dirname} ` } } , test: { root: '.' , globals: true , environment: 'happy-dom' } } ) npm スクリプト に test コマンドを追加します。 { " scripts ": { " build ": " nuxt build ", " dev ": " nuxt dev ", " generate ": " nuxt generate ", " preview ": " nuxt preview ", " postinstall ": " nuxt prepare ", " typecheck ": " nuxt typecheck ", " lint ": " eslint --ext .ts,.js,.vue . ", " test ": " vitest " , }, } コンポーネント レベルのテスト CatCard コンポーネント のテストを記述しましょう。 CatResponse 形式のオブジェクトを props に渡した際に、 コンポーネント が描画できるかテストします。 テストコードは tests ディレクト リ配下に *.spec.ts の形で配置します。 @testing-library/vue でのテストコードは以下のようなります。 コンポーネント が描画できているかの判断には data-testid 属性を使っています。 import { describe , expect , test } from 'vitest' import { render } from '@testing-library/vue' import CatCard from '~/components/CatCard.vue' import { CatResponse } from '~/types' describe ( 'CatCard' , () => { test ( 'コンポーネントの描画ができること' , () => { const catData: CatResponse = { id: 'test' , url: 'https://example.com' , width: 100 , height: 100 } const { getAllByTestId , html } = render ( CatCard , { props: { catData } } ) const results = getAllByTestId ( catData.id ) expect ( results.length ) .toBe ( 1 ) expect ( html ()) .contain ( `data-testid=" ${ catData.id } "` ) } ) } ) テストを実行してみましょう。 yarn test きちんとテストが通れば、以下の出力が得られます。 RERUN tests/components/CatCard.spec.ts x20 ✓ tests/components/CatCard.spec.ts (1) Test Files 1 passed (1) Tests 1 passed (1) Start at 09:40:00 Duration 230ms PASS Waiting for file changes... press h to show help, press q to quit Vitest の VSCode プラグイン と デバッグ テストコードを書くことによって、小さい範囲でコードを動かせるようになりました。 さらに デバッグ もできると開発がより捗ります。 Vitest では公式で VSCode の プラグイン をリリースしており、こちらを活用することでテストコードをもとにデバッガーを起動できます。 Vitest、 https://marketplace.visualstudio.com/items?itemName=ZixuanChen.vitest-explorer VItes - IDE Integrations 、 https://vitest.dev/guide/ide.html テストコードに ブレークポイント を仕込んで、テストタブから「テストの デバッグ 」を選択するとデバッガーが起動できます。 これで開発環境はバッチリですね。 まとめ 本記事では、Nuxt 3 について紹介しました。 今回、記事を書きながら 、これまでの Nuxt の良さを引き継ぎながら正当に進化していると感じました。 nuxi コマンドの充実や TypeScript、 VSCode との親和性の向上、Nitroによるビルド速度の向上など開発がより楽しくなる可能性を感じました。 一方でテストの部分はまだ不安定だと言わざるを得ません。 Nuxt 側の Auto Imports 機能と Vitest の連携がうまくいかなかったり @nuxt/test-utils-edge モジュールが開発中であるため Nuxt 3 のアプリケーションをテストする方法が確立していません。 support for unit testing in a nuxt environment #2465、 https://github.com/nuxt/framework/issues/2465 Nuxt - Testing、 https://nuxt.com/docs/getting-started/testing#testing 直近で Nuxt 3 の導入を検討しているならば、この部分には注意をする必要があります。 最後に、私たちAIトランスフォーメーションセンターでは、一緒に働いてくれる仲間を探しています。 AI 製品開発に興味がある方のご応募をお待ちしております。 AIエンジニア(プロダクト開発) AIプロダクトマネージャー AIコンサルタント AIビジネスプロジェクトマネージャー 執筆: @yamada.y 、レビュー: 寺山 輝 (@terayama.akira) ( Shodo で執筆されました )