快轉到主要內容

整合vitejs + vue3搭配.NET Core網站開發練習

·602 字·3 分鐘
Art
作者
Art
這是我的技術筆記。

這篇文章主要在練習如何整合前後端網站,在開發時期階段的體驗及發佈出去的方法,最終將網站容器化的一個範例

Intro
#

這篇文章主要是針對 Youtube:AspNetCore Api + Vitejs + Vuejs Setup for Dev & Prod - Anton Wieslander所做的練習及心得,建議有時間的人可以花 20 分鐘看一下

example repos
#

  1. LAB-vite: 因為喜歡在 rider 開發,因此新建一個方案檔將前後端專案都拉進來
  2. Lab-vite-vue-dotnetcore: 多了 dockerfile 的部分,其餘大同小異

網站開發框架的選擇
#

webpack 很好沒錯,但專案變大後,不管是在產生 production build 還是在開發時期的編譯時間,都需要很久的時間才能運作,如果再加上開發較複雜的專案,可能同時主機就會需要開好幾個 IDE,有時為了做 PoC 還會需要弄個 docker 架架資料庫 在這樣的背景之下如果還要等待 webpack 是非常難以忍受的事情;同理,在先前就已經了解到了 vue2vue3 的差異,並且希望能夠練習一下 vue3 所帶來的開發體驗,而我又是慣常開發 C# 的人,採用.NET6 mvc也是很自然的一件事情,因此就有了下列的技術架構

  1. 前端採用 vite.js + vue3.js
  2. 後端採用 .NET6 mvc

vite.js 能幹嘛
#

  1. 可以拿來打包程式碼
  2. 開發模式的時候,速度挺快 (利用 瀏覽器支援Native ESM進行運作)
  3. 正式環境的時候,打包程式碼跟 webpack 一樣,都是將所需要的模組全部包在一起 (為了避免因依賴鍊過長,造成瀏覽器持續的請求資源,這當中前端 JS 是停在那邊等待的,畫面會卡)

vue3.js 的好處
#

相比於 vue2 的差異,最主要的感受應該是提供了 Composition API 的寫法,還有效能的提升,差異的部分網路上很多,我覺得這一篇vue3 對比 Vue2.x 差異性、注意點、整體梳理,與 React hook 比又如何挺不錯,專業的評比還是交給專業的來,我轉發一下就好

對我而言,最主要的就是一系列的效能優化改善,以及最直觀的語法上提供了 Composition API 所帶來的優點,也就是關注點分離,使邏輯、資料可以集中存放維護,也便於 reUse

前後端分離開發
#

因為實際上佈署出去的環境,也會影響到開發網站的選擇,這邊因為只是練習,所以打算是將前後端 deploy 到同一個站台發佈出去,之後可以再包裝成 docker 直接容器化,感覺想法還行,那麼就是實作來驗證看看開發體驗順不順,因為是前後端分離專案,兩邊開發,之所以這麼考量,主要還是覺得前端後端其實都有其專業程度,一個專案內如果前後端包在一起,那勢必就會需要開始學習自己不熟悉的部分,有些人喜歡這樣,但也有的人不喜歡,認為為甚麼開發前端,我還要去了解後端的 razor 頁面、生命週期阿啥的東西;或者是反過來開發後端,我還需要去學習 webpacknpm、指令列那些的東西,不能就用 VS2022 IDE 介面上點一點就好了嗎?

我覺得有興趣的話,東西都放在同一個 sln 內,想看可以去看;如果不感興趣,直接開自己的頁面,前端就確定好假資料的 API 自己刻;後端因為也只提供前端呼叫,所以確認好 API 介面後自己單元測試做一做,都挺好的,反正不管怎麼說,前後端始終都有自己的技術堆疊,切割開來會是比較好的選擇

但是兩個專案跑起來兩個站台,假設前端 3000後端5000好了,前端跟後端索取資料,很自然地就會需要 fetch 給網址,像是 http://localhost:5000/Api/Test,但是最終又是會放在一起 deploy 出去的,所以 production 出去的時候,網址應該是打/Api/Test,那有沒有辦法可以解決呢

應該有吧,說不定可以弄一個設定檔之類的東西,看 deploy 出去是 development 還是 production 來切換吧?嗯,可能吧,但想想就有點累,有沒有更方便一點的?

Microsoft.AspNetCore.SpaServices.Extensions
#

