- 原文地址: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、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。