Привет! Меня зовут Сергей Васильев, я фронтенд-разработчик в AGIMA. Наша команда часто работает с PWA — прогрессивными веб-приложениями. Они стали особо популярны в последние три года, когда из-за санкций некоторым корпорациям пришлось отказаться от мобильных приложений. Но и раньше многие компании с интересом смотрели на это решение.
Ниже расскажу, как сделать из обычного веб-приложения прогрессивное: вместе настроим Web App Manifest и Service Worker. Если вам еще не доводилось работать с PWA — текст точно для вас.

Что такое PWA
PWA (Progressive Web App) — это веб-приложение, способное предоставить человеку тот же пользовательский опыт, что и мобильное приложение. Вообще PWA работает в браузере, но его можно установить на домашний экран смартфона. Там его функции будут доступны в том числе офлайн.
Технология PWA стала широко известна в 2015 году благодаря расширению возможностей браузера Google Chrome и продвижению Service Worker и Web App Manifest.
Общий принцип такой: чтобы превратить обычный сайт в PWA-приложение, нужно добавить файл Manifest и реализовать Service Worker. Причем второе необязательно, но сильно улучшит пользовательский опыт. Основной инструмент для отладки — DevTools, а именно вкладка Application.
Ресурсы, помогающие в создании и настройке PWA:
Web App Manifest
Manifest — это файл manifest.json, который содержит метаданные о вашем приложении. Этот файл предоставляет браузеру и операционной системе информацию о том, как отображать ваше веб-приложение и как оно должно взаимодействовать с пользователем. Manifest позволяет вашему веб-приложению выглядеть и вести себя как нативное приложение.
Первым делом создадим файл manifest.json и подключим его в секции Head нашего сайта:
// index.html
<link rel="manifest" crossorigin="use-credentials"
href="/manifest.json">
Важно! Файл manifest.json должен располагаться в корне проекта. |
Пока Manifest пустой, но DevTools сразу подскажет, чего нам не хватает. Затем переходим на вкладку Application, ниже выбираем Manifest, смотрим перечень ошибок:

И теперь заполняем. Обязательные поля:
name — полное название вашего приложения. Используется для отображения названия приложения в различных контекстах, таких как экран установки.
short_name — сокращенное название приложения, которое используется, когда полное название не может быть отображено полностью (например, на домашнем экране устройства).
start_url — URL, который будет открыт при запуске приложения. Определяет точку входа в приложение.
display — определяет предпочтительный режим отображения приложения. Например, standalone открывает приложение в полноэкранном режиме без элементов браузера. Влияет на то, как приложение будет отображаться пользователю.
icons — массив объектов, описывающих иконки вашего приложения. Каждая иконка должна иметь указание на путь к файлу (src), размеры (sizes) и тип (type). Хотя это поле не является строго обязательным, его отсутствие может привести к тому, что приложение не будет корректно отображаться на домашнем экране устройства.
Дополнительные поля:
background_color — цвет фона, который используется на этапе загрузки приложения.
theme_color — основной цвет темы, который может использоваться операционной системой для стилизации элементов интерфейса.
description — краткое описание вашего приложения.
orientation — определяет ориентацию экрана, в которой должно отображаться приложение.
display_override — если браузер умеет, то позволяет управлять элементами управления окна.
shortcuts — позволяют быстро получать доступ к определенным функциям или разделам приложения прямо с домашнего экрана устройства. Для шорткатов можно описать поля: name, short_name, description, url, icons.
screenshots — поле, в котором можно указать скриншоты, которые будут показаны при установке приложения.
capture_links и url_handlers — поля, позволяющие настроить автоматический переход в приложение при переходе по ссылке на сайт. На данный момент эти поля не относятся к стандартным в спецификации манифеста PWA. Поддержка таких функций может варьироваться в зависимости от браузера и его версии.
Пример заполненного манифеста:
// manifest.json
{
"name": "Project",
"short_name": "Project",
"description": "Project - площадка для покупки и продажи",
"start_url": "/?version=1",
"version": "1",
"lang": "ru-RU",
"theme_color": "#21ac50",
"background_color": "#21ac50",
"display": "standalone",
"display_override": ["window-controls-overlay"],
"orientation": "portrait",
"icons": [
{
"purpose": "any",
"src": "/pwa/images/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"purpose": "any",
"src": "/pwa/images/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"purpose": "maskable",
"src": "/pwa/images/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"shortcuts": [
{
"name": "Каталог объявлений",
"url": "/rossiya/",
"icons": [
{
"src": "/pwa/images/icons/icon-catalog.png",
"sizes": "96x96",
"type": "image/png"
}
]
},
{
"name": "Разместить",
"url": "/personal/add/",
"icons": [
{
"src": "/pwa/images/icons/icon-add.png",
"sizes": "96x96",
"type": "image/png"
}
]
},
{
"name": "Сообщения",
"url": "/personal/messages/",
"icons": [
{
"src": "/pwa/images/icons/icon-messages.png",
"sizes": "96x96",
"type": "image/png"
}
]
}
],
"screenshots": [
{
"src": "/pwa/images/screenshots/desktop-1.png",
"sizes": "2310x1866",
"type": "image/jpg",
"form_factor": "wide"
},
{
"src": "/pwa/images/screenshots/desktop-2.png",
"sizes": "2310x1866",
"type": "image/jpg",
"form_factor": "wide"
},
{
"src": "/pwa/images/screenshots/mobile-1.jpg",
"sizes": "720x1424",
"type": "image/png",
"form_factor": "narrow"
},
{
"src": "/pwa/images/screenshots/mobile-2.jpg",
"sizes": "720x1424",
"type": "image/png",
"form_factor": "narrow"
}
],
"capture_links": "existing_client_event",
"url_handlers": [
{
"origin": "https://project.ru/"
}
]
}
После этого на десктопе в адресной строке справа появится иконка, говорящая о том, что сайт является PWA и пользователь может установить его как приложение на компьютер. Вот как это выглядит:

Service Workers
Сервис-воркеры — это скрипты, которые браузер выполняет в фоновом режиме, отдельно от веб-страницы. Основные функции сервис-воркеров:
Офлайн-работа. Сервис-воркеры могут кешировать ресурсы (например, HTML, CSS, JavaScript, изображения) при их первой загрузке, что позволяет приложению работать офлайн, предоставляя пользователям доступ к сохраненным данным.
Управление кешем. Вы можете программно управлять кешированием ресурсов, определяя, какие файлы и данные нужно хранить и как долго. Это позволяет улучшить производительность приложения за счет сокращения времени загрузки.
Фоновая синхронизация. Сервис-воркеры могут синхронизировать данные в фоновом режиме, когда устройство снова подключается к сети. Это полезно для обновления данных без необходимости вмешательства пользователя.
Push-уведомления. Сервис-воркеры могут обрабатывать push-уведомления, позволяя веб-приложениям отправлять сообщения пользователям, даже когда приложение не активно.
Область действия
Расположение файла сервис-воркера определяет область его действия. Сервис-воркер, расположенный по адресу example.com/my-pwa/sw.js, может управлять любой навигацией по пути my-pwa или ниже. Желательно создавать файл сервис-воркера в корне проекта.
Жизненный цикл
Жизненный цикл сервис-воркера начинается с регистрации. Затем браузер пытается загрузить и разобрать файл сервис-воркера. Если парсинг прошел успешно, запускается событие install. Это событие срабатывает только один раз.
Установка сервис-воркера происходит бесшумно, не требуя разрешения пользователя, даже если он не устанавливает PWA. API сервис-воркера доступен даже на платформах, не поддерживающих установку PWA, таких как Safari и Firefox на настольных устройствах.
После установки сервис-воркер необходимо активировать. Когда сервис-воркер будет готов управлять клиентами, сработает событие activate. Однако это не означает, что страница, зарегистрировавшая сервис-воркер, будет управляться. По умолчанию сервис-воркер возьмет управление на себя только при следующем переходе на эту страницу, либо в результате перезагрузки страницы, либо при повторном открытии PWA.
Прослушивать события в глобальной области видимости сервис-воркера можно с помощью объекта self:
// sw.js
self.addEventListener('install', (event) => {
console.log('Service worker установлен');
});
self.addEventListener('activate', (event) => {
console.log('Service worker активирован');
});
Обновление сервис-воркера
Сервис-воркеры обновляются, когда браузер обнаруживает, что установленная версия сервис-воркера отличается от того, что сейчас на сервере.
Важно! Переименовывать сервис-воркер нельзя. Иначе браузер никогда не получит новую версию сервис-воркера. |
После успешной установки новый сервис-воркер будет ожидать активации до тех пор, пока существующий (старый) сервис-воркер не перестанет контролировать клиентов. Это состояние называется ожиданием, и именно так браузер обеспечивает одновременную работу только одной версии сервис-воркера.
Обновление страницы или повторное открытие PWA не приведет к тому, что новый сервис-воркер возьмет управление на себя. Пользователю необходимо закрыть все вкладки и окна, использующие текущий сервис-воркер, или выйти из них, а затем вернуться обратно. Только после этого новый сервис‑воркер возьмет управление на себя. Для получения более подробной информации можно почитать статью «Жизненный цикл сервис‑воркера».
Пример сервис-воркера
В корне проекта создадим файл sw.js и добавим в него обработчики событий install, activate и fetch.
// sw.js
const CACHE_NAME = 'pwa-version-1';
const assets = [
'/manifest.json',
'/pwa/images/icons/icon-192.png',
'/pwa/images/icons/icon-512.png',
'/pwa/fonts/Montserrat-Medium.woff2',
'/pwa/fonts/Montserrat-Medium.woff',
'/fallback.html',
];
self.addEventListener('install', (evt) => {
evt.waitUntil(
self.skipWaiting().then(() => {
return caches.open(CACHE_NAME)
.then((cache) => {
return cache.addAll(assets)
.catch(err => {
console.log('Failed to cache assets:', err)
})
})
})
);
});
CACHE_NAME — название кеша, в котором мы собираемся хранить данные. При изменении сервис-воркера необходимо изменить версию кеша. Это заставит сервис-воркер создать новый кеш и использовать обновленные ресурсы.
assets — массив, в котором указаны все ресурсы, которые необходимо закешировать. В нашем приложении мы кешируем только fallback-страницу, которая будет показана в том случае, если не загрузился сайт.
В первую очередь добавим обработчик на событие install. Это событие вызывается, когда сервис-воркер устанавливается в браузере. После установки создается кеш pwa-version-1 и все ресурсы из assets сохраняются в нем.
// sw.js
self.addEventListener('activate', (evt) => {
evt.waitUntil(
clients.claim().then(() => {
return caches.keys().then(keys => {
return Promise.all(
keys.filter(key => key !== CACHE_NAME)
.map(key => caches.delete(key))
);
})
})
);
});
Следующий код обрабатывает событие activate, которое вызывается, когда сервис-воркер становится активным и начинает управлять клиентами — например, вкладками браузера. Ищутся и удаляются все кеши, которые имеют название, отличное от CACHE_NAME. Это необходимо, чтобы освобождать место и избегать использования устаревших данных.
Далее напишем обработчик для события fetch. В данном случае он используется для того, чтобы отдавать ресурсы из кеша (если он там есть) и чтобы показывать заранее заготовленную fallback-страничку (когда страница недоступна).
// sw.js
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Если ресурс найден в кэше, возвращаем его
if (response) {
return response;
}
// Если ресурс не найден в кэше, пытаемся получить его из сети
return fetch(event.request).catch(() => {
// Если запрос не удался (например, оффлайн), возвращаем резервную страницу
return caches.match('./fallback.html');
});
})
);
});
Файл сервис-воркера готов, осталось его зарегистрировать. В JS-код нашего сайта добавляем:
// script.js
if ('serviceWorker' in navigator) {
// регистрация сервис-воркера
navigator.serviceWorker.register('/service-worker.js')
.then(reg => {
reg.onupdatefound = () => {
const installingWorker = reg.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed' && navigator.serviceWorker.controller) {
// Новая версия сервис-воркера доступна
console.log('New service worker version available.');
// Опционально: показать уведомление пользователю
showUpdateNotification();
}
};
};
})
.catch(err => console.log('service worker not registered', err));
}
В код добавлен механизм отслеживания обновления сервис-воркера. Когда новый сервис-воркер будет установлен, он не начнет работать сразу, если в данный момент присутствует активный контроллер (то есть текущий сервис-воркер). Чтобы сервис-воркер обновился, нужно закрыть все вкладки сайта.
Итого
Теперь всё готово для запуска PWA. Мы добавили файл Manifest и реализовать Service Worker. Если у вас есть вопросы — пишите в комментариях, постараюсь помочь разобраться. А в следующей статье покажу, с какими проблемами мы столкнулись на одном из проектов (в частности на iOS) и как их решали. Также обсудить статью можно в нашем телеграм-канале для тимлидов.