摘要:對于客戶端應用來說,服務端渲染是一個熱門話題。在服務器預渲染初始應用狀態(tài)。重構(gòu)這段腳本,使其可以在服務端運行。如果這些原因和你的情況吻合,那么使用進行服務端渲染將會是個不錯方案。我已經(jīng)發(fā)布兩個庫來支持的服務端渲染和專為應用打造的。
對于客戶端應用來說,服務端渲染是一個熱門話題。然而不幸的是,這并不是一件容易的事,尤其是對于不用 Node.js 環(huán)境開發(fā)的人來說。
我發(fā)布了兩個庫讓 PHP 從服務端渲染成為可能.spatie/server-side-rendering?和?spatie/laravel-server-side-rendering適配 laravel 應用。
讓我們一起來仔細研究一些服務端渲染的概念,權(quán)衡優(yōu)缺點,然后遵循第一法則用 PHP 建立一個服務端渲染。
什么是服務端渲染一個單頁應用(通常也叫做 SPA )是一個客戶端渲染的 App 。這是一個僅在瀏覽器端運行的應用。如果你正在使用框架,比如 React, Vue.js 或者 AngularJS ,客戶端將從頭開始渲染你的 App 。
瀏覽器的工作在 SPA 被啟動并準備使用之前,瀏覽器需要經(jīng)過幾個步驟。
下載 JavaScript 腳本
解析 JavaScript 腳本
運行 JavaScript 腳本
取回數(shù)據(jù)(可選,但普遍)
在原本的空容器渲染應用? (首次有意義的渲染)
準備完成!?(可以交互啦)
用戶不會看到任何有意義的內(nèi)容,直到瀏覽器完全渲染 App(需要花費一點時間)。這會造成一個明顯的延遲,直到 首次有意義的渲染 完成,從而影響了用戶體驗。
這就是為什么服務端渲染(一般被稱作 SSR )登場的原因。SSR 在服務器預渲染初始應用狀態(tài)。這里是瀏覽器在使用服務端渲染后需要經(jīng)過的步驟:
渲染來自服務端的 HTML (首次有意義的渲染)
下載 JavaScript 腳本
解析 JavaScript 腳本
運行 JavaScript 腳本
取回數(shù)據(jù)
使已存在的 HTML 頁面可交互
準備完成!?(可以交互啦)
由于服務器提供了 HTML 的預渲染塊,因此用戶無需等到一切完成后才能看到有意義的內(nèi)容。注意,雖然 交互時間 仍然處于最后,但可感知的表現(xiàn)得到了巨大的提升。
服務端渲染的優(yōu)點服務端渲染的主要優(yōu)點是可以提升用戶體驗。并且,如果你的網(wǎng)站需要應對不能執(zhí)行 JavaScript 的老舊爬蟲,SSR 將是必須的,這樣,爬蟲才能索引服務端渲染過后的頁面,而不是一個空蕩蕩的文檔。
服務端如何渲染?記住服務端渲染并非微不足道,這一點很重要。當你的 Web 應用同時運行在瀏覽器和服務器,而你的 Web 應用依賴 DOM 訪問,那么你需要確保這些調(diào)用不會在服務端觸發(fā),因為沒有 DOM API 可用。
基礎(chǔ)設(shè)施復雜性假設(shè)你決定了服務端渲染你的應用端程序,你如果正在閱讀這篇文章,很大可能正在使用 PHP 構(gòu)建應用的大部分(功能)。但是,服務端渲染的 SPA 需要運行在 Node.js 環(huán)境,所以將需要維護第二個程序。
你需要構(gòu)建兩個應用程序之間的橋梁,以便它們進行通信和共享數(shù)據(jù):需要一個 API。構(gòu)建無狀態(tài) API 相比于構(gòu)建有狀態(tài)是比較 困難 的。你需要熟悉一些新概念,例如基于 JWT 或 OAUTH 的驗證,CORS,REST ,添加這些到現(xiàn)有應用中是很重要的。
有得必有所失,我們已經(jīng)建立了 SSR 以增加 Web 應用的用戶體驗,但 SSR 是有成本的。
服務器端渲染權(quán)衡取舍服務器上多了一個額外的操作。一個是服務器增加了負載壓力,第二個是頁面響應時間也會稍微加長。 不過因為現(xiàn)在服務器返回了有效內(nèi)容,在用戶看來,第二個問題的影響不大。
大部分時候你會使用 Node.js 來渲染你的 SPA 代碼。如果你的后端代碼不是使用 Javascript 編寫的話,新加入 Node.js 堆棧將使你的程序架構(gòu)變得復雜。
為了簡化基礎(chǔ)架構(gòu)的復雜度, 我們需要找到一個方法,使已有的 PHP 環(huán)境作為服務端來渲染客戶端應用。
在 PHP 中渲染 JavaScript在服務器端渲染 SPA 需要集齊以下三樣東西:
一個可以執(zhí)行 JavaScript 的引擎
一個可以在服務器上渲染應用的腳本
一個可以在客戶端渲染和運行應用的腳本
SSR scripts 101下面的例子使用了 Vue.js。你如果習慣使用其它的框架(例如 React),不必擔心,它們的核心思想都是類似的,一切看起來都是那么相似。
簡單起見,我們使用經(jīng)典的 “ Hello World ” 例子。
下面是程序的代碼(沒有 SSR):
// app.js import Vue from "vue" new Vue({ template: `Hello, world!`, el: "#app" })
這短代碼實例化了一個 Vue 組件,并且在一個容器(id 值為 app 的 空 div)渲染。
如果在服務端運行這點腳本,會拋出錯誤,因為沒有 DOM 可訪問,而 Vue 卻嘗試在一個不存在的元素里渲染應用。
重構(gòu)這段腳本,使其 可以 在服務端運行。
// app.js import Vue from "vue" export default () => new Vue({ template: `Hello, world!` }) // entry-client.js import createApp from "./app" const app = createApp() app.$mount("#app")
我們將之前的代碼分成兩部分。app.js 作為創(chuàng)建應用實例的工廠,而第二部分,即 entry-client.js,會運行在瀏覽器,它使用工廠創(chuàng)建了應用實例,并且掛載在 DOM。
現(xiàn)在我們可以創(chuàng)建一個沒有 DOM 依賴性的應用程序,可以為服務端編寫第二個腳本。
// entry-server.js import createApp from "./app" import renderToString from "vue-server-renderer/basic" const app = createApp() renderToString(app, (err, html) => { if (err) { throw new Error(err) } // Dispatch the HTML string to the client... })
我們引入了相同的應用工廠,但我們使用服務端渲染的方式來渲染純 HTML 字符串,它將包含應用初始狀態(tài)的展示。
我們已經(jīng)具備三個關(guān)鍵因素中的兩個:服務端腳本和客戶端腳本。現(xiàn)在,讓我們在 PHP 上運行它吧!
執(zhí)行 JavaScript在 PHP 運行 JavaScript,想到的第一個選擇是 V8Js。V8Js 是嵌入在 PHP 擴展的 V8 引擎,它允許我們執(zhí)行 JavaScript。
使用 V8Js 執(zhí)行腳本非常直接。我們可以用 PHP 中的輸出緩沖和 JavaScript 中的 print 來捕獲結(jié)果。
$v8 = new V8Js(); ob_start(); // $script 包含了我們想執(zhí)行的腳本內(nèi)容 $v8->executeString($script); echo ob_get_contents();
print("Hello, world!")
這種方法的缺點是需要第三方 PHP 擴展,而擴展可能很難或者不能在你的系統(tǒng)上安裝,所以如果有其他(不需要安裝擴展的)方法,它會更好的選擇。
這個不一樣的方法就是使用 Node.js 運行 JavaScript。我們可以開啟一個 Node 進程,它負責運行腳本并且捕獲輸出。
Symfony 的?Process 組件就是我們想要的。
use SymfonyComponentProcessProcess; // $nodePath 是可執(zhí)行的 Node.js 的路徑 // $scriptPath 是想要執(zhí)行的 JavaScript 腳本的路徑 new Process([$nodePath, $scriptPath]); echo $process->mustRun()->getOutput();
console.log("Hello, world!")
注意,(打?。┰?Node 中是調(diào)用 console.log 而不是 print 。
讓我們一起來實現(xiàn)它吧!spatie/server-side-rendering 包的其中一個關(guān)鍵理念是?引擎?接口。引擎就是上述 JavaScript 執(zhí)行的一個抽象概念。
namespace SpatieSsr; /** * 創(chuàng)建引擎接口。 */ interface Engine { public function run(string $script): string; public function getDispatchHandler(): string; }
run?方法預期一個腳本的輸入 (腳本 內(nèi)容,不是一條路徑),并且返回執(zhí)行結(jié)果。?getDispatchHandler?允許引擎聲明它預期腳本如何展示發(fā)布。例如 V8 中的print?方法,或是 Node 中的 console.log?。
V8Js 引擎實現(xiàn)起來并不是很花俏。它更類似于我們上述理念的驗證,帶有一些附加的錯誤處理機制。
namespace SpatieSsrEngines; use V8Js; use V8JsException; use SpatieSsrEngine; use SpatieSsrExceptionsEngineError; /** * 創(chuàng)建一個 V8 類來實現(xiàn)引擎接口類 Engine 。 */ class V8 implements Engine。 { /** @var V8Js */ protected $v8; public function __construct(V8Js $v8) { $this->v8 = $v8; } /** * 打開緩沖區(qū)。 * 返回緩沖區(qū)存儲v8的腳本處理結(jié)果。 */ public function run(string $script): string { try { ob_start(); $this->v8->executeString($script); return ob_get_contents(); } catch (V8JsException $exception) { throw EngineError::withException($exception); } finally { ob_end_clean(); } } public function getDispatchHandler(): string { return "print"; } }
注意這里我們將?V8JsException?重新拋出作為我們的?EngineError。 這樣我們就可以在任何的引擎視線中捕捉相同的異常。
Node 引擎會更加復雜一點。不像 V8Js,Node 需要?文件?去執(zhí)行,而不是腳本內(nèi)容。在執(zhí)行一個服務端腳本前,它需要被保存到一個臨時的路徑。
namespace SpatieSsrEngines; use SpatieSsrEngine; use SpatieSsrExceptionsEngineError; use SymfonyComponentProcessProcess; use SymfonyComponentProcessExceptionProcessFailedException; /** * 創(chuàng)建一個 Node 類來實現(xiàn)引擎接口類 Engine 。 */ class Node implements Engine { /** @var string */ protected $nodePath; /** @var string */ protected $tempPath; public function __construct(string $nodePath, string $tempPath) { $this->nodePath = $nodePath; $this->tempPath = $tempPath; } public function run(string $script): string { // 生成一個隨機的、獨一無二的臨時文件路徑。 $tempFilePath = $this->createTempFilePath(); // 在臨時文件中寫進腳本內(nèi)容。 file_put_contents($tempFilePath, $script); // 創(chuàng)建進程執(zhí)行臨時文件。 $process = new Process([$this->nodePath, $tempFilePath]); try { return substr($process->mustRun()->getOutput(), 0, -1); } catch (ProcessFailedException $exception) { throw EngineError::withException($exception); } finally { unlink($tempFilePath); } } public function getDispatchHandler(): string { return "console.log"; } protected function createTempFilePath(): string { return $this->tempPath."/".md5(time()).".js"; } }
除了臨時路徑步驟之外,實現(xiàn)方法看起來也是相當直截了當。
我們已經(jīng)創(chuàng)建好了 Engine 接口,接下來需要編寫渲染的類。以下的渲染類來自于 spatie/server-side-rendering 擴展包,是一個最基本的渲染類的結(jié)構(gòu)。
渲染類唯一的依賴是 Engine 接口的實現(xiàn):
class Renderer { public function __construct(Engine $engine) { $this->engine = $engine; } }
渲染方法 render 里將會處理渲染部分的邏輯,想要執(zhí)行一個 JavaScript 腳本文件,需要以下兩個元素:
我們的應用腳本文件;
一個用來獲取解析產(chǎn)生的 HTML 的分發(fā)方法;
一個簡單的 render?如下:
class Renderer { public function render(string $entry): string { $serverScript = implode(";", [ "var dispatch = {$this->engine->getDispatchHandler()}", file_get_contents($entry), ]); return $this->engine->run($serverScript); } }
此方法接受 ?entry-server.js?文件路徑作為參數(shù)。
我們需要將解析前的 HTML 從腳本中分發(fā)到 PHP 環(huán)境中。dispatch 方法返回 Engine 類里的 getDispatchHandler 方法,dispatch 需要在服務器腳本加載前運行。
還記得我們的服務器端入口腳本嗎?接下來我們在此腳本中調(diào)用我們的 ?dispatch 方法:
// entry-server.js import app from "./app" import renderToString from "vue-server-renderer/basic" renderToString(app, (err, html) => { if (err) { throw new Error(err) } dispatch(html) })
Vue 的應用腳本無需特殊處理,只需要使用 ?file_get_contents 方法讀取文件即可。
我們已經(jīng)成功創(chuàng)建了一個 PHP 的 SSR 。spatie/server-side-rendering 中的完整渲染器 Renderer?跟我們實現(xiàn)有點不一樣,他們擁有更高的容錯能力,和更加豐富的功能如有一套 PHP 和 JavaScript 共享數(shù)據(jù)的機制。如果你感興趣的話,建議你閱讀下源碼 server-side-rendering 代碼庫?。
三思而后行我們弄清楚了服務器端渲染的利和弊,知道 SSR 會增加應用程序架構(gòu)和基礎(chǔ)結(jié)構(gòu)的復雜度。如果服務器端渲染不能為你的業(yè)務提供任何價值,那么你可能不應該首先考慮他。
如果你 確實 想開始使用服務器端渲染,請先閱讀應用程序的架構(gòu)。大多數(shù) JavaScript 框架都有關(guān)于 SSR 的深入指南。Vue.js 甚至有一個專門的 SSR 文檔網(wǎng)站,解釋了諸如數(shù)據(jù)獲取和管理用于服務器端渲染的應用程序方面的坑。
如果可能,請使用經(jīng)過實戰(zhàn)檢驗的解決方案有許多經(jīng)過實戰(zhàn)檢驗的解決方案,能提供很好的 SSR 開發(fā)體驗。比如,如果你在構(gòu)建 React 應用,可以使用 Next.js,或者你更青睞于 Vue?則可用 Nuxt.js,這些都是很引人注目的項目。
還不夠?嘗試 PHP 服務端渲染你僅能以有限的資源來管理基礎(chǔ)架構(gòu)上的復雜性。你想將服務端渲染作為大型 PHP 應用中的一部分。你不想構(gòu)建和維護無狀態(tài)的 API。 如果這些原因和你的情況吻合,那么使用 PHP 進行服務端渲染將會是個不錯方案。
我已經(jīng)發(fā)布兩個庫來支持 PHP 的服務端 JavaScript 渲染: ?spatie/server-side-rendering? 和專為 Laravel 應用打造的 spatie/laravel-server-side-rendering??。Laravel 定制版在 Laravel 應用中近乎 0 配置即可投入使用,通用版需要根據(jù)運行環(huán)境做一些設(shè)置調(diào)整。當然,詳細內(nèi)容可以參考軟件包自述文件。
如果你僅是想體驗,從 spatie/laravel-server-side-rendering-examples? 檢出項目并參考指南進行安裝。
如果你考慮服務端渲染,我希望這類軟件包可以幫到你,并期待通過 Github 做進一步問題交流和反饋!
更多現(xiàn)代化 PHP 知識,請前往 Laravel / PHP 知識社區(qū)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/28506.html
摘要:為了解決問題,推出了服務端預渲染,以便提高對優(yōu)化。應用,到了,單頁面應用優(yōu)秀的用戶體驗,逐漸成為了主流,頁面整體式渲染出來的,稱之為客戶端渲染。客戶端接收數(shù)據(jù),然后完成最終渲染。通過對客戶端服務端基礎(chǔ)框架的抽象組織,主要關(guān)注的是應用的渲染。 現(xiàn)在前端開發(fā)一般都是前后端分離,mvvm和mvc的開發(fā)框架,如Angular、React和Vue等,雖然寫框架能夠使我們快速的完成開發(fā),但是由于前...
摘要:無需使用服務器實時動態(tài)編譯,而是使用預渲染方式,在構(gòu)建時簡單地生成針對特定路由的靜態(tài)文件。與可以部署在任何靜態(tài)文件服務器上的完全靜態(tài)單頁面應用程序不同,服務器渲染應用程序,需要處于運行環(huán)境。更多的服務器端負載。 目錄結(jié)構(gòu) -no-ssr-demo 未做ssr之前的項目代碼用于對比 -vuecli2ssr 將vuecli生成的項目轉(zhuǎn)為ssr -prerender-demo 使用prer...
摘要:本文只是對官方文檔和對官方的個人學習總結(jié),說得不夠完整的請見諒本文主要對以下幾方面內(nèi)容對的內(nèi)容進行分析總結(jié)出現(xiàn)的原因的總體原理當中的數(shù)據(jù)預取在編寫代碼時候的限制的構(gòu)建原理出現(xiàn)的原因單頁應用有一個很大的缺點就是問題,搜索引擎目前只能對同步的進 本文只是對Vue.js官方SSR文檔和對官方hackernews demo的個人學習總結(jié),說得不夠完整的請見諒 本文主要對以下幾方面內(nèi)容對Vue....
摘要:好在后是支持服務端渲染的,零零散散花費了兩三周事件,通過改造現(xiàn)有項目,基本完成了在現(xiàn)有項目中實踐了服務端渲染。在服務端生成對應的字符串,客戶端接收到對應的字符串,能立即渲染,最高效的首屏耗時。服務端渲染的原理是虛擬。實現(xiàn)前后端同構(gòu)應用。 隨著各大前端框架的誕生和演變,SPA開始流行,單頁面應用的優(yōu)勢在于可以不重新加載整個頁面的情況下,通過ajax和服務器通信,實現(xiàn)整個Web應用拒不更新...
摘要:說起,其實早在出現(xiàn)之前,網(wǎng)頁就是在服務端渲染的。沒有涉及流式渲染組件緩存對的服務端渲染有更深一步的認識,實際在生產(chǎn)環(huán)境中的應用可能還需要考慮很多因素。選擇的服務端渲染方案,是情理之中的選擇,不是對新技術(shù)的盲目追捧,而是一切為了需要。 作者:威威(滬江前端開發(fā)工程師)本文原創(chuàng),轉(zhuǎn)載請注明作者及出處。 背景 最近, 產(chǎn)品同學一如往常笑嘻嘻的遞來需求文檔, 縱使內(nèi)心萬般拒絕, 身體倒是很誠實...
閱讀 2964·2021-10-15 09:41
閱讀 1620·2021-09-22 15:56
閱讀 2104·2021-08-10 09:43
閱讀 3273·2019-08-30 13:56
閱讀 1778·2019-08-30 12:47
閱讀 648·2019-08-30 11:17
閱讀 2770·2019-08-30 11:09
閱讀 2193·2019-08-29 16:19