Добавлена Яндекс.Капча
This commit is contained in:
@@ -18,10 +18,10 @@ npm run web
|
||||
|
||||
| Файл | Размер |
|
||||
|---|---|
|
||||
| `favicon.png` | 64×64 |
|
||||
| `icon-192.png` | 360×360 |
|
||||
| `icon-512.png` | 1024×1024 |
|
||||
| `apple-touch-icon.png` | 360×360 |
|
||||
| `favicon.png` | 32×32 |
|
||||
| `icon-192.png` | 192×192 |
|
||||
| `icon-512.png` | 512×512 |
|
||||
| `apple-touch-icon.png` | 180×180 |
|
||||
|
||||
Manifest: `public/manifest.json`. Корневой HTML: `app/+html.tsx`.
|
||||
|
||||
@@ -34,7 +34,7 @@ Chrome DevTools → Application → Manifest / Service Workers. API и `/auth/`
|
||||
|
||||
## Production
|
||||
|
||||
`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.
|
||||
`npm run build:pwa` создаёт `dist/` с manifest, иконками и `service-worker.js`. Каталог монтируется в корневой nginx; отдельный frontend nginx не используется. Для SPA nginx должен применять `try_files $uri /index.html`, не кэшировать `index.html`, `manifest.json`, `register-sw.js`, `service-worker.js` и бессрочно кэшировать hashed assets.
|
||||
|
||||
Dockerfile собирает статический OCI-артефакт `/dist` без runtime-сервера:
|
||||
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
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 (
|
||||
<html lang="ru">
|
||||
@@ -19,9 +11,9 @@ export default function Root({ children }: PropsWithChildren) {
|
||||
<meta name="theme-color" content="#030213" />
|
||||
<meta name="description" content="Помощник по документам и жизни в России" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="/favicon.png" />
|
||||
<link rel="apple-touch-icon" sizes="360x360" href="/apple-touch-icon.png" />
|
||||
<script dangerouslySetInnerHTML={{ __html: serviceWorkerBootstrap }} />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<script src="/register-sw.js" defer />
|
||||
<ScrollViewStyleReset />
|
||||
</head>
|
||||
<body>{children}</body>
|
||||
|
||||
@@ -7,7 +7,7 @@ export default defineConfig({
|
||||
reporter: "list",
|
||||
use: { baseURL: "http://127.0.0.1:4173", trace: "on-first-retry" },
|
||||
webServer: {
|
||||
command: "npm run build && npx serve dist -l 4173",
|
||||
command: "npm run build:pwa && npx serve dist -l 4173",
|
||||
url: "http://127.0.0.1:4173",
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120_000,
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
{
|
||||
"short_name": "HAN Chat",
|
||||
"name": "HAN Chat",
|
||||
"id": "/",
|
||||
"description": "Помощник по документам и жизни в России",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon.png",
|
||||
"sizes": "64x64",
|
||||
"sizes": "32x32",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icon-192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "360x360",
|
||||
"sizes": "192x192",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/icon-512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "1024x1024",
|
||||
"sizes": "512x512",
|
||||
"purpose": "any maskable"
|
||||
}
|
||||
],
|
||||
@@ -27,5 +28,7 @@
|
||||
"orientation": "portrait",
|
||||
"theme_color": "#030213",
|
||||
"background_color": "#ffffff",
|
||||
"lang": "ru"
|
||||
"lang": "ru",
|
||||
"dir": "ltr",
|
||||
"categories": ["productivity", "utilities"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", () => {
|
||||
navigator.serviceWorker
|
||||
.register("/service-worker.js", { updateViaCache: "none" })
|
||||
.catch((error) => console.warn("Service worker registration failed:", error));
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,23 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("PWA-манифест и Service Worker доступны", async ({ request }) => {
|
||||
const manifestResponse = await request.get("/manifest.json");
|
||||
expect(manifestResponse.ok()).toBe(true);
|
||||
const manifest = await manifestResponse.json();
|
||||
expect(manifest).toMatchObject({
|
||||
name: "HAN Chat",
|
||||
display: "standalone",
|
||||
icons: expect.arrayContaining([
|
||||
expect.objectContaining({ src: "/icon-192.png", sizes: "192x192" }),
|
||||
expect.objectContaining({ src: "/icon-512.png", sizes: "512x512" }),
|
||||
]),
|
||||
});
|
||||
|
||||
const serviceWorkerResponse = await request.get("/service-worker.js");
|
||||
expect(serviceWorkerResponse.ok()).toBe(true);
|
||||
expect(await serviceWorkerResponse.text()).toContain("precacheAndRoute");
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.route("**/api/v1/public/app-config", (route) => route.fulfill({
|
||||
json: {
|
||||
|
||||
@@ -4,6 +4,7 @@ module.exports = {
|
||||
globPatterns: ["**/*.{js,css,html,ico,png,json,woff2,svg,webp}"],
|
||||
globIgnores: ["**/node_modules/**"],
|
||||
swDest: "dist/service-worker.js",
|
||||
cleanupOutdatedCaches: true,
|
||||
navigateFallback: "/index.html",
|
||||
navigateFallbackDenylist: [/^\/api/, /^\/auth/],
|
||||
navigateFallbackDenylist: [/^\/api(?:\/|$)/, /^\/auth(?:\/|$)/],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user