perf: reduce practice page dev bundle from 47MB to 115KB

- Dynamic import echarts-for-react in SkillProgressChart, ValidationCharts,
  and SkillDifficultyCharts to avoid bundling 58MB echarts library
- Lazy load game-registry in CreateRoomModal to prevent all arcade games
  from being bundled into every page using PageWithNav

The practice page was bundling echarts (58MB) and all arcade game code
because of static imports in shared components. These changes ensure:
- echarts only loads when charts are actually rendered
- game-registry only loads when CreateRoomModal is opened

Bundle size: 47MB → 115KB (99.8% reduction)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-12-27 08:02:08 -06:00
parent d1176da9aa
commit fd1df93a8f
4 changed files with 33 additions and 6 deletions

View File

@@ -1,8 +1,14 @@
'use client'
import dynamic from 'next/dynamic'
import { useState, useEffect } from 'react'
import ReactECharts from 'echarts-for-react'
import * as Tabs from '@radix-ui/react-tabs'
// Dynamic import echarts to reduce bundle size
const ReactECharts = dynamic(() => import('echarts-for-react'), {
ssr: false,
loading: () => <div style={{ height: '300px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>Loading chart...</div>,
})
import { css } from '../../../styled-system/css'
interface SkillData {

View File

@@ -1,8 +1,14 @@
'use client'
import * as Tabs from '@radix-ui/react-tabs'
import ReactECharts from 'echarts-for-react'
import dynamic from 'next/dynamic'
import { useEffect, useState } from 'react'
// Dynamic import echarts to reduce bundle size
const ReactECharts = dynamic(() => import('echarts-for-react'), {
ssr: false,
loading: () => <div style={{ height: '300px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>Loading chart...</div>,
})
import { css } from '../../../styled-system/css'
const chartContainerStyles = css({

View File

@@ -1,10 +1,10 @@
import { useState } from 'react'
import { useEffect, useState } from 'react'
import * as Select from '@radix-ui/react-select'
import { animated } from '@react-spring/web'
import { Modal } from '@/components/common/Modal'
import { useCreateRoom, useRoomData } from '@/hooks/useRoomData'
import { getAvailableGames } from '@/lib/arcade/game-registry'
import { RoomShareButtons } from './RoomShareButtons'
import type { GameDefinition } from '@/lib/arcade/game-sdk/types'
export interface CreateRoomModalProps {
/**
@@ -31,8 +31,17 @@ type ModalState = 'creating' | 'created'
export function CreateRoomModal({ isOpen, onClose, onSuccess }: CreateRoomModalProps) {
const { mutateAsync: createRoom, isPending } = useCreateRoom()
const { getRoomShareUrl } = useRoomData()
const availableGames = getAvailableGames()
const [availableGames, setAvailableGames] = useState<GameDefinition<any, any, any>[]>([])
const [error, setError] = useState('')
// Lazy load game registry only when modal opens
useEffect(() => {
if (isOpen && availableGames.length === 0) {
import('@/lib/arcade/game-registry').then(({ getAvailableGames }) => {
setAvailableGames(getAvailableGames())
})
}
}, [isOpen, availableGames.length])
const [gameName, setGameName] = useState<string>('__choose_later__') // Special value = user will choose later
const [accessMode, setAccessMode] = useState<
'open' | 'password' | 'approval-only' | 'restricted'

View File

@@ -1,7 +1,13 @@
'use client'
import ReactECharts from 'echarts-for-react'
import dynamic from 'next/dynamic'
import { useCallback, useMemo, useState } from 'react'
// Dynamic import echarts to avoid bundling 58MB library on pages that don't use charts
const ReactECharts = dynamic(() => import('echarts-for-react'), {
ssr: false,
loading: () => <div style={{ height: '200px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>Loading chart...</div>,
})
import {
getExtendedClassification,
type ExtendedSkillClassification,