1. 程式人生 > 其它 >Angular伺服器渲染常遇的坑

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渲染過程中,如果程式碼中有一些延遲非同步任務,可能會阻塞渲染。

主要包括例如setTimeoutsetInterval、全域性呼叫Observables等方法的使用。在不取消它們的情況下呼叫它們,或者讓它們執行超過伺服器所需的時間可能會導致渲染效果欠佳,從而使得首次載入響應時間過長。

因此需要檢查專案中是否用到以上方法,如有在用完之後需求做清除。

此外,如果需要執行的非同步任務沒有必要在伺服器端使用的話,可以參考問題2的方式,使用isPlatformBrowser跳過伺服器執行。

小結

專案越複雜整改難度越大,在除錯universal服務時需要注意以下幾個點:

  1. 伺服器沒有的api或者第三方庫需要用isPlatformBrowser跳過執行;
  2. 版本升級最好把一些相關的基本寫法調整到新版;
  3. 注意node版本問題;
  4. 使用定時器需要及時清除;