基于Koa實現的服務端渲染 ?

前段時間剛寫完畢業論文,現在一上來就是“基于”,哈哈。🤯 這篇文章持續更新,涉及到的技術棧是Koa、Vue和Vite (用React手搓服務端渲染好麻煩)。但是現在能上生產的服務端渲染估計是Next(配合React)和Nuxt(配合Vue)用的比較多。關于Next框架的學習見煮啵的另一篇文章,也將持續更新。

目錄

1?? 最基本的服務端渲染
2?? Koa配合Vue
3?? Koa配合Vue和Vite


最基本的服務端渲染

懶得BB,直接上代碼:

// koa-pro/demos/basic_ssr.js
export default async (ctx) => {ctx.type = 'html';ctx.body = `<!DOCTYPE html><html><head><title>Hello</title></head><body><h1>Hello World</h1></body></html>`;
}// koa-pro/index.js
import Koa from 'koa';
import Router from '@koa/router';
import basicSSR from './demos/basic_ssr.js'const koa = new Koa();
const router = new Router();router.get('/basic_ssr', basicSSR)
koa.use(router.routes());
koa.listen(3001, () => console.log('Server is running on port 3001'))

這串代碼我們平時根本不屑一顧,但他卻實現了最基本的SSR。因為它在Koa服務端處理了一個 HTTP 請求,并返回了一段完整的 HTML 內容。


Koa配合Vue

Koa配合Vue來實現SSR,一開始煮啵是跟著Vue3官方文檔的教程走的,但是它提供的Demo上有大坑 (也可能不是坑,是因為煮啵技術不行),煮啵只介紹自己實現的過程。首先我們將創建Vue應用的邏輯封裝在自定義的createApp函數中:

// koa-ssr/scripts/vue_ssr/app.js
const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
export default async function createApp() {let createSSRApp; // Vue提供的創建SSR應用的APIif (isBrowser) {createSSRApp = (await import("https://unpkg.com/vue@3.5.13/dist/vue.esm-browser.js")).createSSRApp;} else {createSSRApp = (await import('vue')).createSSRApp;}// 創建我們自己的應用return createSSRApp({data: () => ({ count: 1 }),template: `<button @click="count++">{{ count }}</button>`,});
}

createApp這個函數的封裝在Vue3官方文檔里也有,但最大的區別就是官方文檔里是直接用import createSSRApp from ‘vue’,而此處卻需要根據當前JS代碼所處的運行環境(Node或瀏覽器)來動態的引入createSSRApp這個玩意。為什么這樣做見下文分析。

其次我們需要處理HTML模版,并用Koa的路由來掛載:

// koa-ssr/demos/vue_ssr.js
import { renderToString } from '@vue/server-renderer';
import createApp from '../scripts/vue_ssr/app.js';
export default async (ctx) => {const app = await createApp()const html = await renderToString(app);ctx.type = 'html';ctx.body = `<!DOCTYPE html><html><head><title>Vue SSR</title></head><body><div id="app">${html}</div><script type="module" src="/vue_ssr/client.js"></script></body></html>`
}// koa-ssr/index.js
// ...已有內容省略,見上一處代碼塊。在已有的基礎上添加下面這些代碼??:
import vueSSR from './demos/vue_ssr.js'
import koaStatic from 'koa-static';
import path from "path";
import { fileURLToPath } from "url";const filename = fileURLToPath(import.meta.url);
const dirname = path.dirname(filename);router.get('/vue_ssr', vueSSR)
koa.use(koaStatic(path.join(dirname, 'scripts')));

此處,在模版中用script標簽來加載腳本文件是必要的,否則SSR只返回了“空殼”,而無法提供任何交互。此外,用script標簽來載腳本文件必須對外暴露(此處用的是koa-static)來實現。

為什么必須對外暴露?因為瀏覽器在拿到Koa返回的HTML后,會請求script的腳本文件,在這個Demo中瀏覽器發起的資源請求的URL即為:http://localhost:3001/scripts/vue_ssr/client.js。問題來了,如果沒有用koa-static對外暴露,Koa便沒有處理這個請求的邏輯,會返回404。

最后我們需要實現這個client.js腳本文件,客戶端就是靠這個腳本來“接管”服務端返回的HTML。這個文件只在客戶端執行

// koa-ssr/scripts/vue_ssr/client.js
import createApp from './app.js';
(async function activate() {const app = await createApp();app.mount('#app');
})()

