mirror of https://github.com/langgenius/dify.git
refactor: lazy load large modules (#33888)
This commit is contained in:
parent
1674f8c2fb
commit
0c3d11f920
|
|
@ -8,12 +8,14 @@ import AppListContext from '@/context/app-list-context'
|
|||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
import { useImportDSL } from '@/hooks/use-import-dsl'
|
||||
import { DSLImportMode } from '@/models/app'
|
||||
import dynamic from '@/next/dynamic'
|
||||
import { fetchAppDetail } from '@/service/explore'
|
||||
import DSLConfirmModal from '../app/create-from-dsl-modal/dsl-confirm-modal'
|
||||
import CreateAppModal from '../explore/create-app-modal'
|
||||
import TryApp from '../explore/try-app'
|
||||
import List from './list'
|
||||
|
||||
const DSLConfirmModal = dynamic(() => import('../app/create-from-dsl-modal/dsl-confirm-modal'), { ssr: false })
|
||||
const CreateAppModal = dynamic(() => import('../explore/create-app-modal'), { ssr: false })
|
||||
const TryApp = dynamic(() => import('../explore/try-app'), { ssr: false })
|
||||
|
||||
const Apps = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ import { useDebounceFn } from 'ahooks'
|
|||
import { parseAsStringLiteral, useQueryState } from 'nuqs'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import Input from '@/app/components/base/input'
|
||||
import TabSliderNew from '@/app/components/base/tab-slider-new'
|
||||
import TagFilter from '@/app/components/base/tag-management/filter'
|
||||
import { useStore as useTagStore } from '@/app/components/base/tag-management/store'
|
||||
import CheckboxWithLabel from '@/app/components/datasets/create/website/base/checkbox-with-label'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
|
|
@ -205,12 +205,12 @@ const List: FC<Props> = ({
|
|||
options={options}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckboxWithLabel
|
||||
className="mr-2"
|
||||
label={t('showMyCreatedAppsOnly', { ns: 'app' })}
|
||||
isChecked={isCreatedByMe}
|
||||
onChange={handleCreatedByMeChange}
|
||||
/>
|
||||
<label className="mr-2 flex h-7 items-center space-x-2">
|
||||
<Checkbox checked={isCreatedByMe} onCheck={handleCreatedByMeChange} />
|
||||
<div className="text-sm font-normal text-text-secondary">
|
||||
{t('showMyCreatedAppsOnly', { ns: 'app' })}
|
||||
</div>
|
||||
</label>
|
||||
<TagFilter type="app" value={tagFilterValue} onChange={handleTagsChange} />
|
||||
<Input
|
||||
showLeftIcon
|
||||
|
|
|
|||
|
|
@ -5,17 +5,12 @@ import * as amplitude from '@amplitude/analytics-browser'
|
|||
import { sessionReplayPlugin } from '@amplitude/plugin-session-replay-browser'
|
||||
import * as React from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import { AMPLITUDE_API_KEY, IS_CLOUD_EDITION } from '@/config'
|
||||
import { AMPLITUDE_API_KEY, isAmplitudeEnabled } from '@/config'
|
||||
|
||||
export type IAmplitudeProps = {
|
||||
sessionReplaySampleRate?: number
|
||||
}
|
||||
|
||||
// Check if Amplitude should be enabled
|
||||
export const isAmplitudeEnabled = () => {
|
||||
return IS_CLOUD_EDITION && !!AMPLITUDE_API_KEY
|
||||
}
|
||||
|
||||
// Map URL pathname to English page name for consistent Amplitude tracking
|
||||
const getEnglishPageName = (pathname: string): string => {
|
||||
// Remove leading slash and get the first segment
|
||||
|
|
@ -59,7 +54,7 @@ const AmplitudeProvider: FC<IAmplitudeProps> = ({
|
|||
}) => {
|
||||
useEffect(() => {
|
||||
// Only enable in Saas edition with valid API key
|
||||
if (!isAmplitudeEnabled())
|
||||
if (!isAmplitudeEnabled)
|
||||
return
|
||||
|
||||
// Initialize Amplitude
|
||||
|
|
|
|||
|
|
@ -2,14 +2,24 @@ import * as amplitude from '@amplitude/analytics-browser'
|
|||
import { sessionReplayPlugin } from '@amplitude/plugin-session-replay-browser'
|
||||
import { render } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import AmplitudeProvider, { isAmplitudeEnabled } from '../AmplitudeProvider'
|
||||
import AmplitudeProvider from '../AmplitudeProvider'
|
||||
|
||||
const mockConfig = vi.hoisted(() => ({
|
||||
AMPLITUDE_API_KEY: 'test-api-key',
|
||||
IS_CLOUD_EDITION: true,
|
||||
}))
|
||||
|
||||
vi.mock('@/config', () => mockConfig)
|
||||
vi.mock('@/config', () => ({
|
||||
get AMPLITUDE_API_KEY() {
|
||||
return mockConfig.AMPLITUDE_API_KEY
|
||||
},
|
||||
get IS_CLOUD_EDITION() {
|
||||
return mockConfig.IS_CLOUD_EDITION
|
||||
},
|
||||
get isAmplitudeEnabled() {
|
||||
return mockConfig.IS_CLOUD_EDITION && !!mockConfig.AMPLITUDE_API_KEY
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@amplitude/analytics-browser', () => ({
|
||||
init: vi.fn(),
|
||||
|
|
@ -27,22 +37,6 @@ describe('AmplitudeProvider', () => {
|
|||
mockConfig.IS_CLOUD_EDITION = true
|
||||
})
|
||||
|
||||
describe('isAmplitudeEnabled', () => {
|
||||
it('returns true when cloud edition and api key present', () => {
|
||||
expect(isAmplitudeEnabled()).toBe(true)
|
||||
})
|
||||
|
||||
it('returns false when cloud edition but no api key', () => {
|
||||
mockConfig.AMPLITUDE_API_KEY = ''
|
||||
expect(isAmplitudeEnabled()).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false when not cloud edition', () => {
|
||||
mockConfig.IS_CLOUD_EDITION = false
|
||||
expect(isAmplitudeEnabled()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Component', () => {
|
||||
it('initializes amplitude when enabled', () => {
|
||||
render(<AmplitudeProvider sessionReplaySampleRate={0.8} />)
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import AmplitudeProvider, { isAmplitudeEnabled } from '../AmplitudeProvider'
|
||||
import indexDefault, {
|
||||
isAmplitudeEnabled as indexIsAmplitudeEnabled,
|
||||
resetUser,
|
||||
setUserId,
|
||||
setUserProperties,
|
||||
trackEvent,
|
||||
} from '../index'
|
||||
import {
|
||||
resetUser as utilsResetUser,
|
||||
setUserId as utilsSetUserId,
|
||||
setUserProperties as utilsSetUserProperties,
|
||||
trackEvent as utilsTrackEvent,
|
||||
} from '../utils'
|
||||
|
||||
describe('Amplitude index exports', () => {
|
||||
it('exports AmplitudeProvider as default', () => {
|
||||
expect(indexDefault).toBe(AmplitudeProvider)
|
||||
})
|
||||
|
||||
it('exports isAmplitudeEnabled', () => {
|
||||
expect(indexIsAmplitudeEnabled).toBe(isAmplitudeEnabled)
|
||||
})
|
||||
|
||||
it('exports utils', () => {
|
||||
expect(resetUser).toBe(utilsResetUser)
|
||||
expect(setUserId).toBe(utilsSetUserId)
|
||||
expect(setUserProperties).toBe(utilsSetUserProperties)
|
||||
expect(trackEvent).toBe(utilsTrackEvent)
|
||||
})
|
||||
})
|
||||
|
|
@ -20,8 +20,10 @@ const MockIdentify = vi.hoisted(() =>
|
|||
},
|
||||
)
|
||||
|
||||
vi.mock('../AmplitudeProvider', () => ({
|
||||
isAmplitudeEnabled: () => mockState.enabled,
|
||||
vi.mock('@/config', () => ({
|
||||
get isAmplitudeEnabled() {
|
||||
return mockState.enabled
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@amplitude/analytics-browser', () => ({
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
export { default, isAmplitudeEnabled } from './AmplitudeProvider'
|
||||
export { default } from './lazy-amplitude-provider'
|
||||
export { resetUser, setUserId, setUserProperties, trackEvent } from './utils'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
'use client'
|
||||
|
||||
import type { FC } from 'react'
|
||||
import type { IAmplitudeProps } from './AmplitudeProvider'
|
||||
import dynamic from '@/next/dynamic'
|
||||
|
||||
const AmplitudeProvider = dynamic(() => import('./AmplitudeProvider'), { ssr: false })
|
||||
|
||||
const LazyAmplitudeProvider: FC<IAmplitudeProps> = props => <AmplitudeProvider {...props} />
|
||||
|
||||
export default LazyAmplitudeProvider
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import * as amplitude from '@amplitude/analytics-browser'
|
||||
import { isAmplitudeEnabled } from './AmplitudeProvider'
|
||||
import { isAmplitudeEnabled } from '@/config'
|
||||
|
||||
/**
|
||||
* Track custom event
|
||||
|
|
@ -7,7 +7,7 @@ import { isAmplitudeEnabled } from './AmplitudeProvider'
|
|||
* @param eventProperties Event properties (optional)
|
||||
*/
|
||||
export const trackEvent = (eventName: string, eventProperties?: Record<string, any>) => {
|
||||
if (!isAmplitudeEnabled())
|
||||
if (!isAmplitudeEnabled)
|
||||
return
|
||||
amplitude.track(eventName, eventProperties)
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ export const trackEvent = (eventName: string, eventProperties?: Record<string, a
|
|||
* @param userId User ID
|
||||
*/
|
||||
export const setUserId = (userId: string) => {
|
||||
if (!isAmplitudeEnabled())
|
||||
if (!isAmplitudeEnabled)
|
||||
return
|
||||
amplitude.setUserId(userId)
|
||||
}
|
||||
|
|
@ -27,7 +27,7 @@ export const setUserId = (userId: string) => {
|
|||
* @param properties User properties
|
||||
*/
|
||||
export const setUserProperties = (properties: Record<string, any>) => {
|
||||
if (!isAmplitudeEnabled())
|
||||
if (!isAmplitudeEnabled)
|
||||
return
|
||||
const identifyEvent = new amplitude.Identify()
|
||||
Object.entries(properties).forEach(([key, value]) => {
|
||||
|
|
@ -40,7 +40,7 @@ export const setUserProperties = (properties: Record<string, any>) => {
|
|||
* Reset user (e.g., when user logs out)
|
||||
*/
|
||||
export const resetUser = () => {
|
||||
if (!isAmplitudeEnabled())
|
||||
if (!isAmplitudeEnabled)
|
||||
return
|
||||
amplitude.reset()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
'use client'
|
||||
|
||||
import { IS_DEV } from '@/config'
|
||||
import dynamic from '@/next/dynamic'
|
||||
|
||||
const Agentation = dynamic(() => import('agentation').then(module => module.Agentation), { ssr: false })
|
||||
|
||||
export function AgentationLoader() {
|
||||
if (!IS_DEV)
|
||||
return null
|
||||
|
||||
return <Agentation />
|
||||
}
|
||||
|
|
@ -69,6 +69,7 @@ vi.mock('@/context/i18n', () => ({
|
|||
const { mockConfig, mockEnv } = vi.hoisted(() => ({
|
||||
mockConfig: {
|
||||
IS_CLOUD_EDITION: false,
|
||||
AMPLITUDE_API_KEY: '',
|
||||
ZENDESK_WIDGET_KEY: '',
|
||||
SUPPORT_EMAIL_ADDRESS: '',
|
||||
},
|
||||
|
|
@ -80,6 +81,8 @@ const { mockConfig, mockEnv } = vi.hoisted(() => ({
|
|||
}))
|
||||
vi.mock('@/config', () => ({
|
||||
get IS_CLOUD_EDITION() { return mockConfig.IS_CLOUD_EDITION },
|
||||
get AMPLITUDE_API_KEY() { return mockConfig.AMPLITUDE_API_KEY },
|
||||
get isAmplitudeEnabled() { return mockConfig.IS_CLOUD_EDITION && !!mockConfig.AMPLITUDE_API_KEY },
|
||||
get ZENDESK_WIDGET_KEY() { return mockConfig.ZENDESK_WIDGET_KEY },
|
||||
get SUPPORT_EMAIL_ADDRESS() { return mockConfig.SUPPORT_EMAIL_ADDRESS },
|
||||
IS_DEV: false,
|
||||
|
|
|
|||
|
|
@ -9,16 +9,18 @@ import { flatten } from 'es-toolkit/compat'
|
|||
import { produce } from 'immer'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import CreateAppTemplateDialog from '@/app/components/app/create-app-dialog'
|
||||
import CreateAppModal from '@/app/components/app/create-app-modal'
|
||||
import CreateFromDSLModal from '@/app/components/app/create-from-dsl-modal'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import dynamic from '@/next/dynamic'
|
||||
import { useParams } from '@/next/navigation'
|
||||
import { useInfiniteAppList } from '@/service/use-apps'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import Nav from '../nav'
|
||||
|
||||
const CreateAppTemplateDialog = dynamic(() => import('@/app/components/app/create-app-dialog'), { ssr: false })
|
||||
const CreateAppModal = dynamic(() => import('@/app/components/app/create-app-modal'), { ssr: false })
|
||||
const CreateFromDSLModal = dynamic(() => import('@/app/components/app/create-from-dsl-modal'), { ssr: false })
|
||||
|
||||
const AppNav = () => {
|
||||
const { t } = useTranslation()
|
||||
const { appId } = useParams()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
'use client'
|
||||
|
||||
import { IS_DEV } from '@/config'
|
||||
import { env } from '@/env'
|
||||
import dynamic from '@/next/dynamic'
|
||||
|
||||
const SentryInitializer = dynamic(() => import('./sentry-initializer'), { ssr: false })
|
||||
|
||||
const LazySentryInitializer = () => {
|
||||
if (IS_DEV || !env.NEXT_PUBLIC_SENTRY_DSN)
|
||||
return null
|
||||
|
||||
return <SentryInitializer />
|
||||
}
|
||||
|
||||
export default LazySentryInitializer
|
||||
|
|
@ -2,13 +2,10 @@
|
|||
|
||||
import * as Sentry from '@sentry/react'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { IS_DEV } from '@/config'
|
||||
import { env } from '@/env'
|
||||
|
||||
const SentryInitializer = ({
|
||||
children,
|
||||
}: { children: React.ReactElement }) => {
|
||||
const SentryInitializer = () => {
|
||||
useEffect(() => {
|
||||
const SENTRY_DSN = env.NEXT_PUBLIC_SENTRY_DSN
|
||||
if (!IS_DEV && SENTRY_DSN) {
|
||||
|
|
@ -24,7 +21,7 @@ const SentryInitializer = ({
|
|||
})
|
||||
}
|
||||
}, [])
|
||||
return children
|
||||
return null
|
||||
}
|
||||
|
||||
export default SentryInitializer
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import type { Viewport } from '@/next'
|
||||
import { Agentation } from 'agentation'
|
||||
import { Provider as JotaiProvider } from 'jotai/react'
|
||||
import { ThemeProvider } from 'next-themes'
|
||||
import { NuqsAdapter } from 'nuqs/adapters/next/app'
|
||||
import { IS_DEV } from '@/config'
|
||||
import GlobalPublicStoreProvider from '@/context/global-public-context'
|
||||
import { TanstackQueryInitializer } from '@/context/query-client'
|
||||
import { getDatasetMap } from '@/env'
|
||||
|
|
@ -12,9 +10,10 @@ import { ToastProvider } from './components/base/toast'
|
|||
import { ToastHost } from './components/base/ui/toast'
|
||||
import { TooltipProvider } from './components/base/ui/tooltip'
|
||||
import BrowserInitializer from './components/browser-initializer'
|
||||
import { AgentationLoader } from './components/devtools/agentation-loader'
|
||||
import { ReactScanLoader } from './components/devtools/react-scan/loader'
|
||||
import LazySentryInitializer from './components/lazy-sentry-initializer'
|
||||
import { I18nServerProvider } from './components/provider/i18n-server'
|
||||
import SentryInitializer from './components/sentry-initializer'
|
||||
import RoutePrefixHandle from './routePrefixHandle'
|
||||
import './styles/globals.css'
|
||||
import './styles/markdown.scss'
|
||||
|
|
@ -57,6 +56,7 @@ const LocaleLayout = async ({
|
|||
className="h-full select-auto"
|
||||
{...datasetMap}
|
||||
>
|
||||
<LazySentryInitializer />
|
||||
<div className="isolate h-full">
|
||||
<JotaiProvider>
|
||||
<ThemeProvider
|
||||
|
|
@ -68,26 +68,24 @@ const LocaleLayout = async ({
|
|||
>
|
||||
<NuqsAdapter>
|
||||
<BrowserInitializer>
|
||||
<SentryInitializer>
|
||||
<TanstackQueryInitializer>
|
||||
<I18nServerProvider>
|
||||
<ToastHost timeout={5000} limit={3} />
|
||||
<ToastProvider>
|
||||
<GlobalPublicStoreProvider>
|
||||
<TooltipProvider delay={300} closeDelay={200}>
|
||||
{children}
|
||||
</TooltipProvider>
|
||||
</GlobalPublicStoreProvider>
|
||||
</ToastProvider>
|
||||
</I18nServerProvider>
|
||||
</TanstackQueryInitializer>
|
||||
</SentryInitializer>
|
||||
<TanstackQueryInitializer>
|
||||
<I18nServerProvider>
|
||||
<ToastHost timeout={5000} limit={3} />
|
||||
<ToastProvider>
|
||||
<GlobalPublicStoreProvider>
|
||||
<TooltipProvider delay={300} closeDelay={200}>
|
||||
{children}
|
||||
</TooltipProvider>
|
||||
</GlobalPublicStoreProvider>
|
||||
</ToastProvider>
|
||||
</I18nServerProvider>
|
||||
</TanstackQueryInitializer>
|
||||
</BrowserInitializer>
|
||||
</NuqsAdapter>
|
||||
</ThemeProvider>
|
||||
</JotaiProvider>
|
||||
<RoutePrefixHandle />
|
||||
{IS_DEV && <Agentation />}
|
||||
<AgentationLoader />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ export const AMPLITUDE_API_KEY = getStringConfig(
|
|||
'',
|
||||
)
|
||||
|
||||
export const isAmplitudeEnabled = IS_CLOUD_EDITION && !!AMPLITUDE_API_KEY
|
||||
|
||||
export const IS_DEV = process.env.NODE_ENV === 'development'
|
||||
export const IS_PROD = process.env.NODE_ENV === 'production'
|
||||
|
||||
|
|
|
|||
|
|
@ -1501,11 +1501,6 @@
|
|||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/amplitude/AmplitudeProvider.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/amplitude/utils.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
|
|
|
|||
Loading…
Reference in New Issue