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