Vue3系+TS+Vite を使った0からのSPA開発【 [序章] 環境構築編】

#はじめに
0からVueのSPAを作成した備忘録。詰まったポイント、知識を得たポイントなどを記載。
<font color="red">*現在、本記事『 [ 序章 ] 環境構築編』のみとなっております。少しずつ加筆していく予定です。*</font>

最終的にはGo言語を用いたバック側とAPIのやりとりを行うアプリを開発しますが、本記事を含めた序盤記事では基本的にVueのお話のみとなります。ですが、一部、axiosなどバックとの連携に必要な話も入ります。

**加筆修正に関するおしらせ**

- 2021.08.14：加筆予定だった vue-router 導入部分 ( step6. ) を加筆しました。
- 2021.08.14：加筆予定だった main.ts 変更部分 ( step5. ) を加筆しました。
- 2021.08.15：モジュールの採用理由 ( はじめに項目 ) を加筆しました。
- 2021.08.19：yarn install が欠けていたので ( step1. ) 加筆修正しました。
- 2021.08.19：周辺モジュール組み込み ( step2. ) を修正しました。
- 2021.09.19：周辺モジュール組み込み ( step2. ) を修正しました。

###作成前に持っていた知識

`Vue`：v-if程度なら知っていた。SPA作成経験なし。コンポーネントって何？vue-routerって何？からのレベル。
`TS`：素のJavaScriptはそれなりに書けるもののVue以前はTS経験なし。Cなどの経験から型付けの知識はあり。

###使用技術

- Vue3系 with Composition API
- TypeScript
- vite
- vue-router4系
- vuex
- axios

**Q.** なんで3系、TS、Viteを使うの？
**A.** こちら会社の強いフロントエンジニアの方からアドバイスをいただき選択しました。詳細は以下。

**<font color=deepskyblue>Q1.</font>** Vue **3系+CompositionAPI**を選択した理由は？
**<font color=deepskyblue>A1.</font>** 現状Vueの最新バージョンが3系であり、Vue公式が最終的にはこのアーキテクチャに移行すると名言しているため。折角なのでモダンな技術を採用しようということで。

**<font color=limegreen>Q2.</font>** JSではなく**TS**を選択した理由は？
**<font color=limegreen>A2.</font>** 型付けの強い方が不慮の事故に繋がらないため。

**<font color=darkorchid>Q3.</font>** vue-cliではなく**Vite**を選択した理由は？
**<font color=darkorchid>A3.</font>** Viteのほうが高速であることに加え、Vue公式が推奨しているモジュールでもあるため。

**<font color=darkorange>Q4.</font>** npmではなく**yarn**を選択した理由は？
**<font color=darkorange>A4.</font>** 会社を含めてyarnの方をよく使っているため。正直これに関しては完全に好みだと思います。どちらを採用してもほとんど差異が無いイメージ。

###一連の作成をして感じたこと その1
世の中のドキュメントは「Vueで開発するときに知ること！」として一括で様々な情報が一括してまとめられておりますが、マジョリティではないアーキテクチャを採用して感じたのはそれらの知識が、

全体を通して、

- JavaScriptに関する知識
- TypeScriptに関する知識
- ES5と6に関する知識
- Vueに関する知識
- Vueの2系と3系に関する知識
- CompositionAPI と OptionalAPI に関する知識

環境構築過程では上記に加え、

- npmやyarnに関する知識
- HTML/CSS(SCSS, sass)に関する知識
- Viteやvue-cliに関する知識
- vue-routerの3系と4系に関する知識
- その他、モジュールに関する知識

といった実は境界の異なる様々な内容のものが、一括してVueの知識として扱われている印象。初心者キラー。

そのため、調べる際に「vue 知りたいこと」として調べてもあまり引っかからないことが多いです。どのジャンル区分で引っかかったのかを知らなければならないために試行錯誤が必要でした。

###一連の作成をして感じたこと その2
やはり新しい技術やマイノリティな技術 (特に Vite や CompositionApi ) を導入しようとするとどうしても日本語のドキュメントが少ないです。

結局公式ドキュメントや英語のドキュメント/記事を読まざるを得なくなりました。諦めましょう。ラッキーなことにVue/TS力だけでなく意図せず英語力も上がります。

# 環境構築

yarn を採用していますが、npm でも問題ありません。ちなみにVite公式では npm の解説が先に来ています。

