是該努力點了!
1413 字
7 分鐘
Laravel 11 起手架 - 一站式開發整合
2024-04-11

Laravel 一站式開發整合#

Laravel 在 PHP 中廣為熟知的框架,目前市佔應該佔了不小的面額, 本篇的目的如同該標題提及,整合成全家桶的樣子,需要經歷一些些改造。 在此紀錄上一些操作,避免日後可能有些忘記要全部重新查起。

此外,可能一些人認為整合在一起的優劣有所爭議,這邊不探討該問題。

期望導入的相關技術(許願池):#

以下列導入的技術有順序性(只有前端),請若是不需要什麼,請由對應的步驟往回看,然後反著操作試試看。

  • Laravel ! … [1]
  • Frontend
  • Backend
  • ⁉️ DevOps
    • pre-commit
    • Docker
    • Ansible

BEGIN !#

Unit 1. 建立 Laravel 專案 (請自行注意 PHP8.2 以上才能裝到 Laravel 11)#

composer create-project laravel/laravel laravel-11-template --prefer-dist

Unit 2. Tailwind CSS#

請到 Laravel 根目錄底下運行下列指令。

安裝依賴、並且產生 tailwind.config.js, postcss.config.js#

npm install -D tailwindcss postcss autoprefixer # 安裝依賴
npx tailwindcss init -p # 產生 tailwind.config.js, postcss.config.js

調整配置檔案(tailwind.config.js)#

/** @type {import('tailwindcss').Config} */
export default {
  content: [ // 這裏 --- begin
    "./resources/**/*.blade.php",
    "./resources/**/*.js",
    "./resources/**/*.vue",
  ], // 到這裡 --- end
  theme: {
    extend: {},
  },
  plugins: [],
}

調整 Laravel 框架內的 resources/css/app.css 檔案,新增 Tailwind 導向的 CSS#

@tailwind base;
@tailwind components;
@tailwind utilities;

啟動你的程式#

npm run dev

設定視圖,使 view 可以吃到最終產生出來的 CSS#

@vite('resources/css/app.css') 這段,會去將 vite 的 develop server 或是編譯結果放在這裡。

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  @vite('resources/css/app.css')
</head>
<body>
  <h1 class="text-3xl font-bold underline text-blue-400">
    Hello world!
  </h1>
</body>
</html>

Unit 3. Vue3#

請到 Laravel 根目錄底下運行下列指令。

安裝依賴#

npm install vue
npm install @vitejs/plugin-vue

配置 vite.config.js#

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue' // 這裡也是新增的

export default defineConfig({
    plugins: [
        vue(), // 這裡是新增的
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.js'],
            refresh: true,
        }),
    ],
});

調整 resources/js/app.js,新增 resources/js/App.vue 檔案#

// resources/js/app.js
import './bootstrap';
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount("#app")
// resources/js/App.vue
<template>
    <div class="text-blue-500 text-3xl">Hello App.vue</div>
</template>

調整 Laravel View(welcome.blade.php) ,新增 @vite('resources/js/app.js')#

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Application</title>
  @vite('resources/css/app.css')
</head>
<body>
  <div id="app"></div>
    @vite('resources/js/app.js')
</body>
</html>

SPA Router -> 調整 routes/web.php#

除去 api 前綴的路由,其他都往 welcome.blade.php 的 view 導過去。

use Illuminate\Support\Facades\Route;

Route::get('/{any}', fn () => view('welcome'))->where('any', '^(?!api).*$');

Unit 4. Vue Router#

npm install vue-router@4

修改 Unit 3. 的 resources/js/app.js resources/js/App.vue,新建三個 resources/js/router.js, resources/js/Pages/About.vue, resources/js/Pages/Home.vue#

About.vue, Home.vue 自行在 template 標籤塞入字串即可,用於 Vue router 作用時辨識用是否導過去對應的 Vue 檔案。

// resources/js/router.js
import { createRouter, createWebHashHistory } from 'vue-router';

const routes = [
    { path: "/", component: () => import("./Pages/Home.vue") },
    { path: "/about", component: () => import("./Pages/About.vue") },
];

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

export default router;
// resources/js/app.js
import './bootstrap';
import { createApp } from 'vue'
import App from './App.vue'
import router from './router';

const app = createApp(App);

app.use(router)
app.mount("#app")
// resources/js/App.vue
<template>
    <div class="flex gap-2 p-2">
        <router-link to="/" class="bg-blue-500 text-white px-2 py-1 rounded">Home</router-link>
        <router-link to="/about" class="bg-red-500 text-white px-2 py-1 rounded">About</router-link>
    </div>
    <router-view />
