create-vueはVue公式のVueプロジェクト作成ライブラリです。npm init vue@latestコマンドを実行することでVueプロジェクトの骨組み(scaffolding)の作成とVueのアプリケーション構築に欠かすことできない機能を選択してプロジェクトに追加を行うことができます。本文書はVueの入門者の人がcreate-vueを利用してプロジェクトを作成する際に追加できるすべての機能がどのような役割を持っているのか理解できることを目的にしています。各機能の理解が進むことで機能追加時に迷うことなく自信を持って機能の選択を行うことができます。create-vueではビルドツールとして現在人気急上昇中のViteが利用されているのでViteについて動作確認をしながら説明を行なっているので開発環境と本番環境でのビルドツールの違いなども理解することができます。

VueのプロジェクトはVue CLIというツールで作成することもできますがcreate-vueが登場したことによりメンテナンスモードになりました。Vue3のプロジェクトを新たに作成する場合はcreate-vueを利用することが推奨されています。create-vueはVueCLIに比較すると高速に動作します。

create-vueによるプロジェクトの作成

npm init vue@latestコマンドを実行するとプロジェクト名の設定だけではなくTypeScriptをはじめアプリケーションを構築する上で利用頻度の高い以下の8つの機能をプロジェクトに追加するかどうかプロジェクト作成者が選択することができます。どの機能も追加しないということも可能です。各機能については個別に追加を行いどのような機能なのか説明を行っているので知らない機能があったとしても安心して読み進めてください。本文書を読み終えた時には使いこなせるかは別として各機能がどのような機能なのかは最低限理解できるはずです。各機能を使いこなすためには個別の機能について学習する必要があります。

  • TypeScript
  • JSX Support
  • Vue Router
  • Pinia
  • Vitest
  • Cypress
  • ESLint
  • Prettier

※PrettierについてはESLint機能を追加した場合に追加できる機能として表示されます。

npmコマンドを実行するためにはNode.js, npmがインストールされている必要があります。

追加機能を選択しない場合

Vueのみインストールしたい場合には機能の追加を行わないことも可能です。

ここでは機能の追加を行わずVueのみインストールを行います。Vueのみインストールすることでプロジェクトのデフォルトのフォルダ/ファイル構成、ブラウザ上に表示されるコンテンツの表示までの流れ、create-vueで利用されているViteとはどのようなものなのかを実際に動作を行いながら確認をしていきます。

npm init vue@latestを実行するとプロジェクトの作成が開始されます。最初にプロジェクト名を聞かれるので任意の名前のプロジェクト名を入力してください。その後機能毎にプロジェクトのに追加するかどうか聞かれます。ここではすべてNoを選択します。


 % npm init vue@latest

Vue.js - The Progressive JavaScript Framework

 Project name: … vue3_app
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add Cypress for both Unit and End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes

Scaffolding project in /Users/mac/Desktop/vue3_app...

Done. Now run:

  cd vue3_app
  npm install
  npm run dev

プロジェクトの作成が完了したらプロジェクトフォルダに移動してnpm installコマンドを実行します。npm installコマンド実行後のフォルダ構成は以下のようになっています。Viteが利用されているのでvite.config.jsファイルが存在することが確認することができます。

npm install直後のフォルダ構成
npm install直後のフォルダ構成

npm installが完了しnpm run devコマンドを実行すると開発サーバを起動することができます。ブラウザから起動した開発サーバ(http://localhost:5173)にアクセスすると初期画面が表示されます。

create-vueで作成した場合の初期画面
create-vueで作成した場合の初期画面
viteのバージョンを3を利用している場合は開発サーバのデフォルトのポート番号が5173に変更されています。バージョンによってポート番号が異なります。

画面のToolingの箇所にも記述されていますがVSCodeを利用している場合は拡張機能にVolarをインストールしましょう。

表示内容の確認

create-vueで作成されたVueプロジェクトの理解を深めるために初期画面に表示されているコンテンツがどのファイルに記述されているのか確認していきます。

ブラウザから開発サーバにアクセスすると通常のWEBサーバと同様にindex.htmlファイルが読み込まれるのでindex.htmlファイルを確認します。index.htmlファイルの中にはブラウザ上に表示されているコンテンツの記述はありませんがscriptタグでmain.jsファイルが指定され、bodyタグの中でid属性にappを持つdiv要素を確認することができます。この2つがVueを動作させるために重要な役割を果たします。またscriptタグのtype属性にmoduleが設定されていることも覚えておいてください。


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

scriptタグで指定されている/src/main.jsファイルを確認します。main.jsファイルの中ではcreateApp関数をvueからimportしその引数にはimportしたAppコンポーネントを設定しています。さらにmountメソッドで#appを指定しています。


import { createApp } from 'vue'
import App from './App.vue'

import './assets/main.css'

createApp(App).mount('#app')

mountメソッドで指定した#appはindex.htmlファイルで確認したid属性のappと一致する必要があります。main.jsで指定されたappがcreateAppで作成されるコンテンツを挿入させる場所になります。コンテンツが挿入されるとは何かも後ほど確認します。appの値が異なったり存在しない場合はVueのコンテンツがブラウザ上に表示させることはありません。appは任意の値に設定することができますがmount関数で指定した値をdiv要素が持つidの値と一致する必要があります。

次にcreateAppの引数に指定されているAppコンポーネントの内容を確認します。styleタグの中のコードは少し長いの一部省略しています。


<script setup>
import HelloWorld from './components/HelloWorld.vue'
import TheWelcome from './components/TheWelcome.vue'
</script>

<template>
  <header>
    <img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" />

    <div class="wrapper">
      <HelloWorld msg="You did it!" />
    </div>
  </header>

  <main>
    <TheWelcome />
  </main>
</template>

<style scoped>
header {
  line-height: 1.5;
}

.logo {
  display: block;
  margin: 0 auto 2rem;
}

//略
</style>

scriptタグにsetupが設定されている形式に慣れていない人もいるかもしれません。create-vueではComposition APIという方法でコードを記述しています。Composition APIの記述に慣れていない人は下記の公開済みの文書がお勧めです。

Appコンポーネントの中ではさらにHelloWorldコンポーネントとTheWelcomeコンポーネントがimportされていることが確認できます。HelloWorldコンポーネントにはPropsを利用して”You did it!”の文字列が渡されていることがわかります。

HelloWorld.vueファイルを確認するとブラウザ上に表示されている”You’ve successfully….”などの文字列を確認することができます。このファイルの内容がブラウザ上に表示されていることがわかります。


<script setup>
import HelloWorld from './components/HelloWorld.vue'
import TheWelcome from './components/TheWelcome.vue'
</script>

<template>
  <header>
    <img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" />

    <div class="wrapper">
      <HelloWorld msg="You did it!" />
    </div>
  </header>

  <main>
    <TheWelcome />
  </main>
</template>

<style scoped>
header {
  line-height: 1.5;
}

.logo {
  display: block;
  margin: 0 auto 2rem;
}

@media (min-width: 1024px) {
  header {
    display: flex;
    place-items: center;
    padding-right: calc(var(--section-gap) / 2);
  }

  .logo {
    margin: 0 2rem 0 0;
  }

  header .wrapper {
    display: flex;
    place-items: flex-start;
    flex-wrap: wrap;
  }
}
</style>

ファイルの中身をここでは記述していませんがTheWelcome.vueファイルを確認すると初期画面右側の内容が含まれていることが確認できます。

このようにVueをインストール直後に表示される画面は複数のコンポーネントを組み合わせることで一つの画面を作成していることがわかります。

コンテンツの挿入の確認

id属性にappを持つdiv要素の間にcreateAppで作成されたコンテンツが挿入されているか確認するためにブラウザのデベロッパーツールで確認します。

index.htmlファイルのdiv要素の間には何も入っていませんでしたがデベロッパーツールを確認することでdiv要素の間にコンテンツが入っていることが確認できます。

デベロッパーツールで見た内容
デベロッパーツールで見た内容

デベロッパーツールで見るとコンテンツを確認することができますがソースを見るとindex.htmlファイルの内容と同じなのでdiv要素の間には何もありません。

ブラウザのソースを確認
ブラウザのソースを確認

このことから画面上に表示されている内容はmain.jsに記述されているVue(JavaScriptのコード)を利用して動的にdiv要素の間にコンテンツが挿入されていることがわかります。ブラウザが開発サーバにアクセス後にindex.htmlを受け取りindex.htmlファイルに設定されているmain.jsファイルをダウンロードしてブラウザ上で実行することでdiv要素の間にコンテンツが挿入されます。

ブラウザ上からmain.jsファイルの内容を確認します。srcフォルダのmain.jsファイルを指定しているのでmain.jsの中にコンテンツの内容がすべて含まれているわけではありません。

main.jsファイルの内容の確認
main.jsファイルの内容の確認

ここでindex.htmlファイルで確認したscriptタグのtype属性のmoduleを削除します。削除するとブラウザのデベロッパーツールのコンソールに”Uncaught SyntaxError: Cannot use import statement outside a module”メッセージが表示されブラウザ上の画面には何も表示されません。type属性にmoduleを設定していることでmain.jsのimportが実行されAppが読み込まれることでブラウザ上にコンテンツが表示されることがわかります。import機能をサポートしてないブラウザでmain.jsが実行された場合はコンテンツが表示されることはありません。

buildの実行

Production(本番)で利用する場合はこれまで実行していたnpm run dev(開発)コマンドではなくnpm run buildコマンドを実行します。実行できるコマンドについてはpackage.jsonファイルの中で確認することができます。


{
  "name": "vue3_app",
  "version": "0.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview --port 4173"
  },
  "dependencies": {
    "vue": "^3.2.37"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^3.0.1",
    "vite": "^3.0.4"
  }
}

