[譯] 提高 10 倍效能:優化靜態網站

你的可嚶已上線發表於2018-04-08

提高 10 倍效能:優化靜態網站

幾個月前,我在國外旅行,想給朋友看我個人(靜態)網站上的一個連結。我試著瀏覽我的網站,但花費的時間比我預期的要長。網站絕對沒有任何動態內容--只有動畫和一些響應式設計,而且內容始終保持不變。 我對結果感到震驚,DOMContentLoaded 要 4 s,整個頁面載入要 6.8 s。有 20 項關於靜態網站的請求(總資料的 1MB)被轉移。我習慣了從洛杉磯到我在舊金山的伺服器之間用 1 GB/s 的低延遲網際網路連線,這使得這個怪物看起來像閃電一樣快。在義大利,8 MB/s 的速度讓情況變得完全不同。

[譯] 提高 10 倍效能:優化靜態網站

這是我第一次嘗試優化。到目前為止,每次我想新增一個庫或者資源時,我都只是將它引入並使用 src="" 指向它。從快取到內聯,再到延遲載入,對任何形式的效能我都沒有給予關注。

我開始尋找有相似經歷的人。不幸的是,許多有關靜態優化的文獻很快就過時--那些來自 2010 或者 2011 年的建議,要麼是在討論庫,要麼做一些根本不再試用的假設,要麼就是不斷地重複某些相同的準則。

不過我確實找到了兩個很好的資訊源 -- 高效能瀏覽器網路Dan Luu 類似的靜態網站優化經歷。儘管在剝離格式和內容方面還不如 Dan,但是我確實成功地讓我的頁面載入速度提高了大約 10 倍。DOMContentLoaded 大約需要五分之一秒,而整個頁面載入只有 388 ms(實際上有點不準確,下文將解釋延遲載入的原因)。

[譯] 提高 10 倍效能:優化靜態網站

過程

過程的第一步是對網站進行分析梳理,我想弄清楚哪些地方花費了最長的時間,以及如何最好地並行化一切。我執行了各種工具來分析我的網站,並在世界各地測試它,包括:

其中一些提供了改進建議,但當靜態站點有 50 個請求時,您只能做這麼多 -- 從 90 年代遺留下來的間隔 gif 到不再使用的資源(我載入了 6 種字型但只使用了 1 種字型)。

[譯] 提高 10 倍效能:優化靜態網站

我的網站時間線 -- 我在 Web Archive(譯者注:一家提供網站歷史快照的服務商)上測試了這個卻沒有擷取原始圖片,可是它看起來和我幾個月前看到的還是很相似。

我想改進我所能控制的一切 -- 從 JavaScript 的內容和速度到實際的 Web 伺服器(Ngnix)和 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.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% 的寬高比顯示它。這不僅僅是缺少優化,這幾乎是忽略了使用者對頻寬使用

[譯] 提高 10 倍效能:優化靜態網站

我使用 webspeedtest.cloudinary.com/ 來壓縮所有的影象 -- 它還建議我切換到  webp,但我希望儘可能多的與其他瀏覽器進行相容,所以我堅持使用 jpg。儘管完全有可能建立一個只將 webp 交付給支援它的瀏覽器系統,但我希望儘可能地保持簡單,新增抽象層的好處似乎並不明顯。

改進 Web Server — HTTP2, TLS 等

我做的第一件事是過度到 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

[譯] 提高 10 倍效能:優化靜態網站


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章