###step1. プロジェクト作成
```shell
$ yarn create vite-app プロジェクト名
```
これ一発でok。viteのインストールもこれで勝手に行われている。vue-cli と異なり、プロジェクト作成時点で、系の指定・vue-router や vuex の入れ込み・TS や SCSS の入れ込み、の選択はこの段階では無い。step2. において自分で行う。
<font color="orange">【注】ここで指定した「プロジェクト名」のディレクトリが作成され、そのディレクトリ下に様々なディレクトリやファイルが生成される。<br>そのため、例えばバックとフロントを分けるプロジェクトの際は、フロント用ディレクトリを事前に作成する必要が無い。</font>

次にプロジェクト用パッケージマネージャのインストールを行う。

```shell
$ cd プロジェクト名 #作成したプロジェクトdir(rootdir直下)に移動
$ yarn install #プロジェクト下に yarn をインストール
```
ローンチ方法は以下の通り。初期設定は `localhost:3000` です。

```shell:rootdir
 yarn dev #launch
```
ここのタイミングで一度動作確認をすると吉です。

`localhost:3000` にアクセスするとこのような画面が出ます。
![launch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/583654/57ea0d0f-b469-c894-0b97-b45bd73daea3.png)


***

###step2. 周辺モジュール組み込み

rootdir直下で必要モジュールをCUI系ツールにて

```shell:rootdir
$ yarn add モジュール名@バージョン #dependencies環境
# または
$ yarn add -D モジュール名@バージョン #devDependencies環境
```
でインポートする。そうすると勝手にインストールされ `package.json` が勝手に変更される。

<font color="orange">【注1】インストールの際、バージョンを指定しなくてもインストールすることができる。しかしながら、`vue-router` のように系を指定しないとうまく動作しなくなるものがあるため、注意が必要。</font>

<font color="orange">【注2】この過程で `package.json` の中身を変更する必要はありません。逆に、自分で加筆してもまったくもってモジュール組み込みに影響を及ぼさないため注意が必要。</font>

###*step2.5 周辺モジュール組み込みの補足*

【参考】`package.json` におそらく入っていた方が良いモジュール群

```json
"dependencies": {
  "axios": "^0.21.1",
  "vue": "3.1.4",
  "vue-router": "4.0.6",
  "vuex": "^3.6.2"
},
"devDependencies": {
  "@vue/compiler-sfc": "^3.1.4",
  "scss": "^0.2.4",
  "scss-loader": "^0.0.1",
  "typescript": "^4.3.5",
  "vite": "^1.0.0-rc.13"
}
```

バージョン指定において、最新か否かに関しては最初の1文字目 (例. `1.2.3` で言う `1` )さえ指定できていれば基本的に問題ない。

`vue-router` で言えば `$ yarn add vue-router@4` のような感じ。

