Pull to refresh

Система сборки фронтенда в CleverStyle Framework или почему вам может быть не нужна кастомная

Reading time9 min
Views3.7K

CleverStyle Framework всячески помогает разработчику не только на сервере, но и на фронтенде. Я об этом несколько раз упоминал в прошлых статьях, но никогда не вдавался в подробности того, как именно всё устроено под капотом.


Данная статья будет погружением в подробности работы со статикой для фронтенда, начиная от того как определяются файлы нужные на странице и заканчивая оптимизациями доставки статики вроде HTTP/2 Server Push. Не забудем и о том, почему с использованием CleverStyle Framework можно обойтись без кастомной системы сборки и как при желании интегрировать такую систему сборки в процессы фреймворка.


Данная статья специально упускает из внимания интеграцию Bower/NPM и RequireJS, это будет тема отдельной статьи в недалеком будущем.


Что подключать


Первая задача — определить какие именно файлы (CSS/JS/HTML) нужны на конкретной странице, и дзесь есть две условные группы:


  • общие файлы для всех страниц сайта
  • файлы, которые специфичны для данной страницы (или для нескольких страниц)

Общие файлы


Общие файлы в свою очередь так же можно разделить на три подгруппы.


В первую подгруппу попадают файлы ядра системы из директории assets. Там находится тот базовый минимум служебных файлов, которые нужны самому фреймворку (на самом деле это можно обойти, но об этом дальше). Файлы данной подгруппы загружаются в первую очередь.


Во вторую подгруппу попадают файлы текущей темы оформления интерфейса themes/тема/{css|js|html}, подключаются сразу после файлов ядра.


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


Файлы для конкретных страниц


Конкретные страницы генерируются установленными модулями.


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


Пример для модуля Blogs:


{
    "package"      : "Blogs",
    "category"     : "modules",
...
    "assets"       : {
        "admin/Blogs" : [
            "cs-blogs-admin-*"
        ],
        "Blogs"       : [
            "cs-blogs-*"
        ]
    },
...
}

В модуле Blogs нет CSS и JS файлов для подключения на страницу, только веб-компоненты (которые в свою очередь могут содержать CSS/JS, но это уже другая история, о которой чуть дальше).


В примере выше мы видим, что на всех страницах модуля будут подключены файлы, пути которых начинаются с префикса cs-blogs- (звёздочка отбрасывается и поддерживается исключительно в целях улучшения читабельности), а файлы с префиксом cs-blogs-admin- будут подключаться только на страницах администрирования (этот ключ в списке идет первым, так что сначала отфильтруются файлы с данным префиксом, а остальные попадут в следующий ключ, не смотря на одинаковое начало).


На счёт путей нужно уточнить что они отсчитываются от следующих корней: modules/Modules_name/assets/{css|js|html}, из формата файла ясно в которой директории он находится, то есть в modules/Modules_name/assets/html будет производиться поиск только HTML файлов, но не CSS и/или JS.


Прямые зависимости


Файлы самого модуля это только часть истории, важным звеном в данной системе являются зависимости между модулями.


Посмотрим на модуль Photo gallery для примера и одну из его зависимостей, модуль Uploader:


{
    "package"          : "Photo_gallery",
    "category"         : "modules",
...
    "assets"           : {
        "admin/Photo_gallery" : "admin.css",
        "Photo_gallery"       : "general.*"
    },
...
    "provide"          : "photo_gallery",
    "require"          : [
        "System>=6.25",
        "System<7.0",
        "Composer",
        "Fotorama>=4.4.9",
        "file_upload",
        "composer_assets"
    ],
...
}

{
    "package"      : "Uploader",
    "category"     : "modules",
...
    "assets"       : {
        "admin/Uploader" : "admin.css",
        "Uploader"       : "script.js"
    },
...
    "provide"      : "file_upload",
...
}

Здесь мы видим что модуть Photo gallery имеет прямую зависимость от file_upload. file_upload в свою очередь является не названием модуля, а его функциональностью (подробнее об этом в документации). С точки зрения обработки статики это значит, что файлы предназначенные для модуля Uploader (для модуля в целом, не для подстраниц) будут так же подключены на страницах модуля Photo gallery, при чём перед собственными файлами этого модуля (это важно если зависимость имеет некоторый синхронный код, используемые по зависимости дальше).


Нужно заметить, что не только обязательные (ключ require), но и опциональные (ключ optional) зависимости учитываются в данном сценарии.


Обратные зависимости


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


Вложенные зависимости


Зависимости могут иметь собственные зависимости, в том числе зависимости могут повторяться. Фреймворк это понимает и превращает дерево зависимостей в плоскую структуру с учетом этой особенности (не пытайтесь создавать циклические зависимости, результат будет неопределённым).


Важно заметить, что зависимость от версии системы (модуль System) не влияет на цикличность зависимостей, эффективно можете считать что она будет проигнорирована во время обработки статики.


Интеграция в процесс сбора файлов для подключения


Во время сборки файлов для подключения система генерирует событие System/Page/assets_dependencies_and_map, подписавшись на которое имеется возможность манипулировать собранными системой файлами а так же построенной структурой зависимостей.


В данную структуру можно добавлять собственные файлы, организовывать их в такие себе виртуальные модули и достраивать зависимости. Таким образом модуль Composer assets интегрирует подключение файлов из Bower/NPM пакетов во фреймворк чтобы воспользоваться его внутренними механизмами для обработки файлов (о которых немного дальше).


Собственно подключение файлов на страницу


Фреймворк не всегда просто подключает все нужные для конкретной страницы файлы, а поступает более продвинутым образом в зависимости от конфигурации системы.


На то, как подключать файлы влияют следующие параметры:


  • кэширование и сжатие
  • оптимизация загрузки фронтенда (зависит от предыдущего параметра)
  • вулканизация (также зависит от первого параметра)
  • перемещение JavaScript и HTML за тело страницы
  • отключение поддержки веб-компонентов

Перемещение JavaScript и HTML за тело страницы


Это самое простое, JS/HTML файлы будут подключены перед </body> вместо помещения в <head>, в общем рекомендуется к использованию.


CSS специально подключается в <head> всегда, поскольку базовая стилизация обычно небольшая и задержка в рендеринге странице получается не существеенная (особенно с использованием HTTP/2 и Server Push).


Отключение поддержки веб-компонентов


Тоже достаточно простая настройка, которая, впрочем, работает только для кастомных тем (чтобы не сломать админку с темой оформления по-умолчанию). Приводит к полной фильтрации любых HTML файлов, так же отключает загрузку полифиллов для веб-компонентов и JS файлов из assets/Polymer, что в целом отключает всё что связано с веб-компонентами во фреймворке если оно вам не нужно. Важно понимать, что многие готовые модули перестанут работать, поскольку их интерфейс построен целиком на веб-компонентах.


Кэширование и сжатие


Во-первых, фреймворк берет все собранные файлы для подключения на страницах с учётом зависимостей и пакует файлы в кэш. Для общих файлов создается тройка кэшированных файлов с форматами css/js/html, аналогичные файлы создаются для каждого ключа assets в каждом установленном модуле + отдельно создаются кэши языковых переводов интерфейса + отдельно кэшируется полифилл для веб-компонентов и сохраняется новая структура зависимостей с указанием уже на данные собранные кэшированные файлы.


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


Обработка CSS заключается в:


  • удалении лишних пробелов и переводов строк
  • оптимизации некоторых конструкций вроде сокращения цветовых HEX значений с 6 до 3 символов и конвертации RGB цветов в HEX
  • встраивании небольших изображений и подобных файлов (до 4 КиБ) в base64 виде, не встроенные файлы записываются в отдельный список и будут использованы позже
  • корректировке относительных путей с учетом нового целевого размещения кэшированного CSS

Обработка JS гораздо более скудная, поскольку тащить полноценный минификатор желания нет, а иначе легко сломать код простым поиском и заменой, так что ограничиваемся следующим:


  • удаление гарантировано безопасных переводов строк
  • удаление однострочных комментариев, которые начинаются с начала строки
  • удаление многострочных комментариев, которые начинаются с начала строки
  • звмена </script> на <\/script>, поскольку данный JS код может впоследствии быть использован для вставки в HTML
  • игнорируются многострочные шаблоны из ES2015 (начиная от первого и заканчивая последним — всё внутри исключается из обработки)

Данные минификации CSS/JS достаточно базовые, но позволяют за десяток миллисекунд перебрать весь проект, в то время как полноценные минификаторы требуют несколько секунд и с Gzip дают выигрыш всего лишь до 5% (данные минификаторы вы можете запускать поверх построенного кэша).


