angular10预渲染实践笔记
Posted Fighting_No1
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了angular10预渲染实践笔记相关的知识,希望对你有一定的参考价值。
angular10预渲染实践笔记
参考资料:
- Angular服务端渲染
- Angular预渲染
- Angular开发实践之服务端渲染
- Angular 预渲染实践
- 【Angular项目实战】Angular5服务器渲染(SSR)
- ssr(angular) 相关小笔记
目的:基于Angular的服务端渲染和预渲染功能来生成多页静态页面。
理解Angular服务端渲染和预渲染
Angular Universal 会在服务端通过一个被称为服务端渲染(server-side rendering - SSR)的过程生成静态的应用页面。
它可以生成这些页面,并在浏览器请求时直接用它们给出响应。 它也可以把页面预先生成为 html 文件,然后把它们作为静态文件供服务器使用。
客户端在接收html文件之前,服务端将html标签占位做动态数据填充;服务端处理好一个html字符串文件生成一个静态的html页面文件返回给客户端,客户端即会解析html,渲染呈现出UI/UX;
注意:angular服务端渲染会先在服务器端渲染好的只是静态的页面内容,然后响应客户端,而一些交互在加载完成之前是不可用的。
服务端渲染特性:
1、访问加载速度比其它任意方式几乎都快;
2、利于搜索引擎抓取,方便网站SEO
3、单个静态页只分配特定的路由
4、不用执行javascript
5、chrome中访问后,当前页右键【查看网页源代码】,对应页面内容都可在源代码中查看
6、资源只单次渲染,无任何异步加载
预渲染参照如下流程图:
注解:
1、 服务端存放了打包好的静态资源与文件(./dist/browser下的所有文件)。
2、 浏览器解析渲染返回的html内容。
3、 打包好的bundle.js / chunk.js
不需要预渲染的情况
1、生成针对特定路由的静态HTML 文件,可以是一个项目工程,也可以是一个CDN服务;所以预渲染不适合动态路由的页面项目;
2、如果页面中有动态的操作,以及频繁的数据变动,就不得不经常升级/发布页面,对运维以及开发维护不友好;不建议使用预渲染;
3、Pre-render几乎涵盖了SSR的所有优点,但如果是一个复杂操作功能的项目,且还有N多个页面,那么预渲染在开发维护阶段将会变得异常艰难,因为它生成的是一个个对应的html页面,即有N多个路由,所以在它开发构建之时编译将会变得异常缓慢;
Angular预渲染生成多个页面实践
按照Angular服务端渲染教程执行ng add @nguniversal/express-engine
,自动添加服务端渲染对应的文件和配置,然后执行npm run prerender
,生成静态html页面。
注意事项:
1、路由配置
(1)路由策略配置:angular路由策略只能是默认的PathLocationStrategy,不能是HashLocationStrategy(IE9只能用HashLocationStrategy)。用HashLocationStrategy,会导致找不到对应路由而渲染默认路由,表现为生成的html都是默认的路由内容。
(2)angular.json中的prerender的routes配置
- routes :定义要预先渲染的额外路由数组。——适用于少量路由的情况。
- routesFile :指定一个文件,其中包含要预先渲染的所有路由的列表,以换行符分隔。如果你有大量路由,则此选项很有用。
- guessRoutes : 构建器是否应该提取路由并猜测要渲染的路径。默认为 true 。——true会渲染app-routing.module.ts中定义的所有路由,false则只渲染routes和routesFile中定义的路由。
如果要只预渲染某个特定路由哦/XX/XXX,则可如下配置:
"prerender":
"builder": "@nguniversal/builders:prerender",
"options":
"browserTarget": "projectId:build:production",
"serverTarget": "projectId:server:production",
"guessRoutes": false, // 只渲染routes和routesFile中定义的路由
"routes": [
"/XX/XXX" // 指定的路由,可多个
]
,
"configurations":
"production":
(3)预渲染不支持路由参数(?后的内容),故routes中配置/XX?XXX=XXX这样的路由,执行预渲染命令会直接报错。源码中取路由参数的地方,一定要记得做空判断。
(4)预渲染只能接收静态路由,如product/:id
这样的动态路由,要渲染,必须指定id值,如product/1
,这样才能渲染成html页面。
2、http请求
预渲染的http请求必须使用绝对路径,而非相对路径。如请求assets中的静态资源,使用相对路径的话,预渲染时运行的是server/main.js,而非browser/main.js,会因找不到资源而报错。
由于我项目的要求只是生成多个静态页面,而不是服务器渲染加快响应速度,所以我解决这个问题的方法是:静态资源host配置在环境变量universalServerUrl,默认值为http://localhost:4200
,在执行预渲染前,另开个终端,执行npm run start
,这样预渲染过程才能访问到本地的静态资源,确保http请求成功,生成完整内容的静态页面。
(1)解决翻译JSON文件无法获取的问题
新增个http拦截器,如果是json文件并且是服务端渲染的话,就使用静态资源的host拼接url,构成绝对路径。
import Inject, Injectable, PLATFORM_ID from '@angular/core';
import
HttpEvent, HttpInterceptor, HttpHandler, HttpRequest
from '@angular/common/http';
import Observable from 'rxjs';
import isPlatformServer from '@angular/common';
import environment from 'src/environments/environment';
@Injectable(
providedIn: 'root'
)
export class UniversalInterceptor implements HttpInterceptor
constructor(@Inject(PLATFORM_ID) private platformId: object)
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
let serverSeq = req;
if (req.url.endsWith('.json') && isPlatformServer(this.platformId))
// 服务器渲染,转换url
serverSeq = req.clone( url: environment.universalServerUrl + req.url );
return next.handle(serverSeq);
在app.module.ts的providers中添加拦截器:
provide: HTTP_INTERCEPTORS, useClass: UniversalInterceptor, multi: true
如果是要实现服务器渲染,那么请参考这篇文章解决这个问题:小谈Angular SSR项目的国际化
(2)解决antd icon找不到的问题
我项目的antd icon使用的是动态加载的方式,会发起http请求动态引入,需要修改预渲染时的请求路径。
在app.module.ts的构造器中,如果是服务端渲染的话,则通过 NzIconService 的 changeAssetsSource() 方法来修改图标资源的位置。
export class AppModule
constructor(
@Inject(PLATFORM_ID) private platformId: object,
@Inject(APP_ID) private appId: string,
private iconService: NzIconService)
const platform = isPlatformBrowser(platformId) ? 'in the browser' : 'on the server';
console.log(`Running $platform with appId=$appId`);
if (isPlatformServer(platformId))
this.iconService.changeAssetsSource(environment.universalServerUrl);
(3)解决http请求需要token的问题
预渲染时相当于你本地启动服务,然后打开浏览器访问对应的路由,组件内部执行初始化逻辑(如http请求获取后端数据),渲染页面完毕,你将当前页面的html保存到本地。
由于后端接口请求都需要token,所以我们需要配置个token进行http请求,方便预渲染过程中http请求拿到后端数据后去进行渲染。如果没有配置token,则接口请求会报401,跳转到登录页面,渲染失败。
在环境变量中配置个universalToken变量,预渲染前,先登录获取到token,然后赋值给universalToken变量,再执行预渲染命令,就可以为接口请求带上token了。
修改http拦截器,判断平台是否为服务端,是则取universalToken环境变量作为token参数值。
export class AuthInterceptor implements HttpInterceptor
constructor(
@Inject(PLATFORM_ID) private platformId: object
)
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
let authReq = req;
if (!req.url.endsWith('.json'))
// 添加token参数
if (isPlatformBrowser(this.platformId))
authReq = req.clone( setParams: token: localStorage.getItem('token') );
else
authReq = req.clone( setParams: token: environment.universalToken );
return next.handle(authReq).pipe(
catchError((error: HttpErrorResponse) => this.handleError(error))
);
在app.module.ts的providers中添加拦截器:
provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true
PS:由于不同用户获取的后端数据是不一样的,所以推荐使用个没有数据的用户的token,这样预渲染的时候,只渲染UI,而后端数据为空。页面请求的时候,页面上先显示渲染的空数据,然后再显示请求到的后端数据。
3、Web API兼容性
(1)使用Angular抽象层
服务端应用不能引用浏览器独有的全局对象,比如 window、document、navigator 或 location。但Angular 提供了一些这些对象的可注入的抽象层,比如 Location 或 DOCUMENT,它可以作为你所调用的 API 的等效替身。
比如你在视图初始化时要通过document.querySelector定位某个dom对象,可以采用如下方式实现:
import DOCUMENT from '@angular/common'; // 引入DOCUMENT抽象层
export class AppComponent implements OnInit
constructor(
@Inject(DOCUMENT) private document: Document // 注入DOCUMENT抽象层,当在客户端运行时,就是浏览器的document对象
)
ngInit() // 在视图初始化时去使用document对象
this.element = this.document.querySelector('.class');
(2)判断平台类型,执行不同的代码
export class AppModule
constructor(
@Inject(PLATFORM_ID) private platformId: object) // 注入PLATFORM_ID,值为server | browser
const platform = isPlatformBrowser(platformId) ? 'in the browser' : 'on the server';
console.log(`Running $platform with appId=$appId`);
if (isPlatformServer(platformId))
// 在服务端运行
比如在node中没有localStorage,所以预渲染会报错localStorage对象未定义。通过在源码中添加对平台类型的判断,是客户端才去localStorage中取数据,不是则取默认值,这样就能保证预渲染顺利进行,也不影响在客户端的运行。
以上是关于angular10预渲染实践笔记的主要内容,如果未能解决你的问题,请参考以下文章
Angular isPlatformBrowser 检查 PLATFORM_ID 不会阻止服务器端预渲染
dotnet core + Angular Universal + docker => 由于错误,预渲染失败:错误:找不到模块