perf: optimize webapp asset delivery, split admin webapp bundle
This commit is contained in:
+125
-31
@@ -9,7 +9,6 @@
|
||||
|
||||
import BrandMark from "$lib/webapp/BrandMark.svelte";
|
||||
import PreviewBoard from "./PreviewBoard.svelte";
|
||||
import AdminPanel from "./admin/AdminPanel.svelte";
|
||||
import WebAppShell from "./webapp/WebAppShell.svelte";
|
||||
import AuthScreen from "./webapp/auth/AuthScreen.svelte";
|
||||
import PaymentDialogs from "./webapp/PaymentDialogs.svelte";
|
||||
@@ -120,6 +119,9 @@
|
||||
let scrollLockApplied = false;
|
||||
let adminI18nLoaded = false;
|
||||
let adminI18nPromise = null;
|
||||
let AdminPanelComponent = null;
|
||||
let adminBundlePromise = null;
|
||||
let adminBundleError = "";
|
||||
let tg = null;
|
||||
const telegramSdk = createTelegramSdk({
|
||||
scriptUrl: TELEGRAM_WEBAPP_SCRIPT_URL,
|
||||
@@ -485,12 +487,16 @@
|
||||
if (mode === "app") {
|
||||
if (section === "admin" && isAdmin) {
|
||||
const pathAtStart = window.location.pathname;
|
||||
void ensureI18nScope("admin").finally(() => {
|
||||
if (sectionFromPath(window.location.pathname) !== "admin") return;
|
||||
if (window.location.pathname !== pathAtStart) return;
|
||||
activeTab = "settings";
|
||||
screen = "admin";
|
||||
});
|
||||
void Promise.all([ensureI18nScope("admin"), ensureAdminBundle()])
|
||||
.then(() => {
|
||||
if (sectionFromPath(window.location.pathname) !== "admin") return;
|
||||
if (window.location.pathname !== pathAtStart) return;
|
||||
activeTab = "settings";
|
||||
screen = "admin";
|
||||
})
|
||||
.catch(() => {
|
||||
showToast(t("wa_unavailable"));
|
||||
});
|
||||
return;
|
||||
}
|
||||
const nextSection =
|
||||
@@ -608,6 +614,71 @@
|
||||
return adminI18nPromise;
|
||||
}
|
||||
|
||||
function resolveWebappAssetPath(configValue, fallbackName) {
|
||||
const raw = String(configValue || "").trim() || fallbackName;
|
||||
if (/^(?:https?:)?\/\//i.test(raw) || raw.startsWith("data:")) return fallbackName;
|
||||
if (window.location.protocol === "file:" && raw.startsWith("/")) return raw.slice(1);
|
||||
return raw.startsWith("/") ? raw : `/${raw}`;
|
||||
}
|
||||
|
||||
function appendStylesheetOnce(id, href) {
|
||||
if (!href || document.getElementById(id)) return Promise.resolve();
|
||||
return new Promise((resolve, reject) => {
|
||||
const link = document.createElement("link");
|
||||
link.id = id;
|
||||
link.rel = "stylesheet";
|
||||
link.href = href;
|
||||
link.onload = () => resolve();
|
||||
link.onerror = () => reject(new Error(`stylesheet_load_failed:${href}`));
|
||||
document.head.appendChild(link);
|
||||
});
|
||||
}
|
||||
|
||||
function appendScriptOnce(id, src) {
|
||||
if (!src || document.getElementById(id)) return Promise.resolve();
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script");
|
||||
script.id = id;
|
||||
script.src = src;
|
||||
script.async = true;
|
||||
script.onload = () => resolve();
|
||||
script.onerror = () => reject(new Error(`script_load_failed:${src}`));
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureAdminBundle() {
|
||||
if (AdminPanelComponent) return true;
|
||||
if (adminBundlePromise) return adminBundlePromise;
|
||||
|
||||
const existing = window.SubscriptionWebAppAdminPanel;
|
||||
if (existing) {
|
||||
AdminPanelComponent = existing.default || existing;
|
||||
return true;
|
||||
}
|
||||
|
||||
adminBundleError = "";
|
||||
adminBundlePromise = (async () => {
|
||||
const cssHref = resolveWebappAssetPath(CFG.adminCssAsset, "subscription_webapp_admin.css");
|
||||
const jsSrc = resolveWebappAssetPath(CFG.adminJsAsset, "subscription_webapp_admin.js");
|
||||
await appendStylesheetOnce("subscription-webapp-admin-css", cssHref);
|
||||
await appendScriptOnce("subscription-webapp-admin-js", jsSrc);
|
||||
const loaded = window.SubscriptionWebAppAdminPanel;
|
||||
if (!loaded) throw new Error("admin_bundle_missing_component");
|
||||
AdminPanelComponent = loaded.default || loaded;
|
||||
return true;
|
||||
})()
|
||||
.catch((error) => {
|
||||
adminBundleError = error?.message || "admin_bundle_load_failed";
|
||||
throw error;
|
||||
})
|
||||
.finally(() => {
|
||||
adminBundlePromise = null;
|
||||
});
|
||||
|
||||
return adminBundlePromise;
|
||||
}
|
||||
|
||||
async function boot() {
|
||||
await runWebappBoot({
|
||||
MOCK,
|
||||
@@ -702,7 +773,15 @@
|
||||
const initialAdminSection =
|
||||
section === "admin" ? adminSectionFromPath(window.location.pathname) : null;
|
||||
if (section === "admin" && payload.user?.is_admin) {
|
||||
await ensureI18nScope("admin");
|
||||
try {
|
||||
await ensureI18nScope("admin");
|
||||
await ensureAdminBundle();
|
||||
} catch (_error) {
|
||||
void _error;
|
||||
section = "settings";
|
||||
activeTab = "settings";
|
||||
showToast(t("wa_unavailable"));
|
||||
}
|
||||
}
|
||||
const initialSupportTicketId =
|
||||
section === "support" ? supportTicketIdFromPath(window.location.pathname) : null;
|
||||
@@ -1000,7 +1079,14 @@
|
||||
if (!isAdmin) return;
|
||||
clearLanguageClickGuard();
|
||||
billingStore.closePaymentModal();
|
||||
await ensureI18nScope("admin");
|
||||
try {
|
||||
await ensureI18nScope("admin");
|
||||
await ensureAdminBundle();
|
||||
} catch (_error) {
|
||||
void _error;
|
||||
showToast(t("wa_unavailable"));
|
||||
return;
|
||||
}
|
||||
activeTab = "settings";
|
||||
screen = "admin";
|
||||
syncSectionPath("admin", false, adminSectionFromPath(window.location.pathname));
|
||||
@@ -1135,28 +1221,36 @@
|
||||
setPasswordLoginMode={(enabled) => setPasswordLoginMode(enabled)}
|
||||
/>
|
||||
{:else if screen === "admin" && isAdmin}
|
||||
<AdminPanel
|
||||
{api}
|
||||
onClose={closeAdminPanel}
|
||||
onToast={(text) => showToast(text)}
|
||||
initialSection={adminSectionFromPath(window.location.pathname)}
|
||||
initialUserId={adminUserIdFromPath(window.location.pathname)}
|
||||
onSectionChange={handleAdminSectionChange}
|
||||
onSettingsSaved={handleAdminPersistedSaved}
|
||||
onTariffsSaved={handleAdminPersistedSaved}
|
||||
onThemesSaved={handleAdminPersistedSaved}
|
||||
{brandTitle}
|
||||
{brand}
|
||||
appFaviconUrl={CFG.faviconUrl}
|
||||
appFaviconUseCustom={CFG.faviconUseCustom}
|
||||
appVersion={CFG.appVersion}
|
||||
appRepositoryUrl={CFG.appRepositoryUrl}
|
||||
{currentLang}
|
||||
{languageOptions}
|
||||
{languageBusy}
|
||||
onLanguageChange={accountStore.updateAccountLanguage}
|
||||
{t}
|
||||
/>
|
||||
{#if AdminPanelComponent}
|
||||
<svelte:component
|
||||
this={AdminPanelComponent}
|
||||
{api}
|
||||
onClose={closeAdminPanel}
|
||||
onToast={(text) => showToast(text)}
|
||||
initialSection={adminSectionFromPath(window.location.pathname)}
|
||||
initialUserId={adminUserIdFromPath(window.location.pathname)}
|
||||
onSectionChange={handleAdminSectionChange}
|
||||
onSettingsSaved={handleAdminPersistedSaved}
|
||||
onTariffsSaved={handleAdminPersistedSaved}
|
||||
onThemesSaved={handleAdminPersistedSaved}
|
||||
{brandTitle}
|
||||
{brand}
|
||||
appFaviconUrl={CFG.faviconUrl}
|
||||
appFaviconUseCustom={CFG.faviconUseCustom}
|
||||
appVersion={CFG.appVersion}
|
||||
appRepositoryUrl={CFG.appRepositoryUrl}
|
||||
{currentLang}
|
||||
{languageOptions}
|
||||
{languageBusy}
|
||||
onLanguageChange={accountStore.updateAccountLanguage}
|
||||
{t}
|
||||
/>
|
||||
{:else}
|
||||
<div class="loader">
|
||||
<BrandMark {brand} size="md" />
|
||||
<div>{adminBundleError ? t("wa_unavailable") : t("wa_loading")}</div>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<WebAppShell
|
||||
{screen}
|
||||
|
||||
Reference in New Issue
Block a user