Angular + Capacitor: pusty WebView po sync to webDir, nie framework
Po npx cap sync pusty albo stary WebView? To zwykle webDir: Angular application builder pisze do dist/projekt/browser, nie www ani sam dist/projekt. Build, sync, live-reload.
ng build, potem npx cap sync, odpalasz apkę na telefonie albo emulatorze i dostajesz pusty WebView. Albo, gorzej, stary build sprzed trzech zmian. Angular znów jest niewinny. Capacitor skopiował folder, który mu wskazałeś w webDir, a ten folder albo nie ma index.html na wierzchu, albo trzyma wczorajszy bundle.
Tydzień temu był biały ekran po spakowaniu Electrona: ścieżki dist/.../browser, base href i protokół. Ten odcinek to mobile tej samej historii. Ten sam Angular, inny native shell: Ionic UI opcjonalnie, Capacitor obowiązkowo jako runtime.
Problem: webDir musi trafić w folder z index.html
Capacitor nie buduje frontendu. Bierze już zbudowane assety z katalogu webDir i kopiuje je do projektów Android/iOS przy npx cap sync. Docs mówią wprost: webDir to katalog ze skompilowanymi web assets, a w środku ma leżeć finalne index.html (Capacitor Configuration). Workflow jest prosty: zbuduj web, zsynchronizuj, uruchom (Development Workflow).
Tu wchodzi Angular. Builder application (domyślny od Angulara 17) wrzuca wynik do dist/nazwa-projektu/browser, nie do dist/nazwa-projektu. To ta sama zmiana Output location, o której pisałem przy Electronie: Angular application build system.
A Capacitor przy npx cap init często proponuje www (konwencja starterów Ionic) albo inny domyślny folder frameworka. Oficjalny getting started sam ostrzega: CLI próbuje zgadnąć, ale przy pierwszym sync warto to ręcznie sprawdzić (Installing Capacitor). Gdy sync nie znajduje assetów, docs mówią wprost: popraw webDir.
Efekt w praktyce (przykład, nie anegdota produkcyjna):
webDir: 'www', ang buildpisze dodist/my-app/browser→ sync nie ma czego skopiować albo kopiuje pustkę.webDir: 'dist/my-app'(bezbrowser) → w native ląduje folder z podfolderembrowser, a WebView szukaindex.htmlna wierzchu i dostaje pusty ekran.webDirOK, ale zrobiłeścap syncbez świeżegong build→ na urządzeniu stary UI.
Meat: ustaw webDir i trzymaj kolejność build → sync
Dla zwykłego projektu z ng new (Angular 17+) typowy config wygląda tak:
import type { CapacitorConfig } from "@capacitor/cli";
const config: CapacitorConfig = {
appId: "com.example.myapp",
appName: "My App",
// my-app = nazwa projektu z angular.json; sprawdź outputPath / wynik ng build
webDir: "dist/my-app/browser",
};
export default config;Albo JSON, jeśli nie używasz TypeScriptu w configu:
"appId": "com.example.myapp",
"appName": "My App",
"webDir": "dist/my-app/browser"
}Potem kolejność, której Capacitor oczekuje:
ng build --configuration production
# 2. skopiuj webDir do android/ i ios/ + zaktualizuj natywne zależności
npx cap sync
# 3. odpal na urządzeniu / emulatorze
npx cap run android
# albo: npx cap run iosJeśli startujesz od Ionic CLI, Capacitor jest już w nowym projekcie (ionic start), a wrappery typu ionic capacitor sync robią to samo pod spodem (Using Capacitor with Ionic Framework). Startery Ionic często ustawiają outputPath na www. Wtedy webDir: 'www' jest poprawne. Problem zaczyna się, gdy bierzesz zwykły Angular z dist/.../browser, a config dalej ma Ionicowy www.
Do samego Capacitora Ionic UI nie jest wymagany. Capacitor owija dowolny web bundle. Ionic daje komponenty mobilne (ion-button, ion-tabs, gesty). Jeśli dokładasz Ionic do Angulara i używasz ion-router-outlet, w bootstrapie zwykle ląduje IonicRouteStrategy jako RouteReuseStrategy (tak robią oficjalne scaffoldy, np. Your First Ionic App). To osobny temat od webDir, ale warto wiedzieć, że native shell i routing stack to dwie różne warstwy.
Szybki smoke test po sync (przykład):
ls android/app/src/main/assets/public/index.htmlBrak pliku = zły webDir albo brak buildu przed sync. Plik jest, a ekran pusty = otwórz zdalne debugowanie WebView i patrz na 404 assetów (często stary sync albo zły root).
Pułapki: live-reload, www vs browser, stary sync
Live-reload zostawiony w configu. W server.url możesz wskazać URL dev servera. Docs mówią wprost: to do live-reload, nie do produkcji (Capacitor Configuration). Jeśli zostawisz http://192.168.x.x:4200 w configu i zrobisz release sync, urządzenie będzie celować w laptop, którego nie ma w kieszeni użytkownika. Na czas produkcyjnego sync trzymaj server.url wyłączone.
www nie jest uniwersalne. To konwencja wielu starterów Ionic. Czysty Angular CLI po migracji na application builder pisze do browser. Zawsze zsynchronizuj webDir z realnym outputem ng build, nie z pamięcią mięśniową po tutorialu sprzed dwóch lat.
Sync bez buildu. npx cap sync kopiuje to, co już leży na dysku. Nie uruchamia ng build za Ciebie. Zmieniłeś komponent → najpierw build, potem sync (albo cap copy, jeśli zmieniasz tylko web assets).
Schemat ng add @capacitor/angular. Oficjalna strona Angular + Capacitor pokazuje ng add @capacitor/angular, potem build i npx cap add (Angular & Capacitor). Schematic nie zwalnia z sprawdzenia webDir po pierwszym buildzie.
Ionic CLI wrappers. ionic capacitor sync jest wygodny, ale nadal wymaga poprawnego webDir i świeżego buildu. Wrapper nie naprawi złej ścieżki.
Werdykt
Pusty albo stary WebView po pierwszym cap sync to prawie zawsze webDir i kolejność build → sync, nie „Angular nie lubi mobile”. Ustaw webDir na folder, w którym faktycznie leży index.html (dla nowego Angulara zwykle dist/), buduj przed każdym syncem produkcyjnym i nie zostawiaj server.url z live-reload w release. Ionic UI i IonicRouteStrategy to kolejna warstwa komfortu. Najpierw spraw, żeby Capacitor w ogóle serwował właściwy bundle.
Desktop miał ścieżki po electron-forge make. Mobile ma ścieżki po cap sync. Ten sam Angular, ten sam typ błędu, inny shell.