還真的有,這東西就叫做 Microsoft.AspNetCore.SpaServices.Extensions,但是必須要抱歉的是,我對它不熟悉,而且似乎網上的介紹很少,但藉由一些練習我大概摸索出來的用途如下

  1. 它能夠將發給後端的請求,代理轉發到 SPA 那邊去,所以我們必須要再判斷當前為開發時期的時候才這樣做,因為在正式環境,東西都被我們佈署在一起了,就不需要做代理這件事情
  2. 它能夠指定網站的RootPath,所以當佈署在一起的時候,網站吃的首頁就是該路徑

LAB:建立前後端網站
#

準備工作大概完成了,底下就列出練習的步驟及重點

建立前端網站
#

建立目錄並利用官方 Getting Started的指令建立一個快速的啟動專案

mkdir Lab-vite-vue-dotnetcore
cd Lab-vite-vue-dotnetcore
npm create vite@latest

接著會要求輸入專案名稱,選擇技術框架、採用的語言

完成後可以進入該目錄先安裝套件,並跑一次看看網站是否正常啟動

cd front-end
npm install
npm run dev

建立後端網站
#

回到上層目錄接著開始透過 dotnet cli 建立新的 mvc 專案,完成後一樣測試看看網站是否正常啟動

dotnet new web -n back-end
cd back-end
dotnet run

LAB:前端索取資料
#

這邊我們練習從 Vue Component 裡面去撈後端資料,重點在 fetch 之後,要透過 reactive 包裝,這樣資料才會響應式的隨著變動

底下就列出關鍵的部分

後端給予資料
#

做一個 API 提供前端呼叫,當路由符合 /api/test就回應一個字串,因此做一個測試的 TestController

// Controller/TestController.cs
using Microsoft.AspNetCore.Mvc;

namespace back_end.Controller;

[Route("/api/[controller]")]
public class TestController : Microsoft.AspNetCore.Mvc.Controller
{
    public IActionResult Index()
    {
        return Ok("Test Result");
    }
}

當然也因為測試環境是兩個站台,所以後端為了測試要允許CORS

// program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseCors(b => b.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
}

app.UseRouting();
app.UseEndpoints(e => e.MapDefaultControllerRoute());


app.Run();

前端取得資料
#

在前端 SFC 透過 fetch 拿資料後將結果指派回資料,經由 vue 渲染於畫面上,後端路由:https://localhost:7156/api/Test

<script setup>
  // /src/components/HelloWorld.vue
  import { reactive, ref } from "vue"

  defineProps({
    msg: String,
  })

  const count = ref(0)
  // === new code begin
  const state = reactive({
    message: "empty",
  })

  fetch("https://localhost:7156/api/Test")
    .then((r) => r.text())
    .then((t) => (state.message = t))
  // === new code end
</script>

LAB:Microsoft.AspNetCore.SpaServices.Extensions
#

新增套件並設定
#

後端加入套件Microsoft.AspNetCore.SpaServices.Extensions並設置,因為目前我採用.NET6,所以安裝的版本是 6.0.11 版

dotnet add package Microsoft.AspNetCore.SpaServices.Extensions --version 6.0.11
// program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// 此處指定 dist 是因為之後我們要佈署的時候,前端是把 build 目錄 dist 整個都複製過來
builder.Services.AddSpaStaticFiles(config => config.RootPath = "dist");
var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    // 因為採用套件,開發時期用 proxy 代理,已經不再需要 CORS
    // app.UseCors(b => b.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
}

app.UseRouting();
app.UseEndpoints(e => e.MapDefaultControllerRoute());

// 使用 SPA 的靜態檔案
app.UseSpaStaticFiles();
app.UseSpa(b =>
{
    if (app.Environment.IsDevelopment())
    {
        // 開發階段採用 proxy 指定前端網站
        b.UseProxyToSpaDevelopmentServer("http://localhost:5173/");
    }
});
app.Run();

修改前端 fetch 網址
#

因為請求已經被代理,所以路由就改成相對路徑

fetch("/api/Test")
  .then((r) => r.text())
  .then((t) => (state.message = t))

接著直接開啟後端網站,就可以看到前端SPA,呼叫後端API的結果

這個時候,如果你同時有開啟 Hot Reload , HMR,前後端的程式碼修改後都可以直接看到瀏覽器會去自動更新畫面,當然後端的部分 Hot Reload 有一些條件,但到目前為止整體的開發流程無疑被改善很多了

發佈網站
#

// 發布後端網站到上層的output目錄
dotnet publish -c Release -o ..\output

