【完全解説】create-vueでVue3プロジェクト作成〜Vite爆速開発〜
create-vueはVue公式のVueプロジェクト作成ライブラリです。npm create vue@latest(npm init vue@latest)コマンドを実行することでVueプロジェクトの骨組み(scaffolding)の作成とVueのアプリケーション構築に欠かすことできない機能を選択してプロジェクトに追加を行うことができます。本文書はVueの入門者の人がcreate-vueを利用してプロジェクトを作成する際に追加できるすべての機能がどのような役割を持っているのか理解できることを目的にしています。各機能の理解が進むことで機能追加時に迷うことなく自信を持って機能の選択を行うことができます。create-vueではビルドツールとして現在人気急上昇中のViteが利用されているのでViteについて動作確認をしながら説明を行なっているので開発環境と本番環境でのビルドツールの違いなども理解することができます。
目次
create-vueによるプロジェクトの作成
npm create vue@latestコマンドを実行するとプロジェクト名の設定だけではなくTypeScriptをはじめアプリケーションを構築する上で利用頻度の高い以下の9つの機能をプロジェクトに追加するかどうかプロジェクト作成者が選択することができます。どの機能も追加しないということも可能です。各機能については個別に追加を行いどのような機能なのか説明を行っているので知らない機能があったとしても安心して読み進めてください。本文書を読み終えた時には使いこなせるかは別として各機能がどのような機能なのかは最低限理解できるはずです。各機能を使いこなすためには個別の機能について学習する必要があります。
- TypeScript
- JSX Support
- Vue Router
- Pinia
- Vitest
- End-to-End Testing Solution
- ESLint
- Prettier
- Vue DevTools 7 extension for debugging? (experimental)
※PrettierについてはESLint機能を追加した場合に追加できる機能として表示されます。また2024年7月現在experimentalのVue Devtools 7 extension for debuggingも選択できます。
npmコマンドを実行するためにはNode.js, npmがインストールされている必要があります。
追加機能を選択しない場合
Vueのみインストールしたい場合には機能の追加を行わないことも可能です。
ここでは機能の追加を行わずVueのみインストールを行います。Vueのみインストールすることでプロジェクトのデフォルトのフォルダ/ファイル構成、ブラウザ上に表示されるコンテンツの表示までの流れ、create-vueで利用されているViteとはどのようなものなのかを実際に動作を行いながら確認をしていきます。
npm create vue@latestを実行するとプロジェクトの作成が開始されます。最初にプロジェクト名を聞かれるので任意の名前のプロジェクト名を入力してください。その後機能毎にプロジェクトのに追加するかどうか聞かれます。ここではすべてNoを選択します。
% npm create 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 an End-to-End Testing Solution? » No
✔ Add ESLint for code quality? … No / Yes
✔ Add Vue DevTools 7 extension for debugging? (experimental) » 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 run devコマンドを実行すると開発サーバを起動することができます。ブラウザから起動した開発サーバ(http://localhost: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の中にコンテンツの内容がすべて含まれているわけではありません。
ここで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を実行した時と同じ内容が表示されます。
ブラウザのデベロッパーツールで確認するとid属性にappを持つdiv要素の間にコンテンツが挿入されていることは同じです。
ソースも確認しておきましょう。
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”です。
ここまでの確認で表示されている内容は同じでもnpm run devとnpm run buildでは全く異なる仕組みでブラウザ上にコンテンツを表示していることがわかります。create-vueで利用しているViteでは開発環境と本番環境で異なるビルドツールを利用しています。この違いが確認した内容の違いに反映されています。
開発環境はES modulesを利用しているため必要なモジュール(ファイル)のみネットワーク越しでブラウザに送信するため1つのファイルにすべてのファイルをバンドルするという処理を行っていません。大きな依存関係がある場合はesbuildを利用してprebundlingを行うためネットワークのリクエストの回数を減らし爆速でビルドが行われます。本番環境では先ほど確認したようにすべてのファイルを1つのファイルにバンドルするためにビルドにrollupを利用しています。すべてのファイルを1つのファイルにバンドルするため処理に時間がかかります。
ES modulesを利用した場合にネットワーク越しにファイルを送信すると説明しましたが開発環境(npm run dev)でデベロッパーツールのネットワークタブを確認することでモジュール単位(ファイル単位)でネットワーク越しにファイルを送信していることがわかります。ネットワークタブには初期画面を構成しているコンポーネントの内容が含まれるHelloWorld.vueやTheWelcom.vueファイルを確認することができます。
ファイル名がHelloWorld.vueになっているからといってVueで記述したコードをそのまま送信しているわけではありません。プレビューで送信した内容を確認するとファイルに記述した内容ではなくブラウザが認識できる形への変換も行われていることもわかります。
ここまでの確認で開発環境と本番環境でのビルドの違いが明確になったかと思います。Vueのみをインストールした場合に作成されるプロジェクトの理解が進んだので各機能をインストールした場合の動作確認を行っていきます。
TypeScript機能を追加
TypeScriptには名前にTypeが含まれている通りTypeScriptの機能を利用することでJavaScriptでType(型)の指定が利用できるようになります。
npm create vue@latestコマンドでプロジェクト名を設定した後にTypeScriptのみYesを選択します。
% npm create 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ファイルなどが作成されていることがわかります。
VSCodeで拡張機能のVolarをインストールしている状態でmain.tsファイルを開くとAppのimportの処理でメッセージが表示されています。メッセージの内容は”Cannot find module ‘./App.vue’ or its corresponding type declarations.”です。
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;
}
型チェックが行われるか確認するために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でコンポーネントを利用
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の詳細説明は省略しています。
create-vueでJSX Supportの機能を追加することでコンポーネントをJSXを使って記述できることが確認できました。
Vue Router機能を追加
Vue Routerを利用することでVueを利用してSinge Page Application(SPA)を作成することができます。Vue Router機能が追加されていないデフォルトの状態ではブラウザからどのURLにアクセスしても同じ初期画面しか表示されませんがVue Routerを追加することで/(ルート)以外にアクセスできるパスを追加することができ追加したパスに対して別々の内容を表示させることができます。/aboutにアクセスすればaboutページ、/profileにアクセスすればprofileページを表示させることができます。
npm create vue@latestコマンドでプロジェクト名を設定した後にVue Router for Single Page Application developmentのみYesを選択します。
% npm create 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ファイルが作成されていることわかります。
npm run devコマンドで開発サーバを起動してlocalhost:5173にアクセスするとこれまでの内容の他にHome | Aboutが追加されていることが確認できます。
Aboutはリンクになっているのでクリックすると以下の画面が表示されます。画面が変わるだけではなくアクセスするURLがlocalhost:5173からlocalhost:5173/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ページにアクセスできるように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による新しいページの追加方法を理解することができました。
複雑なアプリケーションを作成するためにはページのネスト化や動的ページの設定方法などを理解する必要がありますがVue Router機能を追加することで複数ページを持つシングルページアプリケーションを構築することができます。aタグのようにページの遷移毎にページのリロードが行われないのでスムーズにページ移動を行うこともできます。
Pinia機能の追加
Piniaは状態管理ライブラリで、複数のコンポーネントでデータを共有する場合に利用することはできます。これまでのVue公式のVue CLIコマンドでプロジェクトを作成する場合はVuexという名前の状態管理ライブラリが利用されていましたがcreate-vueからはPiniaの利用が推奨されています。
npm create vue@latestコマンドでプロジェクト名を設定した後にPinia for state managementのみYesを選択します。
% npm create 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が設定されています。
npm run devコマンドで開発サーバを起動してlocalhost:5173にアクセスしても追加した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増やす処理が記述されています。
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は他のコンポーネントからアクセスすることができます。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″の下に表示されています。
Pinia機能を利用することですべてのコンポーネントから同じ方法を利用してPiniaで設定した状態を管理できることがわかりました。
Piniaについては下記の文書でも説明を行っているのでぜひ参考にしてください。
Vitest機能の追加
VitestはViteでUnit Testing(単体テスト)を行うための機能です。公式サイトはhttps://vitest.dev/です。
npm create vue@latestコマンドでプロジェクト名を設定した後にVitest for Unit TestingのみYesを選択します。
% npm create 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ファイルが作成されています。
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を行うための機能なので表示される内容に影響はありません。
テストの実行
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 create vue@latestコマンドでプロジェクト名を設定した後にCypress for both Unit and End-to-End testingのみYesを選択します。
% npm create 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ファイルが作成されています。
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ファイルが表示されています。
左側のサイドバーのファイルを選択すると処理が開始されテストが実行されます。
右側には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タグが存在しないというメッセージが表示されます。
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 /”が表示されます。
作成したプロジェクトでこれまで一度も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ファイルです。
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 create vue@latestコマンドでプロジェクト名を設定した後にESLint for code qualityのみYesを選択します。ESLint for code qualityをYesに選択するとPrettier for code formattingが表示されるのでこちらもYesを選択します。ESLintとPrettierの機能が追加されることになります。ESLintのみ追加することも可能です。
% npm create 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ファイルが作成されていることが確認できます。
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コンポーネントの箇所には何もコンポーネントが存在しないためブラウザの右側には何も表示されません。
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を確認することができます。このようにドキュメントを確認することでエラー内容の詳細を確認することができます。
たくさんあるルールを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を利用する機会が増えてくると思うので本文書で得た知識をぜひ役に立ててください。