Главная Записи → Как этот сайт работал в 2020
Октябрь 2020

Как этот сайт работал в 2020

Технические детали о том, что нового мы добавили на сайт в 2020 и куда опять с ним переехали

Из-за того, что в 2020 мы все сидели по домам, хомяку серьёзно досталось. Это уже третья заметка на тему: всё началось в 2018 и продолжалось в 2019, а конца пока не видно.

Liquid теперь повсюду

В этом году у меня было достаточно времени, чтобы разобраться как именно можно использовать Liquid для генерации статики в Джекиле. Весь Liquid код можно разделить на объекты, теги и фильтры. Объекты широко используются в Джекиле из коробки. Например, я использую переменные Front Matter типа assets: /assets/posts_data/2021-01-01-cyberpunk-is-dead, которую позже можно вызвать в тексте поста как {{ page.assets }}/picture.png, скажем, для ссылки на картинки. Но по-настоящему полезными стали кастомные Liquid-теги написанные на Ruby.

С Liquid-тегами вместо килобайт html-кода я мог использовать короткие, настраиваемые сниппеты, которые сильно повышали читаемость исходников и сокращали время вёрстки.

Ещё в 2018 я планировал полностью избавиться от html при написании постов — код внутри заметки сильно усложнял ориентирование в исходниках. Контент приходилось «верстать». Это бесило и не позволяло находится в потоке.

Рабочий процесс выглядел последовательно: сначала я оформлял файлы и папки для поста → писал текст → верстал его → вычитывал и перепроверял всё заново исправляя ошибки. Во время вёрстки было очень много ручной работы и копирования сниппетов с кодом.

Первый тег, который я написал, позволял вставлять в пост слайдер с картинками. Раньше для этого нужно было воткнуть в текст поста что-то такое:

<!-- Slider main container -->
<div class="swiper-container">
    <!-- Additional required wrapper -->
    <div class="swiper-wrapper">
        <!-- Slides -->
        <div class="swiper-slide">
            <div class="swiper-slide-image-wrapper">
                <img class="swiper-lazy swiper-slide-image" data-src="{{page.assets}}/bash-vs-zsh-1.png" alt="Alt text for pic1">
                <div class="swiper-lazy-preloader swiper-lazy-preloader-white"></div>
            </div>
            <div class="swiper-slide-description">Caption for the pic1</div>
        </div>
        <div class="swiper-slide">
            <div class="swiper-slide-image-wrapper">
                <img class="swiper-lazy swiper-slide-image" data-src="{{page.assets}}/bash-vs-zsh-2.png" alt="Alt text for pic3">
                <div class="swiper-lazy-preloader swiper-lazy-preloader-white"></div>
            </div>
            <div class="swiper-slide-description">Caption for the pic2</div>
        </div>
        <div class="swiper-slide">
            <div class="swiper-slide-image-wrapper">
                <img class="swiper-lazy swiper-slide-image" style="max-height: 600" data-src="{{page.assets}}/bash-vs-zsh-3.png" alt="Alt text for pic3">
                <div class="swiper-lazy-preloader swiper-lazy-preloader-white"></div>
            </div>
            <div class="swiper-slide-description">Caption for the pic3</div>
        </div>
    </div>
    <!-- If we need pagination -->
    <div class="swiper-pagination"></div>
    <!-- If we need navigation buttons -->
    <div class="swiper-button-prev"></div>
    <div class="swiper-button-next"></div>
</div>

Теперь того же эффекта можно было добиться такими строчками:

{% slider %}
    bash-vs-zsh-1.png | Alt text for pic1 | Caption for the pic1
    bash-vs-zsh-2.png | Alt text for pic2 | Caption for the pic2 | 600
    bash-vs-zsh-3.png | Alt text for pic3 | Caption for the pic3
{% endslider %}

Причём часть параметров между вертикальными разделителями — опциональные. Это, например, позволило довольно элегантно решить проблему вертикальных фоток в горизонтальных слайдерах так чтобы при пролистывании они не взрывали галерею.

