@therealtinhtute/baroiplayer 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +384 -0
- package/dist/context/media.js +2 -0
- package/dist/context/media.js.map +1 -0
- package/dist/context/media.mjs +15 -0
- package/dist/context/media.mjs.map +1 -0
- package/dist/context/store.js +2 -0
- package/dist/context/store.js.map +1 -0
- package/dist/context/store.mjs +5 -0
- package/dist/context/store.mjs.map +1 -0
- package/dist/context-BMteAJDN.js +1140 -0
- package/dist/context-BMteAJDN.js.map +1 -0
- package/dist/context-DCJ_scmO.cjs +23 -0
- package/dist/context-DCJ_scmO.cjs.map +1 -0
- package/dist/core-events-B5W_j8yU.js +480 -0
- package/dist/core-events-B5W_j8yU.js.map +1 -0
- package/dist/core-events-CN0-Vhhq.cjs +2 -0
- package/dist/core-events-CN0-Vhhq.cjs.map +1 -0
- package/dist/core-plugins-Bq9KLany.js +696 -0
- package/dist/core-plugins-Bq9KLany.js.map +1 -0
- package/dist/core-plugins-CPDSHMiQ.cjs +2 -0
- package/dist/core-plugins-CPDSHMiQ.cjs.map +1 -0
- package/dist/core.js +37 -0
- package/dist/core.js.map +1 -0
- package/dist/core.mjs +1217 -0
- package/dist/core.mjs.map +1 -0
- package/dist/enhanced-components-DHv0MFvG.js +1073 -0
- package/dist/enhanced-components-DHv0MFvG.js.map +1 -0
- package/dist/enhanced-components-DTIObgB7.cjs +2 -0
- package/dist/enhanced-components-DTIObgB7.cjs.map +1 -0
- package/dist/hooks-C_r8wexn.cjs +4 -0
- package/dist/hooks-C_r8wexn.cjs.map +1 -0
- package/dist/hooks-Da2wVXlF.js +1630 -0
- package/dist/hooks-Da2wVXlF.js.map +1 -0
- package/dist/index.css +1 -0
- package/dist/index.js +112 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +6126 -0
- package/dist/index.mjs.map +1 -0
- package/dist/minimal-components-DSq5sQpy.js +2002 -0
- package/dist/minimal-components-DSq5sQpy.js.map +1 -0
- package/dist/minimal-components-DflWiihN.cjs +2 -0
- package/dist/minimal-components-DflWiihN.cjs.map +1 -0
- package/dist/minimal-components.css +1 -0
- package/dist/mona-loading.gif +0 -0
- package/dist/performance-monitor-CUW6f5ll.cjs +2 -0
- package/dist/performance-monitor-CUW6f5ll.cjs.map +1 -0
- package/dist/performance-monitor-rMW1RgV3.js +302 -0
- package/dist/performance-monitor-rMW1RgV3.js.map +1 -0
- package/dist/utils-BnscpYYd.js +2798 -0
- package/dist/utils-BnscpYYd.js.map +1 -0
- package/dist/utils-D_czS_0K.cjs +9 -0
- package/dist/utils-D_czS_0K.cjs.map +1 -0
- package/dist/utils.js +2 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.mjs +7 -0
- package/dist/utils.mjs.map +1 -0
- package/package.json +114 -0
- package/src/adapters/videojs-adapter.tsx +470 -0
- package/src/assets/mona-loading.gif +0 -0
- package/src/components/AnimateLoading.tsx +115 -0
- package/src/components/LoadingState.tsx +248 -0
- package/src/components/accessibility/keyboard-help-overlay.tsx +213 -0
- package/src/components/audio-player.tsx +85 -0
- package/src/components/basic/index.ts +37 -0
- package/src/components/enhanced/advanced-features.tsx +316 -0
- package/src/components/enhanced/debug-panel.tsx +422 -0
- package/src/components/enhanced/enhanced-controls.tsx +600 -0
- package/src/components/enhanced/enhanced-media-player.tsx +509 -0
- package/src/components/enhanced/error-handling.tsx +345 -0
- package/src/components/enhanced/index.ts +39 -0
- package/src/components/enhanced/media-player-enhanced.tsx +349 -0
- package/src/components/examples/basic-video-player.tsx +55 -0
- package/src/components/examples/custom-controls-example.tsx +63 -0
- package/src/components/examples/enhanced-controls-example.tsx +114 -0
- package/src/components/examples/enhanced-media-player-example.tsx +293 -0
- package/src/components/examples/loading-states-demo.tsx +142 -0
- package/src/components/examples/react-19-demo.tsx +381 -0
- package/src/components/examples/volume-orientation-demo.tsx +114 -0
- package/src/components/icon-demo.tsx +209 -0
- package/src/components/icons/index.tsx +166 -0
- package/src/components/icons/svg-icons/ArrowLeftIcon.tsx +18 -0
- package/src/components/icons/svg-icons/ArrowRightIcon.tsx +18 -0
- package/src/components/icons/svg-icons/AudioIcon.tsx +22 -0
- package/src/components/icons/svg-icons/BackwardIcon.tsx +12 -0
- package/src/components/icons/svg-icons/CameraIcon.tsx +19 -0
- package/src/components/icons/svg-icons/CheckIcon.tsx +19 -0
- package/src/components/icons/svg-icons/EnablePiPIcon.tsx +17 -0
- package/src/components/icons/svg-icons/ForwardIcon.tsx +12 -0
- package/src/components/icons/svg-icons/FullscreenEnterIcon.tsx +23 -0
- package/src/components/icons/svg-icons/FullscreenExitIcon.tsx +23 -0
- package/src/components/icons/svg-icons/LoadingIcon.tsx +32 -0
- package/src/components/icons/svg-icons/PauseIcon.tsx +23 -0
- package/src/components/icons/svg-icons/PlayIcon.tsx +21 -0
- package/src/components/icons/svg-icons/PlaybackSpeedIcon.tsx +23 -0
- package/src/components/icons/svg-icons/QualityIcon.tsx +21 -0
- package/src/components/icons/svg-icons/SettingsIcon.tsx +12 -0
- package/src/components/icons/svg-icons/SliderIcon.tsx +21 -0
- package/src/components/icons/svg-icons/SubtitleIcon.tsx +21 -0
- package/src/components/icons/svg-icons/SubtitleOffIcon.tsx +25 -0
- package/src/components/icons/svg-icons/UnPiPIcon.tsx +17 -0
- package/src/components/icons/svg-icons/VolumeMutedIcon.tsx +16 -0
- package/src/components/icons/svg-icons/VolumeOneIcon.tsx +22 -0
- package/src/components/icons/svg-icons/VolumeThreeIcon.tsx +16 -0
- package/src/components/icons/svg-icons/VolumeTwoIcon.tsx +16 -0
- package/src/components/legacy.tsx +93 -0
- package/src/components/media-metadata-display.tsx +100 -0
- package/src/components/media-player-audio.tsx +39 -0
- package/src/components/media-player-controls.tsx +226 -0
- package/src/components/media-player-error.tsx +163 -0
- package/src/components/media-player-fullscreen.tsx +109 -0
- package/src/components/media-player-hls.tsx +111 -0
- package/src/components/media-player-loading.tsx +76 -0
- package/src/components/media-player-play.tsx +75 -0
- package/src/components/media-player-root.tsx +340 -0
- package/src/components/media-player-seek.tsx +168 -0
- package/src/components/media-player-time.tsx +99 -0
- package/src/components/media-player-tooltip.tsx +58 -0
- package/src/components/media-player-video.tsx +39 -0
- package/src/components/media-player-volume.tsx +155 -0
- package/src/components/media-player.tsx +257 -0
- package/src/components/media-suspense.tsx +362 -0
- package/src/components/player-settings-form.tsx +163 -0
- package/src/components/react-player-wrapper.tsx +651 -0
- package/src/components/simple-video-player.tsx +60 -0
- package/src/components/ui/enhanced-seek-bar.tsx +235 -0
- package/src/components/ui/fullscreen-button.tsx +60 -0
- package/src/components/ui/media-resource-preloader.tsx +395 -0
- package/src/components/ui/play-button.tsx +107 -0
- package/src/components/ui/seek-bar.tsx +161 -0
- package/src/components/ui/time-display.tsx +40 -0
- package/src/components/ui/volume-control.tsx +143 -0
- package/src/components/unified/base-media-player.tsx +246 -0
- package/src/components/unified/media-player.tsx +75 -0
- package/src/components/variants/minimal/AccessibleMinimalPlayer.tsx +561 -0
- package/src/components/variants/minimal/EnhancedMinimalMediaPlayer.tsx +552 -0
- package/src/components/variants/minimal/MinimalControls.tsx +142 -0
- package/src/components/variants/minimal/MinimalLoading.tsx +32 -0
- package/src/components/variants/minimal/MinimalMediaPlayer.tsx +315 -0
- package/src/components/variants/minimal/MinimalOverlay.tsx +64 -0
- package/src/components/variants/minimal/MinimalPlayButton.tsx +65 -0
- package/src/components/variants/minimal/MinimalSeekBar.tsx +150 -0
- package/src/components/variants/minimal/MinimalVolumeControl.tsx +80 -0
- package/src/components/variants/minimal/SimpleMinimalPlayer.tsx +210 -0
- package/src/components/variants/minimal/components/CenterPlayButton.tsx +22 -0
- package/src/components/variants/minimal/components/ControlBar.tsx +146 -0
- package/src/components/variants/minimal/components/EnhancedSettingsMenu.tsx +361 -0
- package/src/components/variants/minimal/components/PlaybackControls.tsx +51 -0
- package/src/components/variants/minimal/components/RightControls.tsx +173 -0
- package/src/components/variants/minimal/components/SeekBar.tsx +131 -0
- package/src/components/variants/minimal/components/SettingsDropdown.tsx +70 -0
- package/src/components/variants/minimal/components/SubtitleDisplay.tsx +21 -0
- package/src/components/variants/minimal/components/TimeDisplay.tsx +18 -0
- package/src/components/variants/minimal/components/VideoElement.tsx +40 -0
- package/src/components/variants/minimal/components/VolumeControl.tsx +217 -0
- package/src/components/variants/minimal/index.ts +23 -0
- package/src/components/video-player.tsx +100 -0
- package/src/context/error-handling.ts +394 -0
- package/src/context/media-context.tsx +208 -0
- package/src/context/media-provider.tsx +265 -0
- package/src/context/media-store.ts +379 -0
- package/src/context/persistence.ts +453 -0
- package/src/core/events/event-system.ts +442 -0
- package/src/core/events/hooks.ts +302 -0
- package/src/core/events/index.ts +22 -0
- package/src/core/events/middleware.ts +269 -0
- package/src/core/index.ts +22 -0
- package/src/core/optimization/bundle-optimizer.tsx +308 -0
- package/src/core/performance/cicd.ts +96 -0
- package/src/core/performance/index.ts +38 -0
- package/src/core/performance/media-loading-optimizer.ts +538 -0
- package/src/core/performance/performance-budgets.ts +432 -0
- package/src/core/performance/performance-monitor.tsx +476 -0
- package/src/core/performance/performance-testing.ts +459 -0
- package/src/core/plugins/base-plugin.ts +224 -0
- package/src/core/plugins/enhanced-plugin-system.ts +411 -0
- package/src/core/plugins/examples/analytics-plugin.ts +242 -0
- package/src/core/plugins/index.ts +39 -0
- package/src/core/plugins/registry.ts +327 -0
- package/src/core/plugins/types.ts +159 -0
- package/src/core/plugins/utils.ts +316 -0
- package/src/core/progressive-loading.tsx +322 -0
- package/src/core/providers/enhanced-hls-provider.ts +339 -0
- package/src/core/providers/service-provider.tsx +168 -0
- package/src/core/utils.tsx +38 -0
- package/src/features/media-session/media-session-manager.ts +429 -0
- package/src/features/picture-in-picture/pip-manager.ts +429 -0
- package/src/features/playlist/playlist-manager.ts +606 -0
- package/src/features/subtitles/subtitle-manager.ts +441 -0
- package/src/features/subtitles/subtitle-parser.ts +280 -0
- package/src/features/thumbnails/thumbnail-generator.ts +519 -0
- package/src/hooks/accessibility/index.ts +27 -0
- package/src/hooks/accessibility/use-accessibility-preferences.ts +188 -0
- package/src/hooks/accessibility/use-focus-management.ts +176 -0
- package/src/hooks/accessibility/use-keyboard-shortcuts.ts +144 -0
- package/src/hooks/accessibility/use-screen-reader.ts +182 -0
- package/src/hooks/core/index.ts +47 -0
- package/src/hooks/legacy.ts +27 -0
- package/src/hooks/use-enhanced-media-player.ts +468 -0
- package/src/hooks/use-intelligent-preloading.ts +358 -0
- package/src/hooks/use-keyboard-controls.ts +115 -0
- package/src/hooks/use-media-actions.ts +175 -0
- package/src/hooks/use-media-metadata.ts +89 -0
- package/src/hooks/use-media-optimistic.ts +270 -0
- package/src/hooks/use-media-player.ts +312 -0
- package/src/hooks/use-media-state.ts +190 -0
- package/src/hooks/use-memory-optimization.ts +358 -0
- package/src/hooks/use-optimistic-controls.ts +92 -0
- package/src/hooks/use-optimized-media.ts +263 -0
- package/src/hooks/use-touch-gestures.ts +417 -0
- package/src/hooks/useEnhancedSubtitles.ts +258 -0
- package/src/hooks/useKeyboardShortcuts.ts +74 -0
- package/src/hooks/useMediaControls.ts +52 -0
- package/src/hooks/useScreenshot.ts +57 -0
- package/src/hooks/useSubtitles.ts +91 -0
- package/src/hooks/useVideoPlayer.ts +232 -0
- package/src/icons/components/FullscreenIcon.tsx +37 -0
- package/src/icons/components/SettingsIcon.tsx +38 -0
- package/src/icons/components/animated/LoadingIcon.tsx +43 -0
- package/src/icons/components/animated/PlayPauseIcon.tsx +50 -0
- package/src/icons/components/media/PauseIcon.tsx +39 -0
- package/src/icons/components/media/PlayIcon.tsx +38 -0
- package/src/icons/components/volume/VolumeIcon.tsx +39 -0
- package/src/icons/components/volume/VolumeOffIcon.tsx +39 -0
- package/src/icons/index.ts +56 -0
- package/src/icons/provider.tsx +64 -0
- package/src/icons/types.ts +40 -0
- package/src/index-enhanced.ts +35 -0
- package/src/index-legacy.ts +326 -0
- package/src/index.css +102 -0
- package/src/index.ts +216 -0
- package/src/lib/deprecation.ts +51 -0
- package/src/lib/format-time.ts +37 -0
- package/src/lib/index.ts +3 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles/globals.css +462 -0
- package/src/styles/minimal-player.css +589 -0
- package/src/styles/pixel-art.css +46 -0
- package/src/styles/videojs.css +100 -0
- package/src/test/setup.ts +79 -0
- package/src/types/events.ts +47 -0
- package/src/types/formats.ts +95 -0
- package/src/types/index.ts +190 -0
- package/src/types/videojs-plugins.d.ts +14 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { Suspense, lazy, useMemo } from "react"
|
|
4
|
+
import { MediaLoading } from "../../components/media-suspense"
|
|
5
|
+
|
|
6
|
+
// Lazy loaded components for bundle splitting
|
|
7
|
+
const LazyAdvancedControls = lazy(() =>
|
|
8
|
+
import("../../components/advanced-controls").then(module => ({
|
|
9
|
+
default: module.AdvancedControls
|
|
10
|
+
}))
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
const LazyPlaylist = lazy(() =>
|
|
14
|
+
import("../../features/playlist/Playlist").then(module => ({
|
|
15
|
+
default: module.Playlist
|
|
16
|
+
}))
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
const LazySubtitles = lazy(() =>
|
|
20
|
+
import("../../features/subtitles/SubtitleDisplay").then(module => ({
|
|
21
|
+
default: module.SubtitleDisplay
|
|
22
|
+
}))
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const LazyAnalytics = lazy(() =>
|
|
26
|
+
import("../../core/plugins/examples/analytics-plugin").then(module => ({
|
|
27
|
+
default: module.AnalyticsPlugin
|
|
28
|
+
}))
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
// Component types for dynamic loading
|
|
32
|
+
export interface LazyComponentConfig {
|
|
33
|
+
component: string
|
|
34
|
+
fallback?: React.ReactNode
|
|
35
|
+
preload?: boolean
|
|
36
|
+
priority?: "high" | "normal" | "low"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Bundle optimization component
|
|
40
|
+
export function BundleOptimizer({
|
|
41
|
+
children,
|
|
42
|
+
lazyComponents = [],
|
|
43
|
+
preloadStrategy = "viewport"
|
|
44
|
+
}: {
|
|
45
|
+
children: React.ReactNode
|
|
46
|
+
lazyComponents?: LazyComponentConfig[]
|
|
47
|
+
preloadStrategy?: "viewport" | "hover" | "immediate" | "never"
|
|
48
|
+
}) {
|
|
49
|
+
// Memoize component mapping for performance
|
|
50
|
+
const componentMap = useMemo(() => ({
|
|
51
|
+
'advanced-controls': LazyAdvancedControls,
|
|
52
|
+
'playlist': LazyPlaylist,
|
|
53
|
+
'subtitles': LazySubtitles,
|
|
54
|
+
'analytics': LazyAnalytics
|
|
55
|
+
}), [])
|
|
56
|
+
|
|
57
|
+
// Preload components based on strategy
|
|
58
|
+
React.useEffect(() => {
|
|
59
|
+
if (preloadStrategy === "immediate") {
|
|
60
|
+
// Preload all components immediately
|
|
61
|
+
lazyComponents.forEach(({ component }) => {
|
|
62
|
+
const Component = componentMap[component as keyof typeof componentMap]
|
|
63
|
+
if (Component) {
|
|
64
|
+
// Trigger dynamic import
|
|
65
|
+
Component.preload?.()
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
} else if (preloadStrategy === "viewport") {
|
|
69
|
+
// Preload when component is likely to enter viewport
|
|
70
|
+
const observer = new IntersectionObserver(
|
|
71
|
+
(entries) => {
|
|
72
|
+
entries.forEach((entry) => {
|
|
73
|
+
if (entry.isIntersecting) {
|
|
74
|
+
const componentName = entry.target.getAttribute('data-component')
|
|
75
|
+
if (componentName) {
|
|
76
|
+
const Component = componentMap[componentName as keyof typeof componentMap]
|
|
77
|
+
if (Component) {
|
|
78
|
+
Component.preload?.()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
observer.unobserve(entry.target)
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
},
|
|
85
|
+
{ threshold: 0.1, rootMargin: '50px' }
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
// Observe lazy component containers
|
|
89
|
+
lazyComponents.forEach(({ component, priority }) => {
|
|
90
|
+
if (priority === "high") {
|
|
91
|
+
const element = document.querySelector(`[data-component="${component}"]`)
|
|
92
|
+
if (element) {
|
|
93
|
+
observer.observe(element)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
return () => observer.disconnect()
|
|
99
|
+
}
|
|
100
|
+
}, [preloadStrategy, lazyComponents, componentMap])
|
|
101
|
+
|
|
102
|
+
// Render lazy components with suspense boundaries
|
|
103
|
+
const renderLazyComponent = (config: LazyComponentConfig) => {
|
|
104
|
+
const Component = componentMap[config.component as keyof typeof componentMap]
|
|
105
|
+
if (!Component) return null
|
|
106
|
+
|
|
107
|
+
const LazyComponent = Component as React.ComponentType<any>
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<Suspense
|
|
111
|
+
key={config.component}
|
|
112
|
+
fallback={config.fallback || <MediaLoading size="sm" />}>
|
|
113
|
+
<div
|
|
114
|
+
data-component={config.component}
|
|
115
|
+
data-priority={config.priority}>
|
|
116
|
+
<LazyComponent />
|
|
117
|
+
</div>
|
|
118
|
+
</Suspense>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<>
|
|
124
|
+
{children}
|
|
125
|
+
{lazyComponents.map(renderLazyComponent)}
|
|
126
|
+
</>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Progressive loading hook
|
|
131
|
+
export function useProgressiveLoading() {
|
|
132
|
+
const [loadedComponents, setLoadedComponents] = React.useState<Set<string>>(new Set())
|
|
133
|
+
|
|
134
|
+
const loadComponent = React.useCallback(async (componentName: string) => {
|
|
135
|
+
if (loadedComponents.has(componentName)) return
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
// Dynamic import based on component name
|
|
139
|
+
const imports: Record<string, () => Promise<any>> = {
|
|
140
|
+
'advanced-controls': () => import("../../components/advanced-controls"),
|
|
141
|
+
'playlist': () => import("../../features/playlist/Playlist"),
|
|
142
|
+
'subtitles': () => import("../../features/subtitles/SubtitleDisplay"),
|
|
143
|
+
'analytics': () => import("../../core/plugins/examples/analytics-plugin")
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const importFn = imports[componentName]
|
|
147
|
+
if (importFn) {
|
|
148
|
+
await importFn()
|
|
149
|
+
setLoadedComponents(prev => new Set([...prev, componentName]))
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.warn(`Failed to load component ${componentName}:`, error)
|
|
153
|
+
}
|
|
154
|
+
}, [loadedComponents])
|
|
155
|
+
|
|
156
|
+
const preloadComponents = React.useCallback((componentNames: string[]) => {
|
|
157
|
+
componentNames.forEach(loadComponent)
|
|
158
|
+
}, [loadComponent])
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
loadedComponents,
|
|
162
|
+
loadComponent,
|
|
163
|
+
preloadComponents,
|
|
164
|
+
isComponentLoaded: (componentName: string) => loadedComponents.has(componentName)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Tree shaking utilities
|
|
169
|
+
export function createLazyComponent<T extends React.ComponentType<any>>(
|
|
170
|
+
importFn: () => Promise<{ default: T }>,
|
|
171
|
+
componentName: string
|
|
172
|
+
) {
|
|
173
|
+
const LazyComponent = React.lazy(importFn)
|
|
174
|
+
|
|
175
|
+
// Add preload method for manual preloading
|
|
176
|
+
(LazyComponent as any).preload = importFn
|
|
177
|
+
|
|
178
|
+
// Add display name for debugging
|
|
179
|
+
LazyComponent.displayName = `Lazy${componentName}`
|
|
180
|
+
|
|
181
|
+
return LazyComponent
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Bundle analyzer utilities
|
|
185
|
+
export interface BundleMetrics {
|
|
186
|
+
totalSize: number
|
|
187
|
+
chunkSizes: Record<string, number>
|
|
188
|
+
loadedChunks: Set<string>
|
|
189
|
+
pendingChunks: Set<string>
|
|
190
|
+
compressionRatio: number
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function useBundleMetrics() {
|
|
194
|
+
const [metrics, setMetrics] = React.useState<BundleMetrics>({
|
|
195
|
+
totalSize: 0,
|
|
196
|
+
chunkSizes: {},
|
|
197
|
+
loadedChunks: new Set(),
|
|
198
|
+
pendingChunks: new Set(),
|
|
199
|
+
compressionRatio: 1
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
React.useEffect(() => {
|
|
203
|
+
// Monitor bundle loading if performance observer is available
|
|
204
|
+
if (typeof window !== 'undefined' && 'PerformanceObserver' in window) {
|
|
205
|
+
const observer = new PerformanceObserver((list) => {
|
|
206
|
+
for (const entry of list.getEntries()) {
|
|
207
|
+
if (entry.entryType === 'resource' && entry.name.includes('.js')) {
|
|
208
|
+
const chunkName = entry.name.split('/').pop() || 'unknown'
|
|
209
|
+
|
|
210
|
+
setMetrics(prev => ({
|
|
211
|
+
...prev,
|
|
212
|
+
chunkSizes: {
|
|
213
|
+
...prev.chunkSizes,
|
|
214
|
+
[chunkName]: entry.transferSize || 0
|
|
215
|
+
},
|
|
216
|
+
loadedChunks: new Set([...prev.loadedChunks, chunkName]),
|
|
217
|
+
totalSize: prev.totalSize + (entry.transferSize || 0)
|
|
218
|
+
}))
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
observer.observe({ entryTypes: ['resource'] })
|
|
224
|
+
|
|
225
|
+
return () => observer.disconnect()
|
|
226
|
+
}
|
|
227
|
+
}, [])
|
|
228
|
+
|
|
229
|
+
const getLargestChunk = React.useCallback(() => {
|
|
230
|
+
const entries = Object.entries(metrics.chunkSizes)
|
|
231
|
+
if (entries.length === 0) return null
|
|
232
|
+
|
|
233
|
+
return entries.reduce((a, b) => a[1] > b[1] ? a : b)
|
|
234
|
+
}, [metrics.chunkSizes])
|
|
235
|
+
|
|
236
|
+
const formatBytes = React.useCallback((bytes: number) => {
|
|
237
|
+
if (bytes === 0) return '0 Bytes'
|
|
238
|
+
const k = 1024
|
|
239
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB']
|
|
240
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
241
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
|
242
|
+
}, [])
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
...metrics,
|
|
246
|
+
largestChunk: getLargestChunk(),
|
|
247
|
+
formatBytes,
|
|
248
|
+
totalSizeFormatted: formatBytes(metrics.totalSize)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Preloading strategies
|
|
253
|
+
export const PreloadStrategies = {
|
|
254
|
+
// Preload when user hovers over trigger element
|
|
255
|
+
onHover: (element: HTMLElement, importFn: () => Promise<any>) => {
|
|
256
|
+
let timeoutId: NodeJS.Timeout
|
|
257
|
+
|
|
258
|
+
const handleMouseEnter = () => {
|
|
259
|
+
timeoutId = setTimeout(importFn, 200) // 200ms delay
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const handleMouseLeave = () => {
|
|
263
|
+
if (timeoutId) {
|
|
264
|
+
clearTimeout(timeoutId)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
element.addEventListener('mouseenter', handleMouseEnter)
|
|
269
|
+
element.addEventListener('mouseleave', handleMouseLeave)
|
|
270
|
+
|
|
271
|
+
return () => {
|
|
272
|
+
element.removeEventListener('mouseenter', handleMouseEnter)
|
|
273
|
+
element.removeEventListener('mouseleave', handleMouseLeave)
|
|
274
|
+
if (timeoutId) {
|
|
275
|
+
clearTimeout(timeoutId)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
// Preload when component enters viewport
|
|
281
|
+
onViewport: (element: HTMLElement, importFn: () => Promise<any>) => {
|
|
282
|
+
const observer = new IntersectionObserver(
|
|
283
|
+
(entries) => {
|
|
284
|
+
entries.forEach((entry) => {
|
|
285
|
+
if (entry.isIntersecting) {
|
|
286
|
+
importFn()
|
|
287
|
+
observer.unobserve(entry.target)
|
|
288
|
+
}
|
|
289
|
+
})
|
|
290
|
+
},
|
|
291
|
+
{ threshold: 0.1, rootMargin: '50px' }
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
observer.observe(element)
|
|
295
|
+
return () => observer.disconnect()
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
// Preload after idle time
|
|
299
|
+
onIdle: (importFn: () => Promise<any>, delay = 1000) => {
|
|
300
|
+
if ('requestIdleCallback' in window) {
|
|
301
|
+
return (window as any).requestIdleCallback(() => {
|
|
302
|
+
setTimeout(importFn, delay)
|
|
303
|
+
})
|
|
304
|
+
} else {
|
|
305
|
+
return setTimeout(importFn, delay)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// CI/CD integration utilities - Node.js only
|
|
2
|
+
import { performanceBudgetMonitor, type BudgetViolation } from './performance-budgets';
|
|
3
|
+
|
|
4
|
+
export const cicdIntegration = {
|
|
5
|
+
// Check if running in CI environment
|
|
6
|
+
isCI(): boolean {
|
|
7
|
+
return !!(
|
|
8
|
+
process.env.CI ||
|
|
9
|
+
process.env.CONTINUOUS_INTEGRATION ||
|
|
10
|
+
process.env.BUILD_NUMBER ||
|
|
11
|
+
process.env.GITHUB_ACTIONS ||
|
|
12
|
+
process.env.GITLAB_CI ||
|
|
13
|
+
process.env.CIRCLE_CI
|
|
14
|
+
);
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
// Exit with error code if budget violations found
|
|
18
|
+
async enforcebudgets(distPath: string): Promise<void> {
|
|
19
|
+
const violations = await performanceBudgetMonitor.checkBundleSizes(distPath);
|
|
20
|
+
const errorViolations = violations.filter(v => v.budget.severity === 'error');
|
|
21
|
+
|
|
22
|
+
if (errorViolations.length > 0) {
|
|
23
|
+
console.error(`❌ Performance budget violations detected: ${errorViolations.length} errors`);
|
|
24
|
+
errorViolations.forEach(violation => {
|
|
25
|
+
console.error(` - ${violation.budget.name}: ${this.formatValue(violation.actualValue, violation.budget.unit)} (limit: ${this.formatValue(violation.budget.limit, violation.budget.unit)})`);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (this.isCI()) {
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
console.log('✅ All performance budgets passed');
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// Generate budget report for CI
|
|
37
|
+
generateCIReport(): string {
|
|
38
|
+
const summary = performanceBudgetMonitor.getViolationSummary();
|
|
39
|
+
const violations = performanceBudgetMonitor.getViolations();
|
|
40
|
+
|
|
41
|
+
let report = '## Performance Budget Report\n\n';
|
|
42
|
+
|
|
43
|
+
if (summary.total === 0) {
|
|
44
|
+
report += '✅ **All performance budgets passed!**\n\n';
|
|
45
|
+
} else {
|
|
46
|
+
report += `❌ **${summary.total} budget violations detected**\n\n`;
|
|
47
|
+
|
|
48
|
+
report += '### Violations by Category\n';
|
|
49
|
+
Object.entries(summary.byCategory).forEach(([category, count]) => {
|
|
50
|
+
report += `- ${category}: ${count}\n`;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
report += '\n### Violations by Severity\n';
|
|
54
|
+
Object.entries(summary.bySeverity).forEach(([severity, count]) => {
|
|
55
|
+
const emoji = severity === 'error' ? '🔴' : severity === 'warning' ? '🟡' : '🔵';
|
|
56
|
+
report += `- ${emoji} ${severity}: ${count}\n`;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
report += '\n### Detailed Violations\n';
|
|
60
|
+
violations.forEach(violation => {
|
|
61
|
+
const emoji = violation.budget.severity === 'error' ? '🔴' : '🟡';
|
|
62
|
+
report += `${emoji} **${violation.budget.name}**\n`;
|
|
63
|
+
report += `- Limit: ${this.formatValue(violation.budget.limit, violation.budget.unit)}\n`;
|
|
64
|
+
report += `- Actual: ${this.formatValue(violation.actualValue, violation.budget.unit)}\n`;
|
|
65
|
+
report += `- Overage: +${violation.overagePercentage.toFixed(1)}%\n\n`;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return report;
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
formatValue(value: number, unit: 'bytes' | 'ms' | 'score' | 'percent'): string {
|
|
73
|
+
switch (unit) {
|
|
74
|
+
case 'bytes':
|
|
75
|
+
return this.formatBytes(value);
|
|
76
|
+
case 'ms':
|
|
77
|
+
return `${value.toFixed(2)}ms`;
|
|
78
|
+
case 'score':
|
|
79
|
+
return value.toFixed(3);
|
|
80
|
+
case 'percent':
|
|
81
|
+
return `${value.toFixed(1)}%`;
|
|
82
|
+
default:
|
|
83
|
+
return value.toString();
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
formatBytes(bytes: number): string {
|
|
88
|
+
if (bytes >= 1024 * 1024) {
|
|
89
|
+
return `${(bytes / 1024 / 1024).toFixed(2)}MB`;
|
|
90
|
+
}
|
|
91
|
+
if (bytes >= 1024) {
|
|
92
|
+
return `${(bytes / 1024).toFixed(2)}KB`;
|
|
93
|
+
}
|
|
94
|
+
return `${bytes}B`;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Performance Monitoring
|
|
2
|
+
export * from './performance-monitor';
|
|
3
|
+
export { default as PerformanceMonitor } from './performance-monitor';
|
|
4
|
+
|
|
5
|
+
// Performance Testing
|
|
6
|
+
export * from './performance-testing';
|
|
7
|
+
|
|
8
|
+
// Performance Budgets and CI/CD
|
|
9
|
+
export * from './performance-budgets';
|
|
10
|
+
|
|
11
|
+
// Media Loading Optimization
|
|
12
|
+
export * from './media-loading-optimizer';
|
|
13
|
+
|
|
14
|
+
// Re-export key classes and utilities for convenience
|
|
15
|
+
export {
|
|
16
|
+
performanceMonitor,
|
|
17
|
+
PerformanceDashboard,
|
|
18
|
+
usePerformanceMonitor,
|
|
19
|
+
withPerformanceMonitoring
|
|
20
|
+
} from './performance-monitor';
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
performanceBenchmark,
|
|
24
|
+
performanceTestUtils,
|
|
25
|
+
performanceTestFramework
|
|
26
|
+
} from './performance-testing';
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
performanceBudgetMonitor,
|
|
30
|
+
defaultPerformanceBudgets
|
|
31
|
+
} from './performance-budgets';
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
mediaLoadingOptimizer,
|
|
35
|
+
useMediaLoadingOptimizer,
|
|
36
|
+
NetworkMonitor,
|
|
37
|
+
MediaLoadingOptimizer
|
|
38
|
+
} from './media-loading-optimizer';
|