07.02.25

Создание современной PWA: Опыт Callisto

10 мин · Обучающие

Введение


Привет, друзья! Сегодня я расскажу, как мы создали Callisto — прогрессивное веб-приложение (PWA) с использованием Workbox, Webpack и Service Worker. Мы разберем, почему мы выбрали PWA, как его построили и какие уроки вынесли из этого опыта.


Почему PWA?


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


  • Поддержка офлайн-режима – ваше приложение работает даже без интернета. 

  • Быстрая загрузка – благодаря стратегиям кэширования.

  • Push-уведомления – помогают удерживать пользователей.

  • Устанавливаемость – пользователи могут добавить приложение на домашний экран.


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


Как мы это сделали


Мы построили Callisto на основе трех ключевых технологий:


  • Webpack для сборки и оптимизации ресурсов.

  • Workbox для упрощения работы с Service Worker.

  • Web App Manifest для обеспечения установки приложения.


Шаг 1: Настройка Webpack


Мы использовали Webpack для генерации оптимизированных файлов. Важную роль здесь сыграл Workbox-плагин.


const WorkboxPlugin = require('workbox-webpack-plugin');

module.exports = {
  plugins: [
    new WorkboxPlugin.InjectManifest({
      swSrc: './webpack/service-worker.js',
      swDest: 'service-worker.js',
    }),
  ],
};


Шаг 2: Реализация Service Worker


Service Worker — это сердце PWA. Он управляет кэшированием и сетевыми запросами.


import { precacheAndRoute } from "workbox-precaching";
import { NetworkFirst } from "workbox-strategies";

const CACHE_NAME = `callisto`;

precacheAndRoute(self.__WB_MANIFEST);

const networkFirst = new NetworkFirst({
  cacheName: CACHE_NAME,
});


Шаг 3: Обработка офлайн-сценариев


Что делать, если пользователь запрашивает динамический контент в офлайне? Мы реализовали механизм резервного ответа.


function useFallback() {
    return caches.open(CACHE_NAME).then((cache) =>
        cache.match('/').then((matching) => {
            return matching || new Response('OFFLINE', { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
        })
    );
}


Progressive Web Application

Progressive Web Application


Шаг 4: Добавление push-уведомлений


Push-уведомления помогают уведомлять пользователей об изменениях в заявках.


self.addEventListener('push', event => {
    const data = event.data ? event.data.json() : {};
    const title = data.title || 'Новое уведомление';
    const options = { body: data.body || 'У вас новое обновление!' };

    event.waitUntil(self.registration.showNotification(title, options));
});


Шаг 5: Создание Web App Manifest


Для установки приложения мы определили Web App Manifest:


{
  "name": "Callisto FTO",
  "short_name": "Callisto",
  "start_url": "./",
  "display": "standalone",
  "icons": [
    { "src": "./icons/icon-192x192.png", "sizes": "192x192", "type": "image/png" }
  ]
}


Проверка подключения к VPN через Service Worker


Одна из проблем, с которой мы столкнулись, — это необходимость проверки подключения к VPN. Мы решили это через Service Worker, используя периодическую синхронизацию.


self.addEventListener('periodicSync', event => {
    if (event.tag === 'vpn-check') {
        checkVpnStatus();
    }
});

/*
API_URL:
https://api.ipify.org/?format=json
*/ async function checkVpnStatus() { try { const response = await fetch('API_URL'); const data = await response.json(); const currentIp = data.ip; if (currentIp !== 'EXPECTED_VPN_IP') { sendNotification('VPN подключение потеряно', 'Похоже, что ваше VPN соединение отвалилось.'); } } catch (error) { sendNotification('Ошибка подключения', 'Не удалось проверить ваше VPN соединение.'); } }

Отправка уведомлений


function sendNotification(title, message) {
    if (Notification.permission === 'granted') {
        self.registration.showNotification(title, { body: message, tag: 'vpn-status' });
    }
}

Progressive Web Application


Вызов окна установки приложения


Для того чтобы пользователи могли легко установить PWA, мы добавили обработку события beforeinstallprompt.


window.addEventListener('beforeinstallprompt', (event) => {
    event.preventDefault();
    let deferredPrompt = event;

    const installButton = document.getElementById('install-button');
    installButton.style.display = 'block';
    installButton.addEventListener('click', () => {
        deferredPrompt.prompt();
        deferredPrompt.userChoice.then((choiceResult) => {
            if (choiceResult.outcome === 'accepted') {
                console.log('User accepted the install prompt');
            } else {
                console.log('User dismissed the install prompt');
            }
            deferredPrompt = null;
        });
    });
});

Итог


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

Основные выводы:

  • Workbox упрощает работу с Service Worker.

  • Грамотное кеширование повышает производительность.

  • PWA объединяют преимущества веба и нативных приложений.


Заключение


Если вы создаете веб-приложение, подумайте о внедрении PWA. Это современный, быстрый и надежный способ улучшить пользовательский опыт.

Спасибо! Удачного кодинга!