-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathvite.config.ts
More file actions
285 lines (282 loc) · 10.7 KB
/
vite.config.ts
File metadata and controls
285 lines (282 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
import path from 'path';
import react from '@vitejs/plugin-react-swc';
import { defineConfig } from 'vite';
import { imagetools } from 'vite-imagetools';
// Note: rollup-plugin-visualizer is not imported here to avoid build failures
// when dev dependencies are pruned in production (e.g., Netlify builds).
// If you need bundle analysis, temporarily uncomment the import and usage below,
// or use an alternative tool like vite-bundle-visualizer.
// import { visualizer } from 'rollup-plugin-visualizer';
// const isAnalyze = process.env.ANALYZE === 'true';
export default defineConfig(() => ({
base: '/',
plugins: [
react(),
imagetools({
defaultDirectives: (url) => {
// Process images for WebP optimization
if (url.searchParams.has('webp')) {
return new URLSearchParams({
format: 'webp',
quality: '80',
});
}
if (url.searchParams.has('avif')) {
return new URLSearchParams({
format: 'avif',
quality: '70',
});
}
// Auto-generate WebP versions for all static images
if (url.searchParams.has('optimize')) {
return new URLSearchParams({
format: 'webp;png;jpg',
quality: '80',
w: url.searchParams.get('w') || '800',
h: url.searchParams.get('h') || '600',
});
}
return new URLSearchParams();
},
}),
// Note: Netlify automatically provides Brotli and Gzip compression at the edge,
// so we don't need vite-plugin-compression for production deployments
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
react: path.resolve(__dirname, './node_modules/react'),
'react-dom': path.resolve(__dirname, './node_modules/react-dom'),
},
dedupe: ['react', 'react-dom'],
// Narrow extensions list to reduce filesystem checks
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
},
server: {
// Warm up frequently used files for better dev performance
warmup: {
clientFiles: [
'./src/main.tsx',
'./src/App.tsx',
'./src/components/ui/**/*',
'./src/lib/supabase.ts',
'./src/lib/github.ts',
],
},
// Proxy API calls to Netlify functions during development
proxy: {
'/api': {
target: 'http://localhost:8888',
changeOrigin: true,
},
'/.netlify/functions': {
target: 'http://localhost:8888',
changeOrigin: true,
},
},
},
optimizeDeps: {
include: [
'react',
'react-dom',
'react-router',
'@radix-ui/react-slot',
'@radix-ui/react-avatar',
'@radix-ui/react-dropdown-menu',
'@radix-ui/react-dialog',
'@radix-ui/react-select',
'@radix-ui/react-tooltip',
'class-variance-authority',
'clsx',
'tailwind-merge',
// Add chart libraries to prevent initialization errors
'recharts',
'd3-scale',
'd3-shape',
'uplot',
],
exclude: [
'vitest',
'@testing-library/react',
'@testing-library/jest-dom',
'@xenova/transformers', // Exclude embeddings library
'onnxruntime-web', // Exclude ONNX runtime
],
// Remove force: true to avoid aggressive re-optimization
},
build: {
// Enable CSS code splitting for better performance
cssCodeSplit: true,
// Inline small CSS chunks to reduce requests
assetsInlineLimit: 4096, // 4KB threshold for inlining
commonjsOptions: {
// Better handling of CommonJS modules (like some D3 packages)
transformMixedEsModules: true,
strictRequires: 'auto',
},
rollupOptions: {
// Bundle analyzer plugin - temporarily disabled to fix production builds
// Uncomment when visualizer import is restored above
// plugins: isAnalyze
// ? [
// visualizer({
// filename: 'dist/stats.html',
// open: true,
// gzipSize: true,
// brotliSize: true,
// }),
// ]
// : [],
// Conservative tree shaking optimization for better bundle size
treeshake: {
moduleSideEffects: false, // Safe optimization for better bundle size
},
output: {
// Ensure proper module format
format: 'es',
// Use proper ES module syntax
generatedCode: {
constBindings: true,
objectShorthand: true,
arrowFunctions: true,
},
// Ensure proper file extensions for module recognition
entryFileNames: `js/[name]-[hash].js`,
chunkFileNames: `js/[name]-[hash].js`,
// Better asset organization
assetFileNames: (assetInfo) => {
const extType = assetInfo.name?.split('.').pop() || 'asset';
if (/png|jpe?g|svg|gif|webp|avif/i.test(extType)) {
return 'images/[name]-[hash][extname]';
}
if (/css/i.test(extType)) {
return 'css/[name]-[hash][extname]';
}
if (/woff2?|ttf|eot/i.test(extType)) {
return 'fonts/[name]-[hash][extname]';
}
return 'assets/[name]-[hash][extname]';
},
// Allow hoisting for proper module loading
hoistTransitiveImports: true,
// Optimized chunking strategy for better code splitting
manualChunks: (id) => {
if (id.includes('node_modules')) {
// IMPORTANT: Sentry must be checked BEFORE react because @sentry/react contains 'react/'
// Sentry is fully lazy-loaded via dynamic imports in src/lib/sentry-lazy.ts
// Do NOT assign it to any manual chunk - let Vite's code splitting handle it
// See: https://github.com/bdougie/contributor.info/issues/1400
if (id.includes('@sentry')) {
return; // Return undefined to let Vite handle chunking naturally
}
// Core React libraries - loaded immediately
if (id.includes('react/') || id.includes('react-dom/') || id.includes('react-router')) {
return 'vendor-react-core';
}
// UI components that are used everywhere
if (id.includes('@radix-ui')) {
return 'vendor-ui';
}
// NOTE: Nivo is NOT manually chunked - it's lazy-loaded via React.lazy() in
// src/components/features/activity/contributions.tsx. Manual chunking would
// pull it into the initial bundle. See: https://github.com/bdougie/contributor.info/issues/1400
if (id.includes('recharts')) {
return 'vendor-recharts';
}
if (id.includes('d3-')) {
return 'vendor-d3';
}
if (id.includes('uplot')) {
return 'vendor-uplot';
}
// AI SDK - lazy loaded via chat panel
if (id.includes('@ai-sdk') || id.includes('/node_modules/ai/')) {
return 'vendor-ai-sdk';
}
// Supabase SDK
if (id.includes('@supabase')) {
return 'vendor-supabase';
}
// Small utilities bundled together
if (
id.includes('clsx') ||
id.includes('tailwind-merge') ||
id.includes('class-variance-authority') ||
id.includes('date-fns')
) {
return 'vendor-utils';
}
// NOTE: Markdown libraries (react-markdown, remark, rehype, etc.) are NOT manually chunked here.
// They are lazy loaded via React.lazy() in markdown.tsx, which creates natural code splitting.
// Manual chunking caused shared utilities to be bundled into vendor-markdown, creating
// dependencies from other chunks and forcing eager loading.
// Analytics - lazy loaded
if (id.includes('posthog-js')) {
return 'vendor-analytics';
}
// Web vitals - small, keep separate
if (id.includes('web-vitals')) {
return 'vendor-vitals';
}
// Exclude heavy ML libraries
if (id.includes('@xenova/transformers') || id.includes('onnxruntime')) {
return 'embeddings-excluded';
}
}
// Don't split app code - let it stay in main bundle
},
},
},
// Optimize CSS minification
cssMinify: 'esbuild',
// Disable sourcemaps in production to reduce bundle size
// Source maps add 15MB to the dist folder, causing CI failures
// If we need source maps for error tracking, we should upload them separately to PostHog/Sentry
sourcemap: process.env.NODE_ENV === 'production' ? false : true,
// Optimize minification and target for better compression
minify: 'esbuild',
target: 'es2020', // Modern target with good compatibility
// Optimize chunk size warnings
chunkSizeWarningLimit: 1300, // Increased to accommodate vendor-react bundle
// Enable compression reporting
reportCompressedSize: true,
// Module preload optimization - load minimal critical path first for faster LCP
modulePreload: {
polyfill: true, // Enable polyfill for proper module loading
resolveDependencies: (_, deps) => {
// Preload ONLY the absolute minimum for LCP (home page H1 render)
// This reduces initial network waterfall and speeds up LCP significantly
const sorted = deps.sort((a, b) => {
// First priority: Core React libraries (required for any render)
if (a.includes('vendor-react-core')) return -1;
if (b.includes('vendor-react-core')) return 1;
// Second priority: Main app chunk (contains LCP component)
if (a.includes('index-')) return -1;
if (b.includes('index-')) return 1;
// Third priority: Utils for classnames (used by LCP element)
if (a.includes('vendor-utils')) return -1;
if (b.includes('vendor-utils')) return 1;
return 0;
});
// CRITICAL: Only preload chunks needed for initial H1 render (LCP element)
// UI components (Radix), charts, markdown, and analytics load on-demand
// This reduces initial preload from ~10 chunks to 3, improving LCP by 200-300ms
return sorted.filter(
(dep) =>
dep.includes('vendor-react-core') ||
dep.includes('vendor-utils') ||
// Match index- followed by hash, but exclude other prefixed chunks
(dep.includes('index-') && !dep.includes('nivo-') && !dep.includes('charts-'))
);
},
},
// Remove console/debugger in production and strip legal comments
esbuild: {
drop: process.env.NODE_ENV === 'production' ? ['console', 'debugger'] : [],
legalComments: 'none',
},
},
css: {
devSourcemap: true,
},
}));