Published on

如何在 Laravel 專案中使用 Vue CLI 3

Authors
  • avatar
    Name
    米K朗基羅
    Twitter
    @kvzl_

a.k.a 如何使用 Vue CLI 取代 Laravel Mix

方法概述

除了參考 Vue の爸爸 Evan You 的作法以外,也結合了之前工作上使用 Shopify Slate 0.x 時,導入 Webpack 的一些作法,像是:

讓 HtmlWebpackPlugin 動態產生 view

這是因為 Webpack 能在不同 mode 時產生出不同的檔案,譬如 development mode 時可能不需要用 <link> 來引入 CSS (為了利用 style-loader 的 HMR);此外檔名也可能會加上 hash (用來控制 cache)。

正好 Vue CLI 整合了 HtmlWebpackPlugin,其產生的 HTML 能自動引用 Webpack 產生的 bundle,也能透過 EJS 語法來自定 HTML Template,所以 HtmlWebpackPlugin 就被我拿來產生 Blade 檔案啦。

Note: 如果不是因為以下原因,我想是可以不需要 Blade 的:

  • 需要 CSRF Token
  • 沒有完全前後端分離,還是需要透過 Blade 來 render 某些資料

不透過 Dev Server 提供的 URL 進行開發

如果像一般在使用 Vue CLI 那樣,透過 Dev Server 來存取網頁,再透過 proxy 發送請求給真正 server 的話,就沒辦法取得 render 過後的 Blade 頁面了。

即使我們不會在瀏覽器中透過 Dev Server 給的 URL 來開發,但 Dev Server 還是在背景為我們處理 HMR、Live Reloading 等任務。

建立環境

需要先安裝這些東西:

composer global require laravel/installer
npm install -g @vue/cli

以下是本次範例完成後的專案結構:

App
├── Http
│   └── Controllers
│       └── HomeController.php	// 會建立這個,用來回傳 View,
└── ...省略

public
├── client	// 會建立這個,Vue CLI 的 build output
├── views	// 放 blade template
└── ...省略

resources
├── client	// 會建立這個,Vue CLI 的根目錄
│   ├── node_modules
│   ├── public
│   ├── src
│   ├── templates	// 會建立這個,用來放 ejs template
│   ├── package.json
│   └── ...省略
└── ...省略

建立 Laravel 專案

laravel new laravel-vue-cli-3
cd laravel-vue-cli-3

# 先將專案恢復到沒有 assets 的狀態,將 npm、mix、blade template、js、css 等等都刪除掉:
rm -rf package.json \
  webpack.mix.js \
  yarn.lock \
  resources/views/welcome.blade.php \
  resources/{js,sass} \
  public/{js,css}

mkdir resources/client

# 建立一個用來回傳 View 的 Controller
php artisan make:controller HomeController

修改以下檔案:

app/Http/Controllers/HomeController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class HomeController extends Controller
{
    // 把這個 function 加上去
    public function main() {
        return view('main');
    }
}

routes/web.php

<?php

Route::get('/{any}', 'HomeController@main')
  ->where('any', '.*');

建立 Vue 專案

接著使用 vue 指令建立 Vue 專案:

cd resources
vue create client
# ...接著按照專案需求來選擇初始配置,並等待環境建置完成。

# 待會會用到,先建起來放
touch client/vue.config.js

建立 template

如同開頭所描述的,我們有兩種不同的 template 需要建立:

# blade template
mkdir views/layouts
touch views/layouts/app.blade.php

# ejs templates
mkdir clients/templates
touch clients/templates/main.ejs

修改建立好的 template 檔案:

resources/views/layouts/app.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" />

    <title>{{ config('app.name') }}</title>

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}" />

    <!-- Styles -->
    @yield('layout-styles')
  </head>
  <body>
    @yield('layout-content')
    <!-- Scripts -->
    @yield('layout-scripts')
  </body>
</html>

resources/client/templates/main.ejs

