angular10预渲染实践笔记

angular10预渲染实践笔记

参考资料:

目的:基于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中取数据,不是则取默认值,这样就能保证预渲染顺利进行,也不影响在客户端的运行。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值