Оптимизация и ускорение загрузки страницы категории сайта yablukom.ua (WordPress) часть 1

Вибачте цей текст доступний тільки в “Російська”.

Техническое задание.

Давайте начнем процесс ускорения с шаблона страниц категорий, а затем перейдем к шаблону товара. Будем рассматривать процесс на примере страницы https://yablukom.ua/semena-podsolnechnika.

Фиксируем исходные показатели Google Page Speed Insights :

Pagespeed для мобильных блок 1

Страница категории в мобильном разрешении до оптимизации 1

Pagespeed для десктоп блок 1

Страница категории в десктоп разрешении до оптимизации 1

Pagespeed для мобильных блок 2

Страница категории в мобильном разрешении до оптимизации 2

Pagespeed для десктоп блок 2

Страница категории в десктоп разрешении до оптимизации 2

Pagespeed для мобильных блок 3

Страница категории в мобильном разрешении до оптимизации 3

Pagespeed для десктоп блок 3

Страница категории в десктоп разрешении до оптимизации 3

Для мобильных – 14, для компьютеров – 9, успешных аудитов всего 9…  Очень невеселые значения, в идеале сайт должен набирать более 90 баллов, чтобы попасть в зеленую зону. Но на практике мало кому это удается (особенно если это касается мобильной версии — здесь даже в «оранжевую» зону мало кто попадает).

И если посмотреть показатели Pagespeed топовых магазинов Украины :

Pagespeed для мобильных Розетка

Розетка

Pagespeed для мобильных Брейн

Брейн

Pagespeed для мобильных блок 2

ItBox

Pagespeed для десктоп блок 2

Фокстрот

становится понятно, что увеличить показатели Pagespeed очень непросто даже для таких магазинов-монстров


Кому неинтересны технические детали проделанной работы, приведу кратко то, чего мы добились в результате оптимизации (это результаты уже на основном, выделенном сервере):

 

Pagespeed после оптимизации

Страница категории в мобильном разрешении успешные аудиты после оптимизации 1

Pagespeed успешные аудиты

Страница категории в мобильном разрешении успешные аудиты после оптимизации 2

 

Для удобства составил таблицу:

Показатель для мобильных

До оптимизации

После оптимизации

Улучшение %

Pagespeed

14

44

314

Запросов к серверу

117

74

158

Размер страницы

4 040 КБ

580 КБ

697

Время загрузки первого контента

4,8 сек.

2,5 сек.

192

Индекс скорости загрузки

17,7 сек.

11,3 сек.

156

Отрисовка крупного контента

5,7 сек.

3,0 сек.

190

Время загрузки для взаимодействия

27,5 сек.

11,1 сек.

248

Общее время блокировки

2880 мс

1370 мс

210

Успешные аудиты

9

16

178

Хочу заметить, что работа была разбита на 2 этапа… Это показатели уже после второго этапа.

Решил посмотреть, как оптимизация страницы категорий повлияли на остальные страницы сайта, и был удивлен:

Главная моб. после оптимизации

Главная страница в мобильном разрешении после оптимизации

Главная десктоп после оптимизации

Главная страница в десктоп разрешении после оптимизации

А теперь, кому все-таки интересны технические детали процесса, продолжим…

Начнем работу с рекомендаций Google Page Speed Insights.

Рекомендации Google Page Speed Insights

План.

⊕ 1. Уменьшить трафик за счет уменьшения размера изображений до необходимого, поскольку это обычно львиная доля на такого типа страницах.
⊕ 2. Отложить загрузку невидимых на первом экране изображений.
⊕ 3. Убрать, неиспользуемые на данной странице, но подгружаемые JavaScript-библиотеки, скрипты.
⊕ 4.Убрать, неиспользуемые на данной странице, но подгружаемые CSS стили.
⊕ 5. Перенести максимальное количество нужных на данной странице, JavaScript-библиотек и CSS стилей из хедера в футер. 
⊕ 6. Отложить загрузку GTM, binotel, jivosite на несколько секунд после полной загрузки(новый плагин)
7. Очень большой размер html-кода самой страницы, если посмотреть «Просмотр кода страницы» то видно что 50+ % занимает код виджета фильтра berocket_aapf и разные меню… Планирую сделать их подгрузку посредством ajax уже после загрузки страницы.
8. Установить на сервер, настроить и запустить модуль MOD_PAGESPEED от Google и за счет этого надеюсь получить:
– дополнительное автоматическое сжатие изображений по стандартам Google;
– автоматическую переконвертацию изображений из устаревших jpg, png в так любимый Google webp;
– автоматическую min-ификацию JavaScript и CSS файлов;
– автоматическое сжатие html кода страницы;
– ну,там еще есть много всяких автоматических плюшек, которые в будущем пригодятся…
 9. Возможно еще что по ходу возникнет.

