perf: defer heavy modules and add performance baselines
Lazy-load Stripe and the chat reply animation, remove the unused Lottie runtime, and add repeatable bundle and Web Vitals baseline tooling.
This commit is contained in:
@@ -12,7 +12,14 @@
|
||||
* - 新消息到达时自动滚到底部
|
||||
* - 用户向上滚动时不强制拉回(保持阅读上下文)
|
||||
*/
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
||||
import {
|
||||
lazy,
|
||||
Suspense,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from "react";
|
||||
|
||||
import type { UiMessage } from "@/data/dto/chat";
|
||||
|
||||
@@ -23,11 +30,15 @@ import {
|
||||
} from "../chat-render-items";
|
||||
import { AiDisclosureBanner } from "./ai-disclosure-banner";
|
||||
import { DateHeader } from "./date-header";
|
||||
import { LottieMessageBubble } from "./lottie-message-bubble";
|
||||
import { MessageBubble } from "./message-bubble";
|
||||
import styles from "./chat-area.module.css";
|
||||
|
||||
const CHAT_AUTO_SCROLL_BOTTOM_THRESHOLD = 96;
|
||||
const ReplyingAnimation = lazy(() =>
|
||||
import("./lottie-message-bubble").then((module) => ({
|
||||
default: module.LottieMessageBubble,
|
||||
})),
|
||||
);
|
||||
|
||||
export interface ChatAreaProps {
|
||||
messages: readonly UiMessage[];
|
||||
@@ -104,7 +115,11 @@ export function ChatArea({
|
||||
onOpenImage,
|
||||
)}
|
||||
|
||||
{isReplyingAI && <LottieMessageBubble />}
|
||||
{isReplyingAI ? (
|
||||
<Suspense fallback={null}>
|
||||
<ReplyingAnimation />
|
||||
</Suspense>
|
||||
) : null}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ export * from "./fullscreen-image-viewer";
|
||||
export * from "./history-unlock-dialog";
|
||||
export * from "./image-bubble";
|
||||
export * from "./insufficient-credits-dialog";
|
||||
export * from "./lottie-message-bubble";
|
||||
export * from "./message-avatar";
|
||||
export * from "./message-bubble";
|
||||
export * from "./message-content";
|
||||
|
||||
@@ -8,4 +8,3 @@ export * from "./subscription-cta-button";
|
||||
export * from "./subscription-payment-method";
|
||||
export * from "./subscription-payment-success-dialog";
|
||||
export * from "./subscription-vip-offer-section";
|
||||
export * from "./stripe-payment-dialog";
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
"use client";
|
||||
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
import type { StripePaymentDialogProps } from "./stripe-payment-dialog";
|
||||
import { stripePaymentDialogStyles as styles } from "./stripe-payment-dialog.styles";
|
||||
|
||||
const StripePaymentDialog = dynamic(
|
||||
() =>
|
||||
import("./stripe-payment-dialog").then(
|
||||
(module) => module.StripePaymentDialog,
|
||||
),
|
||||
{
|
||||
ssr: false,
|
||||
loading: StripePaymentDialogLoading,
|
||||
},
|
||||
);
|
||||
|
||||
export function LazyStripePaymentDialog(props: StripePaymentDialogProps) {
|
||||
return <StripePaymentDialog {...props} />;
|
||||
}
|
||||
|
||||
function StripePaymentDialogLoading() {
|
||||
return (
|
||||
<div
|
||||
className={styles.overlay}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="stripe-payment-loading-title"
|
||||
>
|
||||
<div className={styles.dialog} aria-busy="true">
|
||||
<div className={styles.header}>
|
||||
<h2 id="stripe-payment-loading-title" className={styles.title}>
|
||||
Preparing secure payment
|
||||
</h2>
|
||||
<p className={styles.content}>Loading payment methods...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from "@/stores/payment/payment-context";
|
||||
import { Logger } from "@/utils";
|
||||
|
||||
import { StripePaymentDialog } from "./stripe-payment-dialog";
|
||||
import { LazyStripePaymentDialog } from "./lazy-stripe-payment-dialog";
|
||||
import { stripePaymentDialogStyles as dialogStyles } from "./stripe-payment-dialog.styles";
|
||||
import { SubscriptionCtaButton } from "./subscription-cta-button";
|
||||
|
||||
@@ -95,7 +95,7 @@ export function SubscriptionCheckoutButton({
|
||||
/>
|
||||
) : null}
|
||||
{paymentLaunch.shouldShowStripeDialog && paymentLaunch.stripeClientSecret ? (
|
||||
<StripePaymentDialog
|
||||
<LazyStripePaymentDialog
|
||||
clientSecret={paymentLaunch.stripeClientSecret}
|
||||
onClose={paymentLaunch.handleStripeClose}
|
||||
onConfirmed={paymentLaunch.handleStripeConfirmed}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "@/stores/payment/payment-context";
|
||||
import { Logger } from "@/utils";
|
||||
|
||||
import { StripePaymentDialog } from "../subscription/components/stripe-payment-dialog";
|
||||
import { LazyStripePaymentDialog } from "../subscription/components/lazy-stripe-payment-dialog";
|
||||
import { stripePaymentDialogStyles as dialogStyles } from "../subscription/components/stripe-payment-dialog.styles";
|
||||
import styles from "./tip-screen.module.css";
|
||||
|
||||
@@ -74,7 +74,7 @@ export function TipCheckoutButton({
|
||||
/>
|
||||
) : null}
|
||||
{paymentLaunch.shouldShowStripeDialog && paymentLaunch.stripeClientSecret ? (
|
||||
<StripePaymentDialog
|
||||
<LazyStripePaymentDialog
|
||||
clientSecret={paymentLaunch.stripeClientSecret}
|
||||
returnPath={returnPath}
|
||||
onClose={paymentLaunch.handleStripeClose}
|
||||
|
||||
Reference in New Issue
Block a user