Круто? Ещё бы!

Когда я немного вкатился в Liquid-теги, то окончательно перестал понимать, кому вообще может понадобится WYSIWYG-админка на PHP.

Вот так курильщики вставляют в маркдаун видео с Ютюба:

<p>
    <div class="youtube">
        <div>
            <iframe src="https://www.youtube-nocookie.com/embed/pdnCXuYcNBg" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>"
        </div>
    </div>
</p>

А так вставляют нормальные ребята:

{% youtube pdnCXuYcNBg %}

Одного этого хватило чтобы сократить исходники постов в полтора раза. Писать собственные теги было очень легко и в течение года я написал небольшой зоопарк который радикально изменил процесс подготовки постов.

_plugins
├── autonotes.rb        автонумерация сносок для сайдбара
├── center.rb           любой контент в центре основной колоки с шириной по выбору
├── coubside.rb         вставить Coud в сайдбар
├── imgc.rb             картинка на всю ширину основного блока с подпись под ней
├── imgend.rb           круглая картинка в конце поста
├── left.rb             любой контент в левой части основной колоки с шириной по выбору
├── mobileicontext.rb   акцент с готовой иконкой для мобильной версии
├── mobileimgtext.rb    акцент с картинкой для мобильной версии
├── mobilesymtext.rb    акцент с эмодзи для мобильной версии
├── mobiletext.rb       акцент с текстом для мобильной версии
├── relposts.rb         похожие посты в подножии
├── right.rb            любой контент в правой части основной колоки с шириной по выбору
├── sidebar.rb          контент в сайдбаре с поддержкой маркдауна
├── slider.rb           вставить карусель картинок
├── subscribe.rb        вставить форму для подписки на рассылку
├── video.rb            вставить видео-файл который хранится на сервере
├── vimeo.rb            вставить видео с Vimeo
└── youtube.rb          вставить видео с Youtube

1 Типа таких

Отдельно хочется отметить autonotes.rb — непарный тег, который позволяет расставлять нумерацию сносок1 не задумываясь о их порядке. Цифры сноски в тексте и в сайдбаре теперь всегда совпадают в то время как раньше их постоянно приходилось проверять глазами и обновлять перед публикацией.

Liquid теперь даже внутри IntelliSense

Следующим логичным шагом было оформить все эти liquid надстройки в сниппеты для VS Code IntelliSense. В прошлом году я уже рассказывал о моём плагине для быстрой подстановки пути до картинок и файлов относящихся к конкретному посту. В этом году пришло время запилить .vscode/liquid-snippets.code-snippets — конфигурационный файл собственных сниппетов для VSCode .

2 Тем более, они одинаково работают во всех IDE.

Не вижу смысла рассказывать как работают сниппеты2, лучше покажу. Вот вставка сноски в сайдбар:

О том как делать кастомные сниппеты довольно подробно написано в документации VS Code.

Отказ от IE

Где-то посреди года мне позвонил Гриша и сказал: «Давай откажемся от IE», а я ответил: «Договорились».

С тех пор этот сайт не поддерживает непопулярный гори-сука-в-аду браузер Internet Explorer, костыли для которого по объёму могут сравниться разве что с основными исходниками.

Фактически, мы решили при вёрстке обращать внимание лишь на 5 последних релизов 3 основных движков, какие есть на рынке.

В общем: маленькое решение для человека, большой скачок для человечества. И понеслось ↓

Прощай jQuery

Раз IE больше не нужен, то можно было выкинуть часть джаваскрипта с костылями для него. Во время ревизии кода мы выяснили, что jQuery, которая обслуживала сайт с первого дня, теперь нужна только для галереи Slick с которой прямо сейчас у нас не было проблем, но мы предвидели их в будущем во время перехода на Lazy loading картинок и Responsive картинки.

jQuery times
Вот были времена…

Вместо того чтобы решать проблемы по мере поступления, мы сделали прототипы Responsive и Lazy loading и выяснили, что с имеющейся галереей Slick первый и правда не работает. А раз так, то пора менять галерею на ту, которой не нужен jQuery.

