Превращение в PWA

В этом модуле превратим наш чат в Progressive Web App — приложение, которое можно установить на главный экран и которое работает офлайн.

Перед началом: Модули 1–4. Инструменты: Vite PWA, Service Worker. Время: ~5 ч.


🔥 Инцидент. Пользователь в метро. Открывает чат — белый экран. Нет кэша, нет офлайн-страницы. Service Worker отсутствует. PWA даёт: установка на телефон, офлайн-доступ, push-уведомления. Но Vite PWA конфликтует с рядом плагинов — нужно аккуратно.


📖 Теория

Фокус: офлайн-first app shell. Service Worker кэширует shell, manifest делает приложение устанавливаемым.

Что такое PWA?

PWA — это веб-приложение, которое:

  • Работает офлайн (Service Worker)
  • Можно установить на устройство
  • Получает push-уведомления
  • Имеет нативный интерфейс

Установка Vite PWA Plugin

bash
npm install -D vite-plugin-pwa

Настройка vite.config.ts

typescript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { VitePWA } from 'vite-plugin-pwa'

export default defineConfig({
  plugins: [
    react(),
    VitePWA({
      registerType: 'autoUpdate',
      manifest: {
        name: 'Serverless Chat',
        short_name: 'Chat',
        theme_color: '#ffffff',
        background_color: '#ffffff',
        display: 'standalone',
        icons: [
          {
            src: 'icon-192.png',
            sizes: '192x192',
            type: 'image/png'
          },
          {
            src: 'icon-512.png',
            sizes: '512x512',
            type: 'image/png'
          }
        ]
      },
      // Важно: исключаем WebSocket из кэширования
      workbox: {
        navigateFallback: 'index.html',
        globIgnores: ['**/ws/**'],
      }
    })
  ]
})

Service Worker

Service Worker автоматически генерируется плагином, но вы можете настроить кастомную логику:

typescript
// sw.ts (если использовать injectManifest: true)
// Обработка входящего пуша
self.addEventListener('push', (event) => {
  const data = event.data?.json() || {
    title: 'Новое сообщение',
    body: ''
  };

  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: 'icon-192.png',
      badge: 'badge-72.png'
    })
  );
});

PWA manifest

Файл manifest.json описывает приложение:

json
{
  "name": "Serverless Chat",
  "short_name": "Chat",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#2563eb",
  "icons": [
    {
      "src": "/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Проверка PWA

  1. Откройте Chrome DevTools → Application
  2. Проверьте:
    • Manifest → отображается ли иконка и название
    • Service Workers → зарегистрирован ли SW
    • Lighthouse → пройдите аудит PWA

В следующем модуле добавим push-уведомления.


💱 Компромиссы (Trade-offs)

РешениеПлюсыМинусы
vite-plugin-pwa (autoUpdate)SW генерится автоматически, skipWaiting+clientsClaimКонфликты с рядом плагинов Vite
Precache app shellОфлайн сразу после установки SWБольшой precache → долгий первый заход
navigateFallback: index.htmlSPA-роутинг работает офлайн200 вместо 404 — битые ссылки не видны
Manifest display: standaloneНативный UX, установка на главный экранРазные иконки/резолюции под платформы
globIgnores: **/ws/**WebSocket не кэшируется (как надо)Легко забыть — WS ломается кэшем

🧩 Проверь себя

1. Precache в PWA — это...
2. vite-plugin-pwa (autoUpdate) — что значит?
3. Web App Manifest задаёт...
4. registerType: 'autoUpdate' — поведение при новой сборке?
5. Offline-first SPA: что работает без сети?

🔗 Мостик к Модулю 6

PWA готов. Добавим push-уведомления.