Chrome Audit 500:第1部分。着陆

Chrome开发者工具具有“审核”标签。 它上面有一个称为Lighthouse的工具,用于分析Web应用程序的制作质量。

图片

我最近决定测试一个应用程序,结果感到震惊。 在几个部分中,评估立即在红色区域中进行。 我开始研究我的应用程序出了什么问题。 在分析结果中,我发现了很多非常有用的建议,并实现了这些建议,并获得了500分。 结果,该应用程序开始运行得更快,我修改了一些有关构建应用程序方法的概念。 在本文中,我想分享我所遇到的最有趣的解决方案。

如果您没有安装Chrome的能力,则可以从npm安装lighthouse并从控制台使用它。

在本文中,我没有将每个建议与特定部分进行比较;相反,我将这些部分分为我应用的解决方案和Ligthouse喜欢的解决方案。 这不是他所建议的全部,只是最有趣的。 其余建议非常简单,例如SEO早已为大家所熟悉。

性能表现


服务器选择


这是最常见的建议,但这正是所有生产力的基础。 幸运的是,找到一个好的解决方案很简单,它是任何Tier 3或Tier 4数据中心。

应用程序初始化


一旦浏览器中只有html。 然后是javascript和业务逻辑。 如今,客户端上的逻辑如此之多,以至于html无法应付,并且不再需要。 但是,因为 浏览器无法开始从JavaScript文件加载,我们将不得不放置一小段html以启动我们的应用程序。

理想情况下,它应该看起来像这样:

<!DOCTYPE html> <html lang="ru"> <head> <title> </title> <link rel="manifest" href="./manifest.webmanifest"> <link rel="shortcut icon" href="content/images/favicon.ico" type="image/x-icon"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" charset="utf-8"/> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="viewport" content="width=device-width" /> <meta name="theme-color" content="#425566"> <meta name="Description" content=" "> </head> <body> <div id="loader"> loading </div> <script async> // todo:     </script> </body> </html> 

其中不应包含任何内容,而应包含初始化应用程序所需的代码,这将加载应用程序本身和内容。

本文不考虑针对机器人的优化,但我会说,捕获特定的机器人并给出特定的机器人需要什么是最容易的。 Google机器人本身会从以后将加载的内容中了解一切。

使用启动画面


在移动应用程序中加载甚至加载操作系统时,我们都习惯于启动屏幕,但是很少有人在Web应用程序中使用启动屏幕。 这就是我们将要放置在加载器块中的内容,以便用户在应用程序本身加载时不会感到无聊。

作为启动屏幕,您可以选择使用css动画或仅使用图片,就像在手机上一样。 唯一的条件是它应该很轻。

图片

我们得到什么? Internet速度慢的用户将立即从该站点收到响应,他们不会欣赏白屏,并且想知道该站点是否在正常运行。 具有快速Internet的用户很可能甚至看不到它,但是即使他们在Internet上也有滞后。

作为使用启动画面的一个有趣示例,我将为您提供坞站站点,在这里,很长的波浪装饰了该站点的很长的负载。 实际上,这就是互联网速度较慢的人们如何看到您的应用程序。

我立即赶紧让那些认为飞溅屏幕会欺骗Lighthouse并在其后面放置沉重应用程序的人感到沮丧。 他看到了所有内容,并且不会给您繁重的应用打上良好的烙印。

应用程序初始化


现在,我们正在用图片分散用户的注意力,现在该下载该应用程序了。 为此,我们将以下脚本插入脚本块中。

 // 1.  ServiceWorker,     PWA if (navigator.serviceWorker && !navigator.serviceWorker.controller) { navigator.serviceWorker.register('pwabuider-sw.js', { scope: './' }); } // 2.    [ "./content/font.css", "./content/grid.css" ].forEach(function(url){ var style = document.createElement("link"); style.href = url; style.rel = "stylesheet"; document.head.appendChild(style); }); // 3.    [ "./scripts/polyfills.min.js", //  vendors.min.js "./scripts/main.min.js" // spa  ].forEach(function(url){ const script = document.createElement("script"); script.src = url; script.async = false; document.head.appendChild(script); }); 