</template>

Unit 5. Pinia#

npm install pinia

調整 resources/js/app.js,新增 resources/js/stores/counter.js#

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router';

const app = createApp(App)
    .use(router)
    .use(createPinia());

app.mount("#app")
// resources/js/stores/counter.js
import { ref, computed } from 'vue';
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', () => {
    const count = ref(0);
    const doubleCount = computed(() => count.value * 2);
    function increment() {
        count.value++;
    }

    return { count, doubleCount, increment };
});
<script setup>
// 選定你要用的頁面在引入,在 dev tool 應該就有看到 pinia 了
import { useCounterStore } from './stores/counter'
const store = useCounterStore()
</script>

<template>
    <div>test pinia !</div>
</template>

Unit 6. Typescript#

安裝依賴#

npm install -D typescript ts-loader
npm install -D @vue/tsconfig # 一些 ts 的設定檔(預設)

對於自己調整 tsconfig.js 有些複雜,因此使用 @vue/tsconfig 來幫忙,先建立 tsconfig.json,內容如下#

{
    "extends": "@vue/tsconfig/tsconfig.json",
    "include": [
        "*.d.ts",
        "resources/**/*.ts",
        "resources/**/*.d.ts",
        "resources/**/*.vue"
    ],
    "compilerOptions": {
        "baseUrl": ".",
        "paths": {
            "@/*": ["resources/js/*"]
        },
        "types": ["vite/client"],
        "allowJs": false,
        "strict": false
    }
}

將 js 改成 ts,並且進入的 view -> welcome.blade.php,更改 vite 載入的檔案, vite.config.js 也要調整#

// resources/view/welcome.blade.php
// 原本: @vite('resources/js/app.js') 
// 改成: @vite('resources/js/app.ts') 
// 將 laravel() 這裡面的 input: 
// ['resources/css/app.css', 'resources/js/app.js'] 變成為下面這樣
// ['resources/css/app.css', 'resources/js/app.ts']
當遇到報錯: 找不到模組 XXX.vue 或其相對應的類別宣告時:#

該問題是因為 ts 認不得 vue 檔案。所以請在上述的 tsconfig.json 中的 include 這個陣列的位置中,隨意添加一個 .d.ts 的檔案,然後在該檔案中加入以下內容:

// shims.d.ts
declare module "*.vue" {
    import { ComponentOptions } from "vue";
    const componentOptions: ComponentOptions;
    export default componentOptions;
}

Unit 7. Test#

TODO

Unit 8. laravel-route-attribute#

作用大概就是… 使用 PHP 8 的特性,使用 attribute 去定義路由,也就是說在 routes/ 資料夾看不到對應的 route, 而是透過 controller 的註解, Laravel 動態解析路由。

安裝,並且 vendor publish#

composer require spatie/laravel-route-attributes
php artisan vendor:publish --provider="Spatie\RouteAttributes\RouteAttributesServiceProvider" --tag="config"

調整 config/route-attributes.php#

return [
    /*
     *  Automatic registration of routes will only happen if this setting is `true`
     */
    'enabled' => true,

    /*
     * Controllers in these directories that have routing attributes
     * will automatically be registered.
     *
     * Optionally, you can specify group configuration by using key/values
     */
    'directories' => [
        // app_path('Http/Controllers'),
        app_path('Modules') => [ // 這邊代表 app/Modules 的這個資料夾下面都會去做掃瞄,如果使用 #[Get()] 就會去註冊路由
           'prefix' => 'api',
           'middleware' => 'api',
            // only register routes in files that match the patterns
           'patterns' => ['*Controller.php'],
           // do not register routes in files that match the patterns
           'not_patterns' => [],
        ],
    ],

    /**
     * This middleware will be applied to all routes.
     */
    'middleware' => [
        \Illuminate\Routing\Middleware\SubstituteBindings::class
    ]
];

寫一個測試用的,並且使用 php artisan route:list 觀察是否路由有正確出現#

// app/Modules/List/Http/Controllers/IndexController.php
namespace App\Modules\List\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Spatie\RouteAttributes\Attributes\Get;

class IndexController extends Controller
{
    #[Get('api/list', name: 'list.index')]
    public function index()
    {
        return response()->json([
            'message' => 'Hello from List Module!',
        ]);
    }
}

Unit 9. Swagger(L5-Swagger)#

安裝 Swagger 來幫助我們產生 API 文件,方便前端人員查看。

composer require "darkaonline/l5-swagger"
php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"
php artisan l5-swagger:generate

Unit 10. Test(Pest)#

TODO

Unit 11. PHP-CS-Fixer#

TODO