Глава 1.7: Гибридные фреймворки и обёртки

Cordova, Capacitor, React Native WebView, Flutter и PWA

Глава 1.7 • Часть 1 из 8

Гибридные фреймворки и обёртки

Многие приложения используют не «голый» WebView, а фреймворки-обёртки. Каждый из них имеет свои особенности, которые нужно учитывать при тестировании.

1 Apache Cordova / Adobe PhoneGap

Apache Cordova — это один из старейших гибридных фреймворков (с 2009 года). Позволяет создавать мобильные приложения, используя HTML, CSS и JavaScript, с доступом к нативным API через плагины.

Adobe PhoneGap — это дистрибутив Cordova от Adobe (закрыт в 2020 году). Сейчас используется только Apache Cordova.

Архитектура Cordova:

Веб-слой
HTML/CSS/JS
Ваш веб-контент
Cordova Layer
cordova.js
Мост между JS и нативкой
Плагины
Native Plugins
Камера, геолокация, файлы
WebView
Системный WebView
Android WebView / iOS WKWebView

Ключевые особенности Cordova:

  • Использует системный WebView: На Android — Android WebView, на iOS — WKWebView.
  • Плагины для доступа к железу: Камера, геолокация, файлы, уведомления — всё через плагины.
  • Конфигурация через config.xml: Все настройки приложения в одном файле.
  • CLI для сборки: cordova build android, cordova build ios.
  • Поддержка множества платформ: Android, iOS, Windows, BlackBerry и др.

Пример структуры проекта Cordova:

Структура проекта Cordova
my-app/
├── config.xml              # Конфигурация приложения
├── www/                    # Веб-контент
│   ├── index.html
│   ├── css/
│   ├── js/
│   └── img/
├── platforms/              # Нативные проекты
│   ├── android/
│   └── ios/
├── plugins/                # Установленные плагины
│   ├── cordova-plugin-camera/
│   └── cordova-plugin-geolocation/
└── hooks/                  # Скрипты для сборки

Пример config.xml:

config.xml — конфигурация Cordova
<?xml version='1.0' encoding='utf-8'?>
<widget id="com.example.myapp" version="1.0.0" xmlns="http://www.w3.org/ns/widgets">
    <name>MyApp</name>
    <description>My Cordova App</description>
    <author>Me</author>
    
    <content src="index.html" />
    
    <!-- Настройки WebView -->
    <preference name="android-minSdkVersion" value="21" />
    <preference name="android-targetSdkVersion" value="33" />
    <preference name="deployment-target" value="12.0" />
    
    <!-- Разрешения -->
    <plugin name="cordova-plugin-camera" spec="^6.0.0" />
    <plugin name="cordova-plugin-geolocation" spec="^4.1.0" />
    
    <!-- Доступ к доменам -->
    <access origin="*" />
    <allow-navigation href="*" />
</widget>

Пример использования плагина камеры:

JavaScript — использование плагина камеры
// Ждём загрузки Cordova
document.addEventListener('deviceready', onDeviceReady, false);

function onDeviceReady() {
    console.log('Cordova готова к работе');
}

// Функция для съёмки фото
function takePicture() {
    navigator.camera.getPicture(onSuccess, onFail, {
        quality: 50,
        destinationType: Camera.DestinationType.FILE_URI,
        sourceType: Camera.PictureSourceType.CAMERA
    });
}

function onSuccess(imageURI) {
    console.log('Фото сделано:', imageURI);
    document.getElementById('myImage').src = imageURI;
}

function onFail(message) {
    console.error('Ошибка:', message);
}

Типичные баги Cordova:

  • Проблема с deviceready: Если не дождаться события deviceready, плагины не будут работать.
  • Конфликт версий плагинов: Разные плагины могут требовать разные версии нативных библиотек.
  • Проблемы с разрешениями: На Android 6.0+ нужно запрашивать runtime permissions.
  • Проблема с CORS: При загрузке локальных файлов может возникать CORS-ошибка.
  • Устаревшие плагины: Многие плагины не обновляются и не поддерживают новые версии ОС.
⚠️ Cordova устаревает

