Stats
Actions
Tags
From aai-stack-vite
Provides Vite HMR patterns for module acceptance, disposal, state preservation including Zustand stores, custom events, and React Fast Refresh. Useful for maintaining state during hot reloads.
How this skill is triggered — by the user, by Claude, or both
Slash command
/aai-stack-vite:vite-hmrThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Patterns for Hot Module Replacement in Vite.
Patterns for Hot Module Replacement in Vite.
// Check if HMR is available
if (import.meta.hot) {
// HMR code here
}
// Accept updates to this module
if (import.meta.hot) {
import.meta.hot.accept()
}
// Accept updates to dependencies
if (import.meta.hot) {
import.meta.hot.accept('./module.js', (newModule) => {
console.log('Module updated:', newModule)
})
}
// Accept multiple dependencies
if (import.meta.hot) {
import.meta.hot.accept(['./a.js', './b.js'], ([a, b]) => {
// Handle updates
})
}
if (import.meta.hot) {
// Cleanup before module is replaced
import.meta.hot.dispose((data) => {
// data can be used to pass state to the new module
data.savedState = getCurrentState()
// Cleanup side effects
clearInterval(intervalId)
socket.disconnect()
})
}
// Access disposed data in new module
if (import.meta.hot) {
import.meta.hot.accept()
// Restore state from previous version
if (import.meta.hot.data.savedState) {
restoreState(import.meta.hot.data.savedState)
}
}
// Decline HMR, trigger full reload
if (import.meta.hot) {
import.meta.hot.decline()
}
if (import.meta.hot) {
// Force parent module to re-import this module
import.meta.hot.invalidate()
}
// Custom state preservation
let state = { count: 0 }
if (import.meta.hot) {
// Restore state from previous version
if (import.meta.hot.data.state) {
state = import.meta.hot.data.state
}
import.meta.hot.accept()
// Save state before disposal
import.meta.hot.dispose((data) => {
data.state = state
})
}
export function increment() {
state.count++
}
// For global stores like Zustand
import { create } from 'zustand'
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
// Preserve store across HMR
if (import.meta.hot) {
if (import.meta.hot.data.store) {
useStore.setState(import.meta.hot.data.store.getState())
}
import.meta.hot.dispose((data) => {
data.store = useStore
})
}
export default useStore
// From server/plugin
server.ws.send({
type: 'custom',
event: 'my-event',
data: { message: 'Hello' },
})
if (import.meta.hot) {
import.meta.hot.on('my-event', (data) => {
console.log('Received:', data.message)
})
}
if (import.meta.hot) {
import.meta.hot.on('vite:beforeFullReload', (payload) => {
console.log('About to full reload:', payload)
})
}
// vite.config.ts
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
react({
fastRefresh: true, // Enabled by default
}),
],
})
// State is automatically preserved in function components
function Counter() {
const [count, setCount] = useState(0)
// count value is preserved during HMR
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
// Add this comment to prevent Fast Refresh
// @refresh reset
function Component() {
// Component will fully remount on changes
}
// Parent.tsx imports Child.tsx
// When Child.tsx changes:
// 1. Vite tries to apply HMR to Child.tsx
// 2. If Child accepts, only Child updates
// 3. If not, Parent is checked
// 4. Chain continues until boundary is found
// 5. If no boundary, full reload
// React Fast Refresh creates boundaries at component level
// Mark module as HMR boundary
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
// Handle update manually
updateComponent(newModule.default)
})
}
// In browser console
localStorage.debug = 'vite:*'
// Or specific
localStorage.debug = 'vite:hmr'
if (import.meta.hot) {
// Before update is applied
import.meta.hot.on('vite:beforeUpdate', (payload) => {
console.log('Before update:', payload)
})
// After update is applied
import.meta.hot.on('vite:afterUpdate', (payload) => {
console.log('After update:', payload)
})
// Before full reload
import.meta.hot.on('vite:beforeFullReload', (payload) => {
console.log('Before reload:', payload)
})
// Before prune (unused modules removed)
import.meta.hot.on('vite:beforePrune', (payload) => {
console.log('Before prune:', payload)
})
// Invalidate
import.meta.hot.on('vite:invalidate', (payload) => {
console.log('Invalidate:', payload)
})
// Error
import.meta.hot.on('vite:error', (payload) => {
console.error('HMR error:', payload)
})
}
// Issue: State resets on save
// Solution: Ensure proper HMR boundaries
// BAD: Anonymous arrow functions
export default () => <div />
// GOOD: Named function components
export default function MyComponent() {
return <div />
}
// Issue: Side effects run multiple times
// Solution: Cleanup in dispose
let interval: number
function startPolling() {
interval = setInterval(fetchData, 1000)
}
if (import.meta.hot) {
import.meta.hot.dispose(() => {
clearInterval(interval)
})
}
startPolling()
// Issue: CSS changes not reflecting
// Vite handles CSS HMR automatically
// If issues, check for:
// 1. CSS modules naming
// 2. PostCSS config errors
// 3. Imported in multiple places
Used by:
frontend-developer agentfullstack-developer agentnpx claudepluginhub bradtaylorsf/alphaagent-teamCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.