Через неделю поисков Гриша нашёл Swiper который умел Lazy, Responsive и всё ,что у нас и так было. Мы быстро переехали на неё, а jQuery был выпилен из проекта без сожалений.

Большие картинки больше не нужны

Картинки для постов всегда оставались проблемой. Они либо плохо выглядят, либо долго загружаются. Выбери нелюбимое. Хранить их в общем репозитории не правильно, но искать и настраивать какой-то CDN мне совсем не хотелось — для этого картинок пока слишком мало.

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

3 Ну нет. Такого уж точно не случится. Фантазёр.

Раньше я беспокоился за картинки рассуждая так: «вчера у меня был FullHD монитор, сегодня 4K с масштабированием, завтра какой-то маркетолог сможет продать нам 8K3. Что я буду делать со вчерашними картинками? Они же будут выглядеть убого.»

Однако, за последние 2 года нейросети научились умножать пиксели на растровых изображениях качественнее, чем это делали традиционные алгоритмы типа Nearest Neighbor, Bilinear, или Lanczos.

Я понял это после того как пристрастился к фиче ML Super Resolution в Пиксельматоре — она предсказуемо увеличивала разрешение любой картинки и та почти не теряла в качестве. Я решил, что уж погромисты придумают как в будущем встроить это в браузерные движки, чтобы сайты из 1997 загружались сразу с 3D 8K лазерными картинками. Ну или что там будет через 3 года?

Всё: проблема исходников картинок больше не моя проблема! Я буду готовить их левой пяткой используя в среднем 1920 px для длинной стороны. Программисты из будущего: не подведите.

Ассеты теперь хранятся отдельно

Картинок с каждым постом становится всё больше, а в некоторых есть гифки. Нельзя продолжать хранить все картинки в основном репозитории вместе с сайтом…

Поэтому давайте заведём отдельный репозиторий для картинок!

Шах и мат, Вселенная. Максимально всратое, на первый взгляд, решение оказалось абсолютно работоспособным в среднесрочной перспективе. Новый репозиторий подключается к основному как git submodule и становится ещё одним кирпичиком в CI/CD которым можно крутить как заблагорассудится и потом переиспользовать артефакты где попало.

Responsive и lazy loading

В вебе есть две потрясающие технологии, которые поддерживаются всеми современными браузерами и до сих пор ограниченно используются разработчиками.

  • Lazy loading позволяет динамически загружать картинки по мере того как пользователь скроллит страницу.
  • Responsive images позволяет хранить разные картинки для разных разрешений браузера. Это значит браузер сам будет решать какую картинку отдать пользователю в зависимости от того открыл он сайт с 8K монитора, или со старого телефона. Всё это сильно оптимизирует скорость рендеринга и трафик. Правда теперь вам нужно иметь 3, а лучше 4 версии одной и той же картинки на сервере. Звучит как море лишних проблем? Это вы ещё HTML5 picture tag не видели.

Lazy loading мы прикрутили довольно быстро. Это получилось сделать как раз благодаря новой галерее и Liquid тегам. Время First Meaningful Paint сразу значително сократилось. Особенно для больших страниц.

Галерея во время пролистывания тоже подгружает картинки динамически. Это локальное превью с несжатыми изображениями.

Для Responsive мы сделали прототип, который нас полностью устраивал и подготовили инфраструктуру, но полноценный переезд так и не совершили — эта история оказалась крупнее, чем я ожидал. Liquid-теги и фильтры тут тоже сильно пригодились.

Варим картинки при помощи Sharp

Из-за того, что все «оригиналы» картинок теперь хранились в каком попало разрешении их нужно было уменьшить перед деплоем хотябы до 1920 px. Во время работы с локальной версией сайта на моей машине проблем не было — огромные картинки часто сервились из соседней с движком директории и такие факторы как работа сети или пинг до сервера были исключены. Но после деплоя на VPS они становились важными параметрами напрямую определяющими скорость загрузки страниц.