此處最重要的便是app.mount(“#app”)這句代碼,它的作用是在客戶端激活Vue。

接著我們回答在封裝createApp的時候遺留的問題:為什么需要根據JS代碼的運行環境來動態導入createSSRApp因為koa-ssr/scripts/vue_ssr/app.js這個文件是需要執行兩次的,一次在客戶端,一次在服務端。
在服務端執行的時候(即在koa-router收到http://127.0.0.1:3001/vue_ssr請求的時候),它負責將Vue組件渲染成HTML內容,然后發送到客戶端。
在客戶端執行的時候,此時瀏覽器會加載client.js文件,client.jsimpoetcreateApp這個函數。這個文件用于做Hydration(激活)操作,將Vue組件的行為附加到已經渲染的HTML上
但問題是,app.js在客戶端執行的時候,如果代碼為import { createSSRApp } from “vue”是會報錯的,在瀏覽器中運行時無法解析“vue”模塊,因為瀏覽器并不像Node或Vite開發環境那樣有“模塊解析系統”,它無法直接識別“vue”是個什么路徑。


Koa配合Vue和Vite

既然用到Vite,也算是半只腳踏入“工程化”的大門了。但是Vite大多數情況下我們會用來開發一個SPA應用,此處可以拋出一個困擾了煮啵很久的一個問題:考慮到如果使用了SSR服務端渲染,那么每次切換瀏覽器的導航,是否都需要服務端都需要根據請求來生成HTML頁面并返回給客戶端?那是否可以認為用Vite構建的SSR應用是無法實現SPA應用的?或者說這兩者一定是對立的?

答案當然是否定的。SSR和SPA并不是完全對立的,兩者常常結合使用,可以大致分為以下兩個步驟:

  1. 服務端渲染:在第一次訪問時,Vite會生成HTML并返回給瀏覽器。這些 HTML 包括了從服務器渲染的數據。
  2. 客戶端接管 (Hydration):一旦頁面加載完成,Vue、React或其他前端框架會在客戶端 “接管” 這個頁面,即把頁面變成一個SPA。瀏覽器加載應用的腳本代碼,綁定事件,并且使得頁面可以進行客戶端路由和狀態管理。

總的來說,SPA和SSR的結合我們可以認為是先SSR,然后由客戶端接管為SPA的過程。且煮啵始終認為,再復雜的應用,只要是SPA應用,服務端渲染便往往只發生在首頁渲染的時候(這個“首頁”可以是SPA應用中的任意一個頁面)。借助Vite官網提供的教程,我們可以快速搭建一個基于Vite的Vue&SSR應用:

// pro/src/entry-client.js
// 客戶端入口文件,瀏覽器靠這個文件在客戶端激活Vue,接管服務端返回的HTML
import { createApp } from './main'
const { app } = createApp()
app.mount('#app')// pro/src/entry-serve.js
// 服務端入口文件,返回的stream用于構建HTML文件
import { renderToWebStream } from 'vue/server-renderer'
import { createApp } from './main'
export function render() {const { app } = createApp()const ctx = {}const stream = renderToWebStream(app, ctx)return { stream }
}// pro/src/main.js
// 創建應用實例。此文件在客戶端和服務端各執行一次,具體原因見《Koa配合Vue》中的分析。
import { createSSRApp } from 'vue'
import App from './App.vue'
import router from './router/index'
export function createApp() {const app = createSSRApp(App)app.use(router)return { app }
}// pro/src/App.vue
<template><div>Hello World!</div><li><router-link to="/home">首頁</router-link></li><li><router-link to="/user">個人中心</router-link></li><div><router-view></router-view></div>
</template>// pro/src/router/index.js
// 路由文件
import { createRouter, createMemoryHistory, createWebHistory } from 'vue-router'
import Home from '../views/home.vue'
import User from '../views/user.vue'
const isSSR = typeof window === 'undefined'
const routes = [{path: '/home',component: Home,name: 'home',meta: { ssr: true }},{path: '/user',component: User,name: 'user',meta: { ssr: true }},
]
const router = createRouter({// 此處必須判斷當前所處環境時服務端還是客戶端。// 因為createWebHistory的實現依賴于瀏覽器全局對象window// 而在服務端渲染的時候是沒有window的history: isSSR ? createMemoryHistory() : createWebHistory(),routes,
})
export default router

主要的文件就是上面這幾個了。之后每次進入前端應用的時候,瀏覽器發送GET請求得到的HTML文件中的內容都不再是只有一個id為app的div那么簡單,而是會將App.vue中靜態的內容渲染出來。但是有一個問題:在SSR的時候能不能獲取數據庫中的數據來填充HTML文件中的內容,然后再返回給客戶端?

如果是客戶端渲染,我們通常會在onmount這個生命周期中做響應操作,但是onmount中的副作用永遠屬于客戶端行為,它只有在客戶端接管HTML后才能發生作用。
但是,這種需求在Next中實現起來很簡單:實現getServerSideProps即可。且在Nuxt中估計也是類似的。框架永遠有一個好處就是幫我們封裝了很多很繁瑣的東西,再向上暴露有限的接口。
但是如果硬要用Vite來操作,煮啵現在并沒找到很好的方法🤯。唯一能想到的就是從pro/src/entry-server.js或者pro/server.js這兩個文件中切入來實現,后續找到方法再來分享。

還有一個問題:既然配合了vue-router使用,那能不能在首次進入頁面的時候,讓SSR返回的HTML內容中包含當前路徑映射到的組件,而不僅僅是一個App.vue的內容?
在Vite最基礎的框架上煮啵也沒找到方法🤯,嘗試過在定義路由表的時候給每個路由選項都添加meta: { ssr: true }這樣的配置,但沒有生效,之后再找方法吧。但是話又說回來了🤓在Next中這樣的功能是內置的,框架已經幫你搞定了。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:
http://www.pswp.cn/diannao/81264.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/81264.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/81264.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux運維——Vim基礎

Vim基礎 一、移動光標1.1、基礎移動1.2、屏幕滾動 二、編輯操作2.1、插入模式2.2、刪除與修改2.3、復制粘貼 三、搜索與替換3.1、搜索3.2、替換 4、分屏與窗口管理4.1、分屏操作4.2、窗口調整 五、宏與批量操作六、效率技巧七、操作符7.1、內置操作符7.2、操作符 文本對象&…

git操作合集

更新文件 在 Git 中更新已經上傳到倉庫的文件 1、檢查當前狀態 首先&#xff0c;打開終端或命令行工具&#xff0c;進入你的 Git 倉庫目錄&#xff08;即包含 .git 文件夾的目錄&#xff09;。運行以下命令來查看當前倉庫的狀態&#xff1a; git status 此命令會顯示哪些文…

【筆記】深度學習模型訓練的 GPU 內存優化之旅⑤:內存分配篇

開設此專題&#xff0c;目的一是梳理文獻&#xff0c;目的二是分享知識。因為筆者讀研期間的研究方向是單卡上的顯存優化&#xff0c;所以最初思考的專題名稱是“顯存突圍&#xff1a;深度學習模型訓練的 GPU 內存優化之旅”&#xff0c;英文縮寫是 “MLSys_GPU_Memory_Opt”。…

SQL Server 存儲過程開發手冊

SQL Server 存儲過程開發手冊&#xff08;更新版&#xff09; 根據要求&#xff0c;重新整理并加入了事務控制、異常日志記錄和返回狀態碼的設計。以下是詳細說明&#xff1a; 1. 總則 1.1 目標 本手冊旨在為 SQL Server 存儲過程的編寫提供一套完整的規范&#xff0c;確保系…

深海科技服務博客簡介

人人可學&#xff0c;人人可用&#xff0c;IT與AI不是高不可攀&#xff01; 博客宗旨 深海科技服務博客致力于&#xff1a; 推廣IT與AI的實際應用&#xff0c;降低入門門檻&#xff0c;讓更多個人和中小企業能夠以最少投入、高效實現信息化、智能化。 分享開源免費軟件、簡單…

本地大模型編程實戰(29)查詢圖數據庫NEO4J(2)

上一篇文章 用大語言模型LLM查詢圖數據庫NEO4J(1) 介紹了使用GraphQACypherChain查詢NEO4J。用它實現簡單快捷&#xff0c;但是不容易定制&#xff0c;在生產環境中可能會面臨挑戰。 本文將基于langgraph 框架&#xff0c;用LLM(大語言模型)查詢圖數據庫NEO4J。它可以定義清晰復…

RPG_5.角色動畫

1.創建一個動畫實例 2.創建該實例的c子類 3.繼續創建該類的子類&#xff0c;但是作用是用來鏈接&#xff08;以后會詳細解釋&#xff09; 4.基于PlayerAnimInstance類創建一個子類 5.目前一共創建了四個c類&#xff0c; 最基的類 角色的類 玩家控制的角色的類 玩家控制的角…

Sigmoid函數導數推導詳解

Sigmoid函數導數推導詳解 在邏輯回歸中&#xff0c;Sigmoid函數的導數推導是一個關鍵步驟&#xff0c;它使得梯度下降算法能夠高效地計算。 1. Sigmoid函數定義 首先回顧Sigmoid函數的定義&#xff1a; g ( z ) 1 1 e ? z g(z) \frac{1}{1 e^{-z}} g(z)1e?z1? 2. 導…

MS31860T——8 通道串行接口低邊驅動器

MS31860T 是一款 8 通道低邊驅動器&#xff0c;包含 SPI 串口通信、 PWM斬波器配置、過流保護、短路保護、欠壓鎖定和過熱關斷功能&#xff0c; 芯片可以讀取每個通道的狀態。MS31860T 可以診斷開路的負載情況&#xff0c;并可以讀取故障信息。外部故障引腳指示芯片的故障狀態。…

騰訊 Kuikly 正式開源,了解一下這個基于 Kotlin 的全平臺框架

在 3月的時候通過 《騰訊 TDF 即將開源 Kuikly 跨端框架&#xff0c;Kotlin 支持全平臺》 我們大致知道了 Kuikly 的基本情況&#xff0c;Kuikly 是一個面向終端技術棧的跨端開發框架&#xff0c;完全基于kotlin語言開發&#xff0c;提供原生的性能和體驗。 按照官方的說法&…

AI驅動UI自動化測試框架調研

隨著應用復雜度增加&#xff0c;手動測試變得費時且易出錯&#xff0c;而自動化測試可提高效率和可靠性。如何借助大模型和一些自動化測試框架進行自動化測試&#xff0c;是一個研發團隊很重要的訴求。 目前主流的自動化測試框架很多&#xff0c;Midscene.js結合Playwright提供…

關系型數據庫設計指南

1. 前言 在自己獨立開發一個項目的過程中&#xff0c;我發現了一些以往寫小 Demo 從來沒有遇到過的問題。 最近在獨立制作一個全棧的通知管理平臺。一開始我沒有考慮太多&#xff0c;直接根據頭腦中零星的想法就開擼后端數據庫 model 和 API&#xff0c;用的是學了半成品的 M…

詳解TypeScript中的類型斷言及其繞過類型檢查機制

TypeScript中的類型斷言及其繞過類型檢查機制 一、類型斷言的本質與工作原理編譯時與運行時的區別TypeScript編譯器處理類型斷言的步驟 二、類型斷言的詳細語法與進階用法基礎語法對比鏈式斷言斷言修飾符1. 非空斷言操作符 (!)代碼分析1. getLength 函數分析用法說明&#xff1…

XLSX.utils.sheet_to_json設置了blankrows:true,但無法獲取到開頭的空白行

在用sheetJs的XLSX庫做導入&#xff0c;遇到一個bug。如果開頭行是空白行的話&#xff0c;調用sheet_to_json轉數組獲得的數據也是沒有包含空白行的。這樣會導致在設置對應的起始行時&#xff0c;解析數據不生效。 目前是直接跳過了開頭的兩行空白行 正確應該獲得一下數據 問…

PostgreSQL 數據庫下載和安裝

官網&#xff1a; PostgreSQL: Downloads 推薦下載網站&#xff1a;EDB downloads postgresql 我選了 postgresql-15.12-1-windows-x64.exe 鼠標雙擊&#xff0c;開始安裝&#xff1a; 安裝路徑&#xff1a; Installation Directory: D:\Program Files\PostgreSQL\15 Serv…

一、Javaweb是什么?

1.1 客戶端與服務端 客戶端 &#xff1a;用于與用戶進行交互&#xff0c;接受用戶的輸入或操作&#xff0c;且展示服務器端的數據以及向服務器傳遞數據。 例如&#xff1a;手機app&#xff0c;微信小程序、瀏覽器… 服務端 &#xff1a;與客戶端進行交互&#xff0c;接受客戶…

奇偶ASCII值判斷

奇偶ASCII值判斷 Description 任意輸入一個字符&#xff0c;判斷其ASCII是否是奇數&#xff0c;若是&#xff0c;輸出YES&#xff0c;否則&#xff0c;輸出NO。例如&#xff0c;字符A的ASCII值是65&#xff0c;則輸出YES&#xff0c;若輸入字符B(ASCII值是66)&#xff0c;則輸…

OpenCV 圖形API(74)圖像與通道拼接函數-----合并三個單通道圖像(GMat)為一個多通道圖像的函數merge3()

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 從3個單通道矩陣創建一個3通道矩陣。 此函數將多個矩陣合并以生成一個單一的多通道矩陣。即&#xff0c;輸出矩陣的每個元素將是輸入矩陣元素的…

多節點監測任務分配方法比較與分析

多監測節點任務分配方法是分布式系統、物聯網&#xff08;IoT&#xff09;、工業監測等領域的核心技術&#xff0c;其核心目標是在資源受限條件下高效分配任務&#xff0c;以優化系統性能。以下從方法分類、對比分析、應用場景選擇及挑戰等方面進行系統闡述&#xff1a; 圖1 多…

【推薦系統筆記】BPR損失函數公式

一、BPR損失函數公式 BPR 損失函數的核心公式如下&#xff1a; L BPR ? ∑ ( u , i , j ) ∈ D ln ? σ ( x ^ u i j ) λ ∣ ∣ Θ ∣ ∣ 2 L_{\text{BPR}} - \sum_{(u, i, j) \in D} \ln \sigma(\hat{x}_{uij}) \lambda ||\Theta||^2 LBPR??(u,i,j)∈D∑?lnσ(x^ui…