{{-- This file was generated by webpack, please run `npm run serve` and modifiy
`client/templates/main.ejs` to regenerate. --}} <% const output = htmlWebpackPlugin.files %>
@extends('layouts.app') @section('layout-content')
<div id="app"></div>
@endsection @section('layout-styles') <% output.css.forEach(css => { %>
<link rel="stylesheet" href="<%= css %>" />
<% }) %> @endsection @section('layout-scripts') <% output.js.forEach(js => { %>
<script type="text/javascript" src="<%= js %>"></script>
<% }) %> @endsection

配置 Webpack

雖然我們已經在用 Vue CLI 了,但無法否認包裝的背後仍然是 Webpack,只不過選項變得更加容易了。

修改以下檔案:

resources/client/vue.config.js

const path = require('path')
const root = (...src) => path.resolve(__dirname, '../../', ...src)

module.exports = {
  publicPath: '/client',
  outputDir: root('public/client'),

  pages: {
    main: {
      entry: 'src/main.js',
      template: 'templates/main.ejs',
      filename: root('resources/views/main.blade.php'),
      inject: false,
      minify: false,
    },
  },

  devServer: {
    port: 5000,
    disableHostCheck: true,
    writeToDisk: true,
  },
}

main 是自定義的 entry 名稱,Vue CLI 會將對應的選項傳入 HtmlWebpackPlugin,更多配置方式可以參考 Vue CLI 的配置參考和 HtmlWebpackPlugin 的 Readme

之所以加上 writeToDisk: true 是因為 Dev Server 在 development 模式時,會將編譯結果放在記憶體中以加快編譯速度。但為了讓 Laravel 能正確返回指定的 view,我們需要將結果寫入硬碟。

由於 Dev Server 是獨立於 Laravel 的另一個 server,因此需要加上 disableHostCheck: true 來避免 CORS 問題。

設定環境變數

Mix 和 Vue CLI 都提供能在前端存取環境變數的方式(MixVue CLI)。以 Laravel Echo 為例,我們需要在前端指定 Pusher 的 keycluster ,而一般實務上是不會讓 secret 進版控的,因此需要透過 .env 和環境變數來指定:

import Echo from 'laravel-echo'
import Pusher from 'pusher-js'

window.Pusher = Pusher

const echo = new Echo({
  broadcaster: 'pusher',
  key: process.env.VUE_APP_PUSHER_APP_KEY,
  cluster: process.env.VUE_APP_PUSHER_APP_CLUSTER,
  encrypted: true,
})

需要將原本的用法稍作修改:

.env

// 如果有的話
-MIX_PUSHER_APP_KEY=YOUR_PUSHER_APP_KEY
-MIX_PUSHER_APP_CLUSTER=YOUR_PUSHER_APP_CLUSTER

// 要改成這樣
+VUE_APP_PUSHER_APP_KEY=YOUR_PUSHER_APP_KEY
+VUE_APP_PUSHER_APP_CLUSTER=YOUR_PUSHER_APP_CLUSTER

但 Vue 讀取的是 Vue 專案底下的 .env ,如果要讓 Vue 找到 Laravel 根目錄底下的 .env ,我們可以建立一個 Symbolic link:

cd resources/client
ln -s ../../.env .env

Note: 在其他機器上建立環境或 deploy 時,需要確保執行 npm run build 之前在 Laravel 根目錄已經有 .env 檔案。

完成

確保產生的檔案和環境變數不被放進版控,修改以下檔案:

.gitignore

+/public/client
+/resources/views/main.blade.php
+.env
+!client/.env

常用的操作指令:

cd resources/client

# 初始化
npm install

# 開發
npm run serve

# 建構
npm run build

Laravel Mix 不好嗎?

這個方法是當初在不影響現有開發流程的前提下,為了在現存的專案中導入所設計的,其結果就是 Mix 的部分整個被替換掉了。然而,真的有必要將 Mix 給替換掉嗎?

確實 Mix 配置起來相當容易,而且可以滿足大部分的全端開發需求。但身為一個 Vue 開發者,Vue CLI 提供的配置能力更能夠滿足我在前端工程上的需求。

參考資料