Cordova — это старая технология. Если вы начинаете новый проект, рассмотрите Capacitor — современную альтернативу от той же команды Ionic. Cordova всё ещё используется в старых проектах, но для новых лучше выбрать Capacitor.

💡 Чек-лист для тестирования Cordova
  • ✅ Проверьте, что событие deviceready срабатывает на всех платформах
  • ✅ Протестируйте все плагины на реальных устройствах (не только в браузере)
  • ✅ Проверьте разрешения на Android 6.0+ и iOS 14+
  • ✅ Протестируйте работу в фоне (background mode)
  • ✅ Проверьте совместимость с разными версиями Android/iOS
  • ✅ Убедитесь, что плагины обновлены до последних версий

2 Capacitor (от Ionic)

Capacitor — это современная альтернатива Cordova, разработанная командой Ionic. Запущен в 2019 году и быстро набирает популярность благодаря более современному подходу и лучшей поддержке.

Ключевые отличия Capacitor от Cordova:

  • Нативные проекты в репозитории: Папки android/ и ios/ — это полноценные нативные проекты, которые можно редактировать в Android Studio и Xcode.
  • Нет config.xml: Конфигурация через capacitor.config.json и нативные файлы (AndroidManifest.xml, Info.plist).
  • Современные API: Использует современные веб-стандарты (Promises, async/await).
  • Лучшая производительность: Более эффективная работа с WebView.
  • Активная разработка: Регулярные обновления, поддержка новых версий ОС.

Архитектура Capacitor:

Веб-слой
HTML/CSS/JS
Ваш веб-контент (React, Vue, Angular, etc.)
Capacitor API
@capacitor/core
Современный JavaScript API
Плагины
Native Plugins
Камера, геолокация, файлы, уведомления
WebView
Системный WebView
Android WebView / iOS WKWebView

Пример структуры проекта Capacitor:

Структура проекта Capacitor
my-app/
├── capacitor.config.json   # Конфигурация Capacitor
├── package.json            # Зависимости Node.js
├── src/                    # Веб-контент (React/Vue/Angular)
│   ├── index.html
│   ├── App.jsx
│   └── ...
├── android/                # Нативный проект Android (полноценный)
│   ├── app/
│   ├── build.gradle
│   └── ...
├── ios/                    # Нативный проект iOS (полноценный)
│   ├── App/
│   ├── Podfile
│   └── ...
└── node_modules/

Пример capacitor.config.json:

capacitor.config.json
{
  "appId": "com.example.myapp",
  "appName": "MyApp",
  "webDir": "dist",
  "bundledWebRuntime": false,
  "server": {
    "androidScheme": "https"
  },
  "plugins": {
    "SplashScreen": {
      "launchShowDuration": 0
    },
    "Keyboard": {
      "resize": "body"
    }
  }
}

Пример использования Capacitor API:

JavaScript — использование Capacitor API
import { Camera, CameraResultType } from '@capacitor/camera';
import { Geolocation } from '@capacitor/geolocation';
import { Toast } from '@capacitor/toast';

// Съёмка фото
async function takePicture() {
  const image = await Camera.getPhoto({
    quality: 90,
    allowEditing: true,
    resultType: CameraResultType.Uri
  });
  
  console.log('Фото:', image.webPath);
  document.getElementById('myImage').src = image.webPath;
}

// Получение геолокации
async function getCurrentPosition() {
  const coordinates = await Geolocation.getCurrentPosition();
  console.log('Координаты:', coordinates);
  
  await Toast.show({
    text: `Широта: ${coordinates.coords.latitude}`,
    duration: 'short'
  });
}

// Проверка разрешений
async function checkPermissions() {
  const permissions = await Camera.checkPermissions();
  if (permissions.camera !== 'granted') {
    await Camera.requestPermissions();
  }
}

Преимущества Capacitor перед Cordova:

Критерий Cordova Capacitor
Нативные проекты Генерируются при сборке Хранятся в репозитории, можно редактировать
Конфигурация config.xml capacitor.config.json + нативные файлы
API Callback-based Promise-based (async/await)
Поддержка фреймворков Ограниченная React, Vue, Angular, Svelte, vanilla JS
Производительность Средняя Выше
Активность разработки Низкая Высокая
Документация Устаревшая Современная, подробная