它由什么组成:

  1. PWA连接 -我们将在下面的相应部分中进行考虑。 您需要尽快连接它,因为pwa可能已经拥有该站点正常工作所需的一切,并且不再有对服务器的请求。
  2. 连接样式 -根据需要连接样式。 理想情况下,此代码根本不应该存在,样式应根据需要连接您的组件。
  3. 连接脚本 -连接程序。 它应该仅包含这些脚本中的两个。 在绘制应用程序的第一个屏幕之后,将加载不影响第一个屏幕(而不是整个页面)显示的所有其他脚本(地图,分析,库)。 加载程序后,分析组件应该已经加载了分析。 分析的质量将不受此影响,并且分析系统支持在下载程序后加载。 只有在用户扫描卡片并将其击中屏幕后,才能将卡片浸入水中。 类似地,使用特定组件所需的第三方库。

结果,稍微改变了优先级,我们就可以快速渲染应用程序。 因此,用户和搜索机器人对速度感到满意,同时又不侵犯分析能力。

延迟加载和渲染


一个非常重要的参数是绘制第一个屏幕的速度以及用户可以开始与此页面进行交互的速度。 在这里值得使用以下优化:

1.延迟渲染。 仅需要绘制用户正在查看的页面部分,并且当用户跳到沉重的组件或图片时,应该已经完成​​了渲染。

一个好的解决方案是lazy-block和lazy-img组件:

 <div> <p></p> <lazy-img src="..."/> </div> <lazy-block>   </lazy-block> <lazy-block>   </lazy-block> <lazy-block>   </lazy-block> 

关键是他们将监视用户的滚动,并且如果该组件落在屏幕区域中,它将被绘制。 这可以与虚拟滚动技术( 示例 )相比较,该技术在社交网络的墙上为每个人所熟悉。 我们可以永远滚动,但它们永远不会减速。

但是,不要忘记Google机器人,它可以看到spa,但不会滚动整个页面。 因此,如果您不小心,那么他将不会看到您的内容。

2.如果任何组件使用外部依赖项,则他将必须根据需要自己加载它。 例如,它可以是带有地图,图表或3D图形的块。 最近,在JS中执行此操作的方法非常简单:

 class Demo { constructor() { this.init(); } private async init() { const module = await import('./external.mjs'); //   module.default(); module.doStuff(); } } 

结果,用户仅加载他需要的内容,从而大大节省了用户和服务器资源。

捆绑最小化


而且...是的,您没有考虑过,这与Terser(UglifyJS)中的缩小无关,而只是将其所需的内容提供给特定的浏览器。

事实是,浏览器在不断发展,它们拥有新的API,开发人员开始使用它,并且为了与旧版浏览器兼容,他们连接了polyfills和transpiler。 结果,出现的问题是,使用最新浏览器(约80%)的用户会获得为IE11用户设计的代码,并进行编译并带有多文件。

此代码的问题在于它包含大量额外的文本,并且其性能(根据我的主观估计)比原始文本低3倍。 为不同版本的浏览器制作多个捆绑包是更合乎逻辑的。 带有适用于Chrome 73的ES2017代码的捆绑软件(带有最少的多文件),带有适用于IE11的ES5的捆绑软件(带有最少的多文件)等

上一篇文章中,我曾写过关于如何一次收集不同版本的捆绑软件的文章 。 为了在浏览器中选择正确的版本,我们稍微修改程序连接脚本:

 var esVersion = ".es2017"; try{ eval('"use strict"; class foo {}'); }catch(e){ esVersion = ".es5"; } [ "./scripts/polyfills" + esVersion + ".min.js", "./scripts/main" + esVersion + ".min.js" ].forEach(function(url){ const script = document.createElement("script"); script.src = url; script.async = false; document.head.appendChild(script); }); 

结果,现代浏览器的用户将收到最轻便,最高效的程序,而IE11用户将得到应有的回报。

缩小尺寸的另一种有趣方式
一个非常有趣的库,用于减少50%的包 ,不幸的是结果不可预测。

代码最小化


一个非常普遍的问题是,当开发人员开始连接他们所看到的一切时。 结果,有时您可以观看重5-15 mb或更大的程序。 因此,应该明智地选择库。

与其选择较重的框架(如Angular或React),不如选择更轻量的框架:vue,preact,mithril等。 它们绝不逊色于其他同类产品,但是在包装尺寸上的节省可以是数倍。

避免使用繁重的库。 与其使用jquery,lodash,moment,rxjs等最小大小大于100kb的库,不如尝试更深入地研究算法并在本机JS中找到解决方案。 通常,您可以在本机脚本上编写更简单的代码,并且摆脱不必要的繁重依赖。

图像缩小


也许所有前端开发人员都知道webp图像格式,并且也知道将图像缩小到所需显示尺寸的需要。 但是由于某种原因,几乎所有开发人员都忽略了这一点。 在我看来,这样做的原因非常简单,人们不了解该操作是如何完成的以及如何在不同的浏览器中应用。

因此,在这里,我将给出一个非常简单的解决图片问题的方法。 该配方基于Sharp图像处理和转换工具 。 它以非常周到的流水线而脱颖而出,因此图像处理速度是类似产品的30-40倍。 而且,来自大量不同尺寸和格式的大量图像的组装时间本身可与现代前端的组装速度相媲美。

要使用Sharp,您需要编写一个脚本,我将其与glob结合使用以在带有源图像的目录中递归搜索图像,并且我将脚本本身从实用程序中隐藏以运行gulp任务。 我的汇编示例:

 gulp.task('core-min-images', async () => { const fs = require('fs'); const path = require('path'); const glob = require('glob'); const sharp = require('sharp'); // 1.          glob const files = await new Promise((resolve, reject) => { glob('src/content/**/*.{jpeg,jpg,png}', {}, async (er, files) => { !er ? resolve(files) : reject(er); }); }); // 2.      let completed = 1; await Promise.all(files.map(async (file) => { const outFile = file.replace(/^src/, 'www'); const outDir = path.dirname(outFile); // 2.1.       if (!fs.existsSync(outDir)) { fs.mkdirSync(outDir, { recursive: true }); } // 2.2.    const origin = sharp(file); // 2.3.     1920     //       jpg/png  webp    (80%) const size1920 = origin.resize({ width: 1920 }); await size1920.toFile(outFile.replace(/\.(jpeg|jpg|png)$/, '-1920w.$1')); await size1920.toFile(outFile.replace(/\.(jpeg|jpg|png)$/, '-1920w.webp')); // 2.4.    480   const size480 = origin.resize({ width: 480 }); await size480.toFile(outFile.replace(/\.(jpeg|jpg|png)$/, '-480w.$1')); await size480.toFile(outFile.replace(/\.(jpeg|jpg|png)$/, '-480w.webp')); // 2.5.    120   const size120 = origin.resize({ width: 120 }); await size120.toFile(outFile.replace(/\.(jpeg|jpg|png)$/, '-120w.$1')); await size120.toFile(outFile.replace(/\.(jpeg|jpg|png)$/, '-120w.webp')); // 2.6.      console.log(`Complete image ${completed++} of ${files.length}:`, file); })); }); 

结果,我们从每个巨大尺寸的源图像中获得了针对不同屏幕尺寸和不同浏览器的优化图像。 现在我们需要学习如何使用它们。 在这里,一切也很简单,如果我们之前这样写的话:

 <img src="sample.jpg"/> 

现在我们需要这样写:

 <picture> <source srcset="img/sample-480w.webp" type="image/webp"> <source srcset="img/sample-480w.jpg" type="image/jpeg"> <img src="img/sample-480w.jpg" alt=" !"> </picture> 

然后浏览器本身将为其选择最方便的格式。 您还可以在响应图像中添加此选项:

 <picture> <source srcset="img/sample-480w.webp, img/sample-960w.webp 2x" type="image/webp"> <source srcset="img/sample-480w.jpg, img/sample-960w.webp 2x" type="image/jpeg"> <img src="img/sample-480w.jpg" alt=" !"> </picture> 

考虑到现在可以在应用程序组装阶段生成图片的事实,事实证明所有图片都将具有相同的格式和分辨率集,这意味着我们可以统一此逻辑并将其隐藏在某些组件后面,例如相同的<lazy-img src="img/sample.jpg">

风格缩小


仅下载使用您的组件的样式。 理想情况下,将样式绑定到组件,并且仅在绘制组件本身时才将样式嵌入到房屋中。

最小化类名。 样式中嵌套或BEM选择器的绝对长度对应用程序的大小有不利影响。 当前,它充满了无法使用唯一选择器生成样式的工具:JSS,样式化组件,CSS模块。

在家缩小


我们都熟悉html,但是很少有人认为这只是对非常复杂的对象树的简单抽象。 div元素的继承链如下:

HTMLDivElement -> HTMLElement -> Element -> Node -> EventTarget

并且此链中的每个对象都有10到100个消耗大量内存的属性和方法。 DOM引擎应该考虑所有这些财富,以构建我们所看到的图像。 因此,请尽量不要在房屋中使用过多的元素。

缩小HTML。 删除在撰写本文时用于格式化html的所有内容。 事实是,在浏览器中编写代码时使用的空格也会变成家中的对象:

TextNode -> Node -> EventTarget

删除评论。 它们也是家庭的元素,消耗大量资源:

Comment -> CharacterData -> Node -> EventTarget

使用jsx模板引擎可能是一个好习惯。 事实是,在编译时,它会变成本机js代码,不会生成空格,注释,并且在打开和关闭标签时不会出错。

不好的做法,我什至要说一场噩梦,是facebook.com 。 这是html片段:

HTML页面摘要
 <!--  1 --> <div class=""> <div class="_42ef"> <div class="_25-w"> <div class="_17pg"> <div class="_1rwk"> <form class=" _129h"> <div class=" _3d2q _65tb _7c_r _4w79"> <div class="_5rp7"> <div class="_1p1t"> <div class="_1p1v" id="placeholder-77m1n" style="white-space: pre-wrap;">  ... </div> </div> </div> </div> <ul class="_1obb"> ...li... </ul> </form> </div> </div> </div> </div> </div> <!--  2 --> <div> <div> <div class="_3nd0"> <div class="_1mwp navigationFocus _395 _4c_p _5bu_ _34nd _21mu _5yk1" role="presentation" style="" id="js_u"> <div class="_5yk2" tabindex="-1"> <div class="_5rp7"> <div class="_1p1t" style=""> <div class="_1p1v" id="placeholder-6t6up" style="white-space: pre-wrap;">    ? </div> </div> <div class="_5rpb"> <div aria-autocomplete="list" aria-controls="js_1" aria-describedby="placeholder-6t6up" aria-multiline="true" class="notranslate _5rpu" contenteditable="true" data-testid="status-attachment-mentions-input" role="textbox" spellcheck="true" style="outline: none; user-select: text; white-space: pre-wrap; overflow-wrap: break-word;"> <div data-contents="true"> <div class="" data-block="true" data-editor="6t6up" data-offset-key="6b02n-0-0"> <div data-offset-key="6b02n-0-0" class="_1mf _1mj"> <span data-offset-key="6b02n-0-0"> <br data-text="true"> </span> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> 


如您所见,使用了十个元素的嵌套,但是此嵌套没有任何作用。 第一个片段仅显示文本“ Write a comment ...”和图标,第二个片段显示“ What's new?”。 由于非理性使用DOM的结果,React模板引擎的整个性能被简单地否定了,该站点成为我所知道的最慢的站点之一。

渐进式Web应用


清单文件


PWA允许您将Web应用程序用作本机应用程序。 在站点上启用支持后,浏览器菜单中会出现用于在设备(Windows,Android,iOS)上安装站点的按钮,此后它开始表现为本地站点并脱机工作,并且所有这些绕过应用程序存储。

在站点上启用PWA支持实际上非常简单。 在html页面中包含清单文件的链接就足够了。 清单文件可以在pwabuilder.com上生成。

在连接过程中,我将不再详细介绍,因为 本部分值得单独撰写一篇大文章,并且在中心上已经有不错的文章了。

服务人员


PWA配置不会在连接清单文件时结束,还需要连接ServiceWorker,它将负责脱机工作。

可以在pwabuilder.com上找到示例代码:

 // This is the service worker with the Cache-first network const CACHE = "pwabuilder-precache"; const precacheFiles = [ /* Add an array of files to precache for your app */ ]; self.addEventListener("install", function (event) { console.log("[PWA Builder] Install Event processing"); console.log("[PWA Builder] Skip waiting on install"); self.skipWaiting(); event.waitUntil( caches.open(CACHE).then(function (cache) { console.log("[PWA Builder] Caching pages during install"); return cache.addAll(precacheFiles); }) ); }); // Allow sw to control of current page self.addEventListener("activate", function (event) { console.log("[PWA Builder] Claiming clients for current page"); event.waitUntil(self.clients.claim()); }); // If any fetch fails, it will look for the request in the cache and serve it from there first self.addEventListener("fetch", function (event) { if (event.request.method !== "GET") return; event.respondWith( fromCache(event.request).then( function (response) { // The response was found in the cache so we responde with it and update the entry // This is where we call the server to get the newest version of the // file to use the next time we show view event.waitUntil( fetch(event.request).then(function (response) { return updateCache(event.request, response); }) ); return response; }, function () { // The response was not found in the cache so we look for it on the server return fetch(event.request) .then(function (response) { // If request was success, add or update it in the cache event.waitUntil(updateCache(event.request, response.clone())); return response; }) .catch(function (error) { console.log("[PWA Builder] Network request failed and no cache." + error); }); } ) ); }); function fromCache(request) { // Check to see if you have it in the cache // Return response // If not in the cache, then return return caches.open(CACHE).then(function (cache) { return cache.match(request).then(function (matching) { if (!matching || matching.status === 404) { return Promise.reject("no-match"); } return matching; }); }); } function updateCache(request, response) { return caches.open(CACHE).then(function (cache) { return cache.put(request, response); }); } 

从代码中可以看到,服务器的所有响应都已缓存,但是该缓存未在线使用。 当与服务器的连接断开时,它们开始被使用。 因此,浏览站点的用户可能不会注意到Internet的短期消失,即使Internet已经消失了很长时间,用户仍然有机会在已经缓存的数据中移动。

上面的脚本很简单,但是仅适用于登录页面,它只是为更严肃的Web应用程序编写工作程序的起点。 但是,在本文的第二部分中将对此进行更多介绍。 而且,该技术很方便,因为它不会破坏旧版浏览器的工作,即 在IE11级别的浏览器中,您无需重写逻辑,离线模式根本无法使用。

辅助功能


具有特殊需求的人的属性正确性


健康状况良好的人很少,但不幸的是,包括视力在内的许多健康状况不佳的人。 为了使这些人更轻松地使用您的网络应用程序,只需遵循相当简单的规则即可:

  • 使用足够的对比色。 根据卫生部的统计,有20%的人有视力障碍。 对比度低的部位只会使他们的生活变得复杂,而健康的人会加剧疲劳。
  • 安排tabindex。 允许您在不使用鼠标和触摸设备的情况下使用该站点。 使用键盘正确地安排转换,可以大大简化表格的填写过程。
  • 链接上的Aria标签属性。 允许屏幕阅读器阅读属性内的文本。
  • 图片中的alt属性。 与上一个相似。 此外,如果无法下载图片,它将显示文本。
  • 文档的语言。 用带有语言lang =“ language code”的属性标记html标记。 这将有助于辅助工具的正确设置。

如您所见,这些要求实际上很少而且很容易满足。 但是由于某些原因,即使是针对有特殊需要的人的专业站点,大多数开发人员也忽略了这些规则。

最佳实务


将前端应用程序与服务器应用程序分开


首先,如果您仍在服务器上呈现html,请停止这样做。将渲染过程转移到客户端两个数量级可以减少服务器的负载,并因此减少支持服务器应用程序的成本。客户得到的应用程序会对他们的行为做出即时反应。

其次,将您的客户端SPA应用程序与后端应用程序分开。您不能将服务器应用程序和Windows应用程序,Android应用程序和iOS应用程序合并在一起。因此,Web应用程序长期以来一直是一个独立的应用程序,即使没有服务器也可以脱机运行。我看到的最普遍的错误是当Spring或Asp.Net之类的后端框架参与静态信息的分发时,包括组装的SPA应用程序。是时候停止这样做了,并在单独的微服务中取出静态数据和SPA,并将其隐藏在专门的Web服务器后面以分发静态数据,例如nginx。

图片

结果,每种技术都会做到应做的事,并且会做得最好。Nginx将以正确的头和最大速度分发静态数据,服务器应用程序将为客户端准备数据,客户端设备将所有数据收集在一起并显示给用户。

配置代理服务器,HTTP / 2,gzip,缓存


您的后端应用程序不应直接与客户端通信,最好将其隐藏在专门的门(例如Nginx代理服务器)后面。而且,您已经可以在其上配置客户端设备和服务器之间进行舒适通信所需的一切。

  • SSL. SSL , , , Nginx. Nginx Asp.Net Core , .
  • GZIP . .
  • Cache . Get, Head , .
  • .

同样,由于这一部分值得单独撰写一篇大文章,因此,我没有详细描述整个配置过程,但是我建议您使用一个站点来生成nginx nginxconfig.io config

搜索引擎优化


在html中创建元标记并使用语义标记


每个人都已经知道这一点,通常他们会使用它。因此,要修复此问题,只需查看Lighthouse的注释列表并进行更正。

结束


乍一看,这里似乎写了很多信息,很难观察,但实际上并非如此。所有这些信息都反映了前端开发的当前状态,遵守所有这些规则几乎不需要时间。

本文不介绍如何优化管理区域,表单和其他企业,但这将是第二部分。

Source: https://habr.com/ru/post/zh-CN440950/


All Articles