• Angular服务器渲染常遇的坑


    前言

    由于官网有SEO需要,因此对现官网项目进行ssr的整改兼容,在调试的过程中遇到了不少问题。本来在官网的demo项目中使用是比较顺畅的,但是由于本项目比较大复杂性比较高,踩了不少的坑。以下是我在调试过程中遇到的一些问题汇总,希望其他人可以避开这些坑。

    1. 使用浏览器 API报错问题

    在运行服务的时候,通常会遇到一下的一些报错

    ReferenceError: window is not defined
    

    或者

    ReferenceError: document is not defined
    

    由于 Universal 应用并没有运行在浏览器中,因此该服务器上可能会缺少浏览器的某些 API 和其它能力。比如,服务端应用不能引用浏览器独有的全局对象,比如 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['CSS'] = null;
    global['Prism'] = null;
    global['DOMTokenList'] = win.DOMTokenList;
    global['Node'] = win.Node;
    global['Text'] = win.Text;
    global['HTMLElement'] = win.HTMLElement;
    global['object'] = win.object;
    global['navigator'] = win.navigator;
    global['localStorage'] = null;
    global['sessionStorage'] = null;
    

    但是,domino并非兼容了所有浏览器的api,只是兼容了大部分方法但是如果是用到的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,echart,layer等等报错

    ReferenceError: $ is not defined
    
    ReferenceError: layer is not defined
    

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

    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');
    	// eg: layer.msg('测试');
     ...
      }
    }
    

    3. 懒加载路由无法加载问题

    由于我的项目原来是angular6版本的,后来升级到了angular9后路由写法有所改变,在浏览器模式下是没有问题的,安装官方例子改造ssr之后懒加载路由无法正常使用,这里需要把路由改成v9版本推荐的写法,修改如下:

    修改前

    {
          path: 'doc',
          loadChildren: 'app/lazy-module/doc.module#DocModule'
    }
    

    修改后:

    {
          path: 'doc',
          loadChildren: loadChildren: () => import('./lazy-module/doc.module').then(m => m.DocModule)
    }
    

    4. 打包node版本报错

    报错信息如下:

    由于我升级了node到12版本,这个报错是node12本身的bug官方暂时还没有修复这个问题,因此需要把node回滚到10.15.3版本,版本回滚之后就没有报错了。

    5. 响应时间TTFB过长问题

    Angular Universal渲染过程中,如果代码中有一些延迟异步任务,可能会阻塞渲染。

    主要包括例如setTimeoutsetInterval、全局调用Observables等方法的使用。在不取消它们的情况下调用它们,或者让它们运行超过服务器所需的时间可能会导致渲染效果欠佳,从而使得首次加载响应时间过长。

    因此需要检查项目中是否用到以上方法,如有在用完之后需求做清除。

    此外,如果需要执行的异步任务没有必要在服务器端使用的话,可以参考问题2的方式,使用isPlatformBrowser跳过服务器执行。

    小结

    项目越复杂整改难度越大,在调试universal服务时需要注意以下几个点:

    1. 服务器没有的api或者第三方库需要用isPlatformBrowser跳过执行;
    2. 版本升级最好把一些相关的基本写法调整到新版;
    3. 注意node版本问题;
    4. 使用定时器需要及时清除;
  • 相关阅读:
    Java与Andriod的区别和关系
    .window.onload()函数和jQuery中的document.ready()有什么区别?
    jquery中$.get()提交和$.post()提交有区别吗?
    Predicate-谓语
    Lambda 表达式
    .什么是JDBC的最佳实践?
    MVC的各个部分都有那些技术来实现?如何实现?
    如何从CDN加载jQuery?
    什么是CDN?哪些是流行的jQuery CDN?使用CDN有什么好处?
    JS 中 == 和 === 区别是什么?
  • 原文地址:https://www.cnblogs.com/huiguo/p/15346314.html
Copyright © 2020-2023  润新知