HTML обрабатывается ещё меньше — по сути весь JS код собирается вместе и прогоняется через упомянутый минификатор, CSS для каждого веб-компонента так же прогоняется через упомянутый минификатор, во избежания проблем вырезается <link rel="import" href="../polymer/polymer.html"> из HTML.


После построения кэша генерируется событие System/Page/rebuild_cache — кастомные минификаторы и прочие вещи вроде построения кастомного кэша можно производить в обработчике этого события.


В структуре кэша каждый файл сопровождается хэшем в части параметров (примеры ниже в части про HTTP/2 Server Push). Хэш считается от содержимого файла, так что при изменении одного файла и перестроения кэша изменится хэш соответствующего кэшированного файла, но не всего кэша сразу.


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


Вулканизация


Вулканизация это процесс обработки HTML файла, при котором CSS/JS код встраивается в итоговый HTML. Для CSS это не имеет никакого значения, поскольку Polymer пока не поддерживает CSP-совместимые конструкции во всех ситуациях, так что CSS всегда встраивается в итоговый HTML.


Реализация во фреймворке собственная, в целом об этом можно почитать в соответствующем проекте на GitHub.


Оптимизация загрузки фронтенда


Это интересный механизм, призвание которого ускорить изначальную отрисовку страницы.


Как было описано выше есть общий CSS/JS/HTML код и специфический для отдельных страниц. Так вот этот общий код можно представить себе как App shell. Оптимизация загрузки фронтенда заключается в том, что изначально на странице подключается только общий JS/HTML, и только после того как он был загружен и отработан начинается асинхронная загрузка и последовательное выполнение остальной части JS/HTML кода (модули дожны быть готовы к тому, что HTML код, который будет общим для всех страниц, может выполниться раньше чем JS код зависимостей данного модуля).


Данный подход позволяет значительно ускорить первую отрисовку страницы и таким образом дать пользователю если не интерактивную страницу, то что-то полезное и максимально быстро.


Для оптимизированной загрузки строится отдельный файл с дополнительной структурой оптимизированного кэша.


HTTP/2 Server Push


При включенном кэшировании нужные странице файлы будут сопровождены соответствующими 'Link:` заголовками, что прокси-сервера (вроде CloudFlare, nghttpx) обычно превращают в Server Push, пример заголовков:


Link:</storage/public_cache/CleverStyle:TinyMCE.html?602ca>; rel=preload; as=document
Link:</storage/public_cache/CleverStyle:System.css?fecf7>; rel=preload; as=style
Link:</storage/public_cache/CleverStyle:Uploader.js?2167a>; rel=preload; as=script
Link:</storage/public_cache/CleverStyle:System.html?14dd7>; rel=preload; as=document
Link:</storage/public_cache/CleverStyle:System.js?a0e9d>; rel=preload; as=script
Link:</storage/public_cache/CleverStyle:Static_pages.html?a292d>; rel=preload; as=document

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


Link:</storage/public_cache/CleverStyle:System.html?14dd7>; rel=preload; as=document
Link:</storage/public_cache/CleverStyle:System.js?a0e9d>; rel=preload; as=script
Link:</storage/public_cache/CleverStyle:System.css?fecf7>; rel=preload; as=style

В Server Push так же попадут файлы, которые не были встроены в CSS из-за их размера, но которые нужны для отрисовки страницы (подробнее в документации).


Ну и, конечно же, Server Push срабатывает только однажды, после чего выставляется cookie во избежание деградации производительности.


Почему вам может быть не нужна кастомная система сборки фронтенда


Если у вас процесс разработки похож на мой: LiveScript->JavaScript, SCSS->CSS, Jade->HTML — всё с помощью File Watchers автоматически генерируется при каждом изменении и целиком попадает в Git, то вы можете обойтись без кастомной системы сборки. Любое изменение любого файла приводит к генерации соответствующего артефакта и пока вы переключаетесь в браузер чтобы нажать F5 (без включенного кэширования во фреймворке) у вас всё готово к работе.


Что если у вас есть кастомная система сборки, вы хотите добавить кастомную систему сборки или заменить то, что делает фреймворк полностью?


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


  • System/Page/assets_dependencies_and_map
  • System/Page/rebuild_cache

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


Напоследок


Всегда рад новым идеям и конструктивным комментариям.
» Репозиторий на GitHub

Tags:
Hubs:
Total votes 8: ↑6 and ↓2+4
Comments13

Articles