Moved to lazy loading modules and configured build chunking

modified:   package-lock.json
	modified:   package.json
	modified:   src/App.tsx
	modified:   src/screens/ActivePickListScreen.test.tsx
	modified:   src/screens/ActivePickListScreen.tsx
	modified:   src/screens/BarcodeScannerScreen.tsx
	modified:   src/screens/HomeScreen.tsx
	modified:   src/screens/ImportExportScreen.tsx
	modified:   src/screens/ManageAreasScreen.test.tsx
	modified:   src/screens/ManageAreasScreen.tsx
	modified:   src/screens/ManageCategoriesScreen.test.tsx
	modified:   src/screens/ManageCategoriesScreen.tsx
	modified:   src/screens/ManageProductsScreen.test.tsx
	modified:   src/screens/ManageProductsScreen.tsx
	modified:   src/screens/PickListsScreen.test.tsx
	modified:   src/screens/PickListsScreen.tsx
	modified:   src/screens/StartPickListScreen.test.tsx
	modified:   src/screens/StartPickListScreen.tsx
	modified:   tsconfig.json
	modified:   vite.config.ts
This commit is contained in:
2025-11-26 09:34:18 +10:00
parent da6992372f
commit d6b784ad11
20 changed files with 219 additions and 39 deletions
+112
View File
@@ -42,6 +42,7 @@
"eslint-plugin-react-refresh": "^0.4.24", "eslint-plugin-react-refresh": "^0.4.24",
"jsdom": "^27.2.0", "jsdom": "^27.2.0",
"msw": "^2.12.3", "msw": "^2.12.3",
"rollup-plugin-visualizer": "^6.0.5",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.47.0", "typescript-eslint": "^8.47.0",
"vite": "^7.2.4", "vite": "^7.2.4",
@@ -3425,6 +3426,16 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/dequal": { "node_modules/dequal": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -4256,6 +4267,22 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/is-docker": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
"dev": true,
"license": "MIT",
"bin": {
"is-docker": "cli.js"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-extglob": { "node_modules/is-extglob": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -4313,6 +4340,19 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-docker": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/isarray": { "node_modules/isarray": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -4806,6 +4846,24 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/open": {
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
"integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"define-lazy-prop": "^2.0.0",
"is-docker": "^2.1.1",
"is-wsl": "^2.2.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.4", "version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -5393,6 +5451,60 @@
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/rollup-plugin-visualizer": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.5.tgz",
"integrity": "sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg==",
"dev": true,
"license": "MIT",
"dependencies": {
"open": "^8.0.0",
"picomatch": "^4.0.2",
"source-map": "^0.7.4",
"yargs": "^17.5.1"
},
"bin": {
"rollup-plugin-visualizer": "dist/bin/cli.js"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"rolldown": "1.x || ^1.0.0-beta",
"rollup": "2.x || 3.x || 4.x"
},
"peerDependenciesMeta": {
"rolldown": {
"optional": true
},
"rollup": {
"optional": true
}
}
},
"node_modules/rollup-plugin-visualizer/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/rollup-plugin-visualizer/node_modules/source-map": {
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
"integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">= 12"
}
},
"node_modules/run-parallel": { "node_modules/run-parallel": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+1
View File
@@ -46,6 +46,7 @@
"eslint-plugin-react-refresh": "^0.4.24", "eslint-plugin-react-refresh": "^0.4.24",
"jsdom": "^27.2.0", "jsdom": "^27.2.0",
"msw": "^2.12.3", "msw": "^2.12.3",
"rollup-plugin-visualizer": "^6.0.5",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.47.0", "typescript-eslint": "^8.47.0",
"vite": "^7.2.4", "vite": "^7.2.4",
+26 -22
View File
@@ -2,16 +2,18 @@ import { CssBaseline, ThemeProvider, createTheme } from '@mui/material';
import { BrowserRouter, Route, Routes } from 'react-router-dom'; import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { DBProvider } from './context/DBProvider'; import { DBProvider } from './context/DBProvider';
import { AppLayout } from './components/AppLayout'; import { AppLayout } from './components/AppLayout';
import { HomeScreen } from './screens/HomeScreen'; import React, { Suspense, lazy } from 'react';
import { StartPickListScreen } from './screens/StartPickListScreen';
import { PickListsScreen } from './screens/PickListsScreen';
import { ActivePickListScreen } from './screens/ActivePickListScreen';
import { ManageProductsScreen } from './screens/ManageProductsScreen';
import { ManageAreasScreen } from './screens/ManageAreasScreen';
import { ManageCategoriesScreen } from './screens/ManageCategoriesScreen';
import { BarcodeScannerScreen } from './screens/BarcodeScannerScreen';
import { useServiceWorker } from './hooks/useServiceWorker'; import { useServiceWorker } from './hooks/useServiceWorker';
import { ImportExportScreen } from './screens/ImportExportScreen';
const HomeScreen = lazy(() => import('./screens/HomeScreen'));
const StartPickListScreen = lazy(() => import('./screens/StartPickListScreen'));
const PickListsScreen = lazy(() => import('./screens/PickListsScreen'));
const ActivePickListScreen = lazy(() => import('./screens/ActivePickListScreen'));
const ManageProductsScreen = lazy(() => import('./screens/ManageProductsScreen'));
const ManageAreasScreen = lazy(() => import('./screens/ManageAreasScreen'));
const ManageCategoriesScreen = lazy(() => import('./screens/ManageCategoriesScreen'));
const BarcodeScannerScreen = lazy(() => import('./screens/BarcodeScannerScreen'));
const ImportExportScreen = lazy(() => import('./screens/ImportExportScreen'));
const theme = createTheme({ const theme = createTheme({
palette: { palette: {
@@ -25,19 +27,21 @@ const theme = createTheme({
const AppRoutes = () => { const AppRoutes = () => {
useServiceWorker(); useServiceWorker();
return ( return (
<Routes> <Suspense fallback={<div>Loading</div>}>
<Route element={<AppLayout />}> <Routes>
<Route path="/" element={<HomeScreen />} /> <Route element={<AppLayout />}>
<Route path="/start" element={<StartPickListScreen />} /> <Route path="/" element={<HomeScreen />} />
<Route path="/pick-lists" element={<PickListsScreen />} /> <Route path="/start" element={<StartPickListScreen />} />
<Route path="/pick-lists/:id" element={<ActivePickListScreen />} /> <Route path="/pick-lists" element={<PickListsScreen />} />
<Route path="/products" element={<ManageProductsScreen />} /> <Route path="/pick-lists/:id" element={<ActivePickListScreen />} />
<Route path="/categories" element={<ManageCategoriesScreen />} /> <Route path="/products" element={<ManageProductsScreen />} />
<Route path="/areas" element={<ManageAreasScreen />} /> <Route path="/categories" element={<ManageCategoriesScreen />} />
<Route path="/scan" element={<BarcodeScannerScreen />} /> <Route path="/areas" element={<ManageAreasScreen />} />
<Route path="/data" element={<ImportExportScreen />} /> <Route path="/scan" element={<BarcodeScannerScreen />} />
</Route> <Route path="/data" element={<ImportExportScreen />} />
</Routes> </Route>
</Routes>
</Suspense>
); );
}; };
+2 -1
View File
@@ -2,10 +2,11 @@ import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { render, screen, within } from '@testing-library/react'; import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { describe, expect, it, vi, beforeEach } from 'vitest'; import { describe, expect, it, vi, beforeEach } from 'vitest';
import { ActivePickListScreen } from './ActivePickListScreen'; import ActivePickListScreen from './ActivePickListScreen';
import { PickItem } from '../models/PickItem'; import { PickItem } from '../models/PickItem';
import { Product } from '../models/Product'; import { Product } from '../models/Product';
//const ActivePickListScreen = lazy(() => import('./screens/ActivePickListScreen'));
const addMock = vi.fn(); const addMock = vi.fn();
const updateMock = vi.fn(); const updateMock = vi.fn();
const pickItemsMock = vi.fn<() => PickItem[]>(); const pickItemsMock = vi.fn<() => PickItem[]>();
+3 -1
View File
@@ -34,7 +34,7 @@ import { ProductAutocomplete } from '../components/ProductAutocomplete';
const normalizeName = (name: string) => name.trim().toLowerCase(); const normalizeName = (name: string) => name.trim().toLowerCase();
export const ActivePickListScreen = () => { const ActivePickListScreen = () => {
const { id } = useParams(); const { id } = useParams();
const pickList = usePickList(id); const pickList = usePickList(id);
const items = usePickItems(id); const items = usePickItems(id);
@@ -558,3 +558,5 @@ export const ActivePickListScreen = () => {
</Container> </Container>
); );
}; };
export default ActivePickListScreen
+3 -1
View File
@@ -26,7 +26,7 @@ interface ProductLookup {
pickLists: { list: PickList; area?: Area }[]; pickLists: { list: PickList; area?: Area }[];
} }
export const BarcodeScannerScreen = () => { const BarcodeScannerScreen = () => {
const db = useDatabase(); const db = useDatabase();
const navigate = useNavigate(); const navigate = useNavigate();
const [lastCode, setLastCode] = useState(''); const [lastCode, setLastCode] = useState('');
@@ -193,3 +193,5 @@ export const BarcodeScannerScreen = () => {
</Container> </Container>
); );
}; };
export default BarcodeScannerScreen
+3 -1
View File
@@ -1,7 +1,7 @@
import { Button, Container, Stack, Typography } from '@mui/material'; import { Button, Container, Stack, Typography } from '@mui/material';
import { Link as RouterLink } from 'react-router-dom'; import { Link as RouterLink } from 'react-router-dom';
export const HomeScreen = () => ( const HomeScreen = () => (
<Container sx={{ py: 4 }}> <Container sx={{ py: 4 }}>
<Typography variant="h4" gutterBottom> <Typography variant="h4" gutterBottom>
StockFill StockFill
@@ -28,3 +28,5 @@ export const HomeScreen = () => (
</Stack> </Stack>
</Container> </Container>
); );
export default HomeScreen;
+3 -1
View File
@@ -19,7 +19,7 @@ import { ImportExportLog } from '../models/ImportExportLog';
const templateFiles: { label: string; file: string }[] = [{ label: 'Products', file: 'products_template.csv' }]; const templateFiles: { label: string; file: string }[] = [{ label: 'Products', file: 'products_template.csv' }];
export const ImportExportScreen = () => { const ImportExportScreen = () => {
const db = useDatabase(); const db = useDatabase();
const [logLines, setLogLines] = useState<string[]>([]); const [logLines, setLogLines] = useState<string[]>([]);
const [history, setHistory] = useState<ImportExportLog[]>([]); const [history, setHistory] = useState<ImportExportLog[]>([]);
@@ -176,3 +176,5 @@ export const ImportExportScreen = () => {
</Box> </Box>
); );
}; };
export default ImportExportScreen
+1 -1
View File
@@ -2,7 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { describe, expect, it, vi } from 'vitest'; import { describe, expect, it, vi } from 'vitest';
import { ManageAreasScreen } from './ManageAreasScreen'; import ManageAreasScreen from './ManageAreasScreen';
const areasMock = [{ id: 'area-1', name: 'Front Counter', created_at: 0, updated_at: 0 }]; const areasMock = [{ id: 'area-1', name: 'Front Counter', created_at: 0, updated_at: 0 }];
+3 -1
View File
@@ -20,7 +20,7 @@ import { v4 as uuidv4 } from 'uuid';
import { useAreas } from '../hooks/dataHooks'; import { useAreas } from '../hooks/dataHooks';
import { useDatabase } from '../context/DBProvider'; import { useDatabase } from '../context/DBProvider';
export const ManageAreasScreen = () => { const ManageAreasScreen = () => {
const db = useDatabase(); const db = useDatabase();
const areas = useAreas(); const areas = useAreas();
const [name, setName] = useState(''); const [name, setName] = useState('');
@@ -128,3 +128,5 @@ export const ManageAreasScreen = () => {
</Container> </Container>
); );
}; };
export default ManageAreasScreen
+1 -1
View File
@@ -2,7 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { beforeEach, describe, expect, it, vi } from 'vitest';
import { ManageCategoriesScreen } from './ManageCategoriesScreen'; import ManageCategoriesScreen from './ManageCategoriesScreen';
const categoriesMock = [{ id: 'cat-1', name: 'Snacks', created_at: 0, updated_at: 0 }]; const categoriesMock = [{ id: 'cat-1', name: 'Snacks', created_at: 0, updated_at: 0 }];
const productsMock = [ const productsMock = [
+3 -1
View File
@@ -20,7 +20,7 @@ import { v4 as uuidv4 } from 'uuid';
import { useDatabase } from '../context/DBProvider'; import { useDatabase } from '../context/DBProvider';
import { useCategories, useProducts } from '../hooks/dataHooks'; import { useCategories, useProducts } from '../hooks/dataHooks';
export const ManageCategoriesScreen = () => { const ManageCategoriesScreen = () => {
const db = useDatabase(); const db = useDatabase();
const categories = useCategories(); const categories = useCategories();
const products = useProducts(); const products = useProducts();
@@ -198,3 +198,5 @@ const deleteCategory = async (categoryId: string, categoryName: string) => {
</Container> </Container>
); );
}; };
export default ManageCategoriesScreen
+1 -1
View File
@@ -4,7 +4,7 @@ import { MemoryRouter } from 'react-router-dom';
import { fireEvent, render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; import { fireEvent, render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { ManageProductsScreen } from './ManageProductsScreen'; import ManageProductsScreen from './ManageProductsScreen';
let mockScannedBarcode = '123456'; let mockScannedBarcode = '123456';
const mockUseProducts = vi.fn(); const mockUseProducts = vi.fn();
+3 -1
View File
@@ -24,7 +24,7 @@ import { BarcodeScannerView } from '../components/BarcodeScannerView';
import { ExternalProductInfo, fetchProductFromOFF } from '../modules/openFoodFacts'; import { ExternalProductInfo, fetchProductFromOFF } from '../modules/openFoodFacts';
import { DEFAULT_BULK_NAME, DEFAULT_UNIT_TYPE, Product } from '../models/Product'; import { DEFAULT_BULK_NAME, DEFAULT_UNIT_TYPE, Product } from '../models/Product';
export const ManageProductsScreen = () => { const ManageProductsScreen = () => {
const db = useDatabase(); const db = useDatabase();
const products = useProducts(); const products = useProducts();
const categories = useCategories(); const categories = useCategories();
@@ -505,3 +505,5 @@ export const ManageProductsScreen = () => {
</Container> </Container>
); );
}; };
export default ManageProductsScreen
+1 -1
View File
@@ -2,7 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
import { render, screen, within } from '@testing-library/react'; import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { beforeEach, describe, expect, it, vi } from 'vitest';
import { PickListsScreen } from './PickListsScreen'; import PickListsScreen from './PickListsScreen';
let areasMock = [ let areasMock = [
{ id: 'area-1', name: 'Front Counter', created_at: 0, updated_at: 0 }, { id: 'area-1', name: 'Front Counter', created_at: 0, updated_at: 0 },
+3 -1
View File
@@ -22,7 +22,7 @@ import { usePickLists, useAreas } from '../hooks/dataHooks';
import { useDatabase } from '../context/DBProvider'; import { useDatabase } from '../context/DBProvider';
import { PickList } from '../models/PickList'; import { PickList } from '../models/PickList';
export const PickListsScreen = () => { const PickListsScreen = () => {
const lists = usePickLists(); const lists = usePickLists();
const areas = useAreas(); const areas = useAreas();
const db = useDatabase(); const db = useDatabase();
@@ -176,3 +176,5 @@ export const PickListsScreen = () => {
</Container> </Container>
); );
}; };
export default PickListsScreen
+1 -1
View File
@@ -2,7 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
import { render, screen, waitFor } from '@testing-library/react'; import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { beforeEach, describe, expect, it, vi } from 'vitest';
import { StartPickListScreen } from './StartPickListScreen'; import StartPickListScreen from './StartPickListScreen';
const navigateMock = vi.fn(); const navigateMock = vi.fn();
+3 -1
View File
@@ -15,7 +15,7 @@ import { v4 as uuidv4 } from 'uuid';
import { useAreas, useCategories } from '../hooks/dataHooks'; import { useAreas, useCategories } from '../hooks/dataHooks';
import { useDatabase } from '../context/DBProvider'; import { useDatabase } from '../context/DBProvider';
export const StartPickListScreen = () => { const StartPickListScreen = () => {
const areas = useAreas(); const areas = useAreas();
const categories = useCategories(); const categories = useCategories();
const db = useDatabase(); const db = useDatabase();
@@ -175,3 +175,5 @@ export const StartPickListScreen = () => {
</Container> </Container>
); );
}; };
export default StartPickListScreen
+1 -1
View File
@@ -11,7 +11,7 @@
"noEmit": true, "noEmit": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"strict": true, "strict": true,
"types": ["vite/client", "vitest/globals"] "types": ["vite/client", "vitest/globals", "vitest"]
}, },
"include": ["src"], "include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }] "references": [{ "path": "./tsconfig.node.json" }]
+45 -1
View File
@@ -1,13 +1,57 @@
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react'; import react from '@vitejs/plugin-react';
import { visualizer } from 'rollup-plugin-visualizer';
function packageNameFromId(id: string) {
// Extract the package name from a node_modules path, including scoped packages.
const match = id.match(/node_modules\/((?:@[^/]+\/)?[^/]+)/);
return match ? match[1] : null;
}
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react(), visualizer({ filename: 'dist/stats.html', open: true })],
server: { server: {
port: 5173, port: 5173,
}, },
build: { build: {
outDir: 'dist', outDir: 'dist',
minify: 'esbuild', // fast minifier
rollupOptions: {
output: {
manualChunks(id: string) {
if (!id) return;
if (id.includes('node_modules')) {
// Always group react + router into one chunk
if (id.includes('react') || id.includes('react-dom') || id.includes('react-router-dom')) {
return 'react-vendor';
}
// MUI splits: specific first, generic later
if (id.includes('@mui/icons-material')) return 'mui-icons';
if (id.includes('@mui/material/esm/styles') || id.includes('@mui/material/styles')) return 'mui-styles';
// remaining @mui material code -> mui-core (covers components, utils, etc.)
if (id.includes('@mui/material') || id.includes('@mui/base')) return 'mui-core';
// emotion / styled engine split
if (id.includes('@emotion') || id.includes('@mui/styled-engine')) return 'mui-styled';
// Keep large libs in their own chunk to avoid a huge 'vendor'
if (id.includes('jszip')) return 'jszip';
if (id.includes('papaparse')) return 'papaparse';
if (id.includes('dexie')) return 'dexie';
if (id.includes('@zxing')) return 'zxing';
// Default: create a per-package chunk name so vendor is split up.
const pkg = packageNameFromId(id);
if (pkg) {
// normalize '@' from scoped packages (can't use '/' in filenames)
return `npm.${pkg.replace('/', '__').replace('@', '')}`;
}
return 'vendor';
}
},
},
},
}, },
test: { test: {
include: ['src/**/*.{test,spec}.{ts,tsx}'], include: ['src/**/*.{test,spec}.{ts,tsx}'],