【Web技术】1869- spa 如何达到 ssr 的秒开技术方案——预渲染

发表于 5月以前  | 总阅读数:254 次

预渲染

SPA(单页应用)在初次加载时,由于需要加载所有必要的 JavaScript 和 CSS 文件,以及应用的主 HTML 文件,因此可能会产生白屏时间较长的问题,对用户体验而言是非常糟糕的。

其中白屏时间主要影响因素之一:SPA 应用在加载完成后,需要再进行一次 DOM 渲染才能显示页面内容。在渲染过程中,可能需要加载大量的 JavaScript 文件、CSS 文件或网络请求,这些操作都需要耗费时间,从而导致白屏时间变长。

对单页面应用进行预渲染,将页面在打包期间渲染成静态 HTML 文件,可以很好的解决白屏时间过长问题

预渲染的几个优势:

  1. 优化 SEO

由于单页面应用通常只有一个入口 HTML 文件,因此其页面内容无法被搜素引擎爬虫捕获到。而使用预渲染功能,可以让项目构建出包含所有动态内容的静态 HTML 页面,从而被搜索引擎爬虫作为内容来源,提高 SEO 优化效果。

  1. 更快的加载速度

使用预渲染功能,可以将动态生成的部分预先生成静态文件,无需等到页面加载完成后再生成,从而提高网站的加载速度。

  1. 更好的用户体验

预渲染后,用户进入网站时可以更快地获取到内容,可以提高用户的体验。

  1. 减轻服务端压力

使用静态资源替代计算资源,可以减轻服务端的压力。预渲染后的页面不需要借助服务器的计算资源,减轻了服务器的压力,提高了页面处理效率。

核心流程

社区也提供了prerender-spa-plugin 这类插件,可以直接集成到项目中使用,由于得物预发、正式环境静态资源都是应用cdn的,会导致预渲染异常。本地启动服务,cdn上无对应资源。最终由团队内手动实现一款具备相同功能的static-generator插件

核心流程

接下来通过代码简单的看一下其各个环节是如何做的

首先需要做的,定义一个gererate函数和一个Renderer类

const generate = () => {}

class Renderer {}

gererate 主要是用于处理参数和流程处理

Renderer 主要是用于启动无头浏览器生成HTML

首先看一下Renderer是如何生产HTML的:

核心是使用puppeteer

Puppeteer 是一个由 Google 推出的 Node.js 库,它提供了一个高级 API ,可以使用 Headless Chrome 或 Chromium 来控制 Chrome 或 Chromium 的行为,用于测试、屏幕截图和数据爬取等。

Puppeteer 可以模拟人类的操作,比如点击、填充表单、下拉、切换页面、截图等,同时还可以拦截网络请求和处理 Cookies 等功能。由于其灵活性和易用性,许多开发者使用它进行爬虫、测试、数据分析等任务。

相关代码:利用puppeteer 启动一个无头浏览器获取页面的HTML

const getHtml = async ({ userAgent, onRequest, url }) => {
  const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] }) // 启动无头浏览器

  const page = await browser.newPage()
  await page.setViewport({ width: 375, height: 812 })
  if (userAgent) await page.setUserAgent(userAgent)
  // 页面实例上下文中执行的方法
  await page.evaluateOnNewDocument(() => (window['_prerender_'] = 'prerender'))
  // 启用请求拦截器
  await page.setRequestInterception(true)
  // 监听请求
  page.on('request', (request) => onRequest?.({ request, pageUrl: url, page }))

  await page.goto(url)
  // 页面实例上下文中执行的方法
  await page.evaluate(observeRender) // 默认5000ms页面加载完成
  const content = await page.content()
  await page.close()
  return content
}

上述代码除了生成HTML外,还有一个十分重要的事情 page.on('request',(request) => onRequest),其拦截了页面的所有请求,将所有的CSS资源进行了缓存

onRequest实现

const cssContent = {}

const onRequest = asycn ({ request }) => {
  const body = await read(url)
  if (cssInline && filename.endsWith('.css')) {
    const byteLength = body.byteLength
    const { maxSize = Infinity, exclude, include } = cssInline

    if (!(byteLength > maxSize || exclude?.test(filename)) && (!include || include.test(filename))) {
      // 把css 先存起来
      cssContent[filename] = body.toString()
    }
  }

  return request.respond({
    status: 200,
    body,
  })
}

其核心就做了两个事情

  1. 获取css文件的内容
  2. 缓存到cssContent中,后面生成html时使用

至此已经可以获取到HTML和所有的CSS了,那么接下来要做的便是将新的HTML替换老的HTML,并将所有通过link标签引入的css资源移除,换成style标签包裹的内联CSS

