跳转至

29 前台页面的渲染方式:如何设计前台页面的渲染策略?

你好,我是杨文坚。

经过前面几节课的学习,在运营搭建平台的页面功能维度中,我们完成了页面发布流程和页面回滚措施,可以放心地把页面发布到生产环境,提供给让外部客户使用。下一步,就是进行前台页面的渲染操作了,这也是运营搭建平台中,页面功能维度的最后一个环节——“页面渲染方式”。

可能你会有困惑,之前我们学过的“页面的编译和运行”里,也讲了页面渲染策略,为什么还有一节课讲解“页面渲染方式”呢?

有这个疑问很正常,因为我们之前学的都是一些分散的技术点,比如Vue.js的服务端渲染、Vue.js组件的多种模块格式等等,这些技术散点都是你的知识储备。今天,我们就要把这些知识储备,通过实际项目应用实践,有机结合起来,应用到搭建平台的前台场景中。

那么“页面渲染方式”这个页面维度的功能模块,在前台服务上,我们如何进行设计和实现呢?

1. 设计前台页面的链接

页面渲染的第一件事,就是在要在浏览器输入页面的URL,也就是页面的链接,所以我们今天第一件事就是设计页面的链接。

前台页面的链接,是面向外部用户的,考虑到用户的传播和使用,我们要尽量让链接简单一些,后续生成的链接二维码图片也会精简一些。

一般我们会这么设计URL。

/*
 https://${域名}/p/${页面uuid}
 例如:
*/
https://xxx.xx/p/10001000-aaaa-bbbb-cccc-ddddeeee0000

你可能会觉得页面uuid太长了,导致URL太长,影响页面链接传播效果。我们可以换种uuid生成方式,在后台服务创建页面生成uuid时候,换一种简单的随机字符串生成方法。

例如,随机生成只有英文和数字的短字符串。

// 随机生成字符串,包括0-9a-z的短字符串内容
Math.random().toString(36).substring(2)

但是,如果业务搭建页面非常频繁,生产出来页面的数量非常庞大,用比较复杂的uuid还是很有必要。因为复杂的uuid能避免重复,从而避免带来数据操作的污染或者干扰。

要特别注意,URL里不能用页面在数据库的自增数字id。因为自增数字id容易被“灰黑产”利用,“灰黑产”可以按自增数字拼接页面URL,用“爬虫”抓取所有页面内容。这也算是一种变相的“拖库”行为,容易造成大量页面数据泄露。

现在,我们有了页面URL,客户就可以在浏览里通过URL访问页面了,那么接下来就要进入页面渲染环节了。

2. 设计前台页面的浏览器端渲染

我们之前学过Vue.js页面的浏览器端渲染(客户端渲染)和服务端渲染原理,CSR和SSR。页面大部分功能,都是基于浏览器端渲染来实现的,服务端渲染的作用更多是减少白屏时间和支持SEO。

所以,我们优先做浏览器端渲染的技术设计和实现。

首先,回顾一下“物料组件多种模块格式”和“页面编译和运行”这两节课,我们可以总结出三种浏览器端渲染方式。

  • Bundle文件渲染
  • 基于ESM模块的动态加载渲染
  • 基于AMD模块的动态加载渲染

接下来结合前面学过的知识点,我们基于前台服务,从前端到服务端,设计这三种浏览器端渲染的技术方案。

第一个浏览器渲染方式是Bundle文件渲染。

我画了步骤图,Bundle文件渲染分成四步。

  • 第一步:浏览器发起前台服务请求。
  • 第二步:服务器接受请求,解析出页面uuid。
  • 第三步:数据库查找页面数据,判断页面是否存在,如果存在就拼接对应的HTML结果。
  • 第四步:浏览器执行和渲染服务端响应回来的HTML结果。

第二个浏览器渲染方式是ESM动态渲染方式。

Bundle文件渲染分成五个步骤。

  • 第一步:浏览器发起前台服务请求。
  • 第二步:服务器接受请求,解析出页面uuid。
  • 第三步:数据库查找页面数据,判断页面是否存在,如果存在就把页面数据埋在HTML全局变量中。
  • 第四步:浏览器执行和渲染服务端响应回来的HTML结果。
  • 第五步:运行ESM运行时来异步加载ESM模块,动态加载物料渲染页面。

