Что такое PWA?
PWA (Progressive Web Application) – это веб-приложение, которое можно установить на устройство как обычное приложение. Оно работает офлайн, может отправлять push-уведомления и иметь доступ к некоторым функциям устройства.
Структура файлов PWA приложения
Основные файлы и их назначение:
my-pwa-app/
├── index.html # Основной HTML файл приложения
├── manifest.json # Описание приложения для установки
├── service-worker.js # Управление кэшированием и офлайн-режимом
├── css/
│ └── styles.css # Стили приложения
├── js/
│ └── app.js # Основной JavaScript код
├── icons/ # Иконки разных размеров
│ ├── icon-192.png # Иконка для Android
│ └── icon-512.png # Большая иконка для заставки
└── img/ # Изображения приложения
└── ...
Описание назначения каждого файла:
index.html
- Точка входа в приложение
- Подключение manifest.json и service-worker
- Основная разметка приложения
manifest.json
- Метаданные для установки приложения
- Настройки отображения (полноэкранный режим и т.д.)
- Пути к иконкам
- Цвета темы и фона
service-worker.js
- Кэширование файлов для офлайн-работы
- Перехват сетевых запросов
- Обновление кэша
- Фоновая синхронизация
icons/
- icon-192.png: основная иконка для Android
- icon-512.png: иконка для заставки и магазинов приложений
- Рекомендуется создавать иконки размером 192×192 и 512×512
- Формат PNG с прозрачностью
css/styles.css
- Стили для адаптивного дизайна
- Особые стили для установленного приложения
- Медиа-запросы для разных устройств
js/app.js
- Регистрация service worker
- Основная логика приложения
- Обработка установки приложения
- Работа с кэшем и офлайн-функционалом
Основные компоненты PWA
1. Manifest.json
Создайте файл manifest.json в корне проекта:
{
"name": "Мое PWA Приложение",
"short_name": "PWA App",
"description": "Описание приложения",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
2. Service Worker
Создайте файл service-worker.js:
const CACHE_NAME = 'my-app-cache-v1';
const urlsToCache = [
'/',
'/index.html',
'/styles/main.css',
'/scripts/app.js'
];
// Установка Service Worker
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
return cache.addAll(urlsToCache);
})
);
});
// Перехват запросов
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
return response || fetch(event.request);
})
);
});
3. Подключение в HTML
Добавьте в <head> вашего HTML файла:
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#000000">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="apple-touch-icon" href="/icons/icon-192.png">
4. Регистрация Service Worker
Добавьте в основной JavaScript файл:
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('ServiceWorker зарегистрирован');
})
.catch(error => {
console.log('Ошибка регистрации ServiceWorker:', error);
});
});
}
5. Добавление кнопки установки (необязательно)
Пример:
initializePWA() {
// Проверяем, не запущено ли приложение уже как PWA
const isStandalone = window.matchMedia('(display-mode: standalone)').matches ||
window.navigator.standalone || // для iOS
document.referrer.includes('android-app://');
if (!isStandalone) {
// Создаем контейнер для основных кнопок управления
const mainControls = document.createElement('div');
mainControls.classList.add('main-controls');
// Перемещаем существующие кнопки в новый контейнер
const timerControls = document.querySelector('.timer-controls');
const existingButtons = timerControls.querySelectorAll('button');
existingButtons.forEach(button => mainControls.appendChild(button));
// Добавляем контейнер с основными кнопками
timerControls.appendChild(mainControls);
// Создаем кнопку установки
const installButton = document.createElement('button');
installButton.textContent = document.querySelector('[data-text="install_app"]')?.textContent || 'Установить приложение на устройство';
installButton.classList.add('install-button');
installButton.style.display = 'none';
timerControls.appendChild(installButton);
// Отслеживаем событие beforeinstallprompt
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
this.deferredPrompt = e;
installButton.style.display = 'block';
});
// Добавляем слушатель для отслеживания изменения режима отображения
window.matchMedia('(display-mode: standalone)').addEventListener('change', (e) => {
if (e.matches) {
installButton.style.display = 'none';
}
});
installButton.addEventListener('click', async () => {
if (!this.deferredPrompt) return;
this.deferredPrompt.prompt();
const { outcome } = await this.deferredPrompt.userChoice;
if (outcome === 'accepted') {
installButton.style.display = 'none';
}
this.deferredPrompt = null;
});
}
}
Проверка работоспособности
Откройте Chrome DevTools (F12)
Перейдите во вкладку Application
В разделе Service Workers проверьте, что ваш service worker активен
В разделе Manifest проверьте корректность данных
Тестирование PWA
Загрузите сайт на HTTPS (PWA требует защищенное соединение)
Откройте сайт в Chrome на телефоне
Должно появиться предложение “Добавить на главный экран”
Возможные проблемы
PWA работает только по HTTPS (кроме localhost)
Нужны иконки всех необходимых размеров
Service Worker обновляется только при изменении его кода
Пример из реального проекта
Manifest с настройками приложения:
{
"name": "Pomodoro Timer",
"short_name": "Pomodoro",
"version": "1.0",
"description": "Pomodoro timer with customizable intervals",
"start_url": "/pomodoro",
"scope": "/",
"display": "standalone",
"orientation": "any",
"background_color": "#ffffff",
"theme_color": "#6c5ce7",
"prefer_related_applications": false,
"icons": [
{
"src": "/pomodorosrc/img/pomodoro-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/pomodorosrc/img/pomodoro-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
Service Worker для кэширования:
const CACHE_VERSION = 'v1';
const CACHE_NAME = `pomodoro-cache-${CACHE_VERSION}`;
const urlsToCache = [
'/pomodoro',
'/css/pomodoro.css',
'/css/main.css',
'/scripts/pomodoro.js',
'/sounds/bell.ogg',
'/pomodorosrc/img/pomodoro-192.png',
'/pomodorosrc/img/pomodoro-512.png',
'/pomodorosrc/manifest.json'
];
// Установка Service Worker и кэширование файлов
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
return cache.addAll(urlsToCache)
.catch(error => {
console.error('Ошибка кэширования:', error);
});
})
);
});
// Обработка запросов
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
if (response) {
return response;
}
return fetch(event.request.clone())
.then(response => {
// Кэшируем только успешные ответы
if (response && response.status === 200) {
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
}
return response;
})
.catch(error => {
console.error('Ошибка fetch:', error);
return new Response('Ошибка загрузки');
});
})
);
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
})
);
});
Инициализация в HTML:
<link rel="manifest" href="/pomodorosrc/manifest.json">
<meta name="theme-color" content="#6c5ce7">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Помодоро">
<link rel="apple-touch-icon" href="/productivity/pomodorosrc/img/pomodoro-192.png">
<link rel="apple-touch-startup-image" href="/pomodorosrc/img/pomodoro-512.png">
Важные моменты:
Структура должна быть логичной и понятной:
- Группируйте файлы по типу (css, js, images)
- Используйте говорящие имена файлов
- Соблюдайте консистентность в именовании
Оптимизация:
- Минифицируйте CSS и JavaScript
- Оптимизируйте изображения
- Используйте кэширование эффективно
Безопасность:
- Размещайте service-worker.js в корне сайта
- Используйте HTTPS
- Проверяйте источники данных
Версионирование:
- Ведите учет версий в manifest.json
- Обновляйте версию кэша в service-worker
- Документируйте изменения
Правильная структура файлов помогает:
- Легко поддерживать код
- Быстро находить нужные файлы
- Эффективно управлять кэшированием
- Упростить обновление приложения
Заключение
PWA – отличный способ сделать ваш сайт более доступным для мобильных пользователей.
Основные преимущества:
- Работает офлайн
- Можно установить на устройство
- Занимает мало места
- Обновляется автоматически
Начните с простого manifest.json и базового service worker, затем постепенно добавляйте новые функции по мере необходимости.
Добавить комментарий