Я с прошлого года использовал Gulp для сборки сайта и теперь оставалось лишь добавить в него новую задачу, которая бы масштабировала все картинки до 1920 px, но для этого нужна библиотека, которая сможет сделать это не только качественно, но и быстро.

Всего файлов Всего папок Несжатый размер После оптимизации
527  41 570 Мб 310 Мб

После поверхностных поисков, я остановился на Sharp — модуле для Node.js, построенном на библиотеке libvips. Он отлично ложится в концепцию задач Gulp и работает очень, очень быстро. Оптимизацию и копирование этих 530 файлов мой MBP 2017 выполняет за 28 секунд.

% gulp serve:dev
[12:27:25] Using gulpfile ~/Github/snnkv.com/gulp/gulpfile.js
[12:27:25] Starting 'serve:dev'...
[12:27:25] Starting 'cache:clear'...
[12:27:25] Finished 'cache:clear' after 125 ms
[12:27:25] Starting 'scale'...
[12:27:53] Finished 'scale' after 28 s
[12:27:53] Starting 'cp:orig-to-cache-non-pics'...
[12:27:54] Finished 'cp:orig-to-cache-non-pics' after 1.36 s
[12:27:54] Starting 'serve:wo-assets'...
[12:27:54] Starting 'symlink:cache'...

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

В исходниках Gulp-задачи масштабирование картинки выглядит максимально просто:

const variantsPerFile = (file, cb) => {
    const bigFile = file.clone()
    bigFile.scale = {
        maxWidth: 1920
    }
    cb(null, [bigFile])
};

Бенчмарк Lighthouse до сих пор не считает хомяка за сайт и рисует ему все возможные проблемы включая ошибки в консоли и отсутствие минифицированного джаваскрипта. В грядущем году мы постараемся правильно уложить все кирпичики, которые готовили в этом. Запустим Responsive в основной пайплайн и попробуем перейти на next-gen форматы типа webp, избавимся от гифок в пользу видео .mp4 вставленного через HTML5 video tag.

Обновления в Gulp

languages Когда на твоём сайте почти нет джаваскрипта, но репозиторий состоит из него на треть.

В прошлом году я использовал Gulp для того чтобы собирать локальную версию сайта из-за того что шаги по сборке локальной и продакшен версии значительно различались. В этом году они стали различаться ещё сильнее и я написал несколько задач для Gulp которые бы собирали сайт в прод. Поэтому теперь Gulp — основной сборщик, который используется внутри CI/CD.

Вот так выглядело дерево задач Gulp в 2020:

% gulp --tasks
[12:38:08] Tasks for ~/Github/snnkv.com/gulp/gulpfile.js
[12:38:08] ├── scale                        Оптимизирует разрешение всех картинок
[12:38:08] ├── cp:all                       Копирует все ассеты прямо /_site/assets/
[12:38:08] ├── cp:cache-to-assets-all       Копирует кэш в папку ассетов (нужна для отладки)
[12:38:08] ├── cp:orig-to-cache-non-pics    Копирует ассеты, которые не являются картинками в кэш
[12:38:08] ├── cache:clear                  Чистит кэш
[12:38:08] ├── serve:wo-assets              jekyll serve без ассетов
[12:38:08] ├── clean                        jekyll clean
[12:38:08] ├── build:default                jekyll build с дефолтовым конфигом
[12:38:08] ├── build:dev                    jekyll build с dev конфигом
[12:38:08] ├── proofer                      Запускает htmlproofer по готовой статике из папки /_site
[12:38:08] ├── symlink:orig                 Устанавливает симлинк на оригинальные ассеты
[12:38:08] ├── symlink:cache                Устанавливает симлинк на ассеты из кэша
[12:38:08] ├─┬ serve:orig
[12:38:08] │ └─┬ <series>
[12:38:08] │   ├── clean
[12:38:08] │   ├── build:dev
[12:38:08] │   └─┬ <parallel>
[12:38:08] │     ├── serve:wo-assets
[12:38:08] │     └── symlink:orig
[12:38:08] ├─┬ serve:cache
[12:38:08] │ └─┬ <parallel>
[12:38:08] │   ├── serve:wo-assets
[12:38:08] │   └── symlink:cache
[12:38:08] ├─┬ serve:dev
[12:38:08] │ └─┬ <series>
[12:38:08] │   ├── cache:clear
[12:38:08] │   ├── scale
[12:38:08] │   ├── cp:orig-to-cache-non-pics
[12:38:08] │   └─┬ <parallel>
[12:38:08] │     ├── serve:wo-assets
[12:38:08] │     └── symlink:cache
[12:38:08] ├─┬ pre-commit
[12:38:08] │ └─┬ <series>
[12:38:08] │   ├── cache:clear
[12:38:08] │   ├── scale
[12:38:08] │   ├── cp:orig-to-cache-non-pics
[12:38:08] │   ├── cp:cache-to-assets-all
[12:38:08] │   └── clean
[12:38:08] ├─┬ ci-bot
[12:38:08] │ └─┬ <series>
[12:38:08] │   ├── cache:clear
[12:38:08] │   ├── scale
[12:38:08] │   └── cp:orig-to-cache-non-pics
[12:38:08] └─┬ build-n-proof
[12:38:08]   └─┬ <series>
[12:38:08]     ├── clean
[12:38:08]     ├── build:default
[12:38:08]     └── proofer

Теперь для билда и работы с локальной версией сайта я выполняю gulp serve:orig, а перед пушем делаю финальную проверку запустив gulp build-n-proof.

Переезд на GitHub Actions

Никогда такого не было и вот опять. После прошлогоднего переезда с Buddy на Gitlab, НИИ Майкрософт успела купить GitHub и меньше чем за год превратить его в город-сад. Мало того что они сильно сместили платные фичи в сторону корпоратов, и анонсировали Codespaces на базе моего любимого VS Code, так ещё завезли собственный CI/CD c твистом — Actions. Пришлось пробовать.

Перевезти пайплайны было довольно просто. Все шишки со взрослым CI были набиты ещё в прошлом году на Гитлабе. Принципиальных отличий между Actions и Gitlab CI я не обнаружил, но дьявол, как всегда, в деталях. И если сами Actions — готовые куски автоматизации для рутинных действий, которые можно добавлять в свои пайплайны избегая написания и тестирования кучи кода, меня не сильно впечатлили, то общая скорость работы внутри инфры Гитхаба первое время сильно удивляла.

Если бы не скорость с которой выполнялся git checkout у меня бы вряд ли получилось разделить репозитории с кодом и картинками. CI просто забирал бы эти картинки несколько минут. Но оба репозитория находятся на площадках Гитхаба и, видимо, поэтому всё работает на удивление быстро.

Github Actions CI

Как можно заметить главное, что изменилось по сравнению с прошлым годом — появился новый этап оптимизации ассетов. Я часто отождествляю ассеты с картинками, но это не совсем так. Там есть видео, гифки и даже обычные файлы типа .pdf или .xlsx. Все они обрабатываются по собственным правилам или просто копируются в нужные директории.

4 Которые кешируются.

Поэтому на первом этапе сборки сайта теперь происходит забор ассетов из отдельного репозитория и их оптимизация. Дальше они в виде артефактов4 передаются в следующий этап на котором происходит сборка сайта и его тестирование. Остальные этапы не сильно изменились с прошлого года.

Этап Длительность Комментарий
0-assets-cooking 2m 19s Оптимизация ассетов
1-build 2m 31s Полная сборка сайта
2-upload-deploy-backup 44s Загрузка, деплой, бэкап
3-check 26s Проверка версии опубликованного сайта
4-report 5s Уведомление о результате в Телеграм
Всего billable 6m 6s Время без учёта развёртывания инфры GH
Всего 7m 10s Полное время выполнения пайплайна

