Джонлука Де Каро, автор материала, перевод которого мы сегодня публикуем, однажды оказался в заграничной поездке и захотел показать другу свою личную страничку в интернете. Надо сказать, что это был обычный статический сайт, но, в процессе демонстрации оказалось, что всё работает медленнее, чем можно было бы ожидать.
На сайте не применялось никаких динамических механизмов — там было немного анимации, он был создан с применением методов отзывчивого дизайна, но содержимое ресурса практически всегда оставалось неизменным. Автор статьи говорит, что то, что он увидел, быстро проанализировав ситуацию, буквально привело его в ужас. События DOMContentLoaded
пришлось ждать около 4-х секунд, на полную загрузку страницы ушло 6.8 секунды. В процессе загрузки было выполнено 20 запросов, общий объём переданных данных составил около мегабайта. А ведь речь идёт о статическом сайте. Тут Джонлука понял, что он раньше считал свой сайт невероятно быстрым лишь потому, что привык к гигабитному интернет-соединению с низкой задержкой, используя которое, он, из Лос-Анджелеса, работал с сервером, расположенным в Сан-Франциско. Теперь же он оказался в Италии и воспользовался интернет-соединением на 8 Мбит/с. А это совершенно поменяло картину происходящего.
В этой статье Джонлука Де Каро расскажет о том, как ему удалось ускорить свой статический сайт в десять раз.
src="…"
. Я совершенно не обращал внимания на производительность, не думал ни о чём, что может на неё повлиять, включая кэширование, встраивание кода, ленивую загрузку.DOMContentLoaded
приходилось примерно пятую часть секунды, а на полную загрузку страницы было нужно 388 мс (надо сказать, что эти результаты нельзя назвать абсолютно точными, так как на них отразилась ленивая загрузка, о которой речь пойдёт ниже).webpack.config.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 Кб. Сюда входило всё кроме изображений (шрифты, CSS, все библиотеки и зависимости, а также мой JS-код). Большая часть этого объёма, 724 из 829 Кб, приходилась на шрифты font-awesomе.fa-github
, fa-envelope
, и fa-code
. Для того чтобы извлечь из библиотеки только нужные мне иконки, я использовал сервис fontello. В результате мне удалось сократить размер загружаемого файла всего до 94 Кб.bundle.js
. Время загрузки составило примерно 118 мс, что было более чем на порядок лучше, чем раньше./var/www/html
. Вот код моего исходного файла nginx.conf
.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;
}
}
nginx.conf
.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. Подробности о этом можно почитать здесь. Кроме того, с помощью конструкций вида http2_push images/Headshot.jpg;
можно воспользоваться push-директивами HTTP2.gzip: on;
, я смог уменьшить размер передаваемых данных с 16000 байт до 8000, а это означает уменьшение их объёма на 50%.Nginx gzip_static: on;
, что позволит системе ориентироваться на использование предварительно сжатых версий файлов. Это согласуется с вышеприведённой конфигурацией webpack, в частности, для того, чтобы организовать предварительное сжатие всех файлов во время сборки, можно воспользоваться ZopfliPlugin. Это экономит вычислительные ресурсы и позволяет максимизировать сжатие, не теряя в скорости.bundle.js
).nginx.conf
) я в итоге пришёл.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/*;
}
nginx.conf
.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
}
}
<img align="center" src="…">
).lazyload
. В результате эти изображения теперь загружаются лишь при щелчке по соответствующим им миниатюрам. Вот как выглядит файл lazyload.js
:$(document).ready(function() {
$("#about").click(function() {
$('#about > .lazyload').each(function() {
// установка src для элемента img на основании data-src
$(this).attr('src', $(this).attr('data-src'));
});
});
$("#articles").click(function() {
$('#articles > .lazyload').each(function() {
// установка src для элемента img на основании data-src
$(this).attr('src', $(this).attr('data-src'));
});
});
});
<img>
, устанавливая <img src="…">
на основании <img data-src="…">
, что позволяет загружать изображения тогда, когда они нужны, а не во время загрузки основных материалов сайта.К сожалению, не доступен сервер mySQL