P.S. Пункты 7 – 9 увы остались нереализованы – заказчик решил, что пока и так хватит…

Картинки

Начнем работу с сокращения трафика за счет уменьшения размера изображений товара до необходимого.

На странице категории Семена подсолнечника основная масса изображений  –  это изображения товара – 15 шт.

В инспекторе Chrome определяем нынешний и необходимый размер изображений для каждой картинки,  для примера возьмем товар «Семена подсолнечника Меркурий» :

 

 

 

Нынешний размер

Необходимый размер

 

 

Файл изображения сейчас

Файл изображения после

Разрешение

Размер файла (byte)

Разрешение

Размер файла (byte)

Экономия трафика (byte)

Уменьшено в %

meteor-2019-400×400.png

meteor-2019-100×100.png

400 х 400рх

271023

100 х 100рх

21762

249261

1245

 

На сриншоте из Web Page Performance Test:

Загрузка картинок до оптимизации

видно, что картинки товаров грузятся в разрешении 400×400 (помечено красным) , грузятся все скопом и их загрузка занимает почти 6 секунд (выделено желтым) …

Еще есть большие картинки в описании категории:

semena-podsolnechinka-1.jpg 300х304 – 374х379

semena-podsolnechinka-2.jpg 900х163 – 374х68

semena-podsolnechinka-3.jpg  500х231 – 374х173

Для того, чтобы еще уменьшить первоначально загружаемый объем графических файлов применим технологию Lazy Load – загружаются только видимые на первом экране графические файлы.

В отличии от Opencart, модифицировать шаблоны в WordPress значительно сложнее. В Opencart все описано обычно в одном месте шаблона и это место модифицируется либо напрямую, либо с помощью модулей \ модификаторов, а потому час работы в WordPress стоит дороже чем в Opencart. Попробую объяснить почему так…

В нужном месте шаблона WordPress, обычно, прописан только хук – место, куда можно что-то вывести, а сам код, который выводит нужный html-виджет\код на страницу, зарыт глубоко в хитросплетении 57197 файлов ядра WordPress, плагинов и темы (на данном сайте в часности ) .

Этот код выводит на страницу сам движёк WordPress, а браузер, в мобильном разрешении,  должен при размерах картинки 106 рх  x 106 рх, загрузить файл картинки  meteor-2019-100×100.png, но он сейчас грузит почему-то в 10+ раз больший по размеру файл meteor-2019-400×400. Не нашел я, виноват в этом WordPress.. или  Хром, хотя проверял и в Мозилле и в Сафари … почему-то они все резко перестали поддерживать такой адаптивный контейнер…

<img 
    src="https://yablukom.ua/wp-content/uploads/2019/01/meteor-2019-300x300.png" 
    srcset="https://yablukom.ua/wp-content/uploads/2019/01/meteor-2019-300x300.png  300w, 
                   https://yablukom.ua/wp-content/uploads/2019/01/meteor-2019-150x150.png  150w, 
                   https://yablukom.ua/wp-content/uploads/2019/01/meteor-2019-400x400.png 400w, 
                   https://yablukom.ua/wp-content/uploads/2019/01/meteor-2019-100x100.png  100w" 
   sizes="(max-width: 300px) 100vw, 300px" 
>

и потому нам нужно это дело исправить…
Покажу на примере поиск кода в woocommerce, который отвечает за вывод миниатюры изображения товара на странице категории:
За вывод категории товаров в woocommerce отвечает шаблон woocommerce\archive-product.php в дирректории темы, а в нем за блок с продуктом отвечает шаблон woocommerce\content-product.php.

<?php wc_get_template_part( 'content', 'product' ); ?>

В свою очередь в этом шаблоне за вывод миниатюры изображения товара отвечает хук:


do_action( 'woocommerce_before_shop_loop_item_title' );

Ищем поиском в редакторе, кто может добавить свой обработчик на этот хук, а туда может подключится кто угодно, в том числе и любой плагин:
В файле wp-content\plugins\woocommerce\includes\wc-template-hooks.php находим, что на этот хук вешается функция ‘woocommerce_template_loop_product_thumbnail()‘:

