编辑整理:整理来源:油管,浏览量:94,时间:2023-04-11 08:32:01
作者: 龙朝忠 WecTeam
转发链接:https://mp.weixin.qq.com/s/MJNaI6HmClodAsGgI26EMA
本文是性能优化清单系列第四篇,可以先看看前边的文章:2020前端性能优化清单(一)2020前端性能优化清单(二)2020前端性能优化清单(三)构建优化34 使用针对目标 JavaScript 引擎的优化。研究哪些 JavaScript 引擎在你的用户群中占主导地位,然后探索对其进行优化的方法。例如,当针对 Blink 浏览器、Node.js 运行时和 Electron 中使用的 V8 进行优化时,请使用脚本流[2]来处理整体脚本。
下载开始后,脚本流允许 async 或 defer scripts 在单独的后台线程上进行解析,因此在某些情况下,页面加载时间最多可缩短 10%。实际上,在 header 中使用 script defer[3],可以使浏览器更早的发现资源[4],然后在后台线程解析它。
警告:Opera Mini 不支持脚本延迟[5],因此,如果你是为印度或非洲开发的, defer 则将被忽略,从而导致渲染被阻塞,直到对脚本执行完毕(感谢Jeremy!)。
你也可以将库从使用它们的代码中分离出来,或者反过来,将库和它们的使用合并到一个脚本中,将小文件分组在一起,避免内联脚本,这样就可以挂接到 V8 的代码缓存中。或者甚至可以使用 v8-compile-cache[6]。
Firefox 最近发布的 Baseline Interpreter[7] 提高了 Firefox 的速度,并且还有一些 JIT 优化策略[8]可用。
pic
渐进式引导[9]意味着使用服务器端渲染来快速获得第一个有意义的图形,同时还包括最少的 JavaScript 以使交互时间紧挨着第一个有意义的图形渲染。
35 客户端渲染还是服务器端渲染?都需要!这是一场非常激烈的对话。最终决定必须由应用程序的性能决定。最终的方法是设置某种渐进式引导[10]:使用服务器端渲染来快速获得第一个有意义的图形,同时还包括一些最少的必需的 JavaScript,以使可交互时间紧挨着第一个有意义的图形的绘制。如果 JavaScript 在第一个有意义的图形的绘制之后出现得太晚,浏览器将在解析、编译和执行后来发现的 JavaScript 时锁定主线程[11],从而削弱了站点或应用程序的交互性[12]。
为了避免这种情况,请务必将函数的执行分解为单独的异步任务,并尽可能使用 requestIdleCallback。考虑使用 WebPack 的[动态import()支持](https://developers.google.com/web/updates/2017/11/dynamic-import "动态import( "动态import()支持")支持")延迟加载部分 UI,避免在用户真正需要它们之前因为加载、解析和编译造成的成本消耗(感谢Addy!)。
本质上,可交互时间(TTI)告诉我们从导航开始到可以可交互之间的时间。该指标是通过查看初始内容渲染后的前5秒窗口来定义的,在这个窗口中,没有 JavaScript 任务需要超过 50ms 的时间。如果出现超过 50ms 的任务,对5秒窗口的搜索将重新开始。因此,浏览器首先会假定它是可交互的,只是为了切换到冻结状态,只是为了最终切换回可交互状态。
进入可交互状态后,我们可以按需或在时间允许的情况下启动应用程序的非必需部分。不幸的是,正如 Paul Lewis 所注意到的那样[13],框架通常没有面向开发者的简单的优先级概念,因此,对于大多数库和框架而言,实现逐步启动并不容易实现。
尽管如此,我们仍在接近目标。如今,我们可以探索几种选择,Houssein Djirdeh 和 ason Miller 在有关“网络渲染”的[14]对话中对这些选择进行了很好的概述。以下概述基于他们的谈话。
完全由服务器端渲染(SSR)在典型的SSR(例如WordPress)中,所有请求都完全在服务器上处理。所请求的内容将作为完成的 HTML 页面返回,浏览器可以立即进行渲染。因此,例如,SSR 应用程序不能真正使用 DOM API。第一个有意义的图形的绘制和互动时间之间的差距通常很小,并且可以将 HTML 以流式传输到浏览器并立即呈现页面。但是,我们最终需要花费更长的服务解析时间导致第一个字节到达时间也会加长,并且我们没有利用现代应用程序的响应式功能和丰富的其他功能。静态SSR(SSR)我们将产品作为单个页面应用程序进行构建,但是在构建步骤中,所有页面都使用最少的 JavaScript 预渲染为静态HTML。因此,我们可以快速显示登录页面,然后为后续页面提前获取 SPA 框架。Netflix 采用了这种方法[15]减少了加载资源数量并使可交互时间减少了50%。带有 (Re)Hydration 的服务端渲染(SSR + CSR)带有 (Re)Hydration 的服务端渲染时,从服务器返回的 HTML 页面还包含一个脚本,该脚本可加载完整的客户端应用程序。借助 React,我们可以在 Node 服务器(如 Express)上[16]使用 `ReactDOMServer` 模块[17],然后调用 renderToString 方法将顶级组件生成为静态 HTML 字符串。使用 Vue,我们可以使用 vue-server-renderer[18] ,调用 renderToString 方法来将 Vue 实例渲染为 HTML。在 Angular 中,我们可以用 `@nguniversal`[19]把客户端的请求转换成完全由服务端渲染你的HTML页面。使用 Next.js[20](React)或 Nuxt.js[21](Vue)也可以立即获得完整的服务器渲染体验。该方法有其缺点。作为结果,我们确实获得了客户端应用程序的全部灵活性,同时提供了更快的服务器端渲染,但是“第一个有效内容绘制”和“可交互时间”之间的间隔也越来越大,并且“首次输入延迟”也增加了。ReHydration 非常昂贵[22],通常仅靠这种策略是不够的,因为它严重延迟了交互时间。使用渐进 (Re)Hydration 进行流式服务器端渲染(SSR + CSR)为了最大程度地缩短“可交互时间”与“第一个有效内容绘制“之间的间隔,我们一次渲染多个请求,并在生成内容时分批发送内容。因此,在将内容发送到浏览器之前,我们不必等待完整的 HTML 字符串,还可以缩短“第一个字节的时间”。在React中,我们可以使用 renderToNodeStream[23] 而不是 renderToString 来通过管道返回响应并将 HTML 分块发送。在 Vue 中,我们可以使用 renderToStream[24] 来实现管道和流传输。随着即将到来的 React Suspse,我们或许也可以使用异步渲染[25]来达到相同目的。在客户端,我们不是一下启动整个应用程序,而是逐步启动组件。首先将应用程序的各个部分分解功能放到独立脚本中,然后逐步“激活”(译注:这里原文是 hydrate,直译为注水,以下激活同理)(按优先级顺序)。实际上,我们可以先将关键组件激活,而其余的则可以随后激活。然后,可以针对每个组件定义为客户端还是服务器端渲染。然后,我们还可以延迟某些组件的激活,直到它们出现在可视区域或用户交互需要或浏览器处于空闲状态为止。对于 Vue,Markus Oberlehner 发布了一份指南,该指南指明在用户交互时使用 hydration[26] 或 vue-lazy-hydration[27](一种早期插件,可在组件可见时或特定用户交互时激活组件)可以减少 SSR 应用程序的交互时间。Angular 团队使用 Ivy Universal[28] 进行渐进客户端”激活“。你也可以使用 Preact 和 Next.js 实现部分 hydration[29]。对于React,部分 “hydration” 功能在 Suspse 计划之中[30](看起来很有希望实现[31]!)。如果你喜欢冒险,Jason Miller 已发布了有关如何使用 React 实现渐进式 “hydration” 的演示程序,因此你可以立即使用它们:演示1[32],演示2[33],演示3[34](也在GitHub上有提供[35])。另外,你可以查看 react-prerendered-component[36] 库。三方同构渲染[37]如果可以使用 service worker[38],“trisomorphic”渲染也很有意思。该技术是指,利用流式服务器渲染初始页面,等 Service Worker 加载后,接管 HTML 的渲染工作。这可以使缓存的组件和模板保持最新,并启用 SPA 式的导航以在同一会话中渲染新视图。当可以在服务器、客户端页面和 Service Worker 之间共享相同模板和路由代码时,此方法最有效。三方同构渲染,在三个位置使用相同的代码渲染:在服务器上,在 DOM 中或在 service worker 中。
客户端预渲染与服务器端预渲染相似,但不是在服务器上动态渲染页面,而是在构建时将应用程序渲染为静态 HTML。Gatsby[39] 是使用 React 的开源静态站点生成器,在构建过程中使用 renderToStaticMarkup 方法而不是 renderToString 方法构建生成一个简单的不需要 DOM 属性的静态页面,这个页面的主 JS 和后续可能会用到的路由会做预加载。对于 Vue,我们可以使用Vuepress[40] 实现相同的目标。你还可以将 prerender-loader 与 Webpack 一起使用[41]。结果是 TTFB 和 FCP 时间变少,并且我们缩短了交互时间和 FCP 之间的间隔。如果预期内容会发生很大变化,我们将无法使用该方法。另外,必须提前知道所有 URL 才能生成所有页面。某些组件可能使用预渲染方式来渲染,但是如果我们需要动态的东西,我们就必须依靠应用程序来获取内容。完全客户端渲染 (CSR)所有逻辑,渲染和启动均在客户端上完成。结果通常是“可交互时间”和 FCP 之间的间隔加大。结果,由于整个应用程序必须在客户端上启动才能呈现任何内容,因此应用程序感觉呆滞。通常来说SSR 比 CSR 快[42]。但是,对于许多应用程序来说,这是最常见的实现。那么,选择客户端渲染还是服务器端渲染?通常,对于完全客户端渲染框架要限制在绝对需要它的页面上才使用。对于高级应用程序,仅仅依靠服务端渲染也不是一个好主意。如果做得不好,服务器渲染和客户端渲染都是灾难。
无论你偏向 CSR 还是 SSR,请确保尽快渲染重要的元素,并最大程度地减少渲染和“可交互时间”之间的间隔。如果页面变化不大,请考虑预渲染;如果可以,请考虑推迟框架的启动。服务端渲染好 HTML 并按块流式吐出,并在客户端实现渐进式“激活”:当进入可见区域或用户交互需要或页面空闲时“激活”,这样能充分利用两者的优势。
服务器渲染到客户端渲染的技术频谱。另外,请查看 Jason 和 Houssein 在 Google I/O 上有关应用程序架构的性能影响[43]的演讲。(图片来源:Jason Miller[44]))
AirBnB 一直在进行 hydration 实验。他们推迟了不需要的组件的激活,增加了用户交互(滚动)或空闲时间的激活,测试表明它可以改善TTI。
36 始终倾向于自行托管第三方资源。通常,默认情况下自托管静态资产[45]是一个很好的经验法则。常见的假设是,如果许多站点使用相同的公共 CDN 和相同版本的 JavaScript 库或网络字体,那么访问者将使用已经存储在浏览器中的脚本和字体登陆我们的网站,从而大大提高了他们的体验。但是,这种情况极不可能发生[46]。
出于安全性考虑,为了避免产生指纹,浏览器已实现了分区缓存[47],该技术在2013年的 Safari 和去年的 Chrome 中引入。因此,如果两个站点指向完全相同的第三方资源 URL,则每个域都将代码下载一次,并且由于隐私问题,缓存将存在关联域名的“沙盒”中(感谢David Calhoun!)。因此,使用公共 CDN 不会[48]自动提高性能。
此外,值得注意的是,资源不会像我们期望的那样存在于浏览器缓存中[49],并且自己的资源比第三方资源更有可能保留在缓存中。因此,自托管通常更可靠,更安全,并且性能也更好。
37 限制第三方脚本的影响。在所有性能优化的情况下,我们经常无法控制来自业务需求的第三方脚本。第三方脚本指标不受终端用户体验的影响,所以通常只要有一个脚本调用讨厌的第三方脚本,就可以最终破坏性能测试的专一性。为了控制和缓解这些脚本带来的性能损失,只是将它们异步加载(可能通过延迟),并通过资源提示(如dns-prefetch或preconnect)加速它们是不够的。
所有 JavaScrip t代码执行时间中有 57%用于第三方代码[50],因此定期审核依赖项和做标记管理非常重要。
正如 Yoav Weiss 在关于第三方脚本的必看演讲[51]中所解释的那样,在许多情况下,这些脚本会下载动态资源。资源会在页面加载中发生变化,所以我们不一定知道哪些主机将从中下载资源,以及它们会是什么资源。
那我们有什么选择呢?考虑通过 service works 来加速加载资源,如果资源加载超时未响应,请返回空响应以告知浏览器继续页面解析。你也可以记录或阻止不成功或不满足特定条件的第三方请求。如果可以,请从你自己的服务器[52]而不是供应商的服务器中加载第三方资源并延迟加载它们。比如,Huddle 创建了一个假的聊天按钮,该按钮仅在单击时下载脚本,避免了页面一加载就加载2.3MB 的聊天小部件[53]。对于大多数不使用聊天窗口小部件的用户,避免了不必要的下载和 JavaScript 执行。
另一种选择是建立内容安全策略(CSP),以限制第三方脚本的影响,例如,禁止下载音频或视频。最好的选择是通过 iframe 嵌入脚本,以使脚本在 iframe 的上下文中运行,因此脚本无法访问页面的DOM,并且不能在你的域上运行任意代码。使用 sandbox属性可以进一步限制 iframe ,你可以禁用 iframe 可能执行的任何功能,例如,阻止脚本运行,阻止警报,表单提交,插件,访问顶部导航等。
例如,可以通过使用< iframe sandbox="allow-scripts">需要允许脚本运行。每个限制都可以通过sandbox属性的各种allow值来取消(几乎所有地方都支持[54]),因此可用控制第三方脚本允许功能的最低限度。
考虑使用 Intersection Observer;这样可以将广告嵌入 iframe中,但不影响事件的触发和获取需要从 DOM 中获取的系信息(如是否可见)。注意新的策略,例如功能策略[55],限制资源大小和设置 CPU /带宽优先级,以限制有害的 Web 功能和脚本,这些脚本和功能会使浏览器减速,例如同步脚本,同步 XHR 请求,document.write 和过时的实现。
要对第三方进行压力测试[56],在DevTools的性能配置文件页面中从头到尾检查,测试如果请求被阻止或超时会发生什么情况–对于后者,你可以使用 WebPageTest 的 Blackhole 服务器blackhole.webpagetest.org,你可以将特定域指向在你的hosts文件中。最好是自托管并使用单个主机名[57],而且还会生成一个请求映射[58],该映射公开第四方调用并检测脚本何时更改。你可以使用 Harry Roberts 的方法来审核第三方资源,[59]并生成像这样的[60]电子表格。Harry 在关于第三方绩效和审计的演讲中[61]还解释了审计工作流程。
必须应对全能的 Google Tag Manager?Barry Pollards 提供一些包含 Google Tag Manager 影响的指南[62]。另外,Christian Schaefer 也在探索2020年加载广告的策略[63]。
Casper.com 发布了一份详细的案例研究,介绍了他们如何通过自动托管 Optimizely 将网站缩短1.7秒。
38 正确设置HTTP缓存报文头。仔细检查 expires、max-age、cache-control 和其他 HTTP 缓存报文头是否已正确设置。通常,资源应该可以在很短的时间内(如果可能会更改)或无限期(如果它们是静态的)[64]缓存,你可以在需要时在 URL 中更改其版本。
使用Cache-control: immutable,用于指纹静态资源,可避免重新验证(在 Firefox,Edge 和 Safari 中受支持[65])。实际上,据 Web Almanac 称,“其使用率已增长到3.4%[66],并且已广泛用于 Facebook 和 Google 的第三方响应中[67]。”
还记得 stale-while-revalidate[68] 吗?你可能知道,我们使用Cache-Control响应头指定了缓存时间,例如 Cache-Control: max-age=604800。经过604800秒后,缓存将重新获取请求的内容,从而导致页面加载速度变慢。可以通过使用 stale-while-revalidate 来避免这种减速;它基本上定义了一个额外的时间窗口,在此期间,缓存可以使用过期的资源,只要它可以在后台重新异步验证它的状态即可。因此,它“隐藏”了客户端的延迟(在网络中和在服务器上)。
在2019年6月至7月,Chrome 和 Firefox 开始对 HTTP Cache-Control stale-while-revalidate 支持,由于过期的资产不再在关键路径中,它可以改善后续的页面加载延迟。效果:对于重复视图,RTT 为零[69]。
你可以用 Heroku 的基本 HTTP 缓存头[70]、 Jake Archibald 的最佳缓存实践[71]和 Ilya Grigorik 的 HTTP 缓存入门[72]作为指南。另外,请注意 vary 报文头[73],尤其是与 CDN 有关的请求头 [74],也需要注意 HTTP 表示形式变体[75],这有助于避免新请求与先前请求略有不同(但不明显)时进行额外的往返验证(谢谢,Guy 和 Mark!)
此外,确保没有发送不必要的报头[76](例如 x-powered-by,pragma,x-ua-compatible,expires等)和确保报文中包含有用的安全和性能相关报文头[77](如 Content-Security-Policy,X-XSS-Protection,X-Content-Type-Options 等)。最后,请注意单页应用程序中CORS 请求的性能成本[78]。
未完结,请继续留意下一篇
作者: 龙朝忠 WecTeam
转发链接:https://mp.weixin.qq.com/s/MJNaI6HmClodAsGgI26EMA
wordpress评论博主ua,wordpress评论框插件,wordpress评论表情
作者:整理来源:油管,时间:2023-04-11 08:32,浏览:95