Мгновенная загрузка веб-приложений с помощью архитектуры оболочки приложения

Оболочка приложения — это минимальный HTML, CSS и JavaScript, обеспечивающий пользовательский интерфейс. Оболочка приложения должна:

  • загружаться быстро
  • кэшироваться
  • динамически отображать контент

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

Оболочка приложения. Разделение оболочки HTML, JS и CSS и содержимого HTML.

Фон

В статье Алекса Рассела « Прогрессивные веб-приложения» описывается, как веб-приложение может постепенно меняться посредством использования и согласия пользователя, чтобы обеспечить более схожее с нативным приложением взаимодействие с автономной поддержкой, push-уведомлениями и возможностью добавления на главный экран. Это во многом зависит от функциональности и производительности сервис-воркера , а также от его возможностей кэширования. Это позволяет вам сосредоточиться на скорости , предоставляя вашим веб-приложениям такую ​​же мгновенную загрузку и регулярные обновления, которые вы привыкли видеть в собственных приложениях.

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

Давайте углубимся в то, как структурировать ваше приложение, используя архитектуру оболочки приложения, дополненную Service Worker . Мы рассмотрим рендеринг как на стороне клиента, так и на стороне сервера, а также поделимся комплексным примером, который вы можете попробовать сегодня.

Чтобы подчеркнуть это, в примере ниже показана первая загрузка приложения, использующего эту архитектуру. Обратите внимание на всплывающее сообщение «Приложение готово к использованию в автономном режиме» в нижней части экрана. Если обновление оболочки станет доступно позже, мы можем сообщить пользователю, что ему необходимо обновиться до новой версии.

Изображение сервисного работника, работающего в DevTools для оболочки приложения

Опять же, что такое сервисные работники?

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

Сервис-воркеры также имеют ограниченный набор API по сравнению с JavaScript в обычном контексте просмотра. Это стандартно для работников в сети. Работник службы не может получить доступ к DOM, но может получить доступ к таким вещам, как Cache API , и может делать сетевые запросы с помощью Fetch API . API IndexedDB и postMessage() также доступны для использования для сохранения данных и обмена сообщениями между сервис-воркером и страницами, которые он контролирует. Push-события, отправленные с вашего сервера, могут вызывать API уведомлений для повышения вовлеченности пользователей.

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

Чтобы узнать больше о сервисных работниках, прочтите Введение в сервисных работников .

Преимущества производительности

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

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

Чтобы протестировать эту архитектуру на реальных устройствах, мы запустили образец оболочки приложения на WebPageTest.org и показали результаты ниже.

Тест 1. Тестирование по кабелю с Nexus 5 с использованием Chrome Dev

При первом представлении приложения необходимо получить все ресурсы из сети, и он не достигает значимой отрисовки в течение 1,2 секунды . Благодаря кэшированию сервисного работника при повторном посещении достигается содержательная отрисовка и полная загрузка завершается за 0,5 секунды .

Тестовая схема веб-страницы для подключения кабеля

Тест 2. Тестирование сети 3G на Nexus 5 с использованием Chrome Dev.

Мы также можем протестировать наш образец с немного более медленным соединением 3G. На этот раз при первом посещении нашей первой значимой краски требуется 2,5 секунды . Полная загрузка страницы занимает 7,1 секунды . Благодаря кэшированию сервис-воркеров при повторном посещении достигается содержательная отрисовка и полная загрузка завершается за 0,8 секунды .

Тестовая диаграмма веб-страницы для подключения 3G

Другие мнения рассказывают аналогичную историю. Сравните 3 секунды , необходимые для первой значимой отрисовки в оболочке приложения:

Временная шкала рисования для первого просмотра при тестировании веб-страницы

до 0,9 секунды , которая требуется при загрузке той же страницы из кэша нашего сервис-воркера. Для наших конечных пользователей экономится более 2 секунд времени.

Временная шкала рисования для повторного просмотра из теста веб-страницы

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

Требует ли сервис-воркер от нас переосмысления того, как мы структурируем приложения?

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

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

А как насчет прогрессивного улучшения?

Хотя Service Worker в настоящее время поддерживается не всеми браузерами, архитектура оболочки содержимого приложения использует прогрессивные улучшения , чтобы гарантировать доступ к содержимому каждому. Например, возьмем наш пример проекта.

Ниже вы можете увидеть полную версию, отображаемую в Chrome, Firefox Nightly и Safari. В самом левом углу вы можете увидеть версию Safari, в которой контент отображается на сервере без сервис-воркера. Справа мы видим версии Chrome и Firefox Nightly, работающие на базе Service Worker.

Изображение оболочки приложения, загруженное в Safari, Chrome и Firefox

Когда имеет смысл использовать эту архитектуру?

Архитектура оболочки приложения наиболее подходит для динамических приложений и сайтов. Если ваш сайт небольшой и статический, вам, вероятно, не понадобится оболочка приложения, и вы можете просто кэшировать весь сайт на этапе oninstall Service Worker. Используйте тот подход, который наиболее подходит для вашего проекта. Ряд фреймворков JavaScript уже поощряют отделение логики вашего приложения от контента, что делает применение этого шаблона более простым.

Есть ли какие-либо производственные приложения, использующие этот шаблон?

Архитектура оболочки приложения возможна при внесении всего лишь нескольких изменений в общий пользовательский интерфейс вашего приложения и хорошо зарекомендовала себя для крупномасштабных сайтов, таких как Google I/O 2015 Progressive Web App и Google Inbox.

Изображение загрузки папки «Входящие» Google. Иллюстрирует папку «Входящие» с использованием Service Worker.

Оболочки автономных приложений обеспечивают значительный выигрыш в производительности и также хорошо демонстрируются в автономном приложении Wikipedia Джейка Арчибальда и прогрессивном веб-приложении Flipkart Lite .

Скриншоты демо-версии Википедии Джейка Арчибальда.

Объяснение архитектуры

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

Первая загрузка и загрузка других страниц

Схема первой загрузки оболочки приложения

В целом архитектура оболочки приложения будет:

  • Установите приоритет начальной загрузки, но позвольте сервисному работнику кэшировать оболочку приложения, чтобы повторные посещения не требовали повторной загрузки оболочки из сети.

  • Ленивая загрузка или фоновая загрузка всего остального. Хороший вариант — использовать кэширование сквозного чтения для динамического контента.

  • Используйте инструменты сервис-воркера, такие как sw-precache , например, для надежного кэширования и обновления сервис-воркера, который управляет вашим статическим контентом. (Подробнее о sw-precache позже.)

Для достижения этой цели:

  • Сервер отправит HTML-контент, который клиент сможет обработать, и будет использовать заголовки истечения срока действия HTTP-кэша в будущем для учета браузеров без поддержки сервис-воркера. Он будет обслуживать имена файлов с использованием хешей, чтобы обеспечить как «управление версиями», так и простые обновления на последующих этапах жизненного цикла приложения.

  • Страницы будут включать встроенные стили CSS в тег <style> внутри документа <head> , чтобы обеспечить быструю первую отрисовку оболочки приложения. Каждая страница будет асинхронно загружать JavaScript, необходимый для текущего представления. Поскольку CSS не может быть загружен асинхронно, мы можем запрашивать стили с помощью JavaScript, поскольку он ЯВЛЯЕТСЯ асинхронным, а не управляемым парсером и синхронным. Мы также можем воспользоваться функцией requestAnimationFrame() , чтобы избежать случаев, когда мы можем получить быстрое попадание в кэш и в конечном итоге стили случайно станут частью критического пути рендеринга. requestAnimationFrame() заставляет отрисовывать первый кадр перед загрузкой стилей. Другой вариант — использовать такие проекты, как loadCSS от Filament Group, для асинхронного запроса CSS с использованием JavaScript.

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

Оболочка приложения для контента

Практическая реализация

Мы написали полностью рабочий образец , используя архитектуру оболочки приложения, стандартный JavaScript ES2015 для клиента и Express.js для сервера. Разумеется, ничто не мешает вам использовать собственный стек как для клиентской, так и для серверной части (например, PHP, Ruby, Python).

Жизненный цикл сервисного работника

Для нашего проекта оболочки приложения мы используем sw-precache , который предлагает следующий жизненный цикл сервисного работника:

Событие Действие
Установить Кэшируйте оболочку приложения и другие ресурсы одностраничного приложения.
Активировать Очистите старые тайники.
Принести Размещайте одностраничное веб-приложение для URL-адресов и используйте кеш для ресурсов и предопределенных фрагментов. Используйте сеть для других запросов.

Биты сервера

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

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

Схема архитектуры оболочки приложения
  • Конечные точки определяются для трех частей вашего приложения: пользователь, обращающийся к URL-адресу (индекс/подстановочный знак), оболочка приложения (сервисный работник) и ваши части HTML.

  • Каждая конечная точка имеет контроллер, который извлекает макет руля , который, в свою очередь, может извлекать части и представления руля. Проще говоря, частичные представления — это представления, представляющие собой фрагменты HTML, копируемые на конечную страницу. Примечание. Фреймворки JavaScript, обеспечивающие более сложную синхронизацию данных, зачастую легче перенести на архитектуру оболочки приложения. Они склонны использовать привязку данных и синхронизацию, а не частичные.

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

  • Оболочка приложения затем будет действовать как одностраничное веб-приложение, используя JavaScript для XHR в контенте для определенного URL-адреса. Вызовы XHR выполняются к конечной точке /partials*, которая возвращает небольшой фрагмент HTML, CSS и JS, необходимый для отображения этого контента. Примечание. Существует множество способов решения этой проблемы, и XHR — лишь один из них. Некоторые приложения встраивают свои данные (возможно, используя JSON) для первоначального рендеринга и поэтому не являются «статическими» в смысле плоского HTML.

  • Браузеры без поддержки Service Worker всегда должны иметь запасной вариант. В нашей демонстрации мы возвращаемся к базовому статическому рендерингу на стороне сервера, но это лишь один из многих вариантов. Аспект сервисного работника предоставляет вам новые возможности для повышения производительности вашего приложения в стиле одностраничного приложения с использованием кэшированной оболочки приложения.

Управление версиями файлов

Возникает один вопрос: как управлять версиями и обновлением файлов. Это зависит от приложения, и доступны следующие варианты:

  • Сначала сеть, в противном случае используйте кэшированную версию.

  • Только сеть и сбой в автономном режиме.

  • Кэшируйте старую версию и обновите ее позже.

Что касается самой оболочки приложения, для настройки сервис-воркера следует использовать подход «сначала кеш». Если вы не кэшируете оболочку приложения, вы не приняли архитектуру должным образом.

Оснастка

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

Снимок экрана: сайт библиотеки Service Worker, посвященный основам веб-технологий

Используйте sw-precache для оболочки вашего приложения

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

Первые посещения вашего приложения вызывают предварительное кэширование полного набора необходимых ресурсов. Это похоже на установку собственного приложения из магазина приложений. Когда пользователи возвращаются в ваше приложение, загружаются только обновленные ресурсы. В нашей демонстрации мы сообщаем пользователям о доступности новой оболочки сообщением «Обновление приложения. Обновите для новой версии». Этот шаблон представляет собой простой способ сообщить пользователям, что они могут обновиться до последней версии.

Используйте sw-toolbox для кэширования во время выполнения

Используйте sw-toolbox для кэширования во время выполнения с различными стратегиями в зависимости от ресурса:

  • cacheFirst для изображений, а также выделенный именованный кэш с настраиваемой политикой срока действия N maxEntries.

  • networkFirst или самый быстрый для запросов API, в зависимости от желаемой свежести контента. Самый быстрый может подойти, но если есть определенный канал API, который часто обновляется, используйте networkFirst.

Заключение

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

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

Если вы уже рассматриваете возможность использования сервис-воркеров в своем приложении, взгляните на архитектуру и оцените, имеет ли она смысл для ваших собственных проектов.

Выражаем благодарность нашим рецензентам: Джеффу Познику, Полу Льюису, Алексу Расселу, Сету Томпсону, Робу Додсону, Тейлору Сэвиджу и Джо Медли.