add_action( 'woocommerce_before_shop_loop_item_title', 'woocommerce_template_loop_product_thumbnail', 10 );

которая в свою очередь определена в файле wp-content\plugins\woocommerce\includes\wc-template-functions.php

function woocommerce_template_loop_product_thumbnail() {
		echo woocommerce_get_product_thumbnail(); 
	}

Искомый html-виджет выводится функцией woocommerce_get_product_thumbnail() из этого же файла:

      function woocommerce_get_product_thumbnail( $size = 'woocommerce_thumbnail', $deprecated1 = 0, $deprecated2 = 0 ) {
		global $product;
		$image_size = apply_filters( 'single_product_archive_thumbnail_size', $size );
		return $product ? $product->get_image( $image_size ) : '';
	}

С помощью метода get_image( $image_size ) класса $product .
Вы заметили глубину копания в недрах WordPress ? 

Думаю, не будем дальше углубляться в детали методов и классов, но я там так за 3 дня и не нашел то место где формируется этот виджет .
Тем более, что исправлять ТАМ НИЗЯ !!!

По плану В , решено было заменить не корректно работающий, надеюсь пока, код Woocommerce\Wordpress на свой, который будет работать так как нам нужно…
Поскольку код самого WordPress, а в частности плагина Woocommerce, модифицировать ни в коем случае нельзя, иначе нельзя будет потом обновится до новых версий, то было решено написать свою функцию вывода этого виджета, и привязать эту функцию на хук woocommerce_before_shop_loop_item_title.
Методом научного тыка многочисленных экспериментов было установлено, что код:

<picture>
  <source media="(max-width: 430px)" srcset=" https://yablukom.ua/wp-content/uploads/2019/01/meteor-2019-100x100.png ">
  <source media="(max-width: 768px)" srcset=" https://yablukom.ua/wp-content/uploads/2019/01/meteor-2019-150x150.png ">
  <img src=" https://yablukom.ua/wp-content/uploads/2019/01/meteor-2019-300x300.png "> 
</picture>

работает во всех браузерах так как нам нужно:
– для размера экрана менее 430px подключает картинку meteor-2019-100×100.png;
– для менее 768px и более 430px – meteor-2019-150×150.png;
– а для больше 768px – meteor-2019-300×300.png. Все как нужно

Отключаем предыдущий обработчик :

remove_filter('woocommerce_before_shop_loop_item_title', 'woocommerce_template_loop_product_thumbnail', 10 );

Подключаем для обработки свою функцию an_woocommerce_template_loop_product_thumbnail:

add_action( 'woocommerce_before_shop_loop_item_title', 'an_woocommerce_template_loop_product_thumbnail', 10 );
function an_woocommerce_template_loop_product_thumbnail() {
    global $product;
    $media_id = $product->get_image_id();
    $an_thumbnail_html = '
    <picture>
      <source media="(max-width: 430px)" srcset="' . wp_get_attachment_image_url( $media_id, 'shop_thumbnail' ) . '">
      <source media="(max-width: 768px)" srcset="' . wp_get_attachment_image_url( $media_id, 'thumbnail' ) . '">
      <img src="' . wp_get_attachment_image_url( $media_id, 'shop_catalog' ) .'" alt="' . $product->get_title() . '" class="attachment-woocommerce_thumbnail size-woocommerce_thumbnail">
    </picture>';
    echo $an_thumbnail_html;
}

Вот что мы видим:
(разработка производится на локальном Веб-сервере, потому привожу скриншоты из Chrome DevTools):

Загрузка картинок до оптимизации 1

Изображения товаров, при стандартном разрешении экрана смартфона 360х640 грузятся в размере 100х100рх.
Но, хочу заметить, что грузятся все 15 картинок сразу, а на первом экране нам нужны
только 2-е, видимые полностью… Зачем грузить то, что не видно…?
И вот для этого, добрые люди-программисты, и придумали технологию Lazy Load – «ленивая загрузка» – загружать только то, что видно на экране в данный момент.
Для реализации этой технологии мной была выбрана JS-библиатека LazySizes, которая, кроме обычных картинок <img> поддерживает и адаптивные HTML5 контейнеры типа <picture>.
Скачиваем, устанавливаем и подключаем эту JS-библиатеку:

wp_enqueue_script('lazysizes', get_template_directory_uri() . '/js/lazysizes.min.js', array('jquery'));

Для реализации Lazy Load нужно изменить нашу функцию an_woocommerce_template_loop_product_thumbnail.