npm run buildコマンドを実行してnpm run devとの違いを確認します。npm run buildコマンドを実行するとプロジェクトフォルダの直下にdistフォルダが作成されindex.htmlファイルを含めjs、cssファイルが作成されます。srcフォルダの中に記述したコードから作成されます。


 % npm run build

> vue3_app@0.0.0 build
> vite build

vite v3.0.5 building for production...
✓ 23 modules transformed.
dist/assets/logo.da9b9095.svg    0.30 KiB
dist/index.html                  0.42 KiB
dist/assets/index.dd55cde3.css   3.59 KiB / gzip: 1.17 KiB
dist/assets/index.bf3237b2.js    61.05 KiB / gzip: 24.34 KiB

distフォルダに作成されたindex.htmlファイルの中身を見るとsrcフォルダの下に存在するindex.htmlファイルとは内容が異なることがわかります。scriptタグ, linkタグで指定されているファイルはbuild実行時に作成されたファイルとなっています。


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
    <script type="module" crossorigin src="/assets/index.bf3237b2.js"></script>
    <link rel="stylesheet" href="/assets/index.dd55cde3.css">
  </head>
  <body>
    <div id="app"></div>
    
  </body>
</html>

本番用にビルドした内容の確認はpackage.jsonファイルに記述されているもう一つのコマンドnpm run previewコマンドを実行して起動するサーバで確認することができます。


 % npm run preview

> vue3_app@0.0.0 preview
> vite preview --port 4173

  ➜  Local:   http://localhost:4173/
  ➜  Network: use --host to expose

ブラウザでhttp://localhost:4173/にアクセスするとnpm run devを実行した時と同じ内容が表示されます。

previewコマンドでページの内容を確認
previewコマンドでページの内容を確認

ブラウザのデベロッパーツールで確認するとid属性にappを持つdiv要素の間にコンテンツが挿入されていることは同じです。

previewの場合のデベロッパーツールの内容
previewの場合のデベロッパーツールの内容

ソースも確認しておきましょう。

previewで表示されている内容のソース
previewで表示されている内容のソース

distフォルダのindex.htmlファイルの内容と同じ内容であることがわかります。scriptタグに指定されている/assets/index.35a612e4.jsを確認していみましょう。npm run devの開発環境のmain.jsファイルとは明らかに異なります。開発環境のmain.jsではimportでAppを読み込んでいましたが本番環境ではmain.jsファイルの中にはブラウザ上に表示されている内容が含まれています。ブラウザ上に表示されている”You did it”を検索するとファイルの中に含まれていることがわかります。右下の色が付いている部分が”You did it”です。

previewのJavaScriptファイルの中身を確認
previewのJavaScriptファイルの中身を確認

ここまでの確認で表示されている内容は同じでもnpm run devとnpm run buildでは全く異なる仕組みでブラウザ上にコンテンツを表示していることがわかります。create-vueで利用しているViteでは開発環境と本番環境で異なるビルドツールを利用しています。この違いが確認した内容の違いに反映されています。

開発環境はES modulesを利用しているため必要なモジュール(ファイル)のみネットワーク越しでブラウザに送信するため1つのファイルにすべてのファイルをバンドルするという処理を行っていません。大きな依存関係がある場合はesbuildを利用してprebundlingを行うためネットワークのリクエストの回数を減らし爆速でビルドが行われます。本番環境では先ほど確認したようにすべてのファイルを1つのファイルにバンドルするためにビルドにrollupを利用しています。すべてのファイルを1つのファイルにバンドルするため処理に時間がかかります。

esbuildはプログラミング言語のGoで記述されています。JavaScriptで記述している他のビルドツールに比較して高速に動作します。

ES modulesを利用した場合にネットワーク越しにファイルを送信すると説明しましたが開発環境(npm run dev)でデベロッパーツールのネットワークタブを確認することでモジュール単位(ファイル単位)でネットワーク越しにファイルを送信していることがわかります。ネットワークタブには初期画面を構成しているコンポーネントの内容が含まれるHelloWorld.vueやTheWelcom.vueファイルを確認することができます。

ネットワークタグでファイルの送信を確認
ネットワークタグでファイルの送信を確認

ファイル名がHelloWorld.vueになっているからといってVueで記述したコードをそのまま送信しているわけではありません。プレビューで送信した内容を確認するとファイルに記述した内容ではなくブラウザが認識できる形への変換も行われていることもわかります。

HelloWorld.vueの送信されている中身
HelloWorld.vueの送信されている中身

ここまでの確認で開発環境と本番環境でのビルドの違いが明確になったかと思います。Vueのみをインストールした場合に作成されるプロジェクトの理解が進んだので各機能をインストールした場合の動作確認を行っていきます。

TypeScript機能を追加

TypeScriptには名前にTypeが含まれている通りTypeScriptの機能を利用することでJavaScriptでType(型)の指定が利用できるようになります。

npm init vue@latestコマンドでプロジェクト名を設定した後にTypeScriptのみYesを選択します。


 % npm init vue@latest

Vue.js - The Progressive JavaScript Framework

✔ Project name: … typescript_vue3_app
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add Cypress for both Unit and End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes

Scaffolding project in /Users/mac/Desktop/typescript_vue3_app...

Done. Now run:

  cd typescript_vue3_app
  npm install
  npm run dev

作成されたフォルダに移動してnpm installコマンドを実行した後のフォルダ構成は下記のようになります。main.jsファイルの名前がmain.tsになり、TypeScriptの設定ファイルtsconfig.jsonやtsconfig.vite-config.jsonファイルなどが作成されていることがわかります。

TypeScriptを追加した場合のフォルダ構成
TypeScriptを追加した場合のフォルダ構成

VSCodeで拡張機能のVolarをインストールしている状態でmain.tsファイルを開くとAppのimportの処理でメッセージが表示されています。メッセージの内容は”Cannot find module ‘./App.vue’ or its corresponding type declarations.”です。

App.vueのimportでメッセージ
App.vueのimportでメッセージ

env.d.tsファイルに以下を追加することでメッセージの表示はなくなります。


declare module '*.vue' {
  import type { DefineComponent } from 'vue';
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
  const component: DefineComponent<{}, {}, any>;
  export default component;
}
上記の設定はcreate-vueではなくViteを利用してTypeScriptを設定した場合に作成されるenv.d.tsファイルの内容をコピーしています。

型チェックが行われるか確認するためにsrc¥componentsにあるHelloWorld.vueファイルに変数yearを追加します。yearに型のnumberを設定しています。数値の2022を設定しても何も問題はありません。


<sript setup lang="ts">
defineProps<{
  msg: string;
}>();
const year: number = 2022;
<script>

しかしnumberという型を設定しているので数値を文字列に変更すると下記のメッセージが表示されます。指定した型と設定した値の型が異なるためです。

型チェックによるメッセージ
型チェックによるメッセージ

エディター(VSCode)の画面上ではメッセージが表示されますがnpm run devコマンドを実行したコンソールにはTypeScriptに関するメッセージが表示されることはありません。VSCodeでメッセージが表示されるのは拡張機能のVolarをインストールしているためです。

コマンドを使って型のチェックを行うためにはnpm run type-checkコマンドを実行します。


% npm run type-script

コマンドを実行すると型チェックが行われVSCodeに表示されていたエラーメッセージと同じ情報が表示されます。


%  npm run typecheck

> typescript_vue3_app@0.0.0 typecheck
> vue-tsc --noEmit

src/components/HelloWorld.vue:5:7 - error TS2322: Type 'string' is not assignable to type 'number'.

5 const year: number = '2022';
        ~~~~


Found 1 error.

本番用にビルドする際にもインストールしたnpm run buildコマンドを実行するとvue-tscによる型のチェックが実行されるようになります。


% npm run build

> typescript_vue3_app@0.0.0 build
> vue-tsc --noEmit && vite build

src/components/HelloWorld.vue:5:7 - error TS2322: Type 'string' is not assignable to type 'number'.

5 const year: number = '2022';
        ~~~~

package.jsonを確認するとtypecheckのコマンドが追加されvue-tscコマンドが設定されています。buildコマンドの場合はvite buildコマンドの前にvue-tsc –noEmitが追加されていることがわかります。


  "scripts": {
    "dev": "vite",
    "build": "run-p type-check build-only",
    "preview": "vite preview --port 4173",
    "build-only": "vite build",
    "type-check": "vue-tsc --noEmit"
  },

create-vueでTypeScript機能を追加した場合は特別な設定を行うことなくTypeScriptを利用できることがわかりました。

TypeScriptの設定を変更したい場合はtsconfig.jsonファイルから行うことができます。


{
  "extends": "@vue/tsconfig/tsconfig.web.json",
  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },

  "references": [
    {
      "path": "./tsconfig.config.json"
    }
  ]
}

JSX Support機能を追加

JSX Support機能をインストールするとReactを利用した経験がある人であるなら馴染みの深いJSXをVueで利用することができます。JSXはJavaScript XMLの略で、HTMLとJavaScriptを1つのファイルに記述することができます。VueのSingle File Components(SFC)のようにscriptタグ、templateタグ、styleタグの3つにわけて記述することはありません。どのようなものかJSX Support機能を追加して動作確認します。

npm init vue@latestコマンドでプロジェクト名を設定した後にJSX SupportのみYesを選択します。


 % npm init vue@latest      

Vue.js - The Progressive JavaScript Framework

✔ Project name: … jsx_vue3_app
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add Cypress for both Unit and End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes

Scaffolding project in /Users/mac/Desktop/jsx_vue3_app...

Done. Now run:

  cd jsx_vue3_app
  npm install
  npm run dev

フォルダ構成などは機能追加をしなかった場合と違いはありませんがpackage.jsonファイルを確認するとplugin-vue-jsxが含まれていることが確認できます。


{
//略
  "devDependencies": {
    "@vitejs/plugin-vue": "^3.0.1",
    "@vitejs/plugin-vue-jsx": "^2.0.0",
    "vite": "^3.0.4"
  }
}

plugin-vue-jsxはvite.config.jsファイルにプラグインとして追加されています。


import { fileURLToPath, URL } from 'url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(), vueJsx()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

はじめてのJSXの記述

JSX Supportを追加後はJSXを利用してコードを記述することができるのでApp.jsxファイルをsrcフォルダの下に作成して以下を記述します。


function App() {
  return <div>Hello World</div>;
}

export default App;

main.jsファイルで読み込むファイルをApp.vueからApp.jsxに変更します。importする際に拡張子のjsxを省略することも可能です。


import { createApp } from 'vue';
import App from './App.jsx';

import './assets/main.css';

createApp(App).mount('#app');

ブラウザで確認すると”Hello World”が表示されます。

JSXで記述したAppコンポーネントを表示
JSXで記述したAppコンポーネントを表示

JSXでコンポーネントを利用

Hello Worldが表示できることが確認できたのでApp.vueに記述している内容をJSXで書き換えます。


import HelloWorld from './components/HelloWorld.vue';
import TheWelcome from './components/TheWelcome.vue';
import logo from './assets/logo.svg';

function App() {
  return (
    <>
      <header>
        <img alt="Vue logo" class="logo" src={logo} width="125" height="125" />

        <div class="wrapper">
          <HelloWorld msg="You did it!" />
        </div>
      </header>
      <main>
        <TheWelcome />
      </main>
    </>
  );
}

export default App;

デザインは少し異なりますがロゴ画像もpropsで渡した”You did it!”も表示されることが確認できます。styleやclassはJSXでも利用することができますが本文書ではJSXが利用できることを説明したいだけなのJSXの詳細説明は省略しています。

JSXで記述したAppコンポーネントの表示
JSXで記述したAppコンポーネントの表示

create-vueでJSX Supportの機能を追加することでコンポーネントをJSXを使って記述できることが確認できました。

Vue Router機能を追加

Vue Routerを利用することでVueを利用してSinge Page Application(SPA)を作成することができます。Vue Router機能が追加されていないデフォルトの状態ではブラウザからどのURLにアクセスしても同じ初期画面しか表示されませんがVue Routerを追加することで/(ルート)以外にアクセスできるパスを追加することができ追加したパスに対して別々の内容を表示させることができます。/aboutにアクセスすればaboutページ、/profileにアクセスすればprofileページを表示させることができます。

npm init vue@latestコマンドでプロジェクト名を設定した後にVue Router for Single Page Application developmentのみYesを選択します。


 % npm init vue@latest

Vue.js - The Progressive JavaScript Framework

✔ Project name: … router_vue3_app
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add Cypress for both Unit and End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes


Scaffolding project in /Users/mac/Desktop/router_vue3_app...

Done. Now run:

  cd router_vue3_app
  npm install
  npm run dev

作成されたフォルダに移動してnpm installコマンドを実行した後のフォルダ構成は下記のようになります。srcフォルダの中にrouterフォルダが作成されその中にindex.jsファイル、viewsフォルダにAboutView.vue, HomeView.vueファイルが作成されていることわかります。

Vue Router機能を追加した場合のフォルダ構成
Vue Router機能を追加した場合のフォルダ構成

npm run devコマンドで開発サーバを起動してlocalhost:5173にアクセスするとこれまでの内容の他にHome | Aboutが追加されていることが確認できます。

Vue Routerを追加した場合の初期ページ
Vue Routerを追加した場合の初期ページ

Aboutはリンクになっているのでクリックすると以下の画面が表示されます。画面が変わるだけではなくアクセスするURLがlocalhost:5173からlocalhost:5173/aboutになっていることも確認できます。

Aboutページにアクセス
Aboutページにアクセス

Vue Routerの設定の確認

Vue Router機能を追加した場合にはどのような設定が行われているのか確認していきます。

main.jsファイルを確認するとプラグインとしてimportされたrouterインスタンスがuseの引数に設定されていることがわかります。


import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

import './assets/main.css'

const app = createApp(App)

app.use(router)

app.mount('#app')

importしているrouterを確認するためにrouterフォルダのindex.jsファイルを確認します。routerのimportではindex.jsが指定されていませんがフォルダを指定するとそのフォルダの中にあるindex.jsファイルがimportされます。

index.jsファイルではcreateRouterの引数にオブジェクトが設定されていますがその中のroutesプロパティを見ると”/”, “/about”の2つのパスが設定されていることがわかります。それぞれの設定にはcomponentプロパティが設定されておりviewフォルダにあるファイルが指定されています。routesプロパティの配列にオブジェクトを追加することで新たなパスを追加することができます。


import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (About.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import('../views/AboutView.vue')
    }
  ]
})

export default router

componetに設定されているviewsフォルダにあるAboutView.vueファイルの中身を確認します。ファイルの中にはlocalhost:5173/aboutにアクセスした時に右側に表示されていた内容のみ記述されています。


<template>
  <div class="about">
    <h1>This is an about page</h1>
  </div>
</template>

<style>
@media (min-width: 1024px) {
  .about {
    min-height: 100vh;
    display: flex;
    align-items: center
  }
}
</style>

左側に表示されていた内容についてはApp.vueに記述されているのでApp.vueファイルを確認します。


<script> setup>
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from '@/components/HelloWorld.vue'
</script>

<template>
  <header>
    <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />

    <div class="wrapper">
      <HelloWorld msg="You did it!" />

      <nav>
        <RouterLink> to="/">Home</RouterLink>
        <RouterLink> to="/about">About</RouterLink>
      </nav>
    </div>
  </header>

  <RouterView />
</template>

<style>>
//略
</style>

AboutView.vueファイルに記述されていた内容はApp.vueファイルのRouterViewタグの場所に表示されることがわかります。また各ページへのリンクはaタグではなくRouterLinkタグで行うこともApp.vueファイルの内容から確認することができます。Vue RouterではRouterViewタグとRouterLinkタグが重要な役割を持っています。

新しいページの追加

Aboutページを参考に新たにProfileページを作成してVue Routerの理解を深めます。

viewsフォルダにあるAboutView.vueファイルを複製してProfileView.vueファイルを作成します。作成したファイルには以下を記述します。


<template>
  <div class="profile">
    <h1>This is a profile page</h1>
  </div>
</template>

<style>
@media (min-width: 1024px) {
  .profile {
    min-height: 100vh;
    display: flex;
    align-items: center;
  }
}
</style>

routerフォルダのindex.jsファイルのroutesプロパティに新たにルーティングパス/profileを追加します。componentには作成したProfileView.vueファイルを指定します。


import { createRouter, createWebHistory } from 'vue-router';
import HomeView from '../views/HomeView.vue';

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView,
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (About.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import('../views/AboutView.vue'),
    },
    {
      path: '/profile',
      name: 'profile',
      component: () => import('../views/ProfileView.vue'),
    },
  ],
});

export default router;

設定完了後、localhost:5173/profileにブラウザからアクセスします。ProfileView.vueファイルに記述した”This is a profile page”が表示されていることが確認できます。

追加したProfileページの確認
追加したProfileページの確認

各ページからリンクでProfileページにアクセスできるようにApp.vueファイルにRouterLinkタグを追加してto属性にProfileページへのパスである/profileを設定します。


<nav>
  <RouterLink to="/">Home</RouterLink>
  <RouterLink to="/about">About</RouterLink>
  <RouterLink to="/profile">Profile</RouterLink>
</nav>

Home, Aboutの横にProfileが追加されていることが確認できます。Vue Routerによる新しいページの追加方法を理解することができました。

RouterLinkで追加したProfileのリンクの確認
RouterLinkで追加したProfileのリンクの確認

複雑なアプリケーションを作成するためにはページのネスト化や動的ページの設定方法などを理解する必要がありますがVue Router機能を追加することで複数ページを持つシングルページアプリケーションを構築することができます。aタグのようにページの遷移毎にページのリロードが行われないのでスムーズにページ移動を行うこともできます。

Pinia機能の追加

Piniaは状態管理ライブラリで、複数のコンポーネントでデータを共有する場合に利用することはできます。これまでのVue公式のVue CLIコマンドでプロジェクトを作成する場合はVuexという名前の状態管理ライブラリが利用されていましたがcreate-vueからはPiniaの利用が推奨されています。

npm init vue@latestコマンドでプロジェクト名を設定した後にPinia for state managementのみYesを選択します。


 % npm init vue@latest


✔ Project name: … pinia_vue3_app
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add Cypress for both Unit and End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes

Scaffolding project in /Users/mac/Desktop/pinia_vue3_app...

Done. Now run:

  cd pinia_vue3_app
  npm install
  npm run dev

作成されたフォルダに移動してnpm installコマンドを実行した後のフォルダ構成は下記のようになります。storesフォルダが作成されその中にcounter.jsファイルが作成されapp.useにpiniaからimportしたcreatePiniaが設定されています。

