angualr 6+ 服務(wù)端渲染 SSR 問題記錄

為什么要使用服務(wù)器端渲染?

創(chuàng)建應(yīng)用的SSR版本有三個主要原因。

  1. 促進(jìn)網(wǎng)絡(luò)爬蟲(SEO)
  2. 提高移動和低功耗設(shè)備的性能
  3. 配合Angular 模塊懶加載 快速顯示首屏

詳細(xì)教程參考官網(wǎng),我做的時候也是基本上跟著官網(wǎng)一步一步走。

問題一:window,documentnavigator,或location等瀏覽器對象報錯:如

ReferenceError: window is not defined

項目在服務(wù)器端渲染(這里用的是express),在node環(huán)境下是沒有以上對象的。解決方案有幾種:
1、在server.ts中添加

import { join } from 'path';
const domino = require('domino');
const fs = require('fs');
const DIST_FOLDER = join(process.cwd(), 'dist');
//我的項目代碼 在運(yùn)行npm run build:ssr 后生生成在 dist/browser 目錄下,請根據(jù)實際路徑修改
const template = fs.readFileSync(join(DIST_FOLDER, 'browser', 'index.html')).toString();
const win = domino.createWindow(template);
// global 對象中添加 對應(yīng)window的對象
global['window'] = win;
global['document'] = win.document;
global['navigator'] = win.navigator;
global['history'] = win.history;

2、利用angular module懶加載,首屏加載的時候不要寫瀏覽器對象相關(guān)的邏輯

3、在生命周期函數(shù)ngAfterViewInit(): void 中寫調(diào)用瀏覽器對象的邏輯,ReferenceError: localStorage is not defined 如localStorage等無法被domino創(chuàng)建的window模擬的。

4、通過判斷代碼運(yùn)行平臺寫不同邏輯

import { PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
 
constructor(@Inject(PLATFORM_ID) private platformId: Object) { ... }
 
ngOnInit() {
  if (isPlatformBrowser(this.platformId)) {
     // Client only code.
     ...
  }
  if (isPlatformServer(this.platformId)) {
    // Server only code.
    ...
  }
}

問題二:SSR時候需要使用絕對路徑發(fā)送請求,這時可以使用express的反向代理中間件, api請求設(shè)置:

import * as proxyMiddleWare from 'http-proxy-middleware';
import * as express from 'express';
const proxyPath = "http://0.0.0.0:80";//目標(biāo)后端服務(wù)地址
const proxyOption ={target:proxyPath,changeOrigoin:true};
// Express server
const app = express();

// use the proxy middleware
app.use("/api",proxyMiddleWare(proxyOption));


問題三,SSR國際化配置 和客戶端使用有一點不同,這里用的是ngx-translate庫。項目多語言切換的時候會出現(xiàn)頁面已經(jīng)返回,但是語言包還沒返回的情況。在頁面初次加載時,模板資源的請求要先于語言包文件的請求,所以在頁面在客戶端渲染時,語言包資源實際還沒就緒,因此在那一瞬間填寫在模板中的語言包鍵名便直接被渲染在了頁面中。

既然Angular的服務(wù)端渲染本身無法實現(xiàn)首次刷新無毛刺的效果,那么我們稍微變換一下思路,能否將語言包資源與模板同時返回給客戶端呢?答案是肯定的。通過Angular提供的狀態(tài)轉(zhuǎn)移功能,我們可以在服務(wù)端獲取語言包,并將其與模板一同返回給客戶端,如此客戶端在渲染模板時便能直接獲取到鍵值對應(yīng)的文本,從而避免鍵值直接渲染在頁面中的問題。

解決這個問題的核心技術(shù)就是Angular的TransferState,除此之外我們還需要結(jié)合ngx-translate的自定義loader功能。

我們這里為服務(wù)端新建一個loader:

//translate-server-loader.service.ts
import { Observable } from "rxjs";
import { TranslateLoader } from '@ngx-translate/core';

declare var require: any;
declare var process: any;

import { makeStateKey, StateKey, TransferState } from '@angular/platform-browser';

const path = require('path');
const fs = require('fs');

export class TranslateServerLoader implements TranslateLoader {

  constructor(
    private prefix: string = 'i18n',
    private suffix: string = '.json',
    private transferState: TransferState) {
  }

  public getTranslation(lang: string): Observable<any> {

    return Observable.create(observer => {
      const assets_folder = path.join(process.cwd(), 'dist', 'checkout-on-board', this.prefix);
      const jsonData = JSON.parse(fs.readFileSync(`${assets_folder}/${lang}${this.suffix}`, 'utf8'));
      // Here we save the translations in the transfer-state
      const key: StateKey<number> = makeStateKey<number>('transfer-translate-' + lang);
      this.transferState.set(key, jsonData);
      observer.next(jsonData);
      observer.complete();
    });
  }
}

修改服務(wù)端 app.server.module.ts

import { TranslateServerLoader } from './translate-server-loader.service'
import { TransferState } from '@angular/platform-browser';

export function translateFactory(transferState: TransferState) {
  return new TranslateServerLoader('/assets/i18n', '.json', transferState);
}

@NgModule({
  imports: [
...
   TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: translateFactory,
        deps: [TransferState]
      }
    })
...
],
  providers:[
    TransferState
  ],
})

參考:
小談Angular SSR項目的國際化
ngx-translate/core issue #754 中的@peterpeterparker 與 @ocombe,@ocombe指出了問題的根本原因,@peterpeterparker則貼出了完整的代碼示例。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容