- 原文地址:10x Performance Increases: Optimizing a Static Site
- 原文做者:JonLuca De Caro
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:Starrier
- 校对者:dandyxu、Hopsken
几个月前,我在国外旅行,想给朋友看我我的(静态)网站上的一个连接。我试着浏览个人网站,但花费的时间比我预期的要长。网站绝对没有任何动态内容--只有动画和一些响应式设计,并且内容始终保持不变。 我对结果感到震惊,DOMContentLoaded 要 4 s,整个页面加载要 6.8 s。有 20 项关于静态网站的请求(总数据的 1MB)被转移。我习惯了从洛杉矶到我在旧金山的服务器之间用 1 GB/s 的低延迟互联网链接,这使得这个怪物看起来像闪电同样快。在乎大利,8 MB/s 的速度让状况变得彻底不一样。javascript
这是我第一次尝试优化。到目前为止,每次我想添加一个库或者资源时,我都只是将它引入并使用 src="" 指向它。从缓存到内联,再到延迟加载,对任何形式的性能我都没有给予关注。css
我开始寻找有类似经历的人。不幸的是,许多有关静态优化的文献很快就过期--那些来自 2010 或者 2011 年的建议,要么是在讨论库,要么作一些根本再也不试用的假设,要么就是不断地重复某些相同的准则。html
不过我确实找到了两个很好的信息源 -- 高性能浏览器网络和 Dan Luu 相似的静态网站优化经历。尽管在剥离格式和内容方面还不如 Dan,可是我确实成功地让个人页面加载速度提升了大约 10 倍。DOMContentLoaded 大约须要五分之一秒,而整个页面加载只有 388 ms(实际上有点不许确,下文将解释延迟加载的缘由)。前端
过程的第一步是对网站进行分析梳理,我想弄清楚哪些地方花费了最长的时间,以及如何最好地并行化一切。我运行了各类工具来分析个人网站,并在世界各地测试它,包括:java
其中一些提供了改进建议,但当静态站点有 50 个请求时,您只能作这么多 -- 从 90 年代遗留下来的间隔 gif 到再也不使用的资源(我加载了 6 种字体但只使用了 1 种字体)。node
个人网站时间线 -- 我在 Web Archive(译者注:一家提供网站历史快照的服务商)上测试了这个却没有截取原始图片,但是它看起来和我几个月前看到的仍是很类似。android
我想改进我所能控制的一切 -- 从 JavaScript 的内容和速度到实际的 Web 服务器(Ngnix)和 DNS 设置。webpack
我注意到的第一件事是,不论是对于 CSS 仍是 JS,我都向各类网站发起十几个请求(没有任何形式的 HTTP keepalive),其中还有一些是 https 请求。这增长了对各类 CDN 或 服务器的屡次往返,一些 JS 文件正在请求其余文件,这致使了上面所示的阻塞级联。ios
我使用 webpack 将全部资源合并到一个 js 文件中。每当我对内容进行更改时,它都会自动简化并将个人全部依赖项转换为单文件。nginx
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const ZopfliPlugin = require("zopfli-webpack-plugin");
module.exports = {
entry: './js/app.js',
mode: 'production',
output: {
path: __dirname + '/dist',
filename: 'bundle.js'
},
module: {
rules: [{
test: /\.css$/,
loaders: ['style-loader', 'css-loader']
}, {
test: /(fonts|images)/,
loaders: ['url-loader']
}]
},
plugins: [new UglifyJsPlugin({
test: /\.js($|\?)/i
}), new ZopfliPlugin({
asset: "[path].gz[query]",
algorithm: "zopfli",
test: /\.(js|html)$/,
threshold: 10240,
minRatio: 0.8
})]
};
复制代码
我尝试了各类不一样的配置。如今,这个 bundle.js 文件在我网站的 <head>
中,而且处于阻塞状态。它的最终大小是 829 kb,包括每一个非图像资源(字体、css、全部的库、依赖项以及 js)。绝大多数字体使用的是 font-awesome,它们占 829 kb 中的 724。
我浏览了 Font Awesome 库,除了我要使用的 fa-github、fa-envelope 和 fa-code 三个图标外,其余的全部图标都已经删除。我使用叫作 fontello 的服务来提取我须要的图标。新的大小只有 94 kb。
按照目前网站的构建方式,若是咱们只有样式表,它看起来是不正确的,因此我接受了单个 bundle.js 的阻塞特性。加载时间为 118 ms,比以前提升了一个数量级。
这也带来了一些额外的好处--我再也不指向第三方资源或 CDN,所以用户不须要:(1)执行对该资源的 DNS 查询,(2)执行 https 握手,(3)等待该资源被完整地下载。
虽然 CDN 和分布式缓存对于大规模的分布式网站多是有意义的,但对于个人小型静态网站来讲却没有意义。是否须要优化这额外的 100 ms 左右时间是值得权衡的。
我加载了一个 8 MB 大小的头像,而后以 10% 的宽高比显示它。这不只仅是缺乏优化,这几乎是忽略了用户对带宽使用。
我使用 webspeedtest.cloudinary.com/ 来压缩全部的图像 -- 它还建议我切换到 webp,但我但愿尽量多的与其余浏览器进行兼容,因此我坚持使用 jpg。尽管彻底有可能创建一个只将 webp 交付给支持它的浏览器系统,但我但愿尽量地保持简单,添加抽象层的好处彷佛并不明显。
我作的第一件事是过分到 https -- 一开始,我在 80 端口运行 Ngnix,只服务于来自 /var/www/html 的文件。
server{
listen 80;
server_name jonlu.ca www.jonlu.ca;
root /var/www/html;
index index.html index.htm;
location ~ /.git/ {
deny all;
}
location ~ / {
allow all;
}
}
复制代码
首先设置 https 并将全部 http 请求重定向到 https。我从 Let’s Encrypt (一个刚开始签署通配符证书的伟大组织!wildcard certificates )那里得到了本身的 TLS 证书。
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name jonlu.ca www.jonlu.ca;
root /var/www/html;
index index.html index.htm;
location ~ /.git {
deny all;
}
location / {
allow all;
}
ssl_certificate /etc/letsencrypt/live/jonlu.ca/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/jonlu.ca/privkey.pem; # managed by Certbot
}
复制代码
只要添加 http2 的指令,Ngnix 就可以利用 HTTP 最新特性的全部优势。注意,若是要利用 HTTP2(之前的 SPDY),您必须使用 HTTPS,在这里阅读更多内容。
您还能够利用 HTTP2 push 指令,使用 http2 push images/Headshot.jpg;
注意:启用 gzip 和 TLS 可能会使您面临 BREACH 风险。因为这是一个静态网站,而 BREACH 实际的风险很低,因此保持压缩状态让我感受舒服。
仅经过使用 Ngnix 还能完成什么呢?首先是缓存和压缩指令。
我以前一直都是发送未经压缩的原始 HTML。只须要一个单独的 gzip;是的,我就能够从 16000 字节减小到 8000 字节,减小 50%。
实际上,咱们可以进一步改进这个数字,若是将 Ngnix 的 gzip 静态设置为开启,它会事先查找全部请求文件的预压缩版本。这与咱们上面的 webpack 配置结合在一块儿 -- 咱们能够在构建时使用 ZopflicPlugin 预压缩全部文件!这节省了计算资源,并容许咱们在不牺牲速度的状况下最大限度地实现压缩。
此外,个人站点变化不多,因此我但愿尽量长时间地缓存资源。这样,在之后的访问中,用户就不须要从新下载全部资源(特别是 bundle.js)。
我更新的服务器配置以下所示。请注意,我不会涉及我所作的全部更改,例如 TCP 设置更改、gzip 指令和文件缓存。若是您想了解更多,请阅读这篇关于 Ngnix 调优的文章。
worker_processes auto;
pid /run/nginx.pid;
worker_rlimit_nofile 30000;
events {
worker_connections 65535;
multi_accept on;
use epoll;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Turn of server tokens specifying nginx version
server_tokens off;
open_file_cache max=200000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
include /etc/nginx/mime.types;
default_type application/octet-stream;
add_header Referrer-Policy "no-referrer";
##
# SSL Settings
##
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_dhparam /location/to/dhparam.pem;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload';
ssl_certificate /location/to/fullchain.pem;
ssl_certificate_key /location/to/privkey.pem;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon;
gzip_min_length 256;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
复制代码
以及相应的服务器块
server {
listen 443 ssl http2;
server_name jonlu.ca www.jonlu.ca;
root /var/www/html;
index index.html index.htm;
location ~ /.git/ {
deny all;
}
location ~* /(images|js|css|fonts|assets|dist) {
gzip_static on; # 告诉 Nginx 首先查找全部请求文件的压缩版本。
expires 15d; # 15 day expiration for all static assets
}
}
复制代码
最后个人实际网站有一个小的变化,它所带来的优化是不可忽视的。有 5 张图片直到您按下相应选项卡后才能看到,但它们是与其余全部内容同时加载的(由于它们位于 <img src=”…”>
标签中)。
我编写了一个简短的脚本,用 lazyload 类修改每一个元素的属性。只有单击相应的框后才会加载这些图像。
$(document).ready(function() {
$("#about").click(function() {
$('#about > .lazyload').each(function() {
// set the img src from data-src
$(this).attr('src', $(this).attr('data-src'));
});
});
$("#articles").click(function() {
$('#articles > .lazyload').each(function() {
// set the img src from data-src
$(this).attr('src', $(this).attr('data-src'));
});
});
});
复制代码
所以一旦文档完成加载,它将修改 <img>
标签,使他们从 <img data-src=”…”>
转到 <img src=”…”>
而后将其加载到后台。
还有一些其余的更改能够提升页面加载速度 -- 最显著的是使用 Service Workers 缓存并拦截全部请求,让站点甚至脱机运行,在 CDN 上缓存内容,这样用户就不须要在 SF 中对服务器进行完整的往返操做。这些都是有价值的改变,但对于我的静态网站来讲并非特别重要,由于它是一个在线简历(关于我)的页面。
这使个人页面加载时间从第一次加载的 8 s 提升到 350 ms,以后的页面加载速度达到了 200 ms。我真的建议阅读高性能浏览器网络 -- 您能够很快就阅读完它,它提供了对现代互联网的一个很是好的概述,并在互联网模型的每一层都进行了优化。
我遗漏了什么事情吗?是否有任何违反最优作法?或者能够改善个人叙述内容甚至是其余方面?请随时指正 -- JonLuca De Caro!
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。