add more dynamic imports to optimize bundle

This commit is contained in:
jeffvli
2026-01-17 07:32:16 -08:00
parent 6cb5c95c1f
commit ef5daad1dd
20 changed files with 529 additions and 163 deletions
@@ -1,4 +1,3 @@
import butterchurnPresets from 'butterchurn-presets';
import { nanoid } from 'nanoid';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -25,6 +24,38 @@ import { Text } from '/@/shared/components/text/text';
import { Textarea } from '/@/shared/components/textarea/textarea';
import { toast } from '/@/shared/components/toast/toast';
type ButterchurnPresetOption = { label: string; value: string };
let butterchurnPresetOptionsCache: ButterchurnPresetOption[] | null = null;
const loadButterchurnPresetOptions = async (): Promise<ButterchurnPresetOption[]> => {
if (butterchurnPresetOptionsCache) return butterchurnPresetOptionsCache;
const mod = await import('butterchurn-presets');
const presets = (mod as any).default ?? mod;
const presetNames = Object.keys(presets);
butterchurnPresetOptionsCache = presetNames.map((presetName) => ({
label: presetName,
value: presetName,
}));
return butterchurnPresetOptionsCache;
};
const useButterchurnPresetOptions = () => {
const [options, setOptions] = useState<ButterchurnPresetOption[]>(
butterchurnPresetOptionsCache ?? [],
);
useEffect(() => {
if (butterchurnPresetOptionsCache) return;
void loadButterchurnPresetOptions().then(setOptions);
}, []);
return options;
};
const modeOptions: { label: string; value: string }[] = [
{ label: i18n.t('visualizer.options.mode.0') as string, value: '0' },
{ label: i18n.t('visualizer.options.mode.1') as string, value: '1' },
@@ -2068,13 +2099,7 @@ const ButterchurnGeneralSettings = () => {
const { t } = useTranslation();
const { updateProperty, visualizer } = useUpdateButterchurn();
const presetOptions = useMemo(() => {
const presets = butterchurnPresets;
return Object.keys(presets).map((presetName) => ({
label: presetName,
value: presetName,
}));
}, []);
const presetOptions = useButterchurnPresetOptions();
return (
<Fieldset legend={t('visualizer.general')}>
@@ -2124,13 +2149,7 @@ const ButterChurnCycleSettings = () => {
const { t } = useTranslation();
const { updateProperty, visualizer } = useUpdateButterchurn();
const presetOptions = useMemo(() => {
const presets = butterchurnPresets;
return Object.keys(presets).map((presetName) => ({
label: presetName,
value: presetName,
}));
}, []);
const presetOptions = useButterchurnPresetOptions();
return (
<Fieldset legend={t('visualizer.cyclePresets')}>
@@ -1,5 +1,4 @@
import AudioMotionAnalyzer from 'audiomotion-analyzer';
import { createRef, useCallback, useEffect, useMemo, useState } from 'react';
import { createRef, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import styles from './visualizer.module.css';
@@ -15,7 +14,31 @@ const VisualizerInner = () => {
const accent = useAccent();
const visualizer = useSettingsStore((store) => store.visualizer);
const opacity = useSettingsStore((store) => store.visualizer.audiomotionanalyzer.opacity);
const [motion, setMotion] = useState<AudioMotionAnalyzer>();
const [motion, setMotion] = useState<any>();
const [libraryLoaded, setLibraryLoaded] = useState(false);
const AudioMotionAnalyzerRef = useRef<any>(null);
useEffect(() => {
let isMounted = true;
const loadLibrary = async () => {
try {
const module = await import('audiomotion-analyzer');
if (isMounted) {
AudioMotionAnalyzerRef.current = module.default;
setLibraryLoaded(true);
}
} catch (error) {
console.error('Failed to load AudioMotionAnalyzer library:', error);
}
};
loadLibrary();
return () => {
isMounted = false;
};
}, []);
// Check if a gradient name is a custom gradient
const isCustomGradient = useCallback(
@@ -162,7 +185,7 @@ const VisualizerInner = () => {
);
const registerCustomGradients = useCallback(
(audioMotionInstance: AudioMotionAnalyzer) => {
(audioMotionInstance: any) => {
if (visualizer.type !== 'audiomotionanalyzer') {
return;
}
@@ -187,8 +210,11 @@ const VisualizerInner = () => {
useEffect(() => {
const { context, gains } = webAudio || {};
let audioMotion: AudioMotionAnalyzer | undefined;
if (gains && context && canvasRef.current && !motion) {
let audioMotion: any | undefined;
if (gains && context && canvasRef.current && !motion && libraryLoaded) {
const AudioMotionAnalyzer = AudioMotionAnalyzerRef.current;
if (!AudioMotionAnalyzer) return;
// Reset gradients registered flag on new instance
setGradientsRegistered(false);
@@ -236,6 +262,7 @@ const VisualizerInner = () => {
options,
isCustomGradient,
motion,
libraryLoaded,
]);
// Re-register custom gradients when they change
@@ -1,5 +1,3 @@
import butterchurn from 'butterchurn';
import butterchurnPresets from 'butterchurn-presets';
import { createRef, useEffect, useRef, useState } from 'react';
import styles from './visualizer.module.css';
@@ -27,6 +25,9 @@ const VisualizerInner = () => {
const visualizerRef = useRef<ButterchurnVisualizer | undefined>(undefined);
const isInitializedRef = useRef(false);
const [isVisualizerReady, setIsVisualizerReady] = useState(false);
const [librariesLoaded, setLibrariesLoaded] = useState(false);
const butterchurnRef = useRef<any>(null);
const butterchurnPresetsRef = useRef<any>(null);
const animationFrameRef = useRef<number | undefined>(undefined);
const resizeObserverRef = useRef<ResizeObserver | undefined>(undefined);
const cycleTimerRef = useRef<NodeJS.Timeout | undefined>(undefined);
@@ -39,6 +40,33 @@ const VisualizerInner = () => {
const playerStatus = usePlayerStatus();
const isPlaying = playerStatus === PlayerStatus.PLAYING;
useEffect(() => {
let isMounted = true;
const loadLibraries = async () => {
try {
const [butterchurnModule, presetsModule] = await Promise.all([
import('butterchurn'),
import('butterchurn-presets'),
]);
if (isMounted) {
butterchurnRef.current = butterchurnModule.default;
butterchurnPresetsRef.current = presetsModule.default;
setLibrariesLoaded(true);
}
} catch (error) {
console.error('Failed to load butterchurn libraries:', error);
}
};
loadLibraries();
return () => {
isMounted = false;
};
}, []);
const cleanupVisualizer = () => {
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
@@ -79,6 +107,7 @@ const VisualizerInner = () => {
canvas &&
container &&
isPlaying &&
librariesLoaded &&
(!isInitializedRef.current || !visualizerRef.current);
if (!needsInitialization) {
@@ -107,13 +136,16 @@ const VisualizerInner = () => {
initializeVisualizer(dimensions.width, dimensions.height);
}
function initializeVisualizer(width: number, height: number) {
if (!gains || gains.length === 0 || !canvas || !context) return;
async function initializeVisualizer(width: number, height: number) {
if (!gains || gains.length === 0 || !canvas || !context || !librariesLoaded) return;
canvas.width = width;
canvas.height = height;
try {
const butterchurn = butterchurnRef.current;
if (!butterchurn) return;
const butterchurnInstance = butterchurn.createVisualizer(context, canvas, {
height,
width,
@@ -138,7 +170,7 @@ const VisualizerInner = () => {
cleanupVisualizer();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [webAudio, isPlaying]);
}, [webAudio, isPlaying, librariesLoaded]);
// Kill visualizer after 5 seconds of pause
useEffect(() => {
@@ -204,9 +236,11 @@ const VisualizerInner = () => {
// Load initial preset when visualizer is ready
useEffect(() => {
const visualizer = visualizerRef.current;
if (!visualizer || !isVisualizerReady || initialPresetLoadedRef.current) return;
if (!visualizer || !isVisualizerReady || initialPresetLoadedRef.current || !librariesLoaded)
return;
const presets = butterchurnPresets;
const presets = butterchurnPresetsRef.current;
if (!presets) return;
const presetNames = Object.keys(presets);
if (presetNames.length > 0) {
@@ -222,14 +256,24 @@ const VisualizerInner = () => {
initialPresetLoadedRef.current = true;
}
}
}, [isVisualizerReady, butterchurnSettings.currentPreset, butterchurnSettings.blendTime]);
}, [
isVisualizerReady,
butterchurnSettings.currentPreset,
butterchurnSettings.blendTime,
librariesLoaded,
]);
// Update preset when currentPreset or blendTime changes (but not when cycling)
const isCyclingRef = useRef(false);
useEffect(() => {
const visualizer = visualizerRef.current;
if (!visualizer || !butterchurnSettings.currentPreset || !initialPresetLoadedRef.current)
if (
!visualizer ||
!butterchurnSettings.currentPreset ||
!initialPresetLoadedRef.current ||
!librariesLoaded
)
return;
// Skip if we're currently cycling (to avoid reloading preset)
@@ -238,7 +282,8 @@ const VisualizerInner = () => {
return;
}
const presets = butterchurnPresets;
const presets = butterchurnPresetsRef.current;
if (!presets) return;
const preset = presets[butterchurnSettings.currentPreset];
if (preset) {
@@ -246,12 +291,17 @@ const VisualizerInner = () => {
// Reset cycle timer when preset changes manually
cycleStartTimeRef.current = Date.now();
}
}, [butterchurnSettings.currentPreset, butterchurnSettings.blendTime]);
}, [butterchurnSettings.currentPreset, butterchurnSettings.blendTime, librariesLoaded]);
// Handle preset cycling
useEffect(() => {
const visualizer = visualizerRef.current;
if (!visualizer || !butterchurnSettings.cyclePresets || !initialPresetLoadedRef.current) {
if (
!visualizer ||
!butterchurnSettings.cyclePresets ||
!initialPresetLoadedRef.current ||
!librariesLoaded
) {
// Clear cycle timer if cycling is disabled or visualizer not ready
if (cycleTimerRef.current) {
clearInterval(cycleTimerRef.current);
@@ -260,7 +310,8 @@ const VisualizerInner = () => {
return;
}
const presets = butterchurnPresets;
const presets = butterchurnPresetsRef.current;
if (!presets) return;
const allPresetNames = Object.keys(presets);
// Get the list of presets to cycle through
@@ -359,6 +410,7 @@ const VisualizerInner = () => {
butterchurnSettings.randomizeNextPreset,
butterchurnSettings.currentPreset,
setSettings,
librariesLoaded,
]);
useEffect(() => {