Pinia機能を追加した場合のフォルダ構成
Pinia機能を追加した場合のフォルダ構成

npm run devコマンドで開発サーバを起動してlocalhost:5173にアクセスしても追加したPiniaに関する情報は何も表示されません。

create-vueで作成した場合の初期画面
piniaを追加した場合の初期画面

Piniaの設定

main.jsファイルを見るとプラグインとしてPiniaが設定されているのでPiniaを利用することができます。


import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

import './assets/main.css'

const app = createApp(App)

app.use(createPinia())

app.mount('#app')

Piniaの設定はstoresフォルダのcounter.jsファイルに記述されているのでcounter.jsファイルを確認します。


import { defineStore } from 'pinia'

export const useCounterStore = defineStore({
  id: 'counter',
  state: () => ({
    counter: 0
  }),
  getters: {
    doubleCount: (state) => state.counter * 2
  },
  actions: {
    increment() {
      this.counter++
    }
  }
}

defineStore関数に設定されているオブジェクトはid, state, getters, actionsのプロパティを持ちidはPinia内で一意に識別するための名前、stateにはcounterとcounterの初期値、gettersにはdoubleCountでstateに設定されたcounterを2倍にする処理が記述されactionsにはincrement関数が設定されてstateのcountの値を1増やす処理が記述されています。

Piniaでは別の変数の状態管理を行いたい場合は別ファイルを作成することで実現することができます。

App.vueファイルを使ってstateのcountの値を表示してみましょう。

storesフォルダのcounter.jsファイルからuseCouterStoreをimportします。useCounterStore関数を実行して戻されるオブジェクトをcounterに保存します。counterにはcounter.jsで定義したstateのcounterやincrement関数が含まれています。templateタグの中でcounterの値はcounter.counterでアクセスすることができます。関数についてはcounter.incrementで実行することができます。


<script setup>
import HelloWorld from './components/HelloWorld.vue';
import TheWelcome from './components/TheWelcome.vue';
import { useCounterStore } from './stores/counter';
const counter = useCounterStore();
</script>

<template>
  <header>
    <img
      alt="Vue logo" class="logo"
      src="./assets/logo.svg"
      width="125" height="125"
    />

    <div class="wrapper">
      <HelloWorld msg="You did it!" />
      <div>
        Count:{{ counter.counter }}
        <button @click="counter.increment">Up</button>
      </div>
    </div>
  </header>

  <main>
    <TheWelcome />
  </main>
</template>

button要素にclickイベントを追加しclickイベントにcounter.incrementを設定します。Upボタンを追加することでボタンをクリックするとincrement関数によってcounterの値が増えるように設定を行っています。

動作確認を行うとボタンをクリックする毎にcounterの値が1増えることが確認できます。

Piniaで設定したcounterの値を表示
Piniaで設定したcounterの値を表示

Piniaで設定したcounterは他のコンポーネントからアクセスすることができます。HelloWorldコンポーネントからアクセスできるか確認します。

HelloWorld.vueファイルではstateのcounter以外にgettersのdoubleCountも表示しています。gettersはVueのcomputedプロパティと同じ働きをします。


<script> setup>
import { useCounterStore } from '../stores/counter';
const counter = useCounterStore();

defineProps({
  msg: {
    type: String,
    required: true,
  },
});
</script>

<template>
  <div class="greetings">
    <h1 class="green">{{ msg }}</h1>
    <div>Count: {{ counter.counter }}/{{ counter.doubleCount }}</div>
    <h3>
      You’ve successfully created a project with
      <a> target="_blank" href="https://vitejs.dev/">Vite</a> +
      <a>. target="_blank" href="https://vuejs.org/">Vue 3</a>.
    </h3>
  </div>
</template>

ボタンを押すとHelloWorldコンポーネントのcounterとdoubleCounterの値も同時に増えていくことが確認できます。HelloWorldコンポーネントのcounterは”You did it!”の直下, Appコンポーネントのcounterは”with Vite + Vue3″の下に表示されています。

HelloWorldコンポーネントでcounterの値を確認
HelloWorldコンポーネントでcounterの値を確認

Pinia機能を利用することですべてのコンポーネントから同じ方法を利用してPiniaで設定した状態を管理できることがわかりました。

Piniaについては下記の文書でも説明を行っているのでぜひ参考にしてください。

Vitest機能の追加

VitestはViteでUnit Testing(単体テスト)を行うための機能です。公式サイトはhttps://vitest.dev/です。

npm init vue@latestコマンドでプロジェクト名を設定した後にVitest for Unit TestingのみYesを選択します。


 % npm init vue@latest

Vue.js - The Progressive JavaScript Framework

✔ Project name: … vitest_vue3_app
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add Cypress for End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes

Scaffolding project in /Users/mac/Desktop/vitest_vue3_app...

Done. Now run:

  cd vitest_vue3_app
  npm install
  npm run dev

作成されたフォルダに移動してnpm installコマンドを実行した後のフォルダ構成は下記のようになります。componentsフォルダの中に__tests__フォルダが作成されその中にHelloWorld.spec.jsファイルが作成されています。

Vitest機能を追加した場合のフォルダ構成
Vitest機能を追加した場合のフォルダ構成

package.jsonファイルを確認するとテストに関係する@vue/test-utilsとjsdom, vitestが追加されていることが確認できます。


{
//略
  "devDependencies": {
    "@vitejs/plugin-vue": "^3.0.1",
    "@vue/test-utils": "^2.0.2",
    "jsdom": "^20.0.0",
    "vite": "^3.0.4",
    "vitest": "^0.21.0"
  }
}

npm run devコマンドで開発サーバを起動してlocalhost:5173にアクセスしてもVitestはUnit Testを行うための機能なので表示される内容に影響はありません。

create-vueで作成したVueの初期画面
create-vueで作成したVueの初期画面

テストの実行

package.jsonファイルを確認するとtest:unitコマンドが追加されていることが確認できます。


  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview --port 5050",
    "test:unit": "vitest --environment jsdom"
  },

npm run test:unitコマンドを実行するとフォルダ構成で確認したsrc/components/__tests__に存在するHelloWorld.spec.jsファイルのテストが実行されテストにパス(成功)していることがわかります。


% npm run test:unit

> vitest_vue3_app@0.0.0 test:unit
> vitest --environment jsdom


 DEV  v0.21.1 /Users/mac/Desktop/vitest_vue3_app

 ✓ src/components/__tests__/HelloWorld.spec.js (1)

Test Files  1 passed (1)
     Tests  1 passed (1)
  Start at  22:53:27
  Duration  1.83s (setup 0ms, collect 155ms, tests 18ms)


 PASS  Waiting for file changes...
       press h to show help, press q to quit

どのようなテストが実行されたのか確認するためにHelloWorld.spec.jsファイルの中身を確認します。


import { describe, it, expect } from 'vitest'

import { mount } from '@vue/test-utils'
import HelloWorld from '../HelloWorld.vue'

describe('HelloWorld', () => {
  it('renders properly', () => {
    const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
    expect(wrapper.text()).toContain('Hello Vitest')
  })
})