Типичные баги Capacitor:

  • Проблема с webDir: Если неправильно указан webDir в конфиге, веб-контент не загрузится.
  • Проблема с androidScheme: На Android 9+ нужно использовать https схему.
  • Проблема с Live Reload: При разработке с ionic cap run может не работать hot reload.
  • Конфликт зависимостей: Нативные зависимости (CocoaPods, Gradle) могут конфликтовать.
  • Проблема с плагинами: Некоторые плагины Cordova несовместимы с Capacitor.
💡 Чек-лист для тестирования Capacitor
  • ✅ Проверьте capacitor.config.json на корректность
  • ✅ Убедитесь, что нативные проекты (android/, ios/) синхронизированы (npx cap sync)
  • ✅ Протестируйте все плагины на реальных устройствах
  • ✅ Проверьте разрешения на Android и iOS
  • ✅ Протестируйте работу в фоне и при сворачивании
  • ✅ Проверьте совместимость с разными версиями Android/iOS
  • ✅ Убедитесь, что используется https схема на Android

3 React Native WebView

React Native WebView — это компонент для React Native, который позволяет встраивать WebView в приложения на React Native. Это один из самых популярных способов использования WebView в кроссплатформенных приложениях.

Ключевые особенности:

  • Компонент React: Используется как обычный React-компонент (<WebView />).
  • JavaScript Bridge: Встроенный механизм для связи между JS и нативным кодом.
  • Поддержка всех фич WebView: Кэширование, cookies, навигация, жесты.
  • Кроссплатформенность: Один и тот же код для Android и iOS.
  • Активная разработка: Поддерживается сообществом React Native.

Пример использования React Native WebView:

React Native — базовый пример
import React from 'react';
import { WebView } from 'react-native-webview';

function MyWebScreen() {
  return (
    <WebView
      source={{ uri: 'https://example.com' }}
      style={{ flex: 1 }}
      javaScriptEnabled={true}
      domStorageEnabled={true}
      startInLoadingState={true}
      // Обработка навигации
      onShouldStartLoadWithRequest={(request) => {
        // Блокируем внешние ссылки
        if (request.url.startsWith('https://external.com')) {
          Linking.openURL(request.url);
          return false;
        }
        return true;
      }}
      // Обработка ошибок
      onError={(syntheticEvent) => {
        const { nativeEvent } = syntheticEvent;
        console.error('WebView error:', nativeEvent);
      }}
      // Обработка загрузки
      onLoadEnd={() => {
        console.log('WebView loaded');
      }}
    />
  );
}

export default MyWebScreen;

Пример JavaScript Bridge:

React Native — JavaScript Bridge
import React, { useRef } from 'react';
import { WebView } from 'react-native-webview';

function BridgeExample() {
  const webViewRef = useRef(null);

  // Отправка сообщения из React Native в WebView
  const sendMessageToWeb = () => {
    const message = JSON.stringify({ type: 'greeting', text: 'Hello from RN!' });
    webViewRef.current?.injectJavaScript(`
      window.receiveMessage(${message});
      true; // Обязательно верните true
    `);
  };

  // Получение сообщения из WebView
  const handleMessage = (event) => {
    const data = JSON.parse(event.nativeEvent.data);
    console.log('Message from web:', data);
  };

  return (
    <WebView
      ref={webViewRef}
      source={{ uri: 'https://example.com' }}
      onMessage={handleMessage}
      injectedJavaScript={`
        // Функция для получения сообщений от React Native
        window.receiveMessage = function(data) {
          console.log('Received:', data);
          // Отправляем ответ обратно
          window.ReactNativeWebView.postMessage(JSON.stringify({
            type: 'response',
            text: 'Hello from web!'
          }));
        };
        true;
      `}
    />
  );
}

Пример в HTML (веб-сторона):

HTML/JavaScript — веб-сторона Bridge
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
  <button onclick="sendToRN()">Отправить в React Native</button>
  
  <script>
    // Отправка сообщения в React Native
    function sendToRN() {
      const message = { type: 'buttonClick', value: 42 };
      window.ReactNativeWebView.postMessage(JSON.stringify(message));
    }
    
    // Получение сообщения от React Native
    window.receiveMessage = function(data) {
      console.log('Получено от RN:', data);
      alert('Получено: ' + data.text);
    };
  </script>