Среднее время исполнения workflow, конечно же, увеличилось. По сравнению с прошлым годом оно выросло вдвое, но всё ещё является абсолютно приемлемым т.к. благодаря Gulp все потенциальные проблемы у меня получается отловить во время локальной сборки, которая занимает жалкие секунды и не ковыряться с ними после выполнения git push.

Форма подписки на рассылку, которой нет

Ещё мы сделали форму для подписки, которую, опять же, при помощи Liquid можно быстро впихнуть на любую страницу с постом. Вот так:

{% subscribe Оставляй имеил и **подписывайся на рассылку которой нет**. Это **единственная в Мире** рассылка которая не будет отвлекать тебя дурацкими письмами, ведь я их не пишу! %}

Выглядеть это будет так:

В тексте описания можно использовать маркдаун.

Обновления во Front Matter

В секцию Front Matter — это такие метаданные о посте, которые хранятся в начале файла и помогают Джекилу рендерить его так как я хочу, пришёл OpenGraph.

OpenGraph — это разметка для HTML страниц которую придумали в Facebook и которая позволяет описывать метаданные страницы, которые позже будут использоваться для превью, например, в тех же соцсетях. В простейшем виде разметка позволяет установить для страницы заголовок, короткий текстовый сниппет и картинку, которую пользователи увидят ещё до клика на ссылку. Так во Front Matter появились поля share-snippet и share-image.

OpenGraph preview
Так выглядит ссылка на страницу с OpenGraph в Телеграме.

Абсолютно техническое нововведение: появилось поле no-breadcrumbs: True. Если его использовать, то у поста будут отключены хлебные крошки. Это очередное временное решение и оно необходимо для отдельных страниц на английском языке с которых я бы не хотел чтобы люди уходили глубже на сайт и конфузились в процессе.

Появилась site-wide переменная {{ site.icons }}, которая является алиасом до папки с векторными иконками, которые переиспользуются в разных заметках. Например, иконки типов файлов, которые есть в статьях про хакатоны. Чтобы вставить такую достаточно в коде указать что-то типа {{ site.icons }}/xls.svg

Акценты мобильной версии

Мы никогда не рисовали сайдбар в мобильной версии — ему там нет места. Из-за этого получалось, что посетитель увидит больше контента с десктопа, чем с телефона. В этом году мы прикрутили компромиссное решение — специальные блоки, которые рендерятся только в мобильной версии и не показываются на десктопе.

Я сразу обмазал всё это Liquid-тегами. К сожалению эти блоки всё-равно остаются дополнительным контентом на оформление которого нужно тратить время. Проблема существующего решения в том, что сам формат блоков не позволяет просто транслировать туда готовый контент из сайдбара. Смотрите сами:

Mobile feature

Пока это не напрягает, но хочется верить, что в будущем мы либо автоматизируем этот кусок — сделаем какой-то транслятор сайдбар → мобильный блок, либо придумаем что-то новое. Пока же я стараюсь по возможности не перегружать сайдбар контентом, чтобы ценность страницы была примерно одинаковой в мобильной и десктопной версиях.

Остальное

По неизвестно какой причине мы с 2015 исользовали Prism для оформления блоков с кодом. В 2020 мы выкинули его из проекта, как и многое другое, заменив на дефолтовый для Джекила Rouge. Зачем мы раньше использовали Prism так и не смогли вспомнить.

В рамках политкорректности на максималках и просто ради моды добавили humans.txt, чтобы кожаным мешкам не было так обидно оттого что на каждом сайте заботятся только о роботах.

Планов на 2021 обычное количество. Среди них:

  • Переход на next-gen форматы
  • Бенчмаркодрочерство
  • Полноценный Responsive
  • Атрибуция чужого контента
  • Автосодержание и разворачивающиеся блоки

Хомяк был придуман для вечных переездов. Поэтому придётся придумать куда бы снова переехать в 2021. На Netlify норм?

Это ↑ заметка о том как работал этот сайт. Вот ещё:

Сообщение об ошибке: