LaravelとVueでSPAを作るときにTypescriptとpugを使う設定をする

> [Laravel](https://github.com/laravel/laravel)、[Vue.js](https://github.com/vuejs/vue)、[Typescript](https://github.com/Microsoft/TypeScript)、[pug](https://github.com/pugjs)で[SPA](https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%B3%E3%82%B0%E3%83%AB%E3%83%9A%E3%83%BC%E3%82%B8%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)を作ったときのメモ。

```
docker --version
Docker version 19.03.8, build afacb8b
```
扱った[Docker](https://www.docker.com/)のバージョンは19.03.8。

```:Fig.1
project
├── db
├── web
├── Dockerfile
├── docker-compose.yml
└── .env
```

以下の手順で `Fig.1` のような構成のレポジトリを作り、`web`ディレクトリの中にLaravelをインストールする。

## レポジトリと各ファイルを作成

```
mkdir project_name && cd $_ && touch {docker-compose.yml,Dockerfile,.env}
```
プロジェクトのディレクトリを作成し( `mkdir project_name` )、作成したディレクトリに移動し( `cd $_` ) [^10] 、`docker-compose.yml`, `Dockerfile`, `.env` ファイルを作成する（ `touch {docker-compose.yml,Dockerfile,.env}` ）。

この時点で `Fig.2` の構成になる。

```:Fig.2
project_name
├── Dockerfile
├── docker-compose.yml
└── .env
```

各ファイルは以下の内容で書く。

### docker-compose.yml

```yml:docker-compose.yml
version: '3'
services:
  web:
    build: .
    container_name: ${PROJECT}-web
    ports:
      - 80:80
      - 3000:3000
    volumes:
      - ./web:/var/www/html/${PROJECT}
    depends_on:
      - db
  db:
    image: mysql:8
    container_name: ${PROJECT}-mysql
    restart: always
    environment:
      MYSQL_DATABASE: ${PROJECT}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
      TZ: ${TZ}
    ports:
      - 3306:3306
    volumes:
      - ./db:/var/lib/mysql
```

### Dockerfile

```Dockerfile:Dockerfile
FROM php:7.4-fpm

COPY --from=node:12
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
RUN apt-get update \
  && apt-get install -y wget git zip unzip vim libpq-dev \
  && : 'Install PHP Extensions' \
  && docker-php-ext-install pdo_mysql pdo_pgsql \

WORKDIR /var/www/html/project_name

```

[マルチステージビルド](https://matsuand.github.io/docs.docker.jp.onthefly/develop/develop-images/multistage-build/)[^11]で [Node.js](https://github.com/nodejs/docker-node/blob/master/README.md) と [Composer](https://github.com/docker-library/docs/tree/master/composer) の [Docker公式イメージ](https://docs.docker.com/docker-hub/official_images) を利用する。

### .env

```.env:.env
PROJECT=project_name
DB_USER=user_name
DB_PASSWORD=password
TZ=Asia/Tokyo
```

## コンテナを起動

```
docker-compose up -d
```

この時点で `Fig.1` の構成になる。

## コンテナに入る

```
docker-compose exec web bash
```

コンテナの中では`composer`や`npm`が使える。

```
# composer --version
Composer version 1.10.6 2020-05-06 10:28:10

# node --version
v12.16.3

# npm --version
6.14.5
```

## Laravelをインストール

```
# composer create-project --prefer-dist laravel/laravel .

// バージョンを指定する場合 (e.g. Laravel6 を指定する場合)
# composer create-project --prefer-dist laravel/laravel . "6.*"
```

## Laravelの設定

### web/config/app.php

```php:app.php
<?php

return [

    // 他の設定

    'timezone' => 'Asia/Tokyo',
    'locale' => 'ja',

    // 他の設定
];
```

設定値を変更。

### web/.env

```.env:.env
// 他の設定
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=project_name
DB_USERNAME=user_name
DB_PASSWORD=password
// 他の設定
```

データベースに接続するための情報を入れる。
MySQLに接続する場合は`DB_CONNECTION=mysql`。
`DB_HOST` は`docker-compose.yml`の`services`で設定した`db`とする。
他の設定も `.env` で設定したものと同じ。

## パッケージをインストール

```
# npm i
# npm i vue vue-router pug pug-plain-loader --save-dev  
# npm i -g typescript
# tsc --init
```
typescriptをグローバルにインストールすると`tsc`コマンドが使えるようになり、

```
# tsc --version
Version 3.8.3
```

`tsc --init`とすると`tsconfig.json`が生成される。[^2] 
`tsc --init` を `git init` と混同して `tsc init` としないように注意。[^3]

## ファイルを編集

### web/webpack.mix.js

```js:webpack.mix.js
const mix = require('laravel-mix')

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

mix
.webpackConfig({
  module: {
    rules: [{
      test: /\.pug$/,
      oneOf: [
        {
          resourceQuery: /^\?vue/,
          use: ['pug-plain-loader']
        },
        {
          use: ['raw-loader', 'pug-plain-loader']
        }
      ]
    }]
  }
})
.browserSync({
  proxy: '0.0.0.0:80',
  open: false,
  files: [
    'resources/**/*',
    'public/**/*'
  ]
})
.ts('resources/ts/app.ts', 'public/js/app.js')
.sass('resources/sass/app.scss', 'public/css')
.version()

```
pugを扱うための設定も記載する。[^5]
[BrowserSync](https://browsersync.io/)の設定を記入。[^6] [^7]
`mix.js()` と書かれていた部分を `mix.ts()` 変えるだけ。[^1] あとは扱うファイルの変更に従って引数のファイルも`js`から`ts`に変更する。

### web/resources/ts/app.ts

```ts:app.ts
import Vue from 'vue'
import router from './router'
import App from './App.vue'
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App />'
})
```

`web/resources/js/app.js` を `web/resources/ts/app.ts` に書き換える。
ここで `App.vue` と `router.ts` を読み込む。

### web/resources/ts/App.vue

```vue:App.vue
<template lang="pug">
div
  main
    RouterView
</template>
```
ルートコンポーネント。
ここで Vue Router が提供する [`RouterView`](https://router.vuejs.org/api/#router-view)[^8] を使う。

### web/resources/ts/router.ts

```ts:router.ts
import Vue from 'vue'
import VueRouter from 'vue-router'
import Foo from './pages/Foo.vue'
import Bar from './pages/Bar.vue'

Vue.use(VueRouter)

const routes: any = [{
    path: '/foo',
    component: Foo
  },
  {
    path: '/bar',
    component: Bar
  }
}]

const router = new VueRouter({
  mode: 'history',
  routes
})

export default router
```

ルーティングの定義の設定。 
[History モード](https://router.vuejs.org/ja/guide/essentials/history-mode.html#%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%81%AE%E8%A8%AD%E5%AE%9A%E4%BE%8B) [^9]にすることでURLがハッシュなしで設定できる。

### web/resources/ts/pages/Foo.vue

```vue:Foo.vue
<template lang="pug">
  .foo
    h1 Foo
</template>
```

### web/resources/ts/pages/Bar.vue

```vue:Bar.vue
<template lang="pug">
  .bar
    h1 Bar
</template>
```

### web/routes/web.php

```php:web.php
<?php

Route::get('/{any?}', function () {
    return view('index');
})->where('any', '.+');
```

### web/resources/views/index.blade.php
```php:index.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>project_name</title>
    <script src="{{ mix('js/app.js') }}" defer></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css">
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
```

### web/vue-shim.d.ts

```vue-shim.d.ts
declare module "*.vue" {
    import Vue from "vue";
    export default Vue;
}
```
`Typescript: IDE reports TS2307: Cannot find module error for Vue components imports` とエラーが出たので作成する。[^4]


## サーバーを起動

```
# php artisan serve --host 0.0.0.0 --port 80
```

## ビルド

```
# npm run watch-poll
```

http://localhost:3000 にアクセスしてブラウザで確認する。

## 参考
[^1]: [JavaScript | Laravel Mix Documentation](https://laravel-mix.com/docs/5.0/mixjs#typescript-support)
[^2]: [Laravel MixでTypeScriptを使う](https://blog.capilano-fw.com/?p=6460)
[^3]: [TypeScript init: error TS6053: File 'init.ts' not found](https://stackoverflow.com/questions/38433052/typescript-init-error-ts6053-file-init-ts-not-found)
[^4]: [Typescript: IDE reports TS2307: Cannot find module error for Vue components imports #1198](https://github.com/vuejs/vue-cli/issues/1198)
[^5]: [Compiler error since v4 - Vue SFC using pug lang for template · Issue #1885 · JeffreyWay/laravel-mix](https://github.com/JeffreyWay/laravel-mix/issues/1885#issuecomment-449764870)
[^6]: [BrowserSync | Laravel Mix Documentation](https://laravel-mix.com/docs/5.0/browsersync)
[^7]: [Browsersync options](https://browsersync.io/docs/options/#option-proxy)
[^8]: [API Reference | Vue Router](https://router.vuejs.org/api/#router-view)
[^9]: [HTML5 History モード | Vue Router](https://router.vuejs.org/ja/guide/essentials/history-mode.html#%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%81%AE%E8%A8%AD%E5%AE%9A%E4%BE%8B)
[^10]: [Linux cd command tutorial for beginners (8 Examples)](https://www.howtoforge.com/linux-cd-command#-how-to-create-directory-and-switch-to-it-using-single-command)
[^11]: [マルチステージビルドの利用 | Docker ドキュメント](https://matsuand.github.io/docs.docker.jp.onthefly/develop/develop-images/multistage-build/)