Angular5 服务器渲染(SSR)


由于业务需求,需要对项目中的文档部分做 SEO 搜索引擎优化,这里我采用的是angular官方推荐的服务器渲染方案Angular Universal。标准的 Angular 应用会运行在浏览器中,它会在 DOM 中渲染页面,以响应用户的操作。而Angular Universal 会在服务端通过一个名叫服务端渲染(server-side rendering - SSR)的过程生成静态的应用页面。在分享之前,我们先看一下服务器渲染的工作原理:

  • Angular提供了一个platform-server 包,它包含了服务端的 DOM 实现、XMLHttpRequest 和其它底层特性,但不再依赖浏览器。我们需要使用 platform-server 模块来编译客户端应用,并且在一个 Web 服务器上运行这个 Universal 应用。

  • 服务器会把客户端对应用页面的请求传给 renderModuleFactory 函数。renderModuleFactory 函数接受一个模板 HTML 页面(通常是 index.html)、一个包含组件的 Angular 模块和一个用于决定该显示哪些组件的路由作为输入。该路由从客户端的请求中传给服务器。 每次请求都会给出所请求路由的一个适当的视图。renderModuleFactory 在模板中的 http://localhost:4200/ 会正常显示项目主页面:

    右键“查看源代码”:

    存在问题

    1.使用浏览器 API

    相信会有同学在打包的时候出现过下面的报错问题

    ReferenceError: window is not defined
    

    或者

    ReferenceError: document is not defined
    

    因为这些是浏览器独有的原生对象(比如 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['DOMTokenList'] = win.DOMTokenList;
    global['Node'] = win.Node;
    global['Text'] = win.Text;
    global['HTMLElement'] = win.HTMLElement;
    global['navigator'] = win.navigator;
    

    但是,domino并非兼容了所有浏览器的api,只是兼容了大部分方法(有兴趣的同学可以看,domin的源码 https://github.com/fgnass/domino)。但是如果是用到的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
    ReferenceError: $ is not defined
    

    方案1:使用Angular官方推荐的方法

    和上面一样,检查当前平台是浏览器还是服务器,执行相应的代码。

    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');
     ...
      }
    }

相关