Еще хочу заметить, что если применить «ленивую загрузку» и для первых 2-х изображений товара на первой мобильной странице, то они начнут загружаться браузером только после загрузки, обработки JS-библиатеки LazySizes… Значит ее нужно тоже грузить в первую очередь, а вот весь, ненужный здесь JS-код, желательно подключать как можно позже… Потому мы будем первые 2 картинки товаров загружать без Lazy Load, для этого заведем глобальную переменную $an_number_image , а остальные 13 картинок уже с Lazy Load.
Вот как изменится функция an_woocommerce_template_loop_product_thumbnail _lazy :

function an_woocommerce_template_loop_product_thumbnail_lazy() {
    global $product;
    global $an_number_image;
    $media_id = $product->get_image_id();
    $an_thumbnail_html = '
    <picture>
      <source media="(max-width: 430px)" ' .(($an_number_image > 2) ? 'data-':'')  . 'srcset="' . wp_get_attachment_image_url( $media_id, 'shop_thumbnail' ) . '">
      <source media="(max-width: 768px)" ' .(($an_number_image > 2) ? 'data-':'')  . 'srcset="' . wp_get_attachment_image_url( $media_id, 'thumbnail' ) . '">
      <img ' .(($an_number_image > 2) ? 'data-':'')  . 'src="' . wp_get_attachment_image_url( $media_id, 'shop_catalog' ) .'" alt="' . $product->get_title() . '" class="attachment-woocommerce_thumbnail size-woocommerce_thumbnail lazyload">
    </picture>';
    echo $an_thumbnail_html;
}

А вот теперь порядок загрузки картинок товаров на странице сильно изменился:

Загрузка картинок с Lazy Load

Сначала загружаются первые две картинки (1) и загружаются кстати прямо из кода страницы, а уже следующие 2 (2) значительно позже, после загрузки JS-библиатеки LazySizes уже с ее помощью и только потому, что эти картинки немного видно на первом экране…
А вот остальные 11 изображений товаров будут загружаться только по мере прокрутки страницы вниз.

Загрузка остальных картинок по мере прокрутки страницы вниз

На анимированной картинке можно видеть загрузку этих, остальных картинок. Справа видно, что подгружаются новые картинки 100х100, которые появляются на экране по мере прокрутки страницы. Но самое главное, эти невидимые 11 картинок, уже абсолютно никак не влияют на начальную загрузку страницы.

Далее, на предыдущем скриншоте видно, что грузятся ОГРОМНЫЕ картинки (3) из описания категории:
semena-podsolnechinka-1.jpg, semena-podsolnechinka-2.jpg и semena-podsolnechinka-3.jpg. Они огромны сами по себе, поскольку рассчитаны для десктопного разрешения, а на мобилке вряд-ли кто-то захочет ждать несколько секунд, пока они загрузятся…

Большая картинка из описания категории

Для них достаточно сложно применить технологию Lazy Load, т.к. для этого нужно добавить CSS-класс lazyload к картинке в каждой категории в описании , а самое главное – заменить атрибут src на data-src.

При количестве категорий 20 – 30+, это очень длительная работа . Все это можно конечно сделать с помощью JavaScript, после загрузки этих элементов, но он у нас пока под запретом на время загрузки первой страницы…

Как вариант, можно написать php-скрипт, который переберет в базе данных все описания категорий и заменит эти атрибуты для всех описаний категорий… а как быть с новыми? Вряд ли менеджер, который будет вводить description новой категории, будет помнить, что для картинки нужно еще что-то там на что-то там заменить в коде на что-то новое…
Значит нужно найти место – хук в движке Woocommerce, где выводится описание категории, поставить свой обработчик на вывод картинок в описании категории и заменить эти атрибуты на лету….

Я не буду здесь уже сильно подробно углубляться в в дебри файлов, хуков и функций Woocommerce –Wordpress…

Я просто приведу сразу решение этой проблемы.
Файл: wp-content\plugins\woocommerce\includes\wc-template-hooks.php:
Хук: add_action( ‘woocommerce_archive_description’, woocommerce_taxonomy_archive_description’, 10 );
ищем функцию woocommerce_taxonomy_archive_description.
Нашел я эту функцию в файле wp-content\plugins\woocommerce\includes\wc-template-functions.php.

Переназначил ее на свою, которая будет заменять атрибуты ‘src’,‘img class=’ на необходимые нам для поддержки lazyload.

