diff --git a/codebase/backend/frontend-test-site/Dockerfile b/codebase/backend/frontend-test-site/Dockerfile index bd24e0f..80574b9 100644 --- a/codebase/backend/frontend-test-site/Dockerfile +++ b/codebase/backend/frontend-test-site/Dockerfile @@ -13,7 +13,7 @@ ENV EXPO_PUBLIC_API_BASE_URL=$EXPO_PUBLIC_API_BASE_URL \ EXPO_PUBLIC_KEYCLOAK_REALM=$EXPO_PUBLIC_KEYCLOAK_REALM \ EXPO_PUBLIC_KEYCLOAK_CLIENT_ID=$EXPO_PUBLIC_KEYCLOAK_CLIENT_ID \ EXPO_PUBLIC_APP_ENV=$EXPO_PUBLIC_APP_ENV -RUN npm run build +RUN npm run build:pwa # One-shot Compose init container copies the immutable export to nginx's volume. FROM alpine:3.22 AS static diff --git a/codebase/backend/frontend-test-site/README.md b/codebase/backend/frontend-test-site/README.md index 147eed4..9a66248 100644 --- a/codebase/backend/frontend-test-site/README.md +++ b/codebase/backend/frontend-test-site/README.md @@ -10,11 +10,31 @@ npm install npm run web ``` -Проверки: `npm test`, `npm run typecheck`, `npm run build`, `npm run test:e2e`. +Проверки: `npm test`, `npm run typecheck`, `npm run build:pwa`, `npm run test:e2e`. + +## PWA + +Иконки лежат в `public/`: + +| Файл | Размер | +|---|---| +| `favicon.png` | 64×64 | +| `icon-192.png` | 360×360 | +| `icon-512.png` | 1024×1024 | +| `apple-touch-icon.png` | 360×360 | + +Manifest: `public/manifest.json`. Корневой HTML: `app/+html.tsx`. + +```bash +npm run build:pwa # export + service-worker.js (Workbox) +npm run serve # локальная проверка dist/ +``` + +Chrome DevTools → Application → Manifest / Service Workers. API и `/auth/` не кэшируются. ## Production -`npm run build` создаёт `dist/`. Каталог монтируется в корневой nginx системы; отдельный frontend nginx не используется. Для SPA nginx должен применять `try_files $uri /index.html`, не кэшировать `index.html` и бессрочно кэшировать hashed assets. +`npm run build:pwa` создаёт `dist/` с manifest, иконками и `service-worker.js`. Каталог монтируется в корневой nginx; отдельный frontend nginx не используется. Для SPA nginx должен применять `try_files $uri /index.html`, не кэшировать `index.html`, `manifest.json`, `service-worker.js` и бессрочно кэшировать hashed assets. Dockerfile собирает статический OCI-артефакт `/dist` без runtime-сервера: diff --git a/codebase/backend/frontend-test-site/app.config.ts b/codebase/backend/frontend-test-site/app.config.ts index 900b6bc..22a0ddf 100644 --- a/codebase/backend/frontend-test-site/app.config.ts +++ b/codebase/backend/frontend-test-site/app.config.ts @@ -9,7 +9,18 @@ const config: ExpoConfig = { userInterfaceStyle: "light", experiments: { typedRoutes: true }, plugins: ["expo-router", "expo-secure-store"], - web: { bundler: "metro", output: "static" }, + web: { + bundler: "metro", + output: "static", + shortName: "HAN Chat", + name: "HAN Chat", + description: "Помощник по документам и жизни в России", + display: "standalone", + orientation: "portrait", + backgroundColor: "#ffffff", + themeColor: "#030213", + favicon: "./public/favicon.png", + }, }; export default config; diff --git a/codebase/backend/frontend-test-site/app/+html.tsx b/codebase/backend/frontend-test-site/app/+html.tsx new file mode 100644 index 0000000..c35b697 --- /dev/null +++ b/codebase/backend/frontend-test-site/app/+html.tsx @@ -0,0 +1,30 @@ +import { ScrollViewStyleReset } from "expo-router/html"; +import type { PropsWithChildren } from "react"; + +const serviceWorkerBootstrap = ` +if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/service-worker.js').catch(() => {}); + }); +} +`; + +export default function Root({ children }: PropsWithChildren) { + return ( + + + + + + + + + + +