HelloWorld.spec.jsファイルの中ではHelloWorldコンポーネントにpropsでHello Vitestの文字列を渡しマウントしたコンポーネントの中のコンテンツに”Hello Vitest”が含まれているかチェックを行っています。HelloWorldコンポーネントはpropsのmsgで”Hello Vitest”を受け取ると”Hello Vitest You’ve successfully created a project with Vite + Vue 3.”と表示されるため”Hello Vitest”を含んでいるのでテストはパス(成功)します。

HelloWorld.spec.jsのtoContain関数の引数の文字列を”Hello Vue”に変更するとテストに失敗し下記のようなAssertionErrorのメッセージが表示されどの場所に問題があるのかがわかります。


Re-running tests... [ src/components/__tests__/HelloWorld.spec.js ]

 > src/components/__tests__/HelloWorld.spec.js (1)
   > HelloWorld (1)
     × renders properly

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

 FAIL  src/components/__tests__/HelloWorld.spec.js > HelloWorld > renders properly
AssertionError: expected 'Hello Vitest You’ve successfully crea…' to include 'Hello Vue'
 ❯ src/components/__tests__/HelloWorld.spec.js:9:28
      7|   it('renders properly', () => {
      8|     const wrapper = mount(HelloWorld, { props: { msg: 'Hello V…
      9|     expect(wrapper.text()).toContain('Hello Vue');
       |                            ^
     10|   });
     11| });

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

Test Files  1 failed (1)
     Tests  1 failed (1)
  Start at  22:56:14
  Duration  1.65s (setup 0ms, collect 154ms, tests 18ms)


 FAIL  Tests failed. Watching for file changes...
       press h to show help, press q to quit

npm run test:unitコマンドを実行するとファイルの更新を検知してくれるので一度コマンドを実行すると停止させるまでファイルの更新の度に自動でテストを再実行させることができます。

実行フォルダとファイル名

テストファイルが実行される条件を確認するために__tests__フォルダの名前を適当な名前に変更します。ここではvitestにしてみます。vitestにフォルダ名を変更してもテストは実行されます。下記のコンソールのメッセージから実行しているフォルダがsrc/components/vitestになっていることが確認できます。


Re-running tests... [ src/components/vitest/HelloWorld.spec.js ]

 √ src/components/vitest/HelloWorld.spec.js (1)

Test Files  1 passed (1)
     Tests  1 passed (1)
  Start at  22:57:41
  Duration  122ms


 PASS  Waiting for file changes...
       press h to show help, press q to quit

ファイル名についてはspec.jsまたはtest.jsをファイル名の最後につけることでnpm run test:unitでファイルの更新を検知してテストを実行することができます。

srcフォルダの下にHelloWorld.spec.jsに移動してもテストは実行されます。フォルダの階層を変更するとファイルのパスが変わるためにエラーが発生するので利用するコンポーネントのパスには注意する必要があります。

jsdom

みなさんが気になる点としてpackage.jsonファイルのtest:unitのコマンドに–environmentオプションでjsdomが設定されていました。名前からするとDOMに関係がありそうですが–environmentオプションを設定しない場合にどのような変化があるか確認しておきましょう。test:unitから–environment jsdomを削除してvitestのみにしています。


"scripts": {
  "dev": "vite",
  "build": "vite build",
  "preview": "vite preview --port 5050",
  "test:unit": "vitest"
},

変更後npm run test:unitを停止し再実行するとReferenceエラーが表示されテストに失敗します。


ReferenceError: document is not defined

エラーの内容からわかるようにjsdomはブラウザのようにDOMにアクセスする際に利用されるライブラリでjsdomを利用しない場合はVitestを利用したテストを実行することができません。

Cypress機能の追加

CypressはUnit Testing(単体テスト)とEnd-to-End testing(End-to-Endテスト)を行うための機能です。Vitestとは異なりCypressではブラウザを利用してテストを実行します。

npm init vue@latestコマンドでプロジェクト名を設定した後にCypress for both Unit and End-to-End testingのみYesを選択します。


 % npm init vue@latest

Vue.js - The Progressive JavaScript Framework

✔ Project name: … cypress_vue3_app
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add Cypress for both Unit and End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes

Scaffolding project in /Users/mac/Desktop/cypress_vue3_app...

Done. Now run:

  cd cypress_vue3_app
  npm install
  npm run dev

作成されたフォルダに移動してnpm installコマンドを実行した後のフォルダ構成は下記のようになります。他の機能とは異なりsrcフォルダの中ではなくプロジェクトフォルダの直下にcypressフォルダが作成されておりさらにcypressフォルダの中にいくつかのフォルダが作成されています。componetsフォルダの中にはVitestと同様に__test__フォルダが作成されHelloWorld.spec.jsファイルが作成されています。

Cypress機能を追加した場合のフォルダ構成
Cypress機能を追加した場合のフォルダ構成

npm run devコマンドで開発サーバを起動してlocalhost:5173にアクセスしてもCypressはUnit TestとEnd-to-End testingを行うための機能なので表示される内容に影響はありません。

Unitテストを実行

package.jsonファイルを確認するとCypressを利用するための複数のコマンドが追加されていることが確認できます。


"scripts": {
  "dev": "vite",
  "build": "vite build",
  "preview": "vite preview --port 5050",
  "test:e2e": "start-server-and-test preview http://127.0.0.1:5050/ 'cypress open'",
  "test:e2e:ci": "start-server-and-test preview http://127.0.0.1:5050/ 'cypress run'",
  "test:unit": "cypress open-ct",
  "test:unit:ci": "cypress run-ct --quiet --reporter spec"
},

Unit Testを行うためにtest:unitを実行してみます。


 % npm run test:unit

> cypress_vue3_app@0.0.0 test:unit
> cypress open-ct

It looks like this is your first time using Cypress: 9.4.1

✔  Verified Cypress! /Users/mac/Library/Caches/Cypress/9.4.1/Cypress.app

実行するとcypressのアプリケーションが起動します。左側のサイドバーにはsrc/componentsの__test__フォルダ下にあるHelloWorld.spec.jsファイルが表示されています。

cypressが起動
cypressが起動

左側のサイドバーのファイルを選択すると処理が開始されテストが実行されます。

Cypressによるテストの実行
Cypressによるテストの実行

右側にはHelloWorldコンポーネントの内容が表示されています。

HelloWorld.spec.jsファイルを開いてどのようなテストが実行されているのか確認します。一つはplaygroundという名前でpropsのmsgでHello Cypressを渡しています。もう一つはrenders properlyという名前でHelloWorldコンポーネントにpropsのmsgでHello Cypressを渡すだけではなくh1タグを取得してその中にHello Cypressが含まれているかチェックしています。


import { mount } from '@cypress/vue';
import HelloWorld from '../HelloWorld.vue';

describe('HelloWorld', () => {
  it('playground', () => {
    mount(HelloWorld, { props: { msg: 'Hello Cypress' } });
  });

  it('renders properly', () => {
    mount(HelloWorld, { props: { msg: 'Hello Cypress' } });
    cy.get('h1').should('contain', 'Hello Cypress');
  });
});

cy.getでh1を指定いる箇所をh2に変更するとCypressの画面上で”AssertionError”が発生します。Expected to find element: h2, but never found it.とメッセージが表示されている通りh2タグが存在しないというメッセージが表示されます。

AssertionErrorの発生
AssertionErrorの発生

test:unitコマンドでは名前の通りHelloWorldというコンポーネントのUnit Testを行っていることがわかります。Vitestとは異なりCypressではブラウザを利用して動作確認を行っていることもわかります。

package.jsonファイルに登録されているtest:unit:ciコマンドを実行するとCypressのアプリケーションは立ち上がらずコンソール上でテストの結果を確認することができます。


 % npm run test:unit:ci

> cypress_vue3_app@0.0.0 test:unit:ci
> cypress run-ct --quiet --reporter spec



  HelloWorld
    ✓ playground (37ms)
    ✓ renders properly (36ms)


  2 passing (87ms)

End-to-Endテストを実行

package.jsonファイルに登録されているtest:e2eを実行します。名前からe2eがEnd-to-Endの略であることがわかります。

npm run test:e2eコマンドを実行するとvite preview –port 5050が実行されていることがわかります。ブラウザからhttp://localhost:5050/にアクセスを行います。


 % npm run test:e2e

> cypress_vue3_app@0.0.0 test:e2e
> start-server-and-test preview http://127.0.0.1:5050/ 'cypress open'

1: starting server using command "npm run preview"
and when url "[ 'http://127.0.0.1:5050/' ]" is responding with HTTP status code 200
running tests using command "cypress open"


> cypress_vue3_app@0.0.0 preview
> vite preview --port 5050

  > Local: http://localhost:5050/
  > Network: use `--host` to expose

ブラウザ上には”Cannot GET /”が表示されます。

Cannot GET /が表示
Cannot GET /が表示

作成したプロジェクトでこれまで一度もbuildコマンドを実行したいない場合に表示されるエラーです。buildコマンドを実行していない場合はbuildコマンドを実行します。


 % npm run build

> cypress_vue3_app@0.0.0 build
> vite build

vite v2.8.1 building for production...
✓ 22 modules transformed.
dist/assets/logo.da9b9095.svg    0.30 KiB
dist/index.html                  0.48 KiB
dist/assets/index.f3fa6de6.css   3.48 KiB / gzip: 1.15 KiB
dist/assets/index.8ad48088.js    10.14 KiB / gzip: 3.89 KiB
dist/assets/vendor.5c83c58f.js   50.30 KiB / gzip: 20.28 KiB

buildコマンドを実行後に再度npm run test:e2eコマンドを実行します。Cypressのアプリケーションが起動します。表示されているexample.spec.jsファイルはcypress¥integrationフォルダに直下にあるexample.spec.jsファイルです。

Cypressアプリの起動
Cypressアプリの起動

expample.spec.jsファイルをクリックするとブラウザが起動してテストが実行されます。左側に表示されているTEST Bodyを見ると”visit /”でlocalhost:5050にアクセスを行いh1タグでYou did it!が存在するかチェックを行っていることがわかります。

テストの実行
テストの実行

example.spec.jsファイルの確認すると”/”にアクセスを行いh1タグで”You did it!”が含まれているかチェックを行っていることがわかります。


// https://docs.cypress.io/api/introduction/api.html

describe('My First Test', () => {
  it('visits the app root url', () => {
    cy.visit('/')
    cy.contains('h1', 'You did it!')
  })
})

Unit TestingではHelloWorldというコンポーネントをmountとしてそのコンポーネントの内容のみ表示してテストを実行していましたがEnd-to-End Testingの場合は本番環境を利用してサーバを起動しアプリケーションに直接アクセスを行ってテストを実行していることがわかります。

example.spec.jsファイルのh1をh2に変更して実行すると”AssertionError”によりテストに失敗していることがわかります。

失敗するテストを実行した場合
失敗するテストを実行した場合

package.jsonファイルにあるtest:e2e:ciコマンドを実行します。Cypressアプリケーションが実行されますが画面が表示されることなくテストが実行されテストが完了するとアプリケーションは自動で停止します。実行結果は下記のように表示されます。


% npm run test:e2e:ci 

> cypress_vue3_app@0.0.0 test:e2e:ci
> start-server-and-test preview http://127.0.0.1:5050/ 'cypress run'

1: starting server using command "npm run preview"
and when url "[ 'http://127.0.0.1:5050/' ]" is responding with HTTP status code 200
running tests using command "cypress run"


> cypress_vue3_app@0.0.0 preview
> vite preview --port 5050

  > Local: http://localhost:5050/
  > Network: use `--host` to expose

==================================================================================================

  (Run Starting)

  ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ Cypress:        9.4.1                                                                          │
  │ Browser:        Electron 94 (headless)                                                         │
  │ Node Version:   v16.13.0 (/usr/local/bin/node)                                                 │
  │ Specs:          1 found (example.spec.js)                                                      │
  └────────────────────────────────────────────────────────────────────────────────────────────────┘


────────────────────────────────────────────────────────────────────────────────────────────────────
                                                                                                    
  Running:  example.spec.js                                                                 (1 of 1)
Browserslist: caniuse-lite is outdated. Please run:
npx browserslist@latest --update-db

Why you should do it regularly:
https://github.com/browserslist/browserslist#browsers-data-updating


  My First Test
    ✓ visits the app root url (233ms)


  1 passing (257ms)


  (Results)

  ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ Tests:        1                                                                                │
  │ Passing:      1                                                                                │
  │ Failing:      0                                                                                │
  │ Pending:      0                                                                                │
  │ Skipped:      0                                                                                │
  │ Screenshots:  0                                                                                │
  │ Video:        true                                                                             │
  │ Duration:     0 seconds                                                                        │
  │ Spec Ran:     example.spec.js                                                                  │
  └────────────────────────────────────────────────────────────────────────────────────────────────┘


  (Video)

  -  Started processing:  Compressing to 32 CRF                                                     
  -  Finished processing: /Users/mac/Desktop/cypress_vue3_app/cypress/videos/example.    (0 seconds)
                          spec.js.mp4                                                               


==================================================================================================

  (Run Finished)


       Spec                                              Tests  Passing  Failing  Pending  Skipped  
  ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ ✔  example.spec.js                          247ms        1        1        -        -        - │
  └────────────────────────────────────────────────────────────────────────────────────────────────┘
    ✔  All specs passed!                        247ms        1        1        -        -        -  

cypress.jsonファイルがプロジェクトフォルダの直下に作成されており中身を確認するとbaseUrlとUnit Testで実行されるフォルダとファイルのルールが記述されています。


{
  "baseUrl": "http://localhost:5050",
  "component": {
    "componentFolder": "src",
    "testFiles": "**/__tests__/*.spec.{js,ts,jsx,tsx}"
  }
}

__tests__フォルダの名前を別の名前に変更するとテストファイルを見つけることができずエラーになります。


 % npm run test:unit:ci

> cypress_vue3_app@0.0.0 test:unit:ci
> cypress run-ct --quiet --reporter spec

Can't run because no spec files were found.

Cypress機能を利用したUnit TestingとEnd-to-End Testingがどのようなものか理解することができました。

ESLint機能の追加

ESLintは構文の記述間違いやあらかじめ決められたルールに沿ってコードが記述されているかをチェックする機能です。ESLintを利用することで潜在的なバグを見つけることができコードの品質を高めることができます。

npm init vue@latestコマンドでプロジェクト名を設定した後にESLint for code qualityのみYesを選択します。ESLint for code qualityをYesに選択するとPrettier for code formattingが表示されるのでこちらもYesを選択します。ESLintとPrettierの機能が追加されることになります。ESLintのみ追加することも可能です。


 % npm init vue@latest

Vue.js - The Progressive JavaScript Framework

✔ Project name: … cypress_vue3_app
 Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add Cypress for both Unit and End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes

Scaffolding project in /Users/mac/Desktop/cypress_vue3_app...

Done. Now run:

 cd eslint_vue3_app
  npm install
  npm run lint
  npm run dev

作成されたフォルダに移動してnpm installコマンドを実行した後のフォルダ構成は下記のようになります。プロジェクトフォルダ直下に.eslintrc.cjsファイルが作成されていることが確認できます。

ESLint機能を追加した場合のフォルダ構成
ESLint機能を追加した場合のフォルダ構成

create-vueのインストール実施時のメッセージの最後にnpm installの後にnpm run lintコマンドを実行するように表示されています。これがESLintを利用してチェックを行うためのコマンドです。

Lintを実行

npm run lintコマンドを実行します。プロジェクトの直後の状態ではESLintのチェックにひっかかるコードが存在しないため何も表示されません。


 % npm run lint

> eslint_vue3_app@0.0.0 lint
> eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore

これではESLintが何をしてくれるものなのかわからないのでApp.vueファイルを開いてimportしているTheWelcomタグをコメントアウトします。


<template>
//略

    <div class="wrapper">
      <HelloWorld msg="You did it!" />
    </div>
  </header>

  <main>
    <!-- <TheWelcome /> -->
  </main>
</template>

npm run devコマンドを実行して開発サーバを起動するとTheWelcomeコンポーネントの箇所には何もコンポーネントが存在しないためブラウザの右側には何も表示されません。

TheWelcomeコンポーネントを非表示
TheWelcomeコンポーネントを非表示

npm run devコマンドのコンソール上には何もメッセージは表示されませんが次にnpm run lintを実行します。ESLintのチェックが行われ今後はエラーメッセージが表示されます。”TheWelcome”が定義されている(importされている)が利用されていないという内容です。ESLintを利用して場合はimportしたコンポーネントを利用していない場合はエラーになることがわかります。メッセージの横にはどのルールのエラーなのかも表示されています。今回のエラーに対応するルール名はno-unused-varsです。


 % npm run lint

> eslint_vue3_app@0.0.0 lint
> eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore


/Users/mac/Desktop/eslint_vue3_app/src/App.vue
  3:8  error  'TheWelcome' is defined but never used  no-unused-vars

✖ 1 problem (1 error, 0 warnings)

no-unused-varsがどのようなルールなのかを確認するためにpackage.jsonでインストールされているeslintに関するパッケージを確認します。


"devDependencies": {
  "@rushstack/eslint-patch": "^1.1.0",
  "@vitejs/plugin-vue": "^2.1.0",
  "@vue/eslint-config-prettier": "^7.0.0",
  "eslint": "^8.5.0",
  "eslint-plugin-vue": "^8.2.0",
  "prettier": "^2.5.1",
  "vite": "^2.7.13"
}

eslint-plugin-vueがVueに関連するルールが含まれているパッケージです。インストールしたパッケージは.eslintrc.cjsファイルの中で設定が行われています。


/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution");

module.exports = {
  root: true,
  extends: [
    "plugin:vue/vue3-essential",
    "eslint:recommended",
    "@vue/eslint-config-prettier",
  ],
  env: {
    "vue/setup-compiler-macros": true,
  },
};

eslint-plugin-vueのドキュメントを確認してルール一覧からno-unused-varsを確認することができます。このようにドキュメントを確認することでエラー内容の詳細を確認することができます。

no-unused-varsのルールの確認
no-unused-varsのルールの確認

たくさんあるルールを1つずつ設定していることは大変なので個別のルールを一つ一つ設定するのではなく複数のルールをまとめてグループとして適用しています。

それが先ほど確認した.eslintrc.cjsの中のextendsプロパティに設定されていたplugin:vue/vue3-essentialです。plugin:vue/vue3-essentialがVueに関するルールでeslint:recommendedがESLintに関するルールです。plugin:vue/vue3-essentialがどのようなルールを含んでいるかもドキュメントから確認することができます。

prettierの動作確認

prettierの機能を利用することでコードの整形を行うことができます。ESLintの機能追加した後にPrettier機能を追加した場合はpackage.jsonを確認することでrushstack/eslint-patch, vue/eslint-config-prettier, prettierのパッケージが追加インストールされることがわかります。


"devDependencies": {
  "@rushstack/eslint-patch": "^1.1.0",
  "@vitejs/plugin-vue": "^2.1.0",
  "@vue/eslint-config-prettier": "^7.0.0",
  "eslint": "^8.5.0",
  "eslint-plugin-vue": "^8.2.0",
  "prettier": "^2.5.1",
  "vite": "^2.7.13"
}

.eslintrc.cjsファイルのextendsプロパティの最後にprettierに関する設定vue/eslint-config-prettierが追加されています。最後に追加することでESLintを含め他の機能が持つコードの整形の設定を上書きしてくれます。


/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution");

module.exports = {
  root: true,
  extends: [
    "plugin:vue/vue3-essential",
    "eslint:recommended",
    "@vue/eslint-config-prettier",
  ],
  env: {
    "vue/setup-compiler-macros": true,
  },
};

Prettierの動作確認を行うためプロジェクトフォルダ直下に.prettirrcファイルを作成します。インデントした時のスペースはデフォルトでは2ですがtabWidthを利用して4に変更を行います。


{
    "tabWidth": 4
}

設定後npm run lintを実行するとApp.vueファイルなどインデント時のスペースの幅が4になっていることを確認できます。


% npm run lint

> eslint_vue3_app@0.0.0 lint
> eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore

tabWidthの設定を削除するか.prettierrc自体を削除し再度npm run lintコマンドを実行するとスペースの幅がデフォルトの2に戻ります。その他の設定できるオプションについてはPrettierのドキュメントで確認することができます。

付録A:TailwindCSSのインストール

Tailwind CSSを利用したい場合はTailwind CSSのドキュメントのVu3 and Viteの手順通りに実行すると設定を行うことができます。

付録B:デフォルトのポートの変更

Viteのバージョン3では開発サーバのデフォルトのポート番号は5173です。3000など別のポート番号に変更したい場合はvite.config.jsファイルで行います。下記ではポート番号を3000に設定しています。


import { fileURLToPath, URL } from 'node:url';

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    },
  },
  server: {
    port: 3000,
  },
});

まとめ

ここまでの内容でcreate-vueを利用して追加できる機能の説明と動作確認は完了です。これまで利用したことがない機能または知らなかった機能の理解が深まり自信を持って機能選択ができるようになっていれば大変うれしいです。今後Vueプロジェクトを作成する場合はcreate-vueを利用する機会が増えてくると思うので本文書で得た知識をぜひ役に立ててください。