Repository navigation
Expand file tree
/
Copy pathvite.config.mjs
More file actions
202 lines (191 loc) · 8.15 KB
/
Copy pathvite.config.mjs
File metadata and controls
202 lines (191 loc) · 8.15 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
import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import { defineConfig } from 'vite';
import svgrPlugin from 'vite-plugin-svgr';
import viteTsconfigPaths from 'vite-tsconfig-paths';
import { visualizer } from 'rollup-plugin-visualizer';
import { generateMetaJson } from './build-utils.mjs';
import { elementCallStaticCopy } from './vite.element-call.mjs';
/**
* Vite configuration for the Alkemio client-web project.
*
* - Integrates React with React Compiler enabled for automatic memoization and performance optimization.
* - Integrates TypeScript path resolution and SVGR plugins.
* - Adds a custom plugin (`no-cache-index`) to prevent caching of `index.html` and `/home` routes in the dev server by setting aggressive no-cache headers.
* - Adds a custom plugin (`generate-meta-json`) to generate `public/meta.json` with version info from `package.json` at build time.
* - Serves the embedded call UI, its configuration and its licence files under `/element-call/` (shared with the
* Sentry build config, see `vite.element-call.mjs`).
* - Sets up path aliasing for `@` to `./src`.
* - Configures the dev server to run on `localhost:3001`.
* - Customizes build output directory, sourcemaps, and Rollup output filenames.
* - Optimizes dependencies for Emotion and MUI Tooltip.
*
* @module vite.config
*/
export default defineConfig({
plugins: [
tailwindcss(),
react({
babel: {
plugins: ['babel-plugin-react-compiler'],
},
}),
viteTsconfigPaths(),
svgrPlugin(),
// Plugin to prevent index.html caching in dev server
{
/**
* Vite plugin server configuration hook to disable caching for all SPA routes.
*
* Adds middleware to the dev server that intercepts requests to all routes that serve index.html.
* This includes '/', '/index.html', and any URL that doesn't contain a file extension and doesn't
* start with '/api/' or '/@' (Vite internal routes). For these routes, it overrides the response
* methods to set aggressive no-cache headers, ensuring that browsers and proxies do not cache
* the index.html responses.
*
* @param {import('vite').ViteDevServer} server - The Vite development server instance.
*/
name: 'no-cache-index',
configureServer(server) {
server.middlewares.use((req, res, next) => {
// Check if this is an HTML request (SPA route) that will serve index.html or meta.json
const isNoCacheRoute =
req.url &&
(req.url === '/' ||
req.url === '/index.html' ||
req.url?.endsWith('/index.html') ||
req.url === '/home' ||
// the files below might not work - headersSent is true
req.url?.startsWith('/meta.json') ||
req.url?.startsWith('/robots.txt') ||
req.url?.startsWith('/env-config.js') ||
(!req.url.includes('.') && !req.url.startsWith('/api/') && !req.url.startsWith('/@')));
if (isNoCacheRoute) {
// Intercept the response to remove caching headers
const originalSend = res.send;
const originalEnd = res.end;
const originalWrite = res.write;
// Override response methods to set headers just before sending
res.send = function(chunk) {
setNoCacheHeaders(res);
return originalSend.call(this, chunk);
};
res.end = function(chunk) {
setNoCacheHeaders(res);
return originalEnd.call(this, chunk);
};
res.write = function(chunk) {
setNoCacheHeaders(res);
return originalWrite.call(this, chunk);
};
function setNoCacheHeaders(response) {
// Remove caching headers
if (response.headersSent) {
// If headers are already sent, we can't modify them
return;
}
try {
response.removeHeader('Last-Modified');
response.removeHeader('ETag');
response.removeHeader('etag');
response.removeHeader('If-Modified-Since');
response.removeHeader('If-None-Match');
} catch (e) {
// ignore errors if headers cannot be removed
}
// Set comprehensive no-cache headers - most aggressive approach
response.setHeader(
'Cache-Control',
'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0, s-maxage=0, private'
);
response.setHeader('Pragma', 'no-cache');
response.setHeader('Expires', 'Thu, 01 Jan 1970 00:00:00 GMT');
response.setHeader('Surrogate-Control', 'no-store');
response.setHeader('X-Accel-Expires', '0');
response.setHeader('Vary', '*');
// Preserve correct Content-Type for known static file types
if (!req.url?.includes('.') || req.url?.endsWith('.html')) {
response.setHeader('Content-Type', 'text/html; charset=utf-8');
}
// Additional anti-cache headers
response.setHeader('X-Cache-Control', 'no-cache');
response.setHeader('X-Powered-By', 'Vite-NoCache-' + Date.now());
}
}
next();
});
},
},
// Plugin to generate meta.json with version info
{
name: 'generate-meta-json',
config() {
generateMetaJson();
},
buildStart() {
generateMetaJson();
},
},
elementCallStaticCopy(),
// Bundle analyzer - generates stats.html when ANALYZE=true
process.env.ANALYZE === 'true' && visualizer({
open: true,
filename: 'build/stats.html',
gzipSize: true,
brotliSize: true,
}),
].filter(Boolean),
resolve: {
alias: {
// These aliases ensure that all parts of the app use the same React instance,
// preventing issues with multiple React versions in node_modules. (Excalidraw)
react: path.resolve("./node_modules/react"),
"react-dom": path.resolve("./node_modules/react-dom"),
'@': path.resolve(__dirname, './src'),
},
},
server: {
port: 3001,
host: 'localhost',
},
build: {
sourcemap: process.env.NODE_ENV === 'development',
emptyOutDir: true,
outDir: 'build',
rollupOptions: {
// The Matrix SSO callback is its own page so it never loads the app bundle.
input: {
main: path.resolve(__dirname, 'index.html'),
matrixCallback: path.resolve(__dirname, 'matrix-callback.html'),
},
output: {
entryFileNames: 'assets/[name][hash].js',
chunkFileNames: 'assets/[name][hash].js',
assetFileNames: 'assets/[name][hash].[ext]',
manualChunks(id) {
if (!id.includes('node_modules')) return;
// Extract the actual package name from the module path.
// With pnpm, paths look like: .pnpm/pkg@ver/node_modules/pkg/file.js
// The package name is what comes after the LAST node_modules/
const parts = id.split('node_modules/');
const segment = parts[parts.length - 1];
const pkg = segment.startsWith('@')
? segment.split('/').slice(0, 2).join('/')
: segment.split('/')[0];
// DO NOT manually chunk react/react-dom/scheduler — Rollup must
// place them to avoid circular cross-chunk dependencies.
if (pkg === '@apollo/client' || pkg === 'apollo-upload-client') return 'vendor-apollo';
if (pkg.startsWith('@tiptap/')) return 'vendor-tiptap';
if (pkg === 'yjs' || pkg === 'y-prosemirror' || pkg === 'socket.io-client') return 'vendor-realtime';
if (pkg.startsWith('@sentry/') || pkg === '@elastic/apm-rum') return 'vendor-monitoring';
if (
pkg === 'lodash-es' || pkg === 'formik' || pkg === 'yup' ||
pkg === 'date-fns' || pkg === 'axios' || pkg === 'react-router' ||
pkg === 'react-i18next' || pkg === 'i18next'
) return 'vendor-utils';
},
},
},
},
});