diff --git a/package-lock.json b/package-lock.json index 092c5a8..5dedfbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^7.9.6", + "terser": "^5.44.1", "uuid": "^13.0.0" }, "devDependencies": { @@ -1518,6 +1519,16 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -2915,7 +2926,6 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -3132,6 +3142,12 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3261,6 +3277,12 @@ "dev": true, "license": "MIT" }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5663,6 +5685,25 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -5808,6 +5849,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/terser": { + "version": "5.44.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", diff --git a/package.json b/package.json index 2e9660d..366f84d 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^7.9.6", + "terser": "^5.44.1", "uuid": "^13.0.0" }, "devDependencies": { diff --git a/vite.config.ts b/vite.config.ts index bc7347e..b4b30cd 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,8 @@ +// vite.config.ts import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; -import { visualizer } from 'rollup-plugin-visualizer'; +import { visualizer } from 'rollup-plugin-visualizer'; +import path from 'path'; function packageNameFromId(id: string) { // Extract the package name from a node_modules path, including scoped packages. @@ -10,20 +12,50 @@ function packageNameFromId(id: string) { export default defineConfig({ plugins: [react(), visualizer({ filename: 'dist/stats.html', open: true })], + resolve: { + alias: { + // Force single copies of react/react-dom to avoid duplicates + react: path.resolve(__dirname, 'node_modules/react'), + 'react-dom': path.resolve(__dirname, 'node_modules/react-dom') + } + }, + optimizeDeps: { + // Prebundle React / MUI / Emotion so deps are stable during dev + include: [ + 'react', + 'react-dom', + 'react-router-dom', + '@mui/material', + '@mui/system', + '@mui/styled-engine', + '@emotion/react', + '@emotion/styled', + '@emotion/cache', + '@emotion/react/jsx-runtime' + ] + }, server: { port: 5173, }, build: { sourcemap: true, outDir: 'dist', - minify: 'esbuild', // fast minifier + // Use terser (safer wrt TDZ/minifier reordering). Use minify: false for debugging. + minify: 'terser', 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')) { + // Put react/react-dom + emotion + styled engine in the same chunk + // so initialization order is stable for mui/emotion runtime code. + if ( + id.includes('react') || + id.includes('react-dom') || + id.includes('react-router-dom') || + id.includes('@emotion') || + id.includes('@mui/styled-engine') + ) { return 'react-vendor'; } @@ -32,9 +64,6 @@ export default defineConfig({ 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';