2018.01.13

Laravel 5.5で簡単!Vue.js + TypeScript


はじめに

Laravel + Vue.js + TypeScriptなアプリケーションの開発方法を書きます。

Laravel はサーバサイドの Web アプリケーションフレームワークなので、Vue.js とか TypeScript とか関係ないんじゃないのと思われるかもしれませんが、Laravel には Laravel Mix という Webpack のラッパーライブラリが付属しており、設定不要なフロントエンド開発が可能となっています。

前提

Tool Ver.
Laravel 5.5
Vue.js 2.5
TypeScript 2.6
Node.js 8.9.1
npm 5.5.1
homestead 7.0.1
homestead (Vagrant Box) 5.0.1

ちなみに開発機はMacです。

まずはプロジェクトの新規作成

homestead
$ cd ~/code
$ composer create-project laravel/laravel --prefer-dist sample_app
$ cd sample_app
$ yarn install

開発環境の設定も行ないます。

Homestead.yaml
folders:
    - map: ~/path/to/your/homestead
      to: /home/vagrant/code

sites:
    - map: homestead.test
      to: /home/vagrant/code/sample_app/public
etc/hosts
192.168.10.10   homestead.test

ここまでで、http://homestead.test でウェルカム画面が見られるはずです。

npmパッケージの追加インストール

homestead
$ yarn add -D ts-loader typescript vue-property-decorator

tsconfig.jsonを追加

tsconfig.json
{
  "compilerOptions": {
    "outDir": "./built/",
    "sourceMap": true,
    "strict": true,
    "noImplicitReturns": true,
    "noImplicitAny": true,
    "module": "es2015",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "moduleResolution": "node",
    "target": "es5",
    "lib": [
      "es2016",
      "dom"
    ]
  },
  "include": [
    "resources/assets/ts/**/*"
  ]
}

プロジェクトルート(.envがあるディレクトリ)に上記tsconfig.jsonファイルを追加します。

TypeScriptの環境設定は以下のサイトを参考にしました。

vue.shims.d.ts

再び上のサイトを参考に、resources/assets/ts配下に下記の型定義ファイルを追加します。単一ファイルコンポーネント(*.vueファイル)についてのコード解析を助けるものらしいです。

vue.shims.d.ts
declare module "*.vue" {
  import Vue from "vue";
  export default Vue;
}

webpack.mix.js を編集

webpack.mix.js
let mix = require('laravel-mix');

mix.ts('resources/assets/ts/app.ts', 'public/js')
  .sass('resources/assets/sass/app.scss', 'public/css');

デフォルトだと mix.js(...) となっているところを ts メソッドを使うように変更します。 TypeScriptコードの格納ディレクトリも asset/js から asset/ts に変更しています。

Let's Coding !

設定はこれで終了!超簡単ですね 🤗 ♨

あとはコードを書いていくだけですが、簡単なサンプルを載せてみます。

テンプレートファイル

まずはテンプレートです。既存の welcome.blade.php ファイルを編集しました。

resources/views/welcome.blade.php
<!doctype html>
<html lang="{{ app()->getLocale() }}">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="csrf-token" content="{{ csrf_token() }}">
  <title>Laravel</title>
  <link rel="stylesheet" href="/css/app.css">
</head>
<body>
<main>
  <div id="app"></div>
</main>
<script src="/js/app.js"></script>
</body>
</html>

エントリポイント

次にエントリポイントとなる app.ts ファイルです。デフォルトでついてる bootstrap.js も(Axiosの設定だけ)活かしてみました。JavaScriptだと require('./bootstrap.js')bootstrap.js の内容を実行しているのですが、TypeScriptではそういうことはできないようなので、関数にしました。

resources/assets/ts/app.ts
import Vue from 'vue';
import bootstrap from './bootstrap';
import AppComponent from './components/App.vue';

bootstrap();

const app = new Vue({
  el: '#app',
  render: h => h(AppComponent)
});
resources/assets/ts/bootstrap.ts
import Axios, { AxiosStatic } from 'axios';