第三个浏览器渲染方式,AMD动态渲染方式,具体步骤和ESM基本一样,只是在最后一步中,动态加载物料组件资源的方式,从纯ESM动态加载渲染,变成基于RequireJS,加载AMD物料组件模块,进行动态渲染。

有了三种浏览器端渲染方式,接下来就要灵活利用这三种渲染方式,构建页面渲染策略。

还记得我们之前设计的“页面渲染策略”吗?当时的策略是优先进行Bundle文件渲染,提升用户体验,如果页面报错导致不可用,就进入降级渲染逻辑,按客户浏览器的兼容情况选择ESM或AMD的渲染方式。

今天我们就来实际实现一下。

3. 做前台渲染测试的落地实现

根据之前课程页面渲染策略的理论技术方案,进入降级渲染逻辑的部分,我稍作调整,画了一张步骤图。

渲染策略分成三个步骤。

  • 第一步:默认Bundle文件渲染,监听页面错误异常。
  • 第二步:渲染提示用户,选择是否降级渲染。
  • 第三步:根据浏览器环境,选择ESM或AMD渲染方式。

第一步,默认Bundle文件渲染,监听页面错误异常。核心操作是要监听页面错误,我们可以分成“Vue.js应用内部错误”和“其它错误”。

在Vue.js应用的内部,可以通过Vue.js官方提供的API(app.config.errorHandler)来捕获错误异常。

app.config.errorHandler = (err, instance, info) => {
  console.log('捕获到Vue.js内部报错: ', err);
  // 处理后续的页面降级处理 
}

其它错误,可以通过监听window的错误事件。

window.addEventListener('error', (err) => {
  console.log('捕获到其它报错: ', err);
  // 处理后续的页面降级处理 
});

第二步,渲染出错误提示,让客户选择是否要降级。看课程代码案例的截图。

图片

你可能会有疑问,为什么要让客户自行选择降级呢?为什么不能直接用代码处理强制降级呢?

这是因为客户遇到页面出现错误的时候,程序员不一定能第一时间得到反馈和处理问题。与此同时,客户遇到页面错误后,不一定影响页面主要功能的使用,这时候强制降级可能会适得其反。

第三步,根据浏览器兼容情况,选择和执行ESM或AMD的渲染方式,对物料组件逐个动态加载,以及隔离组件独立渲染。

如何判断页面是否支持ESM呢?这里我们可以基于impormap,来用执行import操作,检测是否支持ESM模块。

// packages/portal-server/src/public/static/page-helper.js
let canSupportESM = true;
try {
  // 用import()动态加载importmap指向的vue模块
  import('vue').then(() => {
    canSupportESM = true;
  }).catch((err) => {
    console.warn(err);    
    // 如果出错,就证明当前不支持ESM
    canSupportESM = false;
  });
} catch (err) {
  console.warn(err);  
  // 如果出错,就证明当前不支持ESM
  canSupportESM = false;
}

课程案例是直接在第二步时候就判断降级选择方案,然后生成对应的URL,提供给用户触发降级渲染页面时,跳转到降级页面使用。

进行页面降级跳转的时候,进入ESM或AMD降级渲染的页面链接,这个链接其实跟原来的Bundle渲染页面是同一个URL路径Path,只不过携带的参数不同。然后,服务端根据这个参数做对应的服务端处理,输出相应渲染方式的HTML代码,携带不同的JS和CSS静态资源链接。

你可以看对应截图。同一个页面URL的Bundle渲染结果。

图片

基于同一个页面URL路径,带上参数“runModule=amd”的渲染结果。

图片

基于同一个页面URL路径,带上参数“runModule=esm”的渲染结果。

图片

现在,我们完成了浏览器渲染和页面渲染策略的技术实现。接下来就要进入页面的服务端渲染了。

4. 设计和实现前台页面的服务端渲染

