服务端渲染(SSR) - 图1 This article has not been translated, hope that your can PR to translated it. Help us!服务端渲染(SSR) - 图2

服务端渲染(SSR)

本文描述的是 NG-ALAIN 如何支持服务端渲染(SSR)。

NG-ALAIN 不推荐在中后台使用服务端渲染(SSR),这是因为中后台本身对于SSR所带来的好处远大于开发带来的麻烦,但不管怎么样在许多人的要求下,从 9.5 版本开始,已经对所有 @delon/* 类库支持服务端渲染。

开始之前

在开始之前请先阅读以下文章,它们能够更加快速让你了解 Angular 服务端渲染是如何工作的:

教程

添加 @nguniversal/express-engine

在一个完整的 NG-ALAIN 项目下,执行以下命令:

  1. ng add @nguniversal/express-engine

最后运行:

  1. npm run dev:ssr

此时会以 SSR 的形式运行 NG-ALAIN。

但如果采用默认 LocalStorageStore 来存储 Token 的情况下,会提示找不到 localStorage 的错误,这是因为服务端并没有这些,它们可能还包含 windowdocumentsessionStorage 等。

因此,要想在中后台很好的支持 SSR,需要分析所依赖的第三方类库是否支持 SSR,如果没有必须手动处理在服务端下不渲染这些组件。

丢失Token

服务端是无状态的,因此判断请求是否有效授权,目前通用的做法是将 Token 存储在 Cookie 下,在服务端接收请求时再根据 Cookies 来获取 Token 信息。

虽然 NG-ALAIN 提供 CookieStorageStore 但它并不支持 SSR,因为它所依赖的 js-cookie 第三类库并不支持,因此需要手动构建针对 SSR 的 Token 持久化存储。

推荐使用 @ngx-utils/cookies 来处理 Cookies,它同时支持客户端与服务端。

注意: 受限于 #20 的原因,由于一直未处理,有人专门解决了这个问题并发布一个新类库 ngx-utils-cookies-port,暂时只能使用它来代替 @ngx-utils/cookies,用法一模一样只是模块名换一下,在修复之后再换回来。

要创建一个符合 @delon/auth 接口持久化存储类,只需要继承 IStore 即可,例如:

  1. import { Injectable } from '@angular/core';
  2. import { IStore, ITokenModel } from '@delon/auth';
  3. import { CookiesService } from 'ngx-utils-cookies-port';
  4. @Injectable()
  5. export class AuthStorageStore implements IStore {
  6. constructor(private cookies: CookiesService) {}
  7. get(key: string): ITokenModel {
  8. return JSON.parse(this.cookies.get(key) || '{}') || {};
  9. }
  10. set(key: string, value: ITokenModel | null): boolean {
  11. this.cookies.put(key, JSON.stringify(value));
  12. return true;
  13. }
  14. remove(key: string) {
  15. this.cookies.remove(key);
  16. }
  17. }

最后,在 global-config.module.ts 内重新注册它:

  1. const alainProvides = [
  2. { provide: ALAIN_CONFIG, useValue: alainConfig },
  3. + { provide: DA_STORE_TOKEN, useClass: AuthStorageStore },
  4. ];

注意:这里依然需要注册新增的模块,方法请参考 @ngx-utils/cookies 说明。

@ngx-utils/cookies 内部会根据 REQUEST 来获取当前的请求头信息,因此,我们还需要修改 server.ts

  1. // All regular routes use the Universal engine
  2. server.get('*', (req, res) => {
  3. res.render(indexHtml, {
  4. req,
  5. + res,
  6. providers: [
  7. { provide: APP_BASE_HREF, useValue: req.baseUrl },
  8. + { provide: 'REQUEST', useValue: req },
  9. + { provide: 'RESPONSE', useValue: res },
  10. ],
  11. });
  12. });