</body>
</html>

Ключевые пропсы React Native WebView:

Пропс Описание Пример
source URL или HTML для загрузки {`{ uri: 'https://example.com' }`}
javaScriptEnabled Включить JavaScript true
domStorageEnabled Включить DOM Storage (Android) true
onMessage Обработчик сообщений из WebView {`(e) => console.log(e.nativeEvent.data)`}
injectedJavaScript JS-код для внедрения при загрузке {`"console.log('Injected'); true;"`}
onShouldStartLoadWithRequest Контроль навигации {`(req) => req.url.startsWith('https://')`}
originWhitelist Разрешённые origin для навигации {`['https://*', 'http://*']`}
allowsInlineMediaPlayback Видео inline (iOS) true
mediaPlaybackRequiresUserAction Автовоспроизведение (iOS) false

Типичные баги React Native WebView:

  • Проблема с injectedJavaScript: Код должен возвращать true в конце, иначе не сработает.
  • Проблема с onMessage: Сообщение должно быть строкой (используйте JSON.stringify).
  • Проблема с навигацией: По умолчанию WebView открывает все ссылки внутри себя. Нужно обрабатывать onShouldStartLoadWithRequest.
  • Проблема с originWhitelist: Если не указан, могут не работать некоторые ссылки.
  • Проблема с производительностью: На старых устройствах WebView может тормозить.
  • Проблема с postMessage: В iOS postMessage работает асинхронно, в Android — синхронно.
⚠️ Важно: react-native-webview vs встроенный WebView

В React Native есть встроенный компонент WebView (из react-native), но он устарел и не рекомендуется к использованию. Всегда используйте react-native-webview — это отдельная библиотека с активной поддержкой.

💡 Чек-лист для тестирования React Native WebView
  • ✅ Проверьте, что используется react-native-webview, а не встроенный WebView
  • ✅ Протестируйте JavaScript Bridge на обеих платформах
  • ✅ Проверьте обработку навигации (внешние ссылки, deep links)
  • ✅ Протестируйте injectedJavaScript на обеих платформах
  • ✅ Проверьте работу с формами и клавиатурой
  • ✅ Протестируйте загрузку файлов (file upload)
  • ✅ Проверьте производительность на старых устройствах

4 Flutter WebView

Flutter WebView — это способ встраивания WebView в приложения на Flutter. Есть два основных пакета: webview_flutter (официальный) и flutter_inappwebview (более мощный).

webview_flutter (официальный пакет):

  • Простой API: Минимальный набор функций для базовых задач.
  • Поддержка Flutter: Официально поддерживается командой Flutter.
  • Ограниченный функционал: Нет продвинутого JavaScript Bridge, кастомизации.

flutter_inappwebview (более мощный):

  • Полный контроль: Все настройки WebView, кастомизация UI.
  • Мощный JavaScript Bridge: Двусторонняя связь, вызов нативных методов.
  • Дополнительные фичи: HTTP auth, cookies, кэш, заголовки.
  • Поддержка headless WebView: Невидимый WebView для фоновых задач.

Пример использования webview_flutter:

Dart — webview_flutter
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class WebViewScreen extends StatefulWidget {
  @override
  _WebViewScreenState createState() => _WebViewScreenState();
}

class _WebViewScreenState extends State<WebViewScreen> {
  late final WebViewController controller;

  @override
  void initState() {
    super.initState();
    
    // Инициализация контроллера
    controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {
            print('Загрузка: $progress%');
          },
          onPageStarted: (String url) {
            print('Страница начала загружаться: $url');
          },
          onPageFinished: (String url) {
            print('Страница загружена: $url');
          },
          onWebResourceError: (WebResourceError error) {
            print('Ошибка: ${error.description}');
          },
          onNavigationRequest: (NavigationRequest request) {
            // Блокируем внешние ссылки
            if (request.url.startsWith('https://external.com')) {
              return NavigationDecision.prevent;
            }
            return NavigationDecision.navigate;
          },
        ),
      )
      ..loadRequest(Uri.parse('https://example.com'));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('WebView')),
      body: WebViewWidget(controller: controller),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          // Выполнение JavaScript
          String title = await controller.runJavaScriptReturningResult('document.title');
          print('Заголовок: $title');
        },
        child: Icon(Icons.refresh),
      ),
    );
  }
}

Пример использования flutter_inappwebview:

Dart — flutter_inappwebview
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';

class InAppWebViewScreen extends StatefulWidget {
  @override
  _InAppWebViewScreenState createState() => _InAppWebViewScreenState();
}

class _InAppWebViewScreenState extends State<InAppWebViewScreen> {
  InAppWebViewController? webViewController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('InAppWebView')),
      body: InAppWebView(
        initialUrlRequest: URLRequest(url: Uri.parse('https://example.com')),
        initialOptions: InAppWebViewGroupOptions(
          crossPlatform: InAppWebViewOptions(
            javaScriptEnabled: true,
            databaseEnabled: true,
          ),
        ),
        onWebViewCreated: (controller) {
          webViewController = controller;
          
          // Регистрация JavaScript Handler
          controller.addJavaScriptHandler(
            handlerName: 'myHandler',
            callback: (args) {
              print('Вызов из JS: ${args[0]}');
              return 'Ответ из Flutter';
            },
          );
        },
        onLoadStop: (controller, url) async {
          print('Страница загружена: $url');
          
          // Внедрение JavaScript
          await controller.evaluateJavascript(source: '''
            window.flutterHandler = function(message) {
              return window.flutter_inappwebview.callHandler('myHandler', message);
            };
          ''');
        },
        onConsoleMessage: (controller, consoleMessage) {
          print('Console: ${consoleMessage.message}');
        },
      ),
    );
  }
}

Сравнение пакетов:

Критерий webview_flutter flutter_inappwebview
Простота использования ✅ Очень простой ⚠️ Более сложный
JavaScript Bridge ⚠️ Базовый ✅ Мощный, двусторонний
Кастомизация ⚠️ Ограниченная ✅ Полная
HTTP Auth ❌ Нет ✅ Есть
Cookies Management ⚠️ Базовый ✅ Продвинутый
Headless WebView ❌ Нет ✅ Есть
Поддержка ✅ Официальная (Flutter team) ✅ Сообщество
Производительность ✅ Хорошая ✅ Хорошая

Типичные баги Flutter WebView:

  • Проблема с Platform Views: На Android WebView — это Platform View, что может вызывать проблемы с производительностью.
  • Проблема с Hybrid Composition: На Android 10+ нужно использовать Hybrid Composition для корректной работы.
  • Проблема с runJavaScript: Возвращает результат как строку, нужно парсить JSON вручную.
  • Проблема с навигацией: По умолчанию все ссылки открываются внутри WebView.
  • Проблема с клавиатурой: На iOS клавиатура может перекрывать input-поля.
  • Проблема с flutter_inappwebview: Более сложный API, больше настроек — больше шансов ошибиться.
💡 Чек-лист для тестирования Flutter WebView
  • ✅ Определите, какой пакет используется (webview_flutter или flutter_inappwebview)
  • ✅ На Android проверьте, используется ли Hybrid Composition
  • ✅ Протестируйте JavaScript Bridge на обеих платформах
  • ✅ Проверьте обработку навигации (внешние ссылки)
  • ✅ Протестируйте работу с формами и клавиатурой
  • ✅ Проверьте производительность на старых устройствах
  • ✅ Протестируйте загрузку файлов (если используется)

5 PWA (Progressive Web Apps)

PWA (Progressive Web Apps) — это веб-приложения, которые используют современные веб-технологии для предоставления нативного опыта. PWA можно «установить» на устройство как обычное приложение, но они работают в браузере (или в WebView).

Ключевые особенности PWA:

  • Manifest.json: Файл с метаданными приложения (имя, иконки, тема).
  • Service Workers: Фоновые скрипты для кэширования, офлайн-работы, push-уведомлений.
  • HTTPS: PWA работают только по HTTPS (кроме localhost).
  • Responsive Design: Адаптивный дизайн для всех размеров экранов.
  • Installable: Можно «установить» на домашний экран.

Пример manifest.json:

manifest.json
{
  "name": "My PWA App",
  "short_name": "MyApp",
  "description": "A Progressive Web App",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#000000",
  "orientation": "portrait",
  "icons": [
    {
      "src": "/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Пример Service Worker:

service-worker.js
// Регистрация Service Worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js')
    .then(registration => {
      console.log('Service Worker зарегистрирован:', registration);
    })
    .catch(error => {
      console.error('Ошибка регистрации:', error);
    });
}

// Кэширование ресурсов
const CACHE_NAME = 'my-app-cache-v1';
const urlsToCache = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js',
  '/icons/icon-192x192.png'
];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => {
        console.log('Кэширование ресурсов');
        return cache.addAll(urlsToCache);
      })
  );
});

// Обработка запросов
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        // Возвращаем кэшированный ресурс или загружаем из сети
        return response || fetch(event.request);
      })
  );
});

PWA vs Нативное приложение:

Критерий PWA Нативное приложение
Установка ✅ Через браузер (без App Store) ✅ Через App Store / Google Play
Обновления ✅ Автоматические ⚠️ Через App Store (с ревью)
Доступ к железу ⚠️ Ограниченный (камера, геолокация) ✅ Полный доступ
Офлайн-работа ✅ Через Service Workers ✅ Нативная
Push-уведомления ✅ На Android, ⚠️ Ограничено на iOS ✅ Полная поддержка
Производительность ⚠️ Зависит от браузера ✅ Нативная производительность
Размер ✅ Очень маленький (несколько МБ) ⚠️ Больше (десятки МБ)
Разработка ✅ Одна кодовая база ⚠️ Две кодовые базы (iOS + Android)

PWA в WebView:

Когда PWA открывается в WebView (например, через Chrome Custom Tabs или SFSafariViewController), оно работает как обычное веб-приложение, но с дополнительными возможностями:

  • Service Workers: Работают, но с ограничениями (не могут регистрироваться из iframe).
  • Кэш: Работает, но может очищаться при нехватке памяти.
  • Push-уведомления: На Android работают, на iOS — только через Safari (не в WebView).
  • Установка: В WebView нельзя «установить» PWA на домашний экран.

Типичные баги PWA:

  • Проблема с Service Workers: Не регистрируются в WebView или в iframe.
  • Проблема с кэшем: Устаревший кэш не обновляется.
  • Проблема с HTTPS: PWA не работают по HTTP (кроме localhost).
  • Проблема с manifest.json: Неправильные пути к иконкам или неправильный формат.
  • Проблема с iOS: На iOS PWA имеют ограничения (нет push-уведомлений в WebView, ограниченная память).
  • Проблема с офлайн-режимом: Service Worker кэширует не все ресурсы.
⚠️ PWA на iOS

На iOS PWA имеют серьёзные ограничения:

  • Нет push-уведомлений (только через Safari, не в WebView).
  • Ограниченная память: PWA могут быть «убиты» системой при нехватке памяти.
  • Нет доступа к Bluetooth, NFC, USB.
  • Service Workers работают, но с ограничениями.

Если ваше приложение требует push-уведомлений или доступа к железу — PWA не подходит для iOS.

💡 Чек-лист для тестирования PWA
  • ✅ Проверьте manifest.json на корректность (используйте Lighthouse)
  • ✅ Убедитесь, что сайт работает по HTTPS
  • ✅ Протестируйте Service Workers (регистрация, кэширование, офлайн)
  • ✅ Проверьте установку на домашний экран (Android, iOS Safari)
  • ✅ Протестируйте офлайн-режим
  • ✅ Проверьте производительность (Lighthouse audit)
  • ✅ Протестируйте на разных браузерах (Chrome, Safari, Firefox)
  • ✅ Проверьте адаптивность на разных размерах экранов
🎯 Когда использовать PWA

PWA подходит, если:

  • Нужно быстро запустить приложение без публикации в App Store
  • Контентное приложение (новости, блоги, документации)
  • Нужна офлайн-работа
  • Не требуется сложный доступ к железу

PWA не подходит, если:

  • Нужны push-уведомления на iOS
  • Нужен доступ к Bluetooth, NFC, USB
  • Нужна высокая производительность (игры, AR/VR)
  • Нужна интеграция с другими приложениями