declare global {
  interface Window {
    axios: AxiosStatic;
  }
  interface Element {
    content: string;
  }
}

export default function bootstrap() {

  window.axios = Axios;

  window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

  let token = document.head.querySelector('meta[name="csrf-token"]');

  if (token) {
    window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
  } else {
    console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
  }

}

コンポーネント

続いてコンポーネントです。単一ファイルコンポーネントでの記述が可能です。

ポイントはまず、<script> タグに lang="ts" を指定してあげることです。

また vue-property-decoratorを利用していますので ComponentProp といったデコレータを使ってクラスベースでコンポーネントを表現していきます。 なんだか見た目がAngularっぽいですね(??)

上記 bootstrap.ts で設定したAxiosが使えるか確かめたかったので、ダミーデータAPIにリクエストしています。

resources/assets/ts/components/App.vue
<template>
  <div class="App">
    <div class="container">
      <div class="row">
        <div class="col-md-8 col-md-offset-2">
          <h1>{{ message }}</h1>
          <p class="text-center">
            <button class="btn btn-primary" @click="load">Load Users</button>
          </p>
          <users :users="users"></users>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
  import { Vue, Component } from 'vue-property-decorator';
  import UsersComponent from './Users.vue';
  import User from '../models/User';

  @Component({
    components: {
      'users': UsersComponent
    }
  })
  export default class App extends Vue {
    message = 'Laravel + Vue.js + TypeScript';
    users: User[] = [];

    load() {
      // 下記サイトからダミーデータ取得
      // https://jsonplaceholder.typicode.com/
      window.axios.get('https://jsonplaceholder.typicode.com/users')
        .then(response => {
          this.users = response.data;
        })
        .catch(err => console.error(err));
    }
  }
</script>

<style scoped>
  h1 {
    text-align: center;
    margin: 4rem 0;
  }
</style>

下記の通り、<style>lang の指定をするとSassも使用できます。

resources/assets/ts/components/Users.vue
<template>
  <div class="Users">
    <div class="list-group">
      <div class="list-group-item" v-for="user in users">
        <h4>{{ user.name }} <small>@{{ user.username }}</small></h4>
        <p>{{ user.email }}</p>
        <p>{{ user.phone }}</p>
        <address>
          {{ user.address.street }}
          {{ user.address.suite }}
          {{ user.address.city }}
          {{ user.address.zipcode }}
        </address>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
  import { Vue, Component, Prop } from 'vue-property-decorator';
  import User from '../models/User';

  @Component
  export default class Users extends Vue {
    @Prop() users: User[];
  }
</script>

<style lang="scss" scoped>
  .list-group-item {
    padding: 2rem;
    h4 {
      margin-top: 0;
    }
    address {
      margin-bottom: 0;
    }
  }
</style>

ダミーのユーザーデータを取得するので、そのレスポンス形式に合わせてUser型を作成(TypeScriptっぽいことがしてみたかった)。

resources/assets/ts/models/User.ts
export default interface User {
  id: number;
  name: string;
  username: string;
  email: string;
  address: Address;
  phone: string;
  website: string;
  company: Company;
}

export interface Address {
  street: string;
  suite: string;
  city: string;
  zipcode: string;
  geo: {
    lat: string;
    lng: string;
  }
}

export interface Company {
  name: string;
  catchPhrase: string;
  bs: string;
}

ビルド

homestead
$ yarn run dev

ビルドが成功すると以下の画面が表示されるはず。

Screenshot-2018-1-9 Laravel.png

ボタンを押すと、ダミーユーザーたちのデータを取得できています。

Screenshot-2018-1-9 Laravel(1).png

まとめ

以上、Laravel Mix を利用して Vue.js + TypeScript なアプリケーションの開発を行う方法でした。

最近では Poiparcel など、なるべく簡単にフロントエンドの設定を行うビルドツールが求められているようですが、Laravel Mixもなかなか強力なツールなのではないでしょうか。

Laravel Mix は Laravel 本体とは独立した npm パッケージですので、Laravel は使わなくとも、フロントエンドのビルドツールとして Laravel Mix のみ利用するというのもアリかもしれません。