function woocommerce_taxonomy_archive_description() {
    if ( is_product_taxonomy() && 0 === absint( get_query_var( 'paged' ) ) ) {
        $term = get_queried_object();
        if ( $term && ! empty( $term->description ) ) {
	$lazyload_img_archive_description = str_replace(array('src','img class="'),array('data-src','img class="lazyload '), wc_format_content( $term->description ));
            echo '<div class="term-description">' . $lazyload_img_archive_description . '</div>'; 
        }
    }
}

На следующем скриншоте видно, что загрузка изображений из описания категории (3) semena-podsolnechinka-1.jpg, semena-podsolnechinka-2.jpg и semena-podsolnechinka-3.jpg исчезла. Теперь они будут загружаться по мере прокрутки страницы.

Lazy загрузка изображений  из описания категории

Остались еще конечно изображения, которые не нужны на первой странице, но они небольшие и как-то значительно повлиять на скорость вряд ли смогут.
Впрочем возможно потом займемся и ими…

Оптимизация JavaScript

Смотрим какие «лишние» JavaScript-файлы грузятся в самом начале и распределяем их кого куда:

Смотрим какие JavaScript-файлы грузятся в самом начале

Здесь видно, что на странице в самом начале загружаются куча подозрительных CSS, Javascript файлов с названием kendo* и других не менее подозрительных … Причем общий объем всего этого добра насчитывает около 4 Мбайт… А это, на минуточку около 50% объема страницы .
Как оказалось все это «добро» нам грузит плагин NovaPoshta&WooCommerce, который нужен только на странице оформления заказа. Он грузит этих около 4-х ненужных Мбайт как на этой странице категории, так и на абсолютно всех страницах сайта…
Загрузка прописана прямо в коде самого плагина в файле
wp-content\plugins\wc_novaposhta\woo_np.php:
– style_np.css
– include/chosen/chosen.css
– /styles/kendo.common.min.css
– /styles/kendo.common-material.min.css
– /styles/kendo.material.min.css
– /styles/kendo.default.mobile.min.css
– /js/kendo.all.min.js
– kendo_all_js
– js/jquery.cookie.js
– https://code.jquery.com/ui/1.12.1/jquery-ui.min.js тут еще и внешний запрос…
– checkout.js
– include/chosen/chosen.jquery.min.js

Изменять код плагина нельзя, иначе после его обновления все наши «доработки» будут уничтожены…
Потому смотрим как данный плагин внедряется на страницы :

add_action('init', 'np_add_style');

Отключаем его эти некомпетентные происки (я в шоке – ну как такая солидная контора как Новая Почта может себе позволить такой дилентализм в написании плагинов…?):

remove_filter('init', 'np_add_style');

И подключаем ЭТО «добро» только на странице оформления заказа…
Провозился целый день, и ничего не получилось… Либо отключается везде, в том числе и на странице чекаута, либо не отключается нигде…
Но, немного подумал… (утро вечера мудренее) и родился план В
Просто тупо отключаем эти все файлы по их алиасам на всех страницах магазина кроме страницы оформления заказа.

add_action( 'wp_enqueue_scripts', 'an_is_checkout', 20); // Удаление ненужных на странице скриптов, стилей
function an_is_checkout(){
    if ( ! is_checkout()) {
        //$aaa = remove_action('init', 'np_add_style');
      // Нова пошта
        // Dequeue scripts.
        wp_dequeue_script('kendo_all_js');
        wp_dequeue_script('jquery_cookie');
        wp_dequeue_script('jquery-ui');
        wp_dequeue_script('checkout_js');
        wp_dequeue_script('chosen_js');
        // Dequeue styles.
        wp_dequeue_style('np_style'); 
        wp_dequeue_style('chosen_style'); 
        wp_dequeue_style('kendo.common');
        wp_dequeue_style('kendo-common-material-css'); 
        wp_dequeue_style('kendo-material-css'); 
        wp_dequeue_style('kendo-mobile-material-css');
    }
};

Сработало:

Убрали загрузку скриптов и стилей плагина Новая почта

На скриншоте видно, что скрипты и стили плагина Новой почты исчезли со страницы категорий, на странице оформления заказа они остались, скриншот приводить не буду, поверьте на слово…

Получилось очень длинно… но это еще не конец данной доработки…

Продолжение в следующей статье – Оптимизация и ускорение загрузки страницы категории сайта yablukom.ua (WordPress) часть 2.

А вот и отзыв заказчика из Телеграмм:

Скриншот отзыва заказчика из Телеграмм