再看一下gererate函数,内部首先是对参数进行格式化处理

const generate = async (config: GenerateConfigParams) => {
  const generate = async (config: GenerateConfigParams) => {
    const {
      sourceDir, //源文件目录
      staticDir = sourceDir, // 静态资源打包目录
      pages, // 页面路由
      baseUrl = 'https://m.dewu.com',
      cdn = '',
      userAgent = DWUA,
      postProcess, // 处理生成后的html
      inject,
    } = config
    const mutation = typeof config.mutation === 'string' ? [config.mutation] : config.mutation
    const renderer = new Renderer()
    renderer.pages = pages
    renderer.baseUrl = baseUrl
    renderer.userAgent = userAgent
    const sourceHTMLPath = join(sourceDir, 'index.html')

    renderer.postProcess = ({ html, page }) => {
      if (mutation) {
        const dom = new JSDOM(sourceHTML)
        const newDom = new JSDOM(html)
        // 将新的HTML替换老的HTML
        mutation.forEach((mutation) => {
          const oldNode = dom.window.document.querySelector(mutation) as HTMLElement
          const newNode = newDom.window.document.querySelector(mutation) as HTMLElement
          if (oldNode) {
            oldNode.innerHTML = newNode?.innerHTML ?? ''
          }
        })

        // 将所有通过link标签引入的css资源移除,换成style标签包裹的内联CSS
        const links = newDom.window.document.querySelectorAll('link')
        for (const link of links) {
          const originHref = link.getAttribute('href') ?? ''
          const content = cssContent[originHref.split('/').pop() ?? '']
          if (content) {
            const newStyle = dom.window.document.createElement('style')
            newStyle.innerHTML = content
            newStyle.setAttribute('data-href', originHref)
            newStyle.setAttribute('data-css', 'inline')
            dom.window.document.head.appendChild(newStyle)
            const link = dom.window.document.querySelector(`link[href="${originHref}"]`)
            if (link) {
              link.parentElement?.removeChild(link)
            }
          }
        }

        html = dom.serialize()
      }

      return {
        html,
        page,
      }
    }

    renderer.onRequest = () => {
    // 上面的 onRequest 函数 ....
    }
  }
}

预渲染核心的三部分便大致如上述代码,

需要注意的是:接入预渲染的时候,需要找运维同学配合修改一些Nginx的配置,

主要是对路由 进行 文件重定向

慎用三方库

业务中存在一些简单的校验、转换和动效并不需要引入三方库,尤其是因为一个较为简单的功能引入了一个较为大且冷门的库时,不仅会增加项目的打包体积,还会增加项目后续维护的沟通、学习成本。

例如下面一个简单切换动效

是一个比较常规的切换动效,却在项目中引入了一个62.6kb大小的第三方库

该库的使用也是有一些学习成本,因为其具备实现比较复杂的动效能力,在业务动效具备一定复杂度且非首屏的场景下,是可以考虑引入使用的,否则类似这种首屏便需要加载的动效,还是慎重

上述的切换动效CSS实现代码如下

@keyframes bigScale {
  0% {
    opacity: 0;
    transform: scale(0.95);
  }

  to {
    transform: scale(1);
    opacity: 1;
  }
}

@keyframes smallScale {
  0% {
    transform: scale(1);
    opacity: 1;
  }

  to {
    transform: scale(0.95);
    opacity: 0;
  }
}

.squareInCenter {
  animation: 0.3s linear 0s 1 normal forwards running bigScale;
}

.squareOutCenter {
  animation: 0.3s linear 0s 1 normal forwards running smallScale;
}

在业务开发的过程中,尤其是C端的页面,在实现功能时对于引入额外的库是一件需要十分谨慎的事情,在内部就看到不少项目在引入关于日期处理方面的库时,dayjs、momentJS同时都会引用到项目中,B端项目都不能忍,更何况C端项目

字体使用和优化

字体加载和优化是前端开发中的一个重要问题,特别是在移动端和低网络状况下。下面是一些字体加载和优化的技巧

FOUT问题

通过设置 font-display 属性可以控制字体加载时的显示效果,包括 auto、swap、block、fallback 和 optional 几种模式,可以减少字体加载时间和防止文本闪烁

设置属性为fallback时效果

可以看到日期存在明显的FOUT(无样式文本闪现)问题,设置swap也是类似效果,并不符合预期

设置属性为block时效果

img可以看到第一时间并没有渲染日期,而是有点的短暂空白,因为其可以避免 FOUT,字体文件必须在后台下载完全后,文本才能显示

最终选择了font-display: block;效果会更好一些

注意,并不是整个页面都使用block属性,对于一些非首屏关键渲染的样式,使用fallback更为合适一些,因为其会使用浏览器默认字体,所以还是需要结合业务、场景合理使用

字体****库大小,你得懂

先看一个gpt对于签到业务常用字体库打下的统计

DIN Condensed 字体库的大小在几百KB到几MB之间

Helvetica Neue 字体库的大小在几MB到十几MB之间

也就是这两种字体的大小,如果不加以处理,全部加载的大小在几MB到十几MB之间,对于前端项目而言,这是挺夸张的一件事

可以和设计人员沟通,将字体库中常用的字体导出,前端项目仅仅引入需要的字体就好,比如DIN Condensed字体都是使用在阿拉伯数字上,并不会在其他字上使用,那么只需要将阿拉伯数字导出即可。比如汉字,根据《现代汉语通用字表》(GB/T 13000-2018),常用汉字(包括简体字和繁体字)共计3500个,其中常用的一般是指前1000个左右的汉字,那么在使用字体库的时候,是不是可以默认只需要导出部分即可。

经过处理后的字体库大小如下图

字体****库数量,你得控制

上面说了一个字体库的大小是多大,就算是经过处理,最少也会有30KB大小,所以项目引入的字体种类是需要控制的,不能设计同学使用了多少种类字体设计,我们就要照单全收

当设计同学新增字体库时,如果字体使用在3次以内,是不是可以使用图片来代替文字,或者使用现有的字体库来平替

提高稳定性

在优化的过程中,移除了大量的废弃接口、ab和代码逻辑,这样做的代价必然是会造成一些问题,毕竟不管代码现状是怎样,只要线上能跑起来就是可以的,一般也不会大刀阔斧的去改造原有的代码,本着代码可维护性(避免日后接手代码的人内心)的原则,最终还是对其动了手

既然已经选择了动手,那么就要思考一下如何确保稳定性,毕竟生产还是需要敬畏的,否则造成什么比较阻塞性的bug,那可真的是好心办坏事了,关于如何保证稳定性,我是从下面这几方面入手

可行性和风险评估

每次改动和优化代码之前,我会先对功能进行整体的回归一下,再查看对应的代码,在查看代码的过程中,我会确定几个事情

  1. 页面的功能是否存在多种业务逻辑判断的情况,比如符合条件A,执行弹框的逻辑,符合添加B,执行接口调用逻辑。若是业务逻辑较为复杂,那么改造和优化的成本会很大,ROI会比较低
  2. 是否为阻塞性的功能,比如新人引导流程的功能,这个流程出现问题则是非常严重的,这类代码保持能不动则不动
  3. 该功能模块迭代的频率,比如像商品流这块功能,仅近半年就经历过3次大的功能改版,每一次改版都是基于老的业务进行修修补补,导致代码就很难维护且这个痛经常存在,这种改造一下的ROI就会高一些
  4. 功能模块的大小和耦合度,比如下文说的MallScrollShowMore组件,其就是一个单独且较小的功能模块,改动速度快,且改造过程中关注点更多的只需要放在组件内部就好,这种改动ROI也会较高

结合上面的几点,我会综合考虑我接下来要做的事情可行性是怎样的,这么做带来的风险有多大,个人是的风格是,例如MallScrollShowMore组件,其就是一个非常独立的、功能小且非阻塞性功能的组件,可行性高,并不具备太多的改造风险,那么我就会撸起袖子干

做好记录和改动点

由于是优化中携带的代码改动,需要自己做好改动点的沉淀,不仅方便测试回归对应的地方,也便于自测

当发现组件设计或者实现有问题时,我会作记录

严格执行自测

记录只是第一步,测试周开始前,需要针对本次的所有改动进行自测,因为很多改动在这个时候,作为研发的我才是最熟悉整个项目的了,这个时候其实个人才是测试主力,测试同学帮忙回归和验证核心流程。在改造过程中记录的改动点就是测试用例,需要严格执行和回归,毕竟功能优化、改造时,自己才是第一责任人

本文由哈喽比特于5月以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/7a29Ohqem0w7bLmyhADvSQ

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:8月以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:8月以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:8月以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:8月以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:8月以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:8月以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:8月以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:8月以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:8月以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:8月以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:8月以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:8月以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:8月以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:8月以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:8月以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:8月以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:8月以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:8月以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:8月以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:8月以前  |  398次阅读  |  详细内容 »
 相关文章
为Electron程序添加运行时日志 4年以前  |  19600次阅读
Node.js下通过配置host访问URL 5年以前  |  5632次阅读
用 esbuild 让你的构建压缩性能翻倍 4年以前  |  5367次阅读
 目录