前端的部分直接讓他發佈到 output 就好

// vite.config.js
import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"

export default defineConfig({
  plugins: [vue()],
  build: {
    outDir: "../output/dist",
  },
})
npm run build

測試發佈版本
#

目前專案目錄如下

進入 output 目錄,這邊存放的是剛才前後端輸出的結果

直接透過 dotnet back-end.dll執行

將應用程式容器化
#

建立 dockerfile
#

因為不想要把建立搞得太麻煩,所以 build 都在外面先做好,image 就是直接拿 output 複製進去,所以指令變得很單純

FROM mcr.microsoft.com/dotnet/aspnet:6.0

COPY output .

EXPOSE 80

ENTRYPOINT ["dotnet", "back-end.dll"]

產生 image
#

在專案根目錄下執行指令建立 docker image

docker build -t demo .

產生 container
#

當然還需要將 image 透過指令來產生 container,並且測試將 port:7000 指給 container

docker run -d --name=lab -p 7000:80 demo

結論
#

經由這一次的練習,大概可以學到,或者說是複習了

  1. Vite.js + Vue3.js 的威力展示 (HMR)
  2. .NET6 mvc
  3. 前後端整合開發
  4. production 發佈方式
  5. 容器化

可以為之後日益複雜的軟體架構打一些基礎,而過去幾年的技術與現在相比,在開發時期階段更是進步了很多,但是如果舊網站跟不上技術迭代的腳步,可以想見的是維護的人力會越來越辛苦,也越來越少。像是以前的 classic asp 應該已經不會有人想要維護,而傳統的 js 開發方式也很難被現在的開發者接受,舊專案除非已經穩定沒有維護需求那就還好,技術背景停滯在開發當下的階段,如果還要求持續更新專案功能或是除錯維護,無疑是非常挑戰開發人員的一件事情,要嘛弄一些 workAround 避掉,透過增加整體架構的複雜度、犧牲可維護性;要嘛就是乖乖地想辦法把應用程式的技術棧一層層慢慢更新上去,但這其中也有很大的可能性會受限於 客戶端硬體、系統。只能說真的看運氣了,不論如何,軟體開發的演進總是越來越進步,身為開發人員也必須要能夠持續學習成長,才能說是專業從業人員啊 (技術沒有很強至少要有學習心態啊~)

補充:如果你也喜歡用 Rider
#

我非常喜歡使用 Rider 來開發,所以我做了另外一個 Repo:LAB-vite,主要就是為了在 Rider 裡面可以方便的開發,這邊列出一些我碰到的問題還有解法,下面的步驟我就再做一次在新的練習專案:Lab-vite-vue-dotnetcore

我想要利用 IDE 的搜尋功能,找到前端檔案
#

首先幫前端專案複製一個專案檔過去,並修改一下,檔案命名為 front-end.csproj

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <RootNamespace>front_end</RootNamespace>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

</Project>

根目錄下建立方案檔,並將專案加入

dotnet new sln
dotnet sln .\Lab-vite-vue-dotnetcore.sln add .\back-end\back-end.csproj
dotnet sln .\Lab-vite-vue-dotnetcore.sln add .\front-end\front-end.csproj

最後透過開啟方案檔的方式,就可以透過 Search EveryWhere 找到前端檔案了

測試前後端都要分別執行,有沒有 one click 搞定的方法
#

有的,但是仍舊需要做一些前置作業,前端需要一個任務去執行 npm run dev、後端需要一個任務去執行dotnet watch,最後在透過內建的Compound執行兩個任務,

首先我們設定前端的部分,指定好前端路徑

後端的部分則透過.NET Watch Run Configuration,可以很方便的執行

如果一開始是用開啟目錄的方式打開,而不是透過開啟解決方案、專案的話,是沒有辦法設定 dotnet watch外掛的

新增 Compound 任務如下,選擇另外兩個任務就好

點一下之後等待兩個任務跑完,瀏覽器自動開出來

測試一下前端修改之後,HMR 正確執行

後端的部分修改完畢後會自動重載頁面

既然都有一鍵執行了,那一鍵發佈呢
#

本質都是一樣的東西啊,把指令包裝一下就好了

再新增一個執行 output 目錄下 exe 的任務

測試一下發佈的網站結果

那容器化也來一個吧,我真的懶得打指令了
#

新增任務照圖設定

如果想要順便把容器也建立起來,就填一下必要的設定值

測試一下 docker container 的網站