<font color="orange">【注】インストールの際、SCSSを利用したい場合、scss(とscss-loader)のみをインストールするとエラーが出ます。sassを一応インストールしなければならないらしい。なぜなのか。</font>
くわしくはこちら [Vite公式ドキュメント Featurs章 #CSS-Pre-Processors](https://vitejs.dev/guide/features.html#css-pre-processors) (ただし英語)

**【感想】step2. に関する落とし穴など**

この vite に周辺モジュールを入れ込む step2. の際に必要な知識として、

- viteの知識
- yarn(あるいはnpm)の知識
- package.json の知識
- 周辺モジュールの知識

これらが組み合わさっておりました。

vue-cli で環境構築をしたときは基本的にGUIっぽいCUIでオートマティックに選択を行うのため複合知識が必要なく、周辺モジュール ( vue-router とか vuex とかそのへん) の必要/不要の是非程度の知識しか要しなかったというバックグラウンドの下、
Vite の導入ではマニュアルな導入が必要という相違点において、vue-cli での経験が逆に仇となってしまい、最初どこで詰まっているのか気づかなかったです。

ですが、一度導入さえしてしまえば、ローカル環境における Vite はめちゃめちゃ高速なのでストレスフリーです。

**【補足】yarn 関連**
yarnのコマンドのオプション `-D` は `-develop` オプションとのこと。dev環境のみで必要なもの。(詳細は下記【補足の補足】)

**【補足の補足】package.json 関連**

この `package.json` 中に `"dependencies"` と `"devDependencies"` がある。

基本的に、`dependencies` は本番環境でも開発(dev)環境でも使いたいモジュール、`devDependencies` は開発環境でのみ使いたいモジュールの記載。

参考：[Qiita記事『【初心者向け】NPMとpackage.jsonを概念的に理解する』 @righteous さん](https://qiita.com/righteous/items/e5448cb2e7e11ab7d477)

vue-routerやaxiosなどと異なり、scss や ts などの情報は devDependencies にのみ記載する必要があり疑問に思ったので弊社の強い先輩エンジニアの方に聞いてみたところ、本番環境において、scss => cssや ts => js へとviteでトランスパイルされてから稼働するので必要ないそう。(逆に開発環境ではトランスパイルされてないのかーいと思った)

**【現状わからないポイント】**

- `package.json` に "license" を入れろと出る => 必要ないなら無視？( package.json の知識)
- 一括でインストールするコマンドあるんじゃね？( yarn の知識)

**【余談】**当初フロント側も Docker を用いた環境構築を検討しましたが、低速化というデメリットを持ちつつもそこまで便利にならないというメリットが上回らない観点から止めました。

***

###step3. JSをTSに、cssをscssに変更する

(以下、scss に関する箇所は sass も同様。)

- <font color=deepskyblue>main.js</font> の拡張子を変更して <font color=purple>**main.ts**</font> にする。
- 上記 <font color=purple>**main.ts**</font> ファイル内 import './index.css' の .css 拡張子を変更して<br>&nbsp;&nbsp;`import './index.scss`<br>にする。
- <font color=deepskyblue>index.css</font> の拡張子を変更して <font color=purple>**index.scss**</font> にする。
- <font color=purple>**index.html**</font> ファイル内の \<script type="module" src="./src/main.js">\</script> の .js の拡張子を変更して<br>&nbsp;&nbsp;`<script type="module" src="./src/main.ts"></script>`<br>にする。
- すべての .vue ファイル(コンポーネントファイル)の\<script>タグに `lang="ts"` を入れる。

これらが変更されないとエラーには出ないが画面に描画されないなど不具合が起こる。

***

###step4. 簡単なディレクトリ構成を考える

```shell
root/
  ┣ node_modules/
  ┣ public/
  ┣ src/
  ┃  ┣ assets/
  ┃  ┣ components/
  ┃  ┃  ┗ HelloWorld.vue
  ┃  ┣ styles/            # 新規追加dir : CSS(SCSS/sass)用
  ┃  ┣ utils/             # 新規追加dir : 自作モジュール用
  ┃  ┣ App.vue
  ┃  ┣ index.scss
  ┃  ┣ main.ts
  ┃  ┗ router.ts          # 新規追加file : 次項目で解説
  ┣ index.html
  ┣ package.json
  ┣ yarn-error.log
  ┗ yarn.lock
 ```

vue-router を導入する上でここである程度構成されていないとファイルパスの指定が止まってしまうためここで。

***

###step5. main.ts のVue3系への移行

<font color=red>【超重要】</font>
vueの<font color=red>2系以前と3系で記法がかなり異なる部分がある。</font>これを誤ると動かなくなる可能性があるため注意が必要。

**やるべきこと**
**step5-1.** モジュールの `import` を整える
**step5-2.** Vue全体のエントリポイントを作成する

####step5-1. モジュールの import を整える
<font color=purple>**main.ts**</font> ファイルに Vue 発火のエントリポイントに必要なモジュールをインポートする。(既存で存在している場合もあります。)

```ts:main.ts
import { createApp } from 'vue'
import App from './App.vue'
import './index.scss'
```

####step5-2. Vue発火のエントリポイントを作成する

詳細は公式ドキュメントを御覧ください。
[Vue3系公式ドキュメント「アプリケーションとコンポーネントのインスタンス」](https://v3.ja.vuejs.org/guide/instance.html)

Vue2系以前では、<font color=red>*new Vue( )*</font> や <font color=red>*el: '#app'*</font> などを用いて記述する発火ポイントですが、
3系では、<font color=purple>**main.ts**</font> ファイルに `createApp()` を用いてVueインスタンスの作成を行います。

引数に <font color=purple>index.html</font> のタグに設定されている id="app" を渡した `.mount('#app')メソッド` をチェーンさせることで Vue のエントリ先を設定する。

```ts:main.ts
createApp(App)
  .mount('#app')
```

後々、vue-router や vuex を用いる際、この `createApp(App)` に `.use()メソッド` をチェーンさせることがあります。
これにより外部モジュールの発火ポイントを設定することができます。

###*step5.5. main.ts の Vue3系への移行の補足*

【参考】

```ts:main.ts
import { createApp } from 'vue'
import App from './App.vue'
import './index.scss'

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

**【補足】各チェーンメソッドの詳細はこちら。**

- `.mount()`：[Vue3系公式ドキュメント『アプリケーションAPI mount』](https://v3.ja.vuejs.org/api/application-api.html#mount)
- `.use()`：[Vue3系公式ドキュメント『アプリケーションAPI use』](https://v3.ja.vuejs.org/api/application-api.html#use)

**【補足】発火ポイントを設定する意味**
これらを設定することによって、設定範囲における通常の html が Vue.js を用いた動的なDOMとなるらしい。(言われてみれば当たり前といえば当たり前ではあるのだけど、たしかにそうだなと思いました。)

***

###step6. vue-routerを利用する

<font color=red>【超重要】</font>
<font color=red>vue のバージョンと vue-router のバージョンがいい感じになっていないと、うまく動作しない？場合がある。</font><br>=> (2021年7月現在) vueが3系なら、vue-routerは**4系**が良さげ。

<font color=red>【超重要】</font>
vue-router の<font color=red>3系以前と4系で記法がかなり異なる部分がある。</font>これを誤ると動かなくなる場合や正体不明のホワイトアウトエラーに遭遇する可能性があるため注意が必要。

また、この vue-router 3系・4系問題に関して、世の中の記事は3系基本が大半なので注意が必要です。本step5項目ではこれ由来の相違点を<font color=red>【注】</font>で記載しています。

**やるべきこと**
**step6-0.** <font color=purple>**router.ts**</font> ファイルを作成する。
**step6-1.** 作成した <font color=purple>router.ts</font> でモジュールの `import ` を行う。
**step6-2.** 同ファイルでコンポーネントの `import ` を行う。
**step6-3.** 同ファイルで配列にルーティングを記載する。
**step6-4.** 同ファイルで router オブジェクトを作成する。
**step6-5.** 同ファイルで export を行う。
**step6-6.** <font color=purple>**main.ts**</font> で router を登録する

####step6-0. router.ts の作成
`/src/` 下に `router.ts` というファイルを作成する。別なファイル名でも良いが、この名前が一般的だそう。

<font color="orange">【注意】vue-routerを用いる際は予め step2. のモジュールの入れ込み時にvue-routerも入れ込んでおかなければならない。</font>

####step6-1. モジュールのインポート
作成した <font color=purple>**router.ts**</font> ファイルに **vue** と **vue-router** と **vue-router由来の使用モジュール** をインポートする。

*<font color=red>【注】</font> vue-router3系以前では createRouter や createWebHistory などの vue-router 由来のモジュールのインポートが必要なかったようなので間違えないように注意。*

```ts:router.ts
import Vue from 'vue';
import * as VueRouter from 'vue-router';
import { createRouter, createWebHistory } from 'vue-router'
```

####step6-2. コンポーネントのインポート
同 router.ts ファイルにルーティングを行いたいコンポーネントファイル ( .vue ファイル) をインポートする。

<font color=orange>【注】エディタの予測補完等では .vue拡張子 を省略してしまうことがありますが、その場合うまくいかないです。.vue拡張子 を必ずつける。</font>

```ts:router.ts
// HelloWorldコンポーネントとhogeコンポーネントのインポート
import HelloWorld from './components/HelloWorld.vue';
import Hoge from './components/Hoge.vue';
```

####step6-3 ルーティング
同 router.ts ファイルにルーティングを行う。

この際、`const routes = []` 配列の中に1ルーティング1オブジェクトとして定義を行う。
ルーティングオブジェクトとしては **URLパス** `path: '/パス指定'` と **コンポーネント名** `component: コンポーネント名` が登録必須。

```ts:router.ts
const routes = [
  {
    path: '/',
    component: HelloWorld  // HelloWorldコンポーネントルーティング
  },
  {
    path: '/hoge',
    component: Hoge  // Hogeコンポーネントルーティング
  },
]
```

####step6-4 routerオブジェクトの生成

同 router.ts ファイルに、step5-1でインポートした　`createRouter()関数` を用いてrouterインスタンスを作成する。

この際、createRouter()関数の引数として、step6-1でインポートした **createWebHistory()関数** `history: createWebHistory()` や step6-3で作成した **routes配列** `routes` などを登録したオブジェクトを渡す。

*<font color=red>【注 1】</font> vue-router3系以前では、createRouter( ) 関数によるインスタンス作成と次項目の export が一緒となった <font color=red>export default new Router({</font> と記載していた。4系では誤り。本項目 step6-4. および次項目 step6-5 がこの代替記法となる*

*<font color=red>【注 2】</font> vue-router3系以前では history: createWebHistory( ) 部分を <font color=red>mode: 'history'</font> と記載していた。4系では誤り。*

```ts:router.ts
const router = createRouter({
  history: createWebHistory(),
  routes,
});
```

####step6-5. エクスポート

同 router.ts ファイルで、前step6-4で作成したrouterインスタンスをエクスポートする。

*<font color=red>【注】</font> vue-router3系以前では router.ts ( ? ) に <font color=red>Vue.use(Router)</font> を記載していたようだが、これを記載すると誤り。本項目 step6-5. および次項目 step6-6 でこの代替記法となる。*

```ts:router.ts
export default router;
```

以上で <font color=purple>**router.ts**</font> 側の編集は完了。内容一覧は後述の参考項目を参照。

####step6-6. main.ts における router の登録

<font color=purple>**main.ts**</font> ファイルに、前step5-5で export させたrouterインスタンスを `import` 後、登録する。

**step6-6-1**
<font color=purple>main.ts</font> ファイルに router を `import` する。
**step6-6-2**
同ファイルの `createApp()関数` のチェーンメソッドとして引数に router を渡した `.use(router)` メソッドを用いる。

```ts:main.ts
// ...略...
import router from './router'  // step6-6-1. インポート
//...略...
createApp(App)
  .use(router)    // step6-6-2. routerインスタンスの登録
  .mount('#app')
```

###*step6.5. vue-routerを利用するに関する補足*

【参考】

```ts:router.ts
import Vue from 'vue';
import * as VueRouter from 'vue-router';
import { createRouter, createWebHistory } from 'vue-router'

import HelloWorld from './components/HelloWorld.vue';
import Hoge from './components/Hoge.vue';


const routes = [
  {
    path: '/',
    component: HelloWorld
  },
  {
    path: '/hoge',
    component: Hoge
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;
```

***

###step.last 最終的な環境構築結果

```shell
root/
  ┣ node_modules/
  ┣ public/
  ┣ src/
  ┃  ┣ assets/
  ┃  ┣ components/
  ┃  ┃  ┣ HelloWorld.vue  # 内容編集必須
  ┃  ┃  ┣ hoge.vue        # 追加任意
  ┃  ┃  ┗ fuga.vue        # 追加任意
  ┃  ┣ styles/            # 追加推奨
  ┃  ┃  ┗ main.scss       # 追加推奨
  ┃  ┣ utils/             # 追加推奨
  ┃  ┣ App.vue            # 内容編集必須
  ┃  ┣ index.scss         # 必要に応じてファイル名＆内容編集
  ┃  ┣ main.ts            # ファイル名＆内容編集必須
  ┃  ┗ router.ts          # 追加必須
  ┣ index.html            # 内容編集必須
  ┣ package.json          # 必要に応じて内容編集
  ┣ yarn-error.log
  ┗ yarn.lock
 ```

(↑あくまで TS・CompositionAPI・Vue-Router を使うこと前提でコメントアウトに「必須」と書いております。)

```ts:main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import './index.scss'

createApp(App)
  .use(router)
  .mount('#app')
```

```vue:App.vue
<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <div id="nav">
    <router-link to="/">Home</router-link> |
    <router-link to="/hoge">hoge</router-link> |
  </div>
  <router-view />
</template>

<script lang="ts">
  export default {
    name: 'App',
    components: {}
  }
</script>
```

```ts:router.ts
import Vue from 'vue';
import * as VueRouter from 'vue-router';
import { createRouter, createWebHistory } from 'vue-router'

import HelloWorld from './components/HelloWorld.vue';
import Hoge from './components/Hoge.vue';


const routes = [
  {
    path: '/',
    component: HelloWorld
  },
  {
    path: '/hoge',
    component: Hoge
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;
```

#おわりに
Vue という流行りの言語をチョイスしながらも Vue3系+TS+Vite というあまりにも日本語記事の少ないアーキテクチャに挑戦し、いろいろと苦しんだため同じ境遇の方々にお役立てられれば幸いです。

経験が浅いため修正必要箇所等あるかもしれません。もし見つけられた場合はコメントなどいただけると助かります。

P.S. 実装編の方を今後別記事として作成する予定です。お待ちください。