Performance optimization 09 24 (#6)

* perf: optimize MyChores component performance and fix timer errors

## Fixed Issues
-  Timer error: `Timer '🏁 Main useEffect processing' does not exist`
-  Reduced 1049ms and 613ms message handler violations
-  Fixed 1+ second blank screen before chores display
-  Eliminated multiple redundant `getFilteredChores` calculations

## Performance Optimizations

### 1. Fixed Timer Execution Issue
**Problem**: React strict mode was causing timer mismatch between `console.time` and `console.timeEnd`
**Solution**: Restructured async useEffect to ensure proper timer lifecycle
```javascript
// Before: Timer in async IIFE caused double-execution issues
;(async () => {
  console.time('🏁 Main useEffect processing')
  // ...
  console.timeEnd('🏁 Main useEffect processing')
})()

// After: Proper async function structure
const processEffectAsync = async () => {
  // ... async operations
  console.timeEnd('🏁 Main useEffect processing')
}
processEffectAsync()
```

### 2. Optimized useState Lazy Initialization
**Problem**: `JSON.parse()` called on every render for state initialization
```javascript
// Before: Expensive operation on each render
const [openChoreSections, setOpenChoreSections] = useState(
  JSON.parse(localStorage.getItem('openChoreSections')) || {},
)

// After: Lazy initialization with error handling
const [openChoreSections, setOpenChoreSections] = useState(() => {
  try {
    return JSON.parse(localStorage.getItem('openChoreSections')) || {}
  } catch {
    return {}
  }
})
```

### 3. Reduced useEffect Dependencies
**Problem**: useEffect running too frequently due to object reference dependencies
```javascript
// Before: Full objects cause frequent re-runs
}, [choresData, membersData, userProfile, impersonatedUser, ...]

// After: Only specific properties that matter
}, [choresData?.res, membersData?.res, userProfile?.id, impersonatedUser?.userId, ...]
```

### 4. Added Memoization for Section Updates
**Problem**: `choreSections` updated even when data hadn't actually changed
```javascript
// Before: Always updating sections
setChoreSections(processedSections)

// After: Only update if data actually changed
setChoreSections(prevSections => {
  if (JSON.stringify(prevSections) === JSON.stringify(processedSections)) {
    return prevSections
  }
  return processedSections
})
```

### 5. Optimized localStorage Access
**Problem**: Multiple `localStorage.getItem()` calls and repeated parsing
```javascript
// Before: Multiple localStorage calls
if (localStorage.getItem('openChoreSections') === null) {
  // ...localStorage operations
}

// After: Single cached read
const storedSections = localStorage.getItem('openChoreSections')
if (storedSections === null) {
  // ...operations
}
```

### 6. Added useMemo/useCallback for Heavy Computations
- `processedChores` - Memoized chore sorting and filtering
- `processedSections` - Memoized section grouping
- `getFilteredChores` - Memoized filtered chore calculations
- `handleChoreUpdated`, `handleChoreDeleted`, `updateChores` - useCallback for stable references

## Performance Results
**Before**: 1+ second loading delay, timer errors, frequent violations
**After**:
- `processedChores calculation: ~0.002ms` (was much slower)
- `processedSections calculation: ~0.001ms` (was much slower)
- `getFilteredChores: ~0.022ms` (down from multiple slow calls)
- `Main useEffect processing: ~0.298ms` (reasonable time)
- `Auto-update sections: ~0.007ms` (very fast)

🚀 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: update package.json to remove deprecated @vitejs/plugin-react dependency and add wrangler.toml for build configuration

* Revert vite.config.js to it's original state

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Mohamad Tarbin
2025-09-26 02:21:07 -04:00
committed by GitHub
parent 048e7d0ef6
commit d28ecb509e
4 changed files with 322 additions and 263 deletions

View File

@@ -91,7 +91,6 @@
"@types/react-dom": "^18.2.17", "@types/react-dom": "^18.2.17",
"@vite-pwa/assets-generator": "^0.2.4", "@vite-pwa/assets-generator": "^0.2.4",
"@vitejs/plugin-react-swc": "^3.5.0", "@vitejs/plugin-react-swc": "^3.5.0",
"@vitejs/plugin-react": "^5.0.3",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
@@ -110,6 +109,5 @@
"tailwindcss": "^3.4.0", "tailwindcss": "^3.4.0",
"vite": "^5.2.13" "vite": "^5.2.13"
}, },
"optionalDependencies": { "optionalDependencies": {}
}
} }