服务端渲染主要是解决白屏等待问题和SEO需求,其中,最重要的是SEO需求。所以做服务端渲染之前,我们要考虑是否有SEO需求,如果有需求,就要必须要用服务端渲染。

回忆我们学过的Vue.js服务端渲染,我重新画了一张技术步骤图。

服务端渲染可以分成四个步骤。

  • 第一步,浏览器发起前台服务请求。
  • 第二步,服务器接受请求,解析出页面uuid。
  • 第三步,数据库查找页面数据,根据页面布局数据,获取物料组件的CommonJS模块,将其动态编译组装成页面HTML字符串。
  • 第四步,浏览器执行和渲染服务端响应回来的HTML结果。

具体技术实现我们已经学过了,就不再重复(定制化服务端渲染的实现,你可以查看代码案例,在目录:packages/portal-server/src/service/page-app.ts)。

前面提到,有SEO需求,我们必须选择用服务端渲染。那么页面白屏等待问题,就不是选择服务端渲染的必要条件吗?答案是否定的,服务端渲染可以解决白屏等待时间,但是不一定能完全解决,因为服务端渲染存在一些隐患。

服务端渲染存在哪些隐患

课程中,我们用的是Node.js来开发项目的服务端功能。那么基于Node.js在服务端,做运营搭建平台的服务端渲染,会有哪些隐患呢?

首先,页面是由物料组件组成的,而且物料组件不是固定的,是动态的。所以每次做服务端渲染,都需要在Node.js环境里做动态的组件编译,生成HTML结果。

其次,一个页面可以存在多个物料,可能几个,可能几十个。那么Node.js服务在组装页面HTML代码的时候,就要对每个物料Vue.js组件,传入组件的数据源,编译成HTML代码。这个编译过程是外部客户访问页面时候进行的,也需要时间执行操作。页面使用物料一旦变多,服务端动态编译HTML时间就变长。

所以,这也是我们刚刚说的“服务端渲染可以解决白屏等待时间,但是不一定能完全解决”,反而可能因为编译物料组件过多,白屏时间更长

总的来说,Node.js做Vue.js的全栈服务,可能存在的隐患大致可以分成三点。

  • 编译复杂组件耗性能。Node.js服务,处理复杂物料组件,比较耗性能。
  • 组装编译页面耗性能。如果页面布局复杂,物料组件依赖太多,Node.js处理页面的时候,会占用较多的CPU和内存性能。
  • 服务端运行Vue.js不可预测的错误。物料组件的CommonJS模块格式,不一定能百分百在Node.js服务端处理成HTML,可能组件里存在浏览器API的使用,导致在服务器运行时候中报错。

不过,解决办法也是有的,这三点隐患其实都指向一个问题,客户访问页面的时候,要动态“编译组件成HTML”和“组装页面成HTML”,所以,我们可以前置这个步骤,放在后台服务中,在页面发布流程中,就把页面的服务端HTML直接编译出来,放到CDN给前台服务使用。

具体实现思路也很简单。

解决动态编译组件耗Node.js性能问题,可以在后台服务发布流程中,前置编译成HTML文件,发布到CDN或其他静态资源服务上,前台渲染直接使用这个已经编译好的HTML。解决动态组装页面耗Node.js性能,也可以按照同样的物料组件的方式,前置编译HTML文件结果。

总结

今天我们围绕着“页面的渲染方式”,学习了如何在客户视角实现搭建页面的渲染。

首先,我们整合了三种页面渲染方式:Bundle文件渲染、基于ESM模块的动态加载渲染和基于AMD模块的动态加载渲染。

在具体实现方案上,页面渲染,优先使用Bundle渲染,如果出现异常,就提醒客户,让客户自行选择是否降级处理。“让客户选择降级”是一种折中的方案,不是绝对方案。你在实际工作中,可以根据自己业务需求,选择是折中方案或者强制降级方案。

另外,服务端渲染不是万能的,不能绝对解决白屏等待问题。服务端渲染的隐患,基本来自Vue.js组件编译HTML的操作过程,比较难发现,也很难根除,所以,最好解决办法是把隐患前置到“企业内部页面发布阶段”,前置服务端编译组件成HTML的过程,及早发现问题和解决问题。

你应该能发现,今天讲的很多技术方案都是基于以往课程里学过的技术点。其实,技术的成长,就是要依靠长期的“技术储备”和“技术实践”。当你遇到任何技术问题,都能想到使用以前学过的“技术点”,并且比以前更加得心应手地设计技术方案,实现技术功能。那么恭喜你,你的技术已经得到成长了。

思考题

课程中,前台服务的浏览器端渲染和服务端渲染,都是基于Node.js服务的。假设随着业务发展,我们要对前后台服务架构进行分离,面向客户的前台服务要变成Java开发和维护,面向内部员工的后台保持用Node.js。

那么如何保证服务端渲染的功能,从Node.js环境平滑转换支持Java服务端的服务端渲染?

期待看到你的思考,参与讨论。我们下节课见。

完整的代码在这里

精选留言(3)
  • Akili 👍(0) 💬(4)

    老师,请教一个问题,我们现在涉及的都是后台管理系统开发,遇到的问题就是一直在重复的工作,对自己也没有提升,有没有什么方案避免这样的工作方式?谢谢你

    2023-02-17

  • ifelse 👍(0) 💬(0)

    学习打卡

    2024-10-01

  • Geek_12e8fd 👍(0) 💬(0)

    在将前台服务的服务端渲染从 Node.js 平滑转换到 Java 服务端时,需要确保渲染逻辑、数据模型、模板引擎以及可能的缓存机制都能在新的环境中得到支持。以下是一些关键的步骤和考虑因素: 分析现有服务端渲染逻辑: 理解当前 Node.js 服务端渲染的工作流程,包括数据准备、模板渲染、缓存等。 确定哪些部分可以直接移植到 Java,哪些部分需要重写或调整。 选择或开发 Java 中的模板引擎: Node.js 中常用的模板引擎如 Pug(Jade)、EJS、Nunjucks 等在 Java 中可能没有直接对应的实现。 你可能需要选择一个现有的 Java 模板引擎,如 Thymeleaf、FreeMarker、Velocity 等,或者开发一个自定义的模板引擎来匹配现有的模板语法。 数据模型与接口对齐: 确保 Java 后端与 Node.js 后端使用相同的数据模型,或者至少能够提供相同的数据接口。 可能需要对现有的 API 进行调整,以便 Java 后端能够访问所需的数据。 实现服务端渲染逻辑: 在 Java 后端中重写或实现服务端渲染逻辑。 这可能包括数据获取、模板渲染、错误处理等部分。 缓存机制: 如果 Node.js 后端使用了缓存来提高性能,确保 Java 后端也实现了类似的缓存机制。 可以考虑使用 Redis、Memcached 或其他缓存解决方案来共享缓存数据。 测试与部署: 在开发过程中进行详细的测试,确保新的 Java 后端能够正确地渲染页面并与前端交互。 逐步部署新的 Java 后端,并监控生产环境中的性能和用户反馈。 持续集成与持续部署(CI/CD): 建立或更新 CI/CD 流程,以便能够快速、可靠地部署新的 Java 后端。 文档与培训: 编写详细的文档,解释新的 Java 后端的工作原理、配置选项以及维护指南。 对开发团队进行培训,确保他们熟悉新的后端架构和工作流程。 监控与日志记录: 实施监控和日志记录机制,以便能够及时发现和解决问题。 确保新的 Java 后端与现有的监控和日志记录系统兼容。 性能优化: 在将服务端渲染迁移到 Java 后端后,可能需要进行性能优化以确保与 Node.js 后端相当的性能。 这可能包括调整 JVM 设置、优化数据库访问、使用异步处理等技术。 安全性考虑: 确保新的 Java 后端遵循最佳的安全实践,包括输入验证、防止跨站脚本攻击(XSS)、跨站请求伪造(CSRF)等。 通过遵循这些步骤和考虑因素,你可以将服务端渲染功能从 Node.js 平滑转换到 Java 服务端。需要注意的是,这个过程可能需要一定的时间和资源投入,因此建议在进行迁移之前进行充分的规划和准备。

    2024-06-14