【翻譯】10倍效能提升:優化靜態站點——by JonLuca De Caro
10倍效能提升:優化靜態站點——by JonLuca De Caro
原文地址:10x Performance Increases: Optimizing a Static Site by JonLuca De Caro https://link.medium.com/KtPI4fhWdR
幾個月前,我在美國境外旅行,並且我希望向我的朋友展示我的個人靜態網站,我嘗試導航到我的網站,但我花費了比我預期更長的時間。網站中絕對沒有任何的動態內容,只是有一些動畫和響應式設計,但是內容是靜態保持不變的。我對這個結果表示非常震驚,DOM節點內容載入花費了4s,整個網頁載入花費了6.8s。網站中有20個靜態站點請求,傳輸總資料為1Mb,我已經習慣了連線到我在舊金山的伺服器的洛杉磯的1Gb/s 低延遲物聯網速度,這讓這個怪物像閃電般快速。在義大利,以8Mb/秒的網速,這完全是一個不同的畫面。
這是我第一次研究網路優化,到目前為止之前我無論什麼時候想要新增一個庫或者資源,我都會將其拋入並指向到src=‘’。我沒有注意任何形式的效能,從快取到內聯到延遲載入。
我開始尋找有相似經驗的人,但是很不幸,很多關於靜態檔案優化的文獻都已經過時了,都是一些2010年或者2011年的建議或者討論。文庫或者假設都不再是真實的,只是重複以前的理論。
但是我還是找到了兩個很好的資訊資源——高效能瀏覽器網路和Dan Luu 在優化靜態站點方面的類似經驗。雖然在程式碼格式和內容方面我達不到Dan的水平,但是我確實設法讓自己的頁面載入速度大約快了10倍,DOM節點內容載入大約為5分之一秒,整頁面載入只有388ms。(實際是有點不準確,因為它解決了延遲載入)。
過程
這個過程的第一步是分析網站輪廓。我想要弄清楚什麼是載入時間最長的,以及如何最好的並行優化一切。我運行了各種工具來分析我的網站,並且在世界各地進行測試,包括: • https://tools.pingdom.com/ • www.webpagetest.org/ • https://tools.keycdn.com/speed • https://developers.google.com/web/tools/lighthouse/ • https://developers.google.com/speed/pagespeed/insights/ • https://webspeedtest.cloudinary.com/
其中一些提供了有關改進的建議,但是當你的網站有50個請求時,你才可以做到這一點。這從90年代遺留下來的gif空影象,是未使用的資源。(我載入了6種字型卻只使用了其中一種)。
我的網站的時間線 - 我在Web Archive網路存檔上對此進行了測試,因為我沒有擷取原始網站,但它看起來與我幾個月前看到的相似。
我想改進我控制的一切 ———從內容和和通過javascript連線到Web伺服器(Nginx)的速度和DNS設定。
優化
縮小和合並資源
第一件我需要注意的事情是我為CSS和JS(沒有任何形式的HTTP keepalive)製作了十幾個請求,對於不同站點,其中一些是https請求。這增加了對各種CDN或伺服器的多次往返請求,並且一些JS檔案正在請求其他的資源,所以這導致了上面看到的串聯阻塞現象。
我使用webpack將我的所有資源合併到一個js檔案。每當我對我的內容進行更改時,它會自動更新壓縮,將所有依賴項轉換為單個檔案。
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檔案位於我網站的head部分,會形成阻塞。它的最終大小為829kb,包括每一個非圖片資源(css,字型,所有庫和依賴項以及js)。這裡面絕大多數是字型檔案,佔用了829kb中的724kb。
我瀏覽了Font Awesome 庫,並刪除了除了我使用的三個圖示以外的所有,只留下了fa-github,fa-envelope和fa-code圖示。 我使用了一個名為fontello的服務來拉取我需要的圖示,這時候字型資源只需要94kb大小。
網站目前的構建方式,如果我們只要樣式表,那看起來是不正確的,所以我接受了單個bundle.js的阻塞性質。載入時間為118毫秒,比上述要好一個等級。
這也有一些額外的好處,我不再需要指向第三方資源或者CDN,因此使用者不需要 1.對資源執行CDNS查詢; 2.執行https握手; 3.等待資源從第三方完全下載;
雖然CDN和分散式快取可能對大規模的分散式站點有意義,但對我的小型靜態網站沒有意義。額外的100毫秒左右是值得的權衡。
壓縮資源
我載入了8Mb大小的頭部頭像,然後以10% 的寬度高度顯示它。這不僅僅是缺乏優化,這幾乎是對使用者頻寬的浪費使用。
我在這個網站壓縮了所有影象,https://webspeedtest.cloudinary.com/。它還建議我切換到webp格式,但我希望保持更多可能性的瀏覽器相容,所以依然堅持使用jpg格式。其實可以建立一個程式,讓webp僅被傳遞到支援它的瀏覽器。 但是我希望儘可能保持簡單,而且對於增加抽象層程式碼的優勢並不明顯和值得。
改進Web伺服器-Http2,TLS等
我做的第一件事情是轉到https,以前我在埠80上執行Nginx,只是從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獲得了我的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指令,Nginx就可以充分利用最新HTTP功能的所有優勢特性。 請注意,如果要利用HTTP2(以前的SPDY),則必須使用HTTPS。 在這裡閱讀(https://hpbn.co/http2/)更多相關資訊。
您還可以利用帶有http2_push 命令作用於images / Headshot.jpg;
注意:啟用gzip和TLS可能會使您面臨BREACH風險。 由於這是一個靜態站點,面臨BREACH的實際風險很低,所以保持壓縮是是沒有問題的。
利用快取和壓縮指令
通過Nginx還可以實現更多目標嗎?跳出來的第一件事就是快取和壓縮指令。
我傳送原始的未壓縮的html,只需要一個_gzip和_line命令,就能夠從160000位元組變為8000位元組,減少50%。
我們實際上可以能夠進一步提高這個數字——如果設定Nginx的_gzip_static,它會預先查詢所有請求檔案的預壓縮版本。這與我們上面的webpack config結合使用,我們可以用ZopicPlugin在構建時預壓縮我們所有檔案。這節省了計算資源,並允許我們最大限度的提高壓縮率,而無需權衡速度。
此外,我的網站很少更改,所以我希望更可能長時間的快取資源。這將使得在後期使用者訪問中,不需要重新下載資源(尤其是bundle.js)
我更新的伺服器配置如下所示:請注意,我並沒有講解我所有的更改,例如TCP設定更改,gzip指令和檔案快取。如果您想要了解更多相關資訊,請閱讀有關調優Nginx的文章。
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; # Tells nginx to look for compressed versions of all requested files first
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秒以上變為350ms,後續載入時間為200毫秒。我真的建議您閱讀所有高效能瀏覽器網路High Performance Browser Networking ,它很容易閱讀完,但是卻提供了一個令人難以置信的現代網際網路概述,並對優化現代網際網路模型的每一層提出了很好的建議。
我錯過遺漏了什麼嗎?是否有一些違反最佳實踐或可以進一步改善效能的方法?隨意聯絡JonLuca De Caro!