找回密码
 会员注册
查看: 15|回复: 0

前端工程化之H5性能优化篇

[复制链接]

2万

主题

0

回帖

7万

积分

超级版主

积分
72245
发表于 2024-10-6 21:32:36 | 显示全部楼层 |阅读模式
导读:从粗糙到精致,从简单到复杂,全球互联网Web App(网页应用)平均体积已增压到1.6Mb,随着音视频等富媒体内容的流量池膨胀,终端设备上的用户对网页装载速度尤其敏感。页面不能做到秒开,就会有大量用户选择离开。重视并改善网站性能,优化即时网页装载时间,加速即时网页在浏览器平台终端状态展现,进而可以带来网站流量增长。本文源自百度直播研发部,提出了前端渐进增强的页面渲染方案,即“路由分离+预静态化+WebView预创建”方案,来替代模板同步渲染方案,并采用工程化的方式将能力打包下沉赋能产品线。百度架构师“周一见”活动第二期活动开启,文末有惊喜福利~一、背景在直播业务落地的场景中,构建打赏服务精神,创造风格多样,令人难忘的运营活动形式,以满足目标用户需求。进行有效的服务创新,保障H5服务品质,控制访问性能,及时化解金主情绪为商业价值,制造特效惊喜,流畅的用户体验,有利于赢得用户对平台的尊重,提升平台品牌气质:1. 优化访问体验:优化H5首屏性能,围绕提升用户服务感受和提升开发者工作效率进行创新,探索Web追赶原生的体验创新;2. 加速需求闭环:解决H5页面路由与数据接口耦合的问题,实现H5服务自治,释放业务对Web产品形态的旺盛需求;3. 基础设施建设:使用工程化手段沉淀公共通用的前端基础设施,让前端组织更敏捷的支持跨业务的人力调配,技术赋能更多产品线;围绕以用户为中心的功能完善的体验,以客户为中心的商业价值的转化。提升H5服务品质,发挥现代浏览器潜能,赋能传统Web新的解决方案。历史架构的原因,H5页面的加载过程是Server向Smarty模板注入json_encode后的接口主数据,响应给浏览器带有页面完全JSON数据的页面,然后在浏览器端执行JavaScript,最终Paint给用户,流程如下图:△H5优化前的加载与渲染流程△H5首屏关键路径耗时拆解两个影响H5首屏内容(FCP,First Contentful Paint,首次内容绘制)的关键路径为:1. 网络耗时:依赖Server端数据查询及模板编译,当数据查询慢时延迟了首字节到达(TTFB,Time To First Byte,首字节时间)。2. 内核渲染耗时:依赖JavaScript执行读取页面主数据并生成完全的DOM结构。因此,我们针对性地设计并实施了“路由分离+预静态化+WebView预创建”的方案,改进后的页面加载与渲染流程如下图:△H5优化后的加载与渲染流程二、路由分离路由分离之前H5页面的URL由Server分配,前端负责编写TPL模板产物,TPL与最终URL的对应关系在Server通过配置文件做映射,日益暴露出启动开发慢,页面后期维护交接沟通成本高的问题。?所以我们希望页面路由规范化,让前端开发者自主控制页面入口格式,让后端开发者更专注于API接口数据逻辑。因此,我们设计了前端路由分离方案,约定了页面URL与页面源代码目录映射关系,规则如下图:△预定式URL路由规范NGINX直接响应预静态化的HTML文件,首字节到达不依赖数据查询与模版编译。三、预静态化前端路由分离直接在NGINX代理层返回HTML文件,但没有页面完全渲染需要的数据,在执行AJAX请求没有返回之前,需要规避页面一直处于白屏或全局loading状态,提前FCP的时间。?我们采用了预静态化页面的方案。预静态化不像服务器渲染那样即时编译产出完全静态化的 HTML,它只在构建时为了特定的路由生成特定的几个静态页面,我们可以通过 webpack插件将一些特定页面在编译时就注入DOM结构,这样做有几个好处:第一缩短页面白屏时间,第二相对于服务器端渲染节省云基础设施资源成本,第三输出给搜索引擎爬取页面通用内容。?结合实际应用场景和市面上主流的预静态化方式,最终我们开发了基于ReactDomServer原生的服务端渲染能力的webpack插件,提升预静态化性能和效率。?通过webpack插件系统获取每次构建的compilation上下文,通过html-webpack-plugin的before AssetTagGeneration hook获取当前页面bundle,afterTemplateExecution hook获取当前页面编译后的模板HTML,通过eval执行bundle导出的整个页面APP模块,通过ReactDomServer对单页应用的每个路由产出APP HTML与模板HTML合并后落盘为预静态化的HTML。H5预静态化调用序列如下:△H5预静态化调用序列页面入口export APP:module.exports = (locals) => Promise.resolve(locals.preRender({id: containerId, main: App}));基于Node.js环境,创建JSDOM实例提供浏览器宿主环境,使用ReactDOMServer将组件渲染为静态HTML标记。使用自研的预静态化webpack插件替换html-webpack-plugin,并不增加新的编译阶段。?Webpack插件与市面上主流的预静态化方法做个对比,如基于无头浏览器的、基于本地启动HTTP Server、通用的静态站点生成工具等。结果如下:react-snapprerender-spa-pluginreact-snapshotprepsnapshotify@baidu/html-webpack-pre-render-pluginStatesupportedsupportedunsupportedunsupportedexperimentalsupportedDOM implementationpuppeteerphantomjs-prebuiltjsdomnightmarepuppeteerjsdom+implementLoad?performance optimisation+---+-Zero-configuration+-+-++Redux+-+--+Async components+---+-Webpack code splitting++--+-CSSStyleSheet.insertRule+---++implementblob urls+?---+implementAll browser features+--?++implementSpeedslowslowfastslowslowfast(real time)结果如下:综合对比,我们的预静态化速度最优,可在开发阶段实时看到预静态化结果,所见即所得,方便研发调试,不需要增加新的编译阶段。遵循尽可能让页面骨架内容显示最终内容的原则,产出预静态化的HTML文件,对于展示用户个性化内容的动态组件,仍需要等待一个AJAX请求的时间,这部分组件在执行AJAX请求没有返回之前,我们采用了组件级别的Skeleton方案,以保证首屏使用通用内容+动态组件骨架填充:1. 静态组件开发时定义公共部分state,填充页面公共部分内容,代码示例: this.state = { // 预静态化环境的标记(编译阶段预静态化插件注入) isPreRender: window.isPreRender, // 页面公共内容 rights: [ // 权益 { dataIndex: 'cameraAction', icon: require('../../../../assets/cashVideoActivity/ricon_1.png'), button: require('../../../../assets/cashVideoActivity/do_shoot.png'), text: 亿万奖金, 全网最高!, action: {} // 通过 dataIndex 在接口数据里获取 }, { dataIndex: 'renzhengAction', icon: require('../../../../assets/cashVideoActivity/ricon_2.png'), button: require('../../../../assets/cashVideoActivity/do_auth.png'), text: 加V认证, 十倍收益!, action: {} } ] };2. 动态组件根据window.isPrerender标记做预静态化环境与浏览器渲染环境两种环境下的响应,代码示例:// 组件骨架可使用 http://danilowoz.com/create-content-loader/ 在线生成import ContentLoader from 'react-content-loader';const GrowthCardLoader = props => ( );// 组件环境响应核心逻辑{ window.isPreRender ? ( ): (
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-11 06:13 , Processed in 0.437066 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表