Angular伺服器渲染常遇的坑
前言
由於官網有SEO需要,因此對現官網專案進行ssr的整改相容,在除錯的過程中遇到了不少問題。本來在官網的demo專案中使用是比較順暢的,但是由於本專案比較大複雜性比較高,踩了不少的坑。以下是我在除錯過程中遇到的一些問題彙總,希望其他人可以避開這些坑。
1. 使用瀏覽器 API報錯問題
在執行服務的時候,通常會遇到一下的一些報錯
ReferenceError: window is not defined
或者
ReferenceError: document is not defined
由於 Universal 應用並沒有執行在瀏覽器中,因此該伺服器上可能會缺少瀏覽器的某些 API 和其它能力。比如,服務端應用不能引用瀏覽器獨有的全域性物件,比如 window、document、navigator 或 location。如果直接使用會導致執行的時候出現報錯。因此,我們需要對使用瀏覽器的API方法做好相容。
方案1:在server.ts,引入domino做相容
const domino = require('domino'); const win = domino.createWindow(template); global['window'] = win; global['document'] = win.document; global['CSS'] = null; global['Prism'] = null; global['DOMTokenList'] = win.DOMTokenList; global['Node'] = win.Node; global['Text'] = win.Text; global['HTMLElement'] = win.HTMLElement; global['object'] = win.object; global['navigator'] = win.navigator; global['localStorage'] = null; global['sessionStorage'] = null;
但是,domino並非相容了所有瀏覽器的api,只是相容了大部分方法但是如果是用到的api不多,可以考慮用這個方案。如果是一些複雜專案建議還是用下面官方推薦的方法比較好。
方案2:使用Angular官方推薦的方法
通過PLATFORM_ID令牌注入的物件來檢查當前平臺是瀏覽器還是伺服器,從而解決該問題。判斷是瀏覽器環境,才執行使用到瀏覽器方法的程式碼片段。不過個人覺得有些麻煩,因為在用到瀏覽器獨有API方法的地方都得做引入判斷相容。
import { PLATFORM_ID } from '@angular/core'; import { isPlatformBrowser, isPlatformServer } from '@angular/common'; constructor(@Inject(PLATFORM_ID) private platformId: Object) { ... } ngOnInit() { if (isPlatformBrowser(this.platformId)) { // 瀏覽器程式碼 // eg:let url=window.location.href; ... } if (isPlatformServer(this.platformId)) { // 伺服器程式碼 ... } }
2. 使用第三方庫,例如jq,echart,layer等等報錯
ReferenceError: $ is not defined
ReferenceError: layer is not defined
和上面一樣,檢查當前平臺是瀏覽器還是伺服器,執行相應的程式碼。
import { PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
constructor(@Inject(PLATFORM_ID) private platformId: Object) { ... }
ngOnInit() {
if (isPlatformBrowser(this.platformId)) {
// 瀏覽器程式碼
// eg:let userID =$.cookie('userID');
// eg: layer.msg('測試');
...
}
}
3. 懶載入路由無法載入問題
由於我的專案原來是angular6版本的,後來升級到了angular9後路由寫法有所改變,在瀏覽器模式下是沒有問題的,安裝官方例子改造ssr之後懶載入路由無法正常使用,這裡需要把路由改成v9版本推薦的寫法,修改如下:
修改前
{
path: 'doc',
loadChildren: 'app/lazy-module/doc.module#DocModule'
}
修改後:
{
path: 'doc',
loadChildren: loadChildren: () => import('./lazy-module/doc.module').then(m => m.DocModule)
}
4. 打包node版本報錯
報錯資訊如下:
由於我升級了node到12版本,這個報錯是node12本身的bug官方暫時還沒有修復這個問題,因此需要把node回滾到10.15.3版本,版本回滾之後就沒有報錯了。
5. 響應時間TTFB過長問題
Angular Universal渲染過程中,如果程式碼中有一些延遲非同步任務,可能會阻塞渲染。
主要包括例如setTimeout
、setInterval
、全域性呼叫Observables
等方法的使用。在不取消它們的情況下呼叫它們,或者讓它們執行超過伺服器所需的時間可能會導致渲染效果欠佳,從而使得首次載入響應時間過長。
因此需要檢查專案中是否用到以上方法,如有在用完之後需求做清除。
此外,如果需要執行的非同步任務沒有必要在伺服器端使用的話,可以參考問題2的方式,使用isPlatformBrowser跳過伺服器執行。
小結
專案越複雜整改難度越大,在除錯universal服務時需要注意以下幾個點:
- 伺服器沒有的api或者第三方庫需要用isPlatformBrowser跳過執行;
- 版本升級最好把一些相關的基本寫法調整到新版;
- 注意node版本問題;
- 使用定時器需要及時清除;