Когда PWA упирается в потолок стора, тот же React-код можно отдать в App Store и Google Play, не переписывая на Swift и Kotlin. Этим занимается Capacitor — основной инструмент, который берёт собранное веб-приложение, кладёт его в нативный контейнер с WebView и даёт мост к нативным API устройства. Для продукт-инженера это способ закрыть мобильную платформу той же кодовой базой, которой уже сделан frontend.
Что такое Capacitor
Capacitor — runtime от команды Ionic. Он не фреймворк UI и не заменяет React: приложение остаётся обычным веб-проектом, который собирается в статику. Capacitor оборачивает эту статику в нативный контейнер: на устройстве запускается системный WebView (WKWebView на iOS, WebView на Android), внутри которого крутится привычный JavaScript. Сверху Capacitor добавляет мост (bridge) — канал, через который веб-код вызывает нативные возможности: камеру, геолокацию, файловую систему, push.
В отличие от чистой PWA, результат — настоящий нативный бинарь (.ipa и .apk/.aab), который проходит ревью сторов и ставится из них. Веб-слой при этом не теряется: те же исходники работают и в браузере, и в обёртке.
Устройство проекта: web, ios, android
Стартует всё с веб-проекта. Capacitor подключается двумя пакетами и инициализацией:
npm install @capacitor/core
npm install -D @capacitor/cli
npx cap init
npx cap init спрашивает имя приложения, appId (обратный домен вида ru.company.app) и записывает конфиг. После сборки веба добавляются нативные платформы — каждая отдельным npm-пакетом и отдельной командой:
npm install @capacitor/ios @capacitor/android
npx cap add ios
npx cap add android
Команда add создаёт реальные нативные проекты в каталогах ios/ и android/ рядом с веб-кодом. Это ключевое: проекты под Xcode и Android Studio лежат в репозитории и принадлежат разработчику — их можно открывать, править, добавлять нативные зависимости. Конфигурация живёт в capacitor.config.ts:
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'ru.company.app',
appName: 'My App',
webDir: 'dist',
};
export default config;
webDir указывает на каталог веб-сборки (dist, build, www — зависит от сборщика); именно его содержимое Capacitor копирует в нативные проекты.
Плагины и нативный мост
Доступ к нативным API даёт мост и плагины. Плагин — это обычный npm-пакет вида @capacitor/<что-то>, который на JS-стороне выглядит как промис-функция, а под капотом дёргает нативный код через мост. Официальные плагины ставятся как зависимости и подключаются командой npx cap sync:
npm install @capacitor/camera
npx cap sync
В коде плагин — это импорт и вызов. Никакого ручного моста писать не нужно:
import { Camera, CameraResultType } from '@capacitor/camera';
async function takePhoto() {
const photo = await Camera.getPhoto({
quality: 90,
resultType: CameraResultType.Uri,
});
return photo.webPath;
}
Тот же код можно держать платформо-зависимым: на устройстве идёт нативный вызов, в браузере — веб-реализация плагина или запасной вариант. Понять, где исполняется код, помогает проверка среды — ветка ниже срабатывает только на iOS и Android, в браузере управление идёт мимо неё:
import { Capacitor } from '@capacitor/core';
if (Capacitor.isNativePlatform()) {
loadNativeModule();
}
Подробнее про набор нативных возможностей и их веб-аналоги — в статье про нативные API.
Сборка и запуск
Цикл разработки строится вокруг sync. После каждой пересборки веба её нужно перенести в нативные проекты:
npm run build
npx cap sync
npx cap sync делает две вещи: копирует свежую веб-сборку из webDir в нативные проекты и обновляет нативные зависимости (доустанавливает плагины, правит конфиги платформ). Дальше проект открывается в нативной среде для запуска на симуляторе или устройстве и для сборки финального бинаря:
npx cap open ios
npx cap open android
open поднимает Xcode или Android Studio с готовым проектом — оттуда идёт подпись, профили и выкладка в стор. Важно помнить порядок: сначала собрать веб, потом sync, иначе в обёртку попадёт старая версия. Тонкости самих WebView-контейнеров разобраны отдельно — WKWebView на iOS и системный Android WebView. TWA — это не контейнер Capacitor, а отдельный, альтернативный способ упаковки PWA (движок Chrome, Digital Asset Links); см. TWA на Android.
Capacitor против Cordova
Capacitor — идейный наследник Cordova и решает ту же задачу, но иначе. Cordova прячет нативные проекты под platforms/ и генерит их каждый раз заново из конфигурации и хуков — править их руками бессмысленно, всё перезатрётся. Capacitor наоборот: нативные проекты создаются один раз и становятся частью репозитория, разработчик владеет их исходниками и нативными зависимостями напрямую. Мост в Capacitor современнее и быстрее, плагины — обычные npm-пакеты с типами TypeScript, а не XML-конфигурации. При этом совместимость не порвана: большинство старых Cordova-плагинов Capacitor умеет подхватывать. Практический итог — меньше магии, больше контроля над нативной частью, что для боевого приложения в сторе важнее, чем кажется на старте.
Где это в UCP
Capacitor — это та же дисциплина повторного использования, что и в backend: одна кодовая база закрывает несколько каналов доставки, а различия платформ изолированы за тонким мостом. Продукт-инженер выходит в сторы силами frontend-команды, не открывая параллельную ветку нативной разработки, и держит мобильное приложение в общем продуктовом цикле. Когда обёртки уже недостаточно и нужен честный нативный код — этот выбор разбирается в границах нативной разработки.