diff --git a/frontend/src/components/payment/StripePaymentInline.vue b/frontend/src/components/payment/StripePaymentInline.vue index 65f5bb18c..48b0748e7 100644 --- a/frontend/src/components/payment/StripePaymentInline.vue +++ b/frontend/src/components/payment/StripePaymentInline.vue @@ -111,7 +111,7 @@ let elementsInstance: StripeElements | null = null onMounted(async () => { try { - const { loadStripe } = await import('@stripe/stripe-js') + const { loadStripe } = await import('@stripe/stripe-js/pure') const stripe = await loadStripe(props.publishableKey) if (!stripe) { initError.value = t('payment.stripeLoadFailed'); return } diff --git a/frontend/src/views/user/StripePaymentView.vue b/frontend/src/views/user/StripePaymentView.vue index 56a5bc2b6..840eea912 100644 --- a/frontend/src/views/user/StripePaymentView.vue +++ b/frontend/src/views/user/StripePaymentView.vue @@ -165,7 +165,7 @@ onMounted(async () => { const publishableKey = paymentStore.config?.stripe_publishable_key if (!publishableKey) { initError.value = t('payment.stripeNotConfigured'); return } - const { loadStripe } = await import('@stripe/stripe-js') + const { loadStripe } = await import('@stripe/stripe-js/pure') const stripe = await loadStripe(publishableKey) if (!stripe) { initError.value = t('payment.stripeLoadFailed'); return } diff --git a/frontend/src/views/user/StripePopupView.vue b/frontend/src/views/user/StripePopupView.vue index 0e852e520..0c3629c54 100644 --- a/frontend/src/views/user/StripePopupView.vue +++ b/frontend/src/views/user/StripePopupView.vue @@ -137,7 +137,7 @@ async function initStripe(clientSecret: string, publishableKey: string) { return } try { - const { loadStripe } = await import('@stripe/stripe-js') + const { loadStripe } = await import('@stripe/stripe-js/pure') const stripe = await loadStripe(publishableKey) if (!stripe) { error.value = t('payment.stripeLoadFailed'); return } diff --git a/frontend/src/views/user/__tests__/StripePaymentView.spec.ts b/frontend/src/views/user/__tests__/StripePaymentView.spec.ts index 1d53c4994..9b672048b 100644 --- a/frontend/src/views/user/__tests__/StripePaymentView.spec.ts +++ b/frontend/src/views/user/__tests__/StripePaymentView.spec.ts @@ -56,7 +56,7 @@ vi.mock('@/api/payment', () => ({ }, })) -vi.mock('@stripe/stripe-js', () => ({ +vi.mock('@stripe/stripe-js/pure', () => ({ loadStripe, })) diff --git a/frontend/src/views/user/__tests__/stripeLazyLoading.spec.ts b/frontend/src/views/user/__tests__/stripeLazyLoading.spec.ts new file mode 100644 index 000000000..be1c1f59b --- /dev/null +++ b/frontend/src/views/user/__tests__/stripeLazyLoading.spec.ts @@ -0,0 +1,33 @@ +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { describe, expect, it } from 'vitest' + +const frontendRoot = resolve(__dirname, '../../../..') +const stripeConsumers = [ + 'src/views/user/StripePaymentView.vue', + 'src/views/user/StripePopupView.vue', + 'src/components/payment/StripePaymentInline.vue', +] + +function readFrontendFile(path: string): string { + return readFileSync(resolve(frontendRoot, path), 'utf8') +} + +describe('Stripe lazy-loading contract', () => { + it.each(stripeConsumers)('%s uses the side-effect-free Stripe loader', (path) => { + const source = readFrontendFile(path) + + expect(source).toContain("await import('@stripe/stripe-js/pure')") + expect(source).not.toMatch(/await import\(['"]@stripe\/stripe-js['"]\)/) + }) + + it('keeps Stripe out of the shared vendor chunk', () => { + const viteConfig = readFrontendFile('vite.config.ts') + const stripeRule = viteConfig.indexOf("id.includes('/@stripe/stripe-js/')") + const miscFallback = viteConfig.indexOf("return 'vendor-misc'") + + expect(stripeRule).toBeGreaterThan(-1) + expect(viteConfig.slice(stripeRule, miscFallback)).toContain("return 'vendor-stripe'") + expect(stripeRule).toBeLessThan(miscFallback) + }) +}) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 387707045..303a4bf77 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -96,6 +96,11 @@ export default defineConfig(({ mode }) => { return 'vendor-i18n' } + // Stripe 仅在支付流程中按需加载,避免进入首页公共依赖。 + if (id.includes('/@stripe/stripe-js/')) { + return 'vendor-stripe' + } + // 其他小型第三方库合并 return 'vendor-misc' }