这篇文章是我阅读 Web Performance 101 以后的进行的粗糙的翻译做为笔记,英语还行的童鞋能够直接看原文。css
这篇文章主要介绍了现代 web 加载性能(注意不涉及代码算法等),学习为何加载性能很重要、有哪些优化的方法以及有哪些工具能够帮助咱们对网站进行优化。html
首先,加载缓慢的网站让人很不舒服!react
最明显的例子就是当一个移动网站加载太慢的时候,用户体验如同观看一部恐怖电影。webpack
图片来源: Luke Wroblewskinginx
第二,网站性能直接影响你的产品质量。git
—— 2016 年,AliExpress 将他们网站的性能提高了三分之一,而后他们收到的订单增长了 10.5%!github
——2006 年,谷歌曾经尝试将他们的搜索放慢 0.5 秒而后发现用户的搜索(请求)次数减小了 25%。web
——2008 年,Aberdeen 集团发现将网站放慢 1s,会致使用户满意度降低 16%。算法
此外还有一系列如上的数据,无论是新的仍是旧的:(wpostats.com · pwastats.com)。chrome
这就是为何网站性能很重要。
如今,咱们须要弄懂当咱们说一个网站很快意味着什么。
在什么状况下能够说一个网站很快?
——它必须加载很快(文件下载、界面渲染),
——而后,在加载以后,它必须很快的执行(好比动画不跳帧、滚动很丝滑)。
网站加载很快意味着:
——服务器对于客户端请求响应很快,
——网站自身加载渲染很快。
在这篇文章中,咱们将会讨论这个因素:如何让网站快速加载以及渲染。
先从 JavaScript 开始吧。一般状况下,JavaScript 是网站加载缓慢的根源。
第一种 JavaScript 优化方式是压缩,若是你已经知道了的话,直接跳过吧。
什么是压缩?在通常状况下,人们写 JavaScript 代码会使用一种方便的格式,包含缩进、富有含义的长变量名、写注释等等。由于这种方式,代码具备很高的可读性,可是多余的空格和注释会使得 JavaScript 文件变得很大。
为了解决这个问题,人们想到了代码压缩。在压缩的过程当中,代码会被去掉全部没必要要的字母,替换成短的变量名,去掉注释等等。在最后,代码文件变得比以前更小,可是代码的功能并不受影响。
代码压缩能够将代码文件减少大约 30% ~ 40%。
主流的代码打包工具都支持代码压缩:
—— mode: production
in webpack,
—— babel-preset-minify
in Babel,
—— gulp-uglify
in Gulp
async
和 defer
接下来,你写了一个 JavaScript 脚本,而后进行了压缩,如今想要在页面中加载它。该如何作呢?
最简单的方式就是写一个 script 标签,而后 src 属性指向你所写脚本的路径,而后它就能够照常开始工做啦!
可是,你知道这种方法有什么问题吗?
问题就在于 JavaScript 会阻塞渲染。
这是什么意思?
当你的浏览器加载页面的时候,它会转换 HTML 文档成为标签,而后构建 DOM 树。随后它会使用 DOM 树渲染页面。
问题在于,JavaScript 代码能够改变 DOM 树的构建方式。
例如,JavaScript 能够经过 document.write 写一个 HTML 注释的起始标签到文档中,而后整个 DOM 树都会被毁掉。
这就是为何浏览器在碰到 script 标签的时候会中止渲染页面,这样作能够防止 document 作多余的工做。
从浏览器的角度来看:
——浏览器遍历文档,而后会解析它
——在某些时刻,浏览器遇到了 script 标签,而后中止了 HTML 转换,它开始下载并执行那些 script 代码
——一旦代码执行完毕,浏览器继续遍历 HTML 文档,而后渲染页面
实际上,这意味着当你添加一个 script 标签到页面中时,它后面的内容在它下载并执行完毕以前都是不可见的。若是你添加一个 script 到 head 标签中,全部的内容都会变得不可见——直到 script 被下载执行完毕。
那咱们该怎么办呢?应该使用 async
和 defer
属性。
这些属性让浏览器直到 script 脚本能够在后台下载,没必要阻塞文档渲染,下面是详细的介绍:
——async
让浏览器异步下载(在后台)script 代码,而后继续解析渲染 HTML。(若是在页面渲染完毕以前,script 代码已经下载好了,那么就先中止渲染,先执行 script 代码。因为下载所消耗的时间一般大于 HTML 转化,因此这种状况实际上很少见)。
——defer
会告诉浏览器在后台异步下载 script 代码,直到 HTML 转化渲染完毕才开始执行这些 script 代码。
这里有两大不一样点:
——async
script 标签会在下载以后尽快地执行,它们的执行顺序没有规律。这就意味着有 async 属性的 React bundle script 和 app bundle script 在同一时刻开始下载,因为 app bundle 更小因此会先下载完毕,致使 app 的 bundle script 先执行。而后网站就崩掉了~
——defer
不像 async
,会在加载以及文档渲染完毕以后按照 script 标签的顺序开始执行,所以,defer
是更适合的优化方案。
继续。
不少时候,应用都是打包到一个 bundle 里面,而后每次请求都发送到客户端。可是这样作的问题在于有些页面咱们见到的场景不多,可是它们的代码一样被打包到了咱们的 bundle 中,这样每次页面加载的代码多于实际须要,形成了性能浪费。
这个问题一般使用代码切割进行解决,把大的 bundle 切割成一个个小的。
经过代码切割,咱们把不一样功能的代码打包到了不一样的文件,只在必要的时候加载必要的代码。因为使用这样的作法,用户不再会下载他们不须要用到的代码了。
那么咱们怎么切割代码呢?
首先,你须要一个代码打包工具,好比 Webpack、Parcel 或者 Rollup。全部的这几个工具都支持一个特殊函数 import()
。
在浏览器中,import()
接受传递给它的 JS 文件并异步下载该文件。这能够用于加载应用程序一开始不须要可是接下来可能会用到的库。
可是在打包工具中,import()
的功能又有所不一样。若是你在代码中传递了一个文件给 import()
而且在以后进行打包,打包工具会把这个文件以及其全部的依赖打包到一个单独的文件中。app 运行到 import 函数时会单独下载这个文件。
所以,在上方的例子中,webpack 会把 ChangeAvatarModal.js
及其依赖打包到单独文件中。在代码执行到 import 时,这个单独文件会被下载。
这就是实际的代码切割。
第二,在 React 和 Vuejs 中,会有基于 import()
的工具可以让你的代码切割工做更加轻松。
例如,react-loadable
是一个组件,用于等待其余组件加载,在其余组件加载时,它会进行占位。React 16.6 添加了一个类似的内置功能组件,叫作 Suspense
。此外 Vuejs 也已经支持异步组件一段时间了。
若是优化得很好的话,咱们能够减小不少没必要要的数据的下载,代码切割可以成为最重要的流量优化工具。
若是你的 app 只能作一种优化的话,那就是代码切割。
另一个重要的优化点在于包的依赖。
——例如,momentjs 这个库,用于进行时间操做,它包含了大约 160 kb 大小的不一样语言的文件包。
——React 甚至把 propTypes
包含在生产环境的包中,尽管这不是必要的。
——Lodash,你颇有可能引入了整个完整的包,尽管你可能只须要其中的一两个方法。
上面这些就是把没必要要的代码引入打包的状况。
为了帮助开发者移除多余的代码,做者和谷歌一块儿维护了一个 repo 收集关于如何在 webpack 中优化你的依赖,使用这些建议可让你的 app 更快更轻巧!
→ GoogleChromeLabs/webpack-libs-optimizations
以上都是 JavaScript 的优化方式,总结起来就是:
——压缩你的 js 代码
——使用 async
和 defer
加载 script
——切割你的代码,让应用只加载必须的代码
——移除依赖中实际未使用的代码
接下来是如何优化 css 代码。
首先,压缩 CSS,就像 JavaScript 代码同样。删除没必要要的空格和字母来使你的代码更小。
这些工具能够帮助你压缩 CSS 代码:
—— webpack’s postcss-loader
with cssnano
—— PostCSS’s cssnano
—— Gulp’s gulp-clean-css
第2、styles 阻塞渲染,就像以前 script 那样。
由于没有样式的网站看起来很奇怪。
若是浏览器在样式加载以前渲染页面,那么用户就会看到上面那样的状况。
而后页面就会闪烁,而后就会看到上面截图这样子,很难说是一种好的用户体验。
这就是为何样式加载的时候页面会是空白的。
如今有一种比较机智的优化方式。浏览器在加载样式以前保持空白页是颇有理由的,咱们没必要从这一点下手。可是咱们仍然能够想办法让页面渲染更快——让页面只加载渲染初始界面所必要的样式,剩余的样式在以后加载,这些渲染初始界面所必要的样式称为“Critical CSS”。
让咱们看看是怎么作的。
一、把页面样式分为 critical 的和 non-critical 的。
二、把 critical CSS 嵌入到 HTML,真可以让它们尽快地被加载。
如今,当你加载页面的时候,页面可以很快地被渲染,可是你仍然得加载那些不重要的 CSS。
有多种方式能够加载剩余的 CSS,下面的方式是我所倾向的:
三、使用<link rel="preload">
获取非必要的 CSS。
四、一旦文件被加载到缓存之后,把 rel
属性从 preload
切换为 stylesheet
。这可让浏览器从缓存中获取 CSS 并应用到页面中。
那咱们怎么知道哪些 CSS 是必须的,哪些 CSS 是没必要须的呢?一般状况下,规则以下:
移除 CSS 样式知道页面看起来变得滑稽,那么剩下的 CSS 就是必要的。
复制代码
例如,页面的布局样式或者文章的文本样式是必须的,由于缺乏它们会使得页面看起来很烂。而 JavaScript 弹出窗或者页脚的样式是非必须的,由于用户不会在一开始就看到它们,缺乏那些样式,页面看起来仍然十分完美。
听起来可能比较复杂,可是有不少自动化工具能够帮助咱们完成这项工做。
—— styled-components
. It’s a CSS-in-JS library that extracts and returns critical styles during server-side rendering. It works only if you already write styles using styled-components
, but if you do, it works really well.
——critical
. It’s a utility that takes an HTML page, renders it in a headless browser and extracts styles for above-the-fold content. Because critical
runs only over a single page, it might not work well for complex single-page apps.
—— penthouse
. It’s similar to critical
but works with URLs instead of HTML pages.
这种作法通常能够节约 200 ~ 500 ms 左右的首屏渲染时间。
了解更多 Critical CSS 的知识,阅读 the amazing Smashing Magazine’s guide.
这就是 CSS 优化的主要策略,总结起来就是:
——压缩 CSS 代码
——提取必要的 CSS,让页面首先加载它们
如今让咱们看看 HTTP 的优化。
让 HTTP 传输较少数据的方式仍然是压缩代码,本节主要说压缩 HTML 代码,JS、CSS 的代码压缩在以前已经讲过了。
压缩代码的第二种方式是 GZIP 压缩。
Gzip 是一种算法,它可使用复杂的归档算法压缩你发送到客户端的数据。在压缩以后,你的文件看起来像是没法打开的二进制文件,可是它们的体积会减少 60% 到 80%。浏览器接受这些文件以后会自动进行解压缩。
基本上,使用 Gzip 已是生产环境的标准,所以若是你使用一些流行的服务器软件好比 Apache 或者 Nginx,你就能够修改配置文件开启 Gzip 压缩。
Apache instructions · Nginx instructions
注意:
使用这些说明启用 Gzip 将会致使服务器动态压缩资源,这会增长服务器响应时间。在大多数状况下你不须要关心这一点,但若是你但愿提升响应时间,能够在构建的时候进行资源预压缩。
注意:
不要对文本文件以外的文件进行 Gzip 压缩!
图像、字体、视频或者其余二进制文件一般已经被压缩过了,所以对它们进行 Gzip 压缩只会延长响应时间。SVG 图片是惟一的例外,由于它也是文本。
Gzip 有一个替代品,一种叫 Brotli 的算法。
__Brotli 的优势:__一样的 CPU 载荷下,它压缩效率比 Gzip 高 20% 到 30%。就是说能够减小 30% 下载量!
__Brotli 的缺点:__它很年轻,浏览器以及服务器的支持度还不够,因此你不能用它来替代 Gzip。可是能够针对不一样的浏览器使用 Gzip 或者 Brotli。
Apache 从 2.4.26 开始支持 Brotli,Nginx 有外部模块支持 Brotli。
Apache instructions · Nginx module
注意:
不要把 Brotli 的压缩等级设置到最大,那样会让它压缩得比 Gzip 慢。设置为 4 是最好的,可让 Brotli 压缩得比 Gzip 更小更快。
如今,咱们聊聊 CDN。
什么是 CDN?假设你在美国假设了一个应用。若是你的用户来自华沙,他们的请求不得不从波兰发出,一路颠簸来到美国,而后又得回到波兰。这个请求过程将会消耗不少时间:
——网络请求要跨越很长的一段距离
——网络请求要通过不少路由或者相似设备(每一个设备都有一段处理时间)
若是用户想要获取 app 数据,并且只有美国的服务器知道如何处理数据,那上面这些过程好像都是必要的。但对于静态内容而言,上面的请求过程彻底没有必要,由于它们请求的只是一些静态内容,彻底能够部署到任何服务器上。
CDN 服务就是用来解决这个问题的。CDN 表明“Content Delivery Network(静态内容分发)”,CDN 服务在全世界提供许多服务器来 “serve” 静态文件。若是要使用的话,只须要在一个 CDN 服务注册,而后上传你的静态文件,而后更新 app 中引用的文件的地址,而后每一个用户都会引用离他们最近的服务器上的静态文件了。
根据咱们的经验,CDN 基本上能把每一个请求的延迟从上百毫秒减小到 5-10 毫秒。考虑到当页面打开时有不少资源要加载,CDN 的优化效果是很惊人的。
你知道吗?谷歌在你开始点击搜索以前已经在加载搜索结果的第一项了。这是由于三分之一的用户会首先点击第一个搜索结果,预加载内容可让用户更快的看到目标页面。
若是你肯定你的页面或者资源会在不久以后被用到,浏览器容许你进行预加载。
有五种方法能够实现预加载,它们每一种的适用场景都不一样:
——<link rel="dns-prefetch">
提示浏览器对一个 IP 地址提早进行 DNS 请求。这对于 CDN 请求颇有用,此外一些你知道域名可是不知道具体地址的资源的预加载也可使用。
——<link rel="preconnect">
提示浏览器提早链接到某台服务器。在 dns-prefetch
适用的场景一样适用,但它能够创建完整的链接并节约不少时间。缺点是打开新的链接很消耗资源,所以不要过分使用。
——<link rel="prefetch">
会在后台对资源进行低优先级预加载而后缓存,这个比较有用,好比在进入 SPA 的下一个页面以前加载 bundle。
——<link rel="preload">
会在后台对资源进行高优先级的预加载。这对于加载短期内即将用到的资源而言比较有用。
——<link rel="prerender">
会在后台预加载特定页面,而后在不可见的 tab 中渲染页面。当用户进入这个页面时,页面能够立马呈如今用户面前。这是谷歌用于预加载第一条搜索结果的方案。
注意:
不要过分使用预加载,虽然预加载可以提高用户体验、加速应用,可是会致使没必要要的流量消耗;尤为是在移动端,用户会消耗过多的不要的流量,这一样会下降用户体验。
阅读更多:Preload, prefetch and priorities in Chrome · Prefetching, preloading, prebrowsing
HTTP 优化方式:
—— 使用 CDN 节省静态资源的下载时间
—— 预加载一会将要用到的资源
继续,说说图片优化。
图片消耗了大量的流量,但庆幸的是图片加载不阻塞渲染。但图片优化仍然是必要的,咱们须要让图片加载更快、消耗更少的流量。
第一,也是最重要的一点,选择合适的图片格式。
最多见的图片格式是:svg
、jpg
、png
、webp
和 gif
。
svg
最适合矢量图,好比 icon 和 logo。
jpg
最适合照片,由于它压缩图片时质量损耗最小,以致于肉眼难以发现。
png
适合没有任何质量损失的光栅图形 - 例如光栅图标或像素艺术。
webp
最适合照片或者光栅图片,由于它支持有损或者无损压缩。它的压缩比也比 jpg
和 png
更优秀。
不幸的是 webp
只能在 chrome 使用,可是你仍然可使用 jpg
和 png
来实现一个 fallback。
上面就是具体实现。
这样写的话,支持 webp
的浏览器会加载 webp
格式的图片,不支持 webp
格式的浏览器会加载 jpg
最为备用方案。
最后是 gif
。
不要使用 gif
,它很是笨重。超过 1M 的 gif 最好使用视频文件代替,能够更好的压缩内容。
See also: Replace Animated GIFs with Video at WebFundamentals
除了使用合适的图片格式之外,图片压缩也能够是优化方案。下面是几种图片压缩方式:
首先是 svg
:
——压缩。由于 svg 图片是文本,因此能够移除空格和注释
——简化 path,若是 svg 是自动工具生成的,其内部的 path 可能会很复杂,这种状况下,移除那些不影响 svg 样式的 path
——简化 svg 文件结构,若是 svg 是自动工具生成的,一般会包含不少多余的 meta 元素,移除它们能够减少文件体积
这些优化方式均可以直接使用 svgo
实现,它还有 UI 界面:a great UI for svgo
第二个:jpg
。
——减少图片维度。根据个人经验,这是一个开发人员使用 jpg 常犯的错误
这种状况常发生于咱们把一张大尺寸的图片塞进一个小尺寸的容器中时。好比咱们把一张 2560 * 1440 px 的图片放到一个 533 * 300 px 的容器中。
当这种状况发生时,浏览器会加载过大的文件,而后还要花时间缩小图片,知道可以塞进去那个小小的容器,这些都是无用功。
要解决这个问题,能够直接在你的 PS 或者其余工具中对图片进行编辑;或者你也可使用 webpack loader(好比 responsive-loader
)。若是要使用大尺寸图片适配高分屏,能够经过 <picture>
或者 <img srcset>
代替。
还能够对 jpg 进行图片降维压缩,图片质量压缩到原来的 70 ~ 80,图片压缩致使的质量损失会很难发现。
上面能够看出压缩后图片质量损失不大。
可是咱们能够看到图片的大小减少了不少。这就是为何推荐对 jpg 图片进行 70-80 水平的压缩,由于图片信息损失很小,可是体积压缩很大。
除了以上方式外,咱们还可使用渐进式图片。
上方是非渐进式图片加载的方法。
这是一张渐进式的图片的加载方式。
能够经过 PS 或者 Gimp 制做渐进式图片。也可使用 webpack-loader(好比 image-webpack-loader
)或者其余工具。
注意:
渐进式图片可能比常规图片更大,并且解码更慢。
第三,png
。
——使用隔行扫描 PNG。 隔行扫描 PNG 的工做方式与渐进式 JPEG 相同:它从低质量开始渲染,但在加载时进行改进。 但它不是适合全部场景。例如,逐步加载 PNG 图标看起来很奇怪 - 但它可能适用于其余某些图像。
——使用索引颜色。 经过使用索引颜色,PNG 图片将其全部颜色放入调色板中并使用它来引用每种颜色。 这使得每一个像素所需的字节数更小,而且可能有助于下降总体图像权重。 因为调色板大小有限(最多256种颜色),所以此解决方案不适用于具备大量颜色的图像。
这两种方式均可以经过图片编辑器或者 image-webpack-loader
或者其余工具实现。
以上的全部优化均可以使用自动化工具完成,以前都已经提到过,可是这里再总结一下:
— webpack has image-webpack-loader
which runs on every build and does pretty much every optimization from above. Its default settings are OK
— For you need to optimize an image once and forever, there’re apps like ImageOptim and sites like TinyPNG.
— If you can’t plug a webpack loader into your build process, there’re also a number of CDNs and services that host and optimize images for you (e.g., Akamai, Cloudinary, or imgix).
图片优化总结:
——经过图片降维、质量压缩或者使用渐进式图片优化图片加载时间
最后一个优化方式就是字体了。
有时候页面加载好了,全部的样式、布局都已经可见了,可是字体尚未出现或者显示异常,这就是字体问题所致使的,自定义字体还没有下载完毕,这个时候浏览器会等待几秒,若是仍然未下载,浏览器才会使用备用字体做为替代。
这种行为在某种程度上避免了字体的闪烁,可是在缓慢的网络条件下,这种行为使得页面加载变得缓慢。
咱们须要了解一下如何优化这种状况。
首先,要记得设置 fallback 字体。
fallback 字体会在自定义字体没法下载或者下载时间过长时被使用。它在 CSS 的 font
或者 font-family
的第一个指定字体后面指定,好比上方的Arial, sans-serif
。
fallback 字体应当是比较流行的内置字体(好比 Georgia);也能够是比较通用的字体系列(如 serif 或者 sans-serif);一般状况下,即便你指定了内置的字体做为 fallback,可是你仍然须要添加一个通用的字体系列——由于内置字体可能也会在某些设备上缺失。
没有 fallback 字体的话,一旦自定义字体缺失,浏览器会使用默认的 serif font 进行渲染。这样可能会致使页面比较难看。
使用 fallback 字体,至少你有机会定义一个和你的自定义字体相近的字体做为备用方案。
font-display
第二点优化,使用 CSS 的 font-display
属性指定自定义字体。
font-display
属性会调整自定义字体的应用方式。默认状况下,它会设置为 auto
,在大部分主流浏览器中,意味着浏览器会等待自定义字体加载 3s。这意味着若是网络太慢的话,用户须要等待 3s 后字体才会显示。
这很很差,为了优化这一点,指定 font-display
。
Note: in Microsoft Edge, the font-display: auto
behavior is different. With it, if the custom font is not cached, Edge immediately renders the text in the fallback font and substitutes the custom font later when it’s loaded. This is not a bug because font-display: auto
lets browsers define the loading strategy.
有两个 font-display
的值我认为比较适用于大部分状况。
第一个是 font-display: fallback
。这样指定的话,浏览器会使用最先可以得到的字体当即渲染,无论是已经缓存的自定义字体仍是 fallback 字体。若是自定义字体没有被缓存的话,浏览器会下载它。若是下载得足够快(一般是 3s 内),浏览器会使用自定义字体替换 fallback 字体。
这种状况下,用户可能会在读 fallback 字体的文本时,浏览器忽然进行字体替换,这对于用户体验而言并非不好,总比不显示任何字体要强。
第二个适用的 font-display
值是 optional
。使用这个值,浏览器一样会当即使用可得到的字体进行文本渲染:无论是已缓存的自定义字体仍是 fallback 字体。可是当自定义字体未缓存时,在下载好自定义字体后,浏览器不会当即替换已有的 fallback 字体,直到页面下一次刷新。
这种行为意味着用户始终只会看到一种字体,不会出现字体替换的状况。
那咱们该如何选择这两个值呢?
我相信这是一个品味问题。 我我的更喜欢用自定义字体展现文本,所以我选择 font-display:fallback
值。 若是你以为访问者第一次访问时看到 fallback 字体的页面没有什么关系,那么 font-display:optional
对您来讲很是有用。
Note: this font-display
trick is not applicable to icon fonts. In icon fonts, each icon is usually encoded by a rarely used Unicode character. Using font-display
to render icons with a fallback font will make random characters appear in their place.
字体优化方案的总结:
—— 指定合适的 fallback(备用)字体 (还有通用的字体系列)
—— 使用 font-display
来配置自定义字体的应用方式。
最后是一些有助于页面性能优化的工具。
第一个是 Google PageSpeed Insights。
第二个是 Lighthouse。
第三个是 WebPageTest。
最后一个是 webpack 插件:webpack-bundle-analyzer。
具体的介绍就没写了,点进去直接用就知道啦。
感谢阅读!
原做者推特:@iamakulov。
Thanks to Arun, Anton Korzunov, Matthew Holloway, Bradley Few, Brian Rosamilia,Rafael Keramidas, Viktor Karpov, and Artem Miroshnyk (in no particular order) for providing feedback on drafts.
译者水平有限,不免存在纰漏,敬请各位斧正。