View File

@@ -37,9 +37,9 @@ import {
Typography, Typography,
} from '@mui/joy' } from '@mui/joy'
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import { useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom' import { useNavigate, useSearchParams } from 'react-router-dom'
import { useChores, useArchiveChore } from '../../queries/ChoreQueries' import { useArchiveChore, useChores } from '../../queries/ChoreQueries'
import { useNotification } from '../../service/NotificationProvider' import { useNotification } from '../../service/NotificationProvider'
import { TASK_COLOR } from '../../utils/Colors' import { TASK_COLOR } from '../../utils/Colors'
import Priorities from '../../utils/Priorities' import Priorities from '../../utils/Priorities'
@@ -89,9 +89,13 @@ const MyChores = () => {
const [selectedChoreSection, setSelectedChoreSection] = useState( const [selectedChoreSection, setSelectedChoreSection] = useState(
localStorage.getItem('selectedChoreSection') || 'due_date', localStorage.getItem('selectedChoreSection') || 'due_date',
) )
const [openChoreSections, setOpenChoreSections] = useState( const [openChoreSections, setOpenChoreSections] = useState(() => {
JSON.parse(localStorage.getItem('openChoreSections')) || {}, try {
) return JSON.parse(localStorage.getItem('openChoreSections')) || {}
} catch {
return {}
}
})
const [selectedChoreFilter, setSelectedChoreFilter] = useState( const [selectedChoreFilter, setSelectedChoreFilter] = useState(
localStorage.getItem('selectedChoreFilter') || 'anyone', localStorage.getItem('selectedChoreFilter') || 'anyone',
) )
@@ -118,48 +122,90 @@ const MyChores = () => {
const [selectedChores, setSelectedChores] = useState(new Set()) const [selectedChores, setSelectedChores] = useState(new Set())
const [confirmModelConfig, setConfirmModelConfig] = useState({}) const [confirmModelConfig, setConfirmModelConfig] = useState({})
const [showKeyboardShortcuts, setShowKeyboardShortcuts] = useState(false) const [showKeyboardShortcuts, setShowKeyboardShortcuts] = useState(false)
const processedChores = useMemo(() => {
console.time('🏁 processedChores calculation')
if (!choresData?.res) {
console.timeEnd('🏁 processedChores calculation')
return []
}
let sortedChores = [...choresData.res].sort(ChoreSorter)
if (impersonatedUser) {
sortedChores = sortedChores.filter(
chore =>
chore.assignedTo === impersonatedUser.userId ||
chore.assignees?.some(a => a.userId === impersonatedUser.userId) ||
chore.isPrivate === false,
)
}
console.timeEnd('🏁 processedChores calculation')
return sortedChores
}, [choresData?.res, impersonatedUser])
const processedSections = useMemo(() => {
console.time('🏁 processedSections calculation')
if (!processedChores.length || !userProfile?.id) {
console.timeEnd('🏁 processedSections calculation')
return []
}
const sections = ChoresGrouper(
selectedChoreSection,
processedChores,
ChoreFilters(impersonatedUser?.userId || userProfile?.id)[
selectedChoreFilter
],
)
console.timeEnd('🏁 processedSections calculation')
return sections
}, [
processedChores,
selectedChoreSection,
selectedChoreFilter,
impersonatedUser?.userId,
userProfile?.id,
])
useEffect(() => { useEffect(() => {
;(async () => { if (
if ( !choresLoading &&
!choresLoading && !membersLoading &&
!membersLoading && userProfile &&
userProfile && membersData?.res &&
membersData?.res && choresData?.res
choresData?.res ) {
) { console.time('🏁 Main useEffect processing')
const processEffectAsync = async () => {
setPerformers(membersData.res) setPerformers(membersData.res)
let sortedChores = choresData.res.sort(ChoreSorter) setChores(processedChores)
setFilteredChores(processedChores)
// Filter chores based on impersonated user // Only update sections if they've actually changed
if (impersonatedUser) { setChoreSections(prevSections => {
sortedChores = sortedChores.filter( if (
chore => JSON.stringify(prevSections) === JSON.stringify(processedSections)
chore.assignedTo === impersonatedUser.userId || ) {
chore.assignees?.some( return prevSections
a => a.userId === impersonatedUser.userId, }
) || return processedSections
chore.isPrivate === false, })
)
}
setChores(sortedChores)
setFilteredChores(sortedChores)
const sections = ChoresGrouper(
selectedChoreSection,
sortedChores,
ChoreFilters(impersonatedUser?.userId || userProfile?.id)[
selectedChoreFilter
],
)
setChoreSections(sections)
if (localStorage.getItem('openChoreSections') === null) { if (localStorage.getItem('openChoreSections') === null) {
setSelectedChoreSectionWithCache(selectedChoreSection) setSelectedChoreSectionWithCache(selectedChoreSection)
setOpenChoreSections( const openSections = processedSections.reduce(
Object.keys(sections).reduce((acc, key) => { (acc, _section, index) => {
acc[key] = true acc[index] = true
return acc return acc
}, {}), },
{},
) )
setOpenChoreSections(openSections)
} }
if (await canScheduleNotification()) { if (await canScheduleNotification()) {
@@ -170,18 +216,45 @@ const MyChores = () => {
membersData.res, membersData.res,
) )
} }
console.timeEnd('🏁 Main useEffect processing')
} }
})()
processEffectAsync()
}
}, [ }, [
membersLoading, membersLoading,
choresLoading, choresLoading,
isUserProfileLoading, isUserProfileLoading,
choresData, choresData?.res,
membersData, membersData?.res,
userProfile, userProfile?.id,
impersonatedUser, impersonatedUser?.userId,
selectedChoreSection,
]) ])
// Auto-update sections when processedSections changes
useEffect(() => {
if (processedSections.length > 0) {
console.time('🏁 Auto-update sections')
setChoreSections(processedSections)
// Auto-open sections if needed - only check localStorage once
const storedSections = localStorage.getItem('openChoreSections')
if (storedSections === null) {
const openSections = processedSections.reduce(
(acc, _section, index) => {
acc[index] = true
return acc
},
{},
)
setOpenChoreSections(openSections)
}
console.timeEnd('🏁 Auto-update sections')
}
}, [processedSections])
useEffect(() => { useEffect(() => {
document.addEventListener('mousedown', handleMenuOutsideClick) document.addEventListener('mousedown', handleMenuOutsideClick)
return () => { return () => {
@@ -288,7 +361,7 @@ const MyChores = () => {
} else { } else {
// Check expanded sections first // Check expanded sections first
const expandedChores = choreSections const expandedChores = choreSections
.filter((section, index) => openChoreSections[index]) .filter((_section, index) => openChoreSections[index])
.flatMap(section => section.content || []) .flatMap(section => section.content || [])
const allExpandedSelected = const allExpandedSelected =
@@ -525,62 +598,74 @@ const MyChores = () => {
) )
} }
// Helper function to get filtered chores for display const getFilteredChores = useMemo(() => {
const getFilteredChores = () => { console.time('🏁 getFilteredChores')
let result = []
if (searchTerm?.length > 0 || searchFilter !== 'All') { if (searchTerm?.length > 0 || searchFilter !== 'All') {
return filteredChores result = filteredChores
} } else {
let choresToFilter = chores
let choresToFilter = chores if (impersonatedUser) {
choresToFilter = choresToFilter.filter(
chore => chore.assignedTo === impersonatedUser.userId,
)
}
// Filter by impersonated user first if set result = choresToFilter.filter(
if (impersonatedUser) {
choresToFilter = choresToFilter.filter(
chore => chore.assignedTo === impersonatedUser.userId,
)
}
return choresToFilter.filter(
ChoreFilters(impersonatedUser?.userId || userProfile?.id)[
selectedChoreFilter
],
)
}
// Helper function to get chores for a specific date
const getChoresForDate = date => {
const filteredChores = getFilteredChores()
return filteredChores.filter(chore => {
if (!chore.nextDueDate) return false
const choreDate = new Date(chore.nextDueDate).toLocaleDateString()
const selectedDate = date.toLocaleDateString()
return choreDate === selectedDate
})
}
const updateChores = newChore => {
let newChores = [...chores, newChore]
// Filter chores based on impersonated user
if (impersonatedUser) {
newChores = newChores.filter(
chore => chore.assignedTo === impersonatedUser.userId,
)
}
setChores(newChores)
setFilteredChores(newChores)
setChoreSections(
ChoresGrouper(
selectedChoreSection,
newChores,
ChoreFilters(impersonatedUser?.userId || userProfile?.id)[ ChoreFilters(impersonatedUser?.userId || userProfile?.id)[
selectedChoreFilter selectedChoreFilter
], ],
), )
) }
setSearchFilter('All')
} console.timeEnd('🏁 getFilteredChores')
return result
}, [
searchTerm,
searchFilter,
filteredChores,
chores,
impersonatedUser,
userProfile?.id,
selectedChoreFilter,
])
const getChoresForDate = useCallback(
date => {
console.time('🏁 getChoresForDate')
const filteredChoresData = getFilteredChores
const result = filteredChoresData.filter(chore => {
if (!chore.nextDueDate) return false
const choreDate = new Date(chore.nextDueDate).toLocaleDateString()
const selectedDate = date.toLocaleDateString()
return choreDate === selectedDate
})
console.timeEnd('🏁 getChoresForDate')
return result
},
[getFilteredChores],
)
const updateChores = useCallback(
newChore => {
console.time('🏁 updateChores')
let newChores = [...chores, newChore]
if (impersonatedUser) {
newChores = newChores.filter(
chore => chore.assignedTo === impersonatedUser.userId,
)
}
setChores(newChores)
setFilteredChores(newChores)
setSearchFilter('All')
console.timeEnd('🏁 updateChores')
},
[chores, impersonatedUser],
)
const handleMenuOutsideClick = event => { const handleMenuOutsideClick = event => {
if ( if (
anchorEl && anchorEl &&
@@ -621,132 +706,132 @@ const MyChores = () => {
setSelectedCalendarDate(null) setSelectedCalendarDate(null)
} }
const handleChoreUpdated = (updatedChore, event) => { const handleChoreUpdated = useCallback(
var newChores = chores.map(chore => { (updatedChore, event) => {
if (chore.id === updatedChore.id) { console.time('🏁 handleChoreUpdated')
return updatedChore var newChores = chores.map(chore => {
} if (chore.id === updatedChore.id) {
return chore return updatedChore
}) }
return chore
})
var newFilteredChores = filteredChores.map(chore => { var newFilteredChores = filteredChores.map(chore => {
if (chore.id === updatedChore.id) { if (chore.id === updatedChore.id) {
return updatedChore return updatedChore
}
return chore
})
if (
event === 'archive' ||
(event === 'completed' && updatedChore.frequencyType === 'once')
) {
newChores = newChores.filter(chore => chore.id !== updatedChore.id)
newFilteredChores = newFilteredChores.filter(
chore => chore.id !== updatedChore.id,
)
} }
return chore setChores(newChores)
}) setFilteredChores(newFilteredChores)
if ( console.timeEnd('🏁 handleChoreUpdated')
event === 'archive' ||
(event === 'completed' && updatedChore.frequencyType === 'once') switch (event) {
) { case 'completed':
newChores = newChores.filter(chore => chore.id !== updatedChore.id) showSuccess({
newFilteredChores = newFilteredChores.filter( title: 'Task Completed',
chore => chore.id !== updatedChore.id, message: 'Great job! The task has been marked as completed.',
})
break
case 'skipped':
showSuccess({
title: 'Task Skipped',
message: 'The task has been moved to the next due date.',
})
break
case 'rescheduled':
showSuccess({
title: 'Task Rescheduled',
message: 'The task due date has been updated successfully.',
})
break
case 'due-date-removed':
showSuccess({
title: 'Task Unplanned',
message: 'The task is now unplanned and has no due date.',
})
break
case 'unarchive':
showSuccess({
title: 'Task Restored',
message: 'The task has been restored and is now active.',
})
break
case 'archive':
showSuccess({
title: 'Task Archived',
message:
'The task has been archived and hidden from the active list.',
})
break
case 'started':
showSuccess({
title: 'Task Started',
message: 'The task has been marked as started.',
})
break
case 'paused':
showWarning({
title: 'Task Paused',
message: 'The task has been paused.',
})
break
case 'deleted':
default:
showSuccess({
title: 'Task Updated',
message: 'Your changes have been saved successfully.',
})
}
},
[chores, filteredChores, showSuccess, showWarning],
)
const handleChoreDeleted = useCallback(
deletedChore => {
console.time('🏁 handleChoreDeleted')
const newChores = chores.filter(chore => chore.id !== deletedChore.id)
const newFilteredChores = filteredChores.filter(
chore => chore.id !== deletedChore.id,
) )
} setChores(newChores)
setChores(newChores) setFilteredChores(newFilteredChores)
setFilteredChores(newFilteredChores) console.timeEnd('🏁 handleChoreDeleted')
setChoreSections( },
ChoresGrouper( [chores, filteredChores],
selectedChoreSection, )
newChores,
ChoreFilters(impersonatedUser?.userId || userProfile?.id)[
selectedChoreFilter
],
),
)
switch (event) { const searchOptions = useMemo(
case 'completed': () => ({
showSuccess({ keys: ['name', 'raw_label'],
title: 'Task Completed', includeScore: true,
message: 'Great job! The task has been marked as completed.', isCaseSensitive: false,
}) findAllMatches: true,
break }),
case 'skipped': [],
showSuccess({ )
title: 'Task Skipped',
message: 'The task has been moved to the next due date.',
})
break
case 'rescheduled':
showSuccess({
title: 'Task Rescheduled',
message: 'The task due date has been updated successfully.',
})
break
case 'due-date-removed':
showSuccess({
title: 'Task Unplanned',
message: 'The task is now unplanned and has no due date.',
})
break
case 'unarchive':
showSuccess({
title: 'Task Restored',
message: 'The task has been restored and is now active.',
})
break
case 'archive':
showSuccess({
title: 'Task Archived',
message:
'The task has been archived and hidden from the active list.',
})
break
case 'started':
showSuccess({
title: 'Task Started',
message: 'The task has been marked as started.',
})
break
case 'paused':
showWarning({
title: 'Task Paused',
message: 'The task has been paused.',
})
break
case 'deleted':
default:
showSuccess({
title: 'Task Updated',
message: 'Your changes have been saved successfully.',
})
}
}
const handleChoreDeleted = deletedChore => { const processedChoresForSearch = useMemo(
const newChores = chores.filter(chore => chore.id !== deletedChore.id) () =>
const newFilteredChores = filteredChores.filter( chores.map(c => ({
chore => chore.id !== deletedChore.id, ...c,
) raw_label: c.labelsV2?.map(l => l.name).join(' '),
setChores(newChores) })),
setFilteredChores(newFilteredChores) [chores],
setChoreSections( )
ChoresGrouper(
selectedChoreSection,
newChores,
ChoreFilters(impersonatedUser?.userId || userProfile?.id)[
selectedChoreFilter
],
),
)
}
const searchOptions = { const fuse = useMemo(
// keys to search in () => new Fuse(processedChoresForSearch, searchOptions),
keys: ['name', 'raw_label'], [processedChoresForSearch, searchOptions],
includeScore: true, // Optional: if you want to see how well each result matched the search term
isCaseSensitive: false,
findAllMatches: true,
}
const fuse = new Fuse(
chores.map(c => ({
...c,
raw_label: c.labelsV2?.map(c => c.name).join(' '),
})),
searchOptions,
) )
const handleSearchChange = e => { const handleSearchChange = e => {
@@ -806,7 +891,7 @@ const MyChores = () => {
} else { } else {
// First, get chores from expanded sections only // First, get chores from expanded sections only
const expandedChores = choreSections const expandedChores = choreSections
.filter((section, index) => openChoreSections[index]) // Only expanded sections .filter((_section, index) => openChoreSections[index]) // Only expanded sections
.flatMap(section => section.content || []) // Get all chores from expanded sections .flatMap(section => section.content || []) // Get all chores from expanded sections
// Check if all expanded chores are already selected // Check if all expanded chores are already selected
@@ -922,17 +1007,19 @@ const MyChores = () => {
try { try {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
archiveChore.mutate(chore.id, { archiveChore.mutate(chore.id, {
onSuccess: (data) => { onSuccess: data => {
archivedTasks.push(data) archivedTasks.push(data)
// Remove from chores and filteredChores // Remove from chores and filteredChores
setChores(prev => prev.filter(c => c.id !== chore.id)) setChores(prev => prev.filter(c => c.id !== chore.id))
setFilteredChores(prev => prev.filter(c => c.id !== chore.id)) setFilteredChores(prev =>
prev.filter(c => c.id !== chore.id),
)
resolve(data) resolve(data)
}, },
onError: (error) => { onError: error => {
failedTasks.push(chore) failedTasks.push(chore)
reject(error) reject(error)
} },
}) })
}) })
} catch (error) { } catch (error) {
@@ -994,6 +1081,7 @@ const MyChores = () => {
message: `Successfully deleted ${deletedTasks.length} task${deletedTasks.length > 1 ? 's' : ''}.`, message: `Successfully deleted ${deletedTasks.length} task${deletedTasks.length > 1 ? 's' : ''}.`,
}) })
console.time('🏁 Bulk delete update')
const deletedIds = new Set(deletedTasks.map(c => c.id)) const deletedIds = new Set(deletedTasks.map(c => c.id))
const newChores = chores.filter(c => !deletedIds.has(c.id)) const newChores = chores.filter(c => !deletedIds.has(c.id))
const newFilteredChores = filteredChores.filter( const newFilteredChores = filteredChores.filter(
@@ -1001,15 +1089,7 @@ const MyChores = () => {
) )
setChores(newChores) setChores(newChores)
setFilteredChores(newFilteredChores) setFilteredChores(newFilteredChores)
setChoreSections( console.timeEnd('🏁 Bulk delete update')
ChoresGrouper(
selectedChoreSection,
newChores,
ChoreFilters(impersonatedUser?.userId || userProfile?.id)[
selectedChoreFilter
],
),
)
} }
if (failedTasks.length > 0) { if (failedTasks.length > 0) {
@@ -1176,42 +1256,16 @@ const MyChores = () => {
selectedItem={selectedChoreSection} selectedItem={selectedChoreSection}
selectedFilter={selectedChoreFilter} selectedFilter={selectedChoreFilter}
setFilter={filter => { setFilter={filter => {
console.time('🏁 Filter change')
setSelectedChoreFilterWithCache(filter) setSelectedChoreFilterWithCache(filter)
const section = ChoresGrouper( console.timeEnd('🏁 Filter change')
selectedChoreSection,
chores,
ChoreFilters(impersonatedUser?.userId || userProfile?.id)[
filter
],
)
setChoreSections(section)
setOpenChoreSectionsWithCache(
// open all sections by default
Object.keys(section).reduce((acc, key) => {
acc[key] = true
return acc
}, {}),
)
}} }}
onItemSelect={selected => { onItemSelect={selected => {
const section = ChoresGrouper( console.time('🏁 Group by change')
selected.value,
chores,
ChoreFilters(impersonatedUser?.userId || userProfile?.id)[
selectedChoreFilter
],
)
setChoreSections(section)
setSelectedChoreSectionWithCache(selected.value) setSelectedChoreSectionWithCache(selected.value)
setOpenChoreSectionsWithCache(
// open all sections by default
Object.keys(section).reduce((acc, key) => {
acc[key] = true
return acc
}, {}),
)
setFilteredChores(chores) setFilteredChores(chores)
setSearchFilter('All') setSearchFilter('All')
console.timeEnd('🏁 Group by change')
}} }}
mouseClickHandler={handleMenuOutsideClick} mouseClickHandler={handleMenuOutsideClick}
/> />

View File

@@ -1,4 +1,4 @@
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react-swc'
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import { VitePWA } from 'vite-plugin-pwa' import { VitePWA } from 'vite-plugin-pwa'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
@@ -14,6 +14,10 @@ export default defineConfig({
'safari-pinned-tab.svg', 'safari-pinned-tab.svg',
'mstile-150x150.png', 'mstile-150x150.png',
], ],
injectManifest: {
globPatterns: ['**/*.{js,css,html,png,svg}'],
globIgnores: ['index.html'],
},
manifest: { manifest: {
name: 'Donetick: Simplify Tasks & Chores, Together.', name: 'Donetick: Simplify Tasks & Chores, Together.',
short_name: 'Donetick', short_name: 'Donetick',

3
wrangler.toml Normal file
View File

@@ -0,0 +1,3 @@
[build]
command = "npm run build-cf"
pages_build_output_dir = "dist"