拆分代码 + 动态加载 + 预加载,减少首屏资源,提升首屏性能及应用体验

本文主要是介绍拆分代码 + 动态加载 + 预加载,减少首屏资源,提升首屏性能及应用体验,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

github 原文地址

我们看一些针对《如何提升应用首屏加载体验》的文章,提到的必不可少的措施,便是减少首屏幕加载资源的大小,而减少资源大小必然会想到按需加载措施。本文提到的便是一个基于webpack 插件与 react 组件实现的一套研发高度自定义、组件按需加载的资源预加载方案. 简单来说是为了通过配置 webpack 插件及少量业务代码即可实现Code Splitting + 组件懒加载 + 组件预加载。

🧐 为什么要做这么一套预加载方案?它存在的必要性在哪里?🧐

常规组件按需加载方案缺点

  • React.lazy 组件按需加载 - 组件渲染时加载组件资源
react.lazy(()=>import('xxxx/component'))

优点:拆分组件代码,按需加载, 减少首屏的资源加载大小及数量,提升页面首屏渲染速度。

  • import 动态加载 - 执行代码 import() 时加载组件资源
useEffect(() => {import('xxxx/component').then((loadScript) => {})
}, [])

优点:拆分组件代码,开发者可以更细粒度地控制组件按需加载的时机。

共有缺点:

代码拆分后,组件资源异步加载存在耗时,当组件资源特别大或网络不稳定时都有可能会出现 loading 时间过长导致组件迟迟无法渲染到视图上,以致于影响用户体验

如图是我们项目中实际出现的场景之一:

image.png

image.png

由于资源加载存在近4s的耗时,组件渲染被延迟,这种情况下,便导致了我们虽然通过减少了首屏资源提升了首屏加载体验,但却让用户在后续使用过程中出现了体验断层,甚至是页面白屏的情况,这对用户而言是不能接受的用户体验。

且这种情况并非网络不好或资源过大等极端情况下才会出现,随着应用使用量的上升,该情况会多次出现,影响用户体验,以下为网络波动的场景之一:
image.png

那么如果要保证一个spa应用的后续交互体验,那么就是不拆包,要么就需要引入组件预加载机制。
预加载的必要性:让被懒加载的组件资源提前进行对应的资源请求,而不是渲染时请求以减少组件渲染时间,保证应用不会因为组件拆包影响用户体验

react.lazy有一个局限就是必须放在<Suspense>组件内,无法独立渲染<LazyComponent />
image.png

为什么不是react-lodable?

其实 react 社区提供的 react-lodable 解决了以上两个问题:

  • 不强依赖 <Suspense>,可独立渲染<LazyComponent />
  • 提供了preload预加载方案,减少异步加载耗时,保证用户体验。
    image.png

但是有个问题是模块过多时,侵入式的代码也变多了,且看起来重复且冗余,同时被预加载的模块并没有进行统一管理,后续维护也不会很方便,不直观。

那么我们在 webpack 编译层面是可以获取到打包chunk的详细信息的,是不是可以在 plugin 层面对按需加载的chunk进行统一维护,同时减少侵入式代码,于是便有了此方案 route-resource-preload, 其具备的特性:

  1. 拆分模块按需加载,减少应用首屏资源请求大小及数量,提升加载体验.
    image.png
    dynamic 是基于 import()做的一个封装函数。
    image.png

  2. 支持组件资源批量自动预加载,同时支持自定义触发时机,如hover到某个组件上、某组件渲染时、出现在视图内时。( Component / Module-Federation / UMD / Svg / Png 等静态资源).

    自动预加载步骤:

    2.1 构建时添加插件
    image.png
    2.2 基于暴露的 dynamic API 对组件进行动态加载(拆包)并渲染, 同时可基于<PreloadLink>进行自动预加载。
    image.pngimage.png

  3. 支持手动调用预加载, 类 react-lodable 的方式,但支持批量.
    单个组件手动预加载
    image.png 多个组件手动预加载
    image.png

  4. 支持React <Suspense>,但不依赖。

  5. 完备的 typescript 类型推导.
    image.png

DEMO演示

在线体验地址
react.lazy 正常拆包并加载效果.gif
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

route-resource-preload 拆包并预加载效果.gif
preoload.gif

正常懒加载(react.lazy)普通组件 及 Module-Federation

WX20230424-113712.png

route-resource-preload 预加载普通组件 及 Module-Federation

WX20230424-113747.png

加载耗时如下:

资源正常懒加载 - react.lazy (ms)预加载 (ms)
普通组件 (单个资源文件)1841
Module-Federation 组件 (6个资源文件)4058

从表中可以看出,预加载显着提升了组件的加载速度,尤其是对于复杂的组件,加载速度的提升更为明显。 这说明在复杂的业务场景下,预加载可以显着提升组件加载速度和用户体验.

方案&流程介绍

该方案基于 @route-resource-preload/webpack-plugin 及 @route-resource-preload/react, 分别对应构建时运行时:

构建时流程图:
image.png

构建时 通过 dynamic API 及 webpack plugin 对模块进行拆包的同时,还会将preloadKey(开发者自定义的预加载标识)import-module-url(import 模块路径)chunk(output产物)三者之间的关系以json形式进行保存,并允许应用端访问。
image.png

生成的JSON文件:
image.png

JSON:
image.png

开发者基于 JSON,可以判断出可被预加载的chunk已配置预加载的chunk具体有哪些,同时也能知道插件中配置的预加载标识preloadKeychunk间的映射关系。

运行时流程图:
image.png
运行时 则是基于构建出的json,开发者通过设置Preloader 或者是<PreloadLink>preloadKey,对应的相关资源将被预加载,并基于 dynamic API 渲染组件。

项目效果演示

1. 真实用户场景打开 Modal( Modal基于 webpack module federation 引入)体验模拟
  • 无预加载时:点击按钮后,拉取对应的拆包资源及远程 module federation 组件资源,请求完成后渲染组件,存在体验卡顿,如下图。
    iShot_2023-07-20_18.25.23.gif
  • 有预加载时:hover到某个区域/某个组件渲染时(开发者自定义)即可触发资源预加载,点击按钮后立即渲染组件,不存在体验卡顿,如下图。
    iShot_2023-07-20_18.34.29.gif
2. 离线场景体验模拟

为了对比效果(有/无预加载)更加直观,以下将采用离线网络的场景下进行展示。

  • 无预加载时:按需加载在离线网络环境下会无法正常渲染,导致白屏。
    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
  • 有预加载时:按需加载在离线网络环境下,页面渲染体验正常,即实现拆包按需加载的用户体验等同于未拆包

预加载机制存在的必要性

  1. Any code can be split: 通过以上的预加载机制,实现应用内 Any code can be split(一切代码都可以被拆包),且能保证不影响用户体验,让开发者没有了因为单页面资源过大影响应用性能的烦恼,SPA(单页面应用) 也可以拥有极致的首屏幕加载体验和交互体验,🐟与🐻掌兼得。
  2. module federation(模块联邦) 组件预加载: 对于 webpack 的 module federation(模块联邦)而言,由于 module federation 打包出来的资源默认采用了按需动态加载的方案,因此当我们渲染一个比较大的 module federation 组件时,也会存在体验卡顿的情况,这时对该 module federation 组件进行预加载便可解决该体验问题。
  3. 革新开发者对组件懒加载的了解,减少开发者心智负担:开发者可以简单粗暴地基于页面维度对某个路由渲染的组件进行懒加载,不需要再从组件纬度去分析是因为哪个组件资源包过大导致的体验问题。

这篇关于拆分代码 + 动态加载 + 预加载,减少首屏资源,提升首屏性能及应用体验的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/382472

相关文章

MySQL 分区与分库分表策略应用小结

《MySQL分区与分库分表策略应用小结》在大数据量、复杂查询和高并发的应用场景下,单一数据库往往难以满足性能和扩展性的要求,本文将详细介绍这两种策略的基本概念、实现方法及优缺点,并通过实际案例展示如... 目录mysql 分区与分库分表策略1. 数据库水平拆分的背景2. MySQL 分区策略2.1 分区概念

使用Python实现全能手机虚拟键盘的示例代码

《使用Python实现全能手机虚拟键盘的示例代码》在数字化办公时代,你是否遇到过这样的场景:会议室投影电脑突然键盘失灵、躺在沙发上想远程控制书房电脑、或者需要给长辈远程协助操作?今天我要分享的Pyth... 目录一、项目概述:不止于键盘的远程控制方案1.1 创新价值1.2 技术栈全景二、需求实现步骤一、需求

Spring Shell 命令行实现交互式Shell应用开发

《SpringShell命令行实现交互式Shell应用开发》本文主要介绍了SpringShell命令行实现交互式Shell应用开发,能够帮助开发者快速构建功能丰富的命令行应用程序,具有一定的参考价... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定义S

Java中Date、LocalDate、LocalDateTime、LocalTime、时间戳之间的相互转换代码

《Java中Date、LocalDate、LocalDateTime、LocalTime、时间戳之间的相互转换代码》:本文主要介绍Java中日期时间转换的多种方法,包括将Date转换为LocalD... 目录一、Date转LocalDateTime二、Date转LocalDate三、LocalDateTim

MySQL中动态生成SQL语句去掉所有字段的空格的操作方法

《MySQL中动态生成SQL语句去掉所有字段的空格的操作方法》在数据库管理过程中,我们常常会遇到需要对表中字段进行清洗和整理的情况,本文将详细介绍如何在MySQL中动态生成SQL语句来去掉所有字段的空... 目录在mysql中动态生成SQL语句去掉所有字段的空格准备工作原理分析动态生成SQL语句在MySQL

jupyter代码块没有运行图标的解决方案

《jupyter代码块没有运行图标的解决方案》:本文主要介绍jupyter代码块没有运行图标的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录jupyter代码块没有运行图标的解决1.找到Jupyter notebook的系统配置文件2.这时候一般会搜索到

C语言函数递归实际应用举例详解

《C语言函数递归实际应用举例详解》程序调用自身的编程技巧称为递归,递归做为一种算法在程序设计语言中广泛应用,:本文主要介绍C语言函数递归实际应用举例的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录前言一、递归的概念与思想二、递归的限制条件 三、递归的实际应用举例(一)求 n 的阶乘(二)顺序打印

Python通过模块化开发优化代码的技巧分享

《Python通过模块化开发优化代码的技巧分享》模块化开发就是把代码拆成一个个“零件”,该封装封装,该拆分拆分,下面小编就来和大家简单聊聊python如何用模块化开发进行代码优化吧... 目录什么是模块化开发如何拆分代码改进版:拆分成模块让模块更强大:使用 __init__.py你一定会遇到的问题模www.

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La