-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvite.config.ts
More file actions
133 lines (125 loc) · 4.61 KB
/
Copy pathvite.config.ts
File metadata and controls
133 lines (125 loc) · 4.61 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
import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import fs from 'node:fs/promises';
import {defineConfig, type Plugin} from 'vite';
import { viteApiPlugin } from './src/server/vite-api-plugin';
import { config as loadDotenv } from 'dotenv';
// Make .env.local available to API handlers loaded by the dev plugin.
loadDotenv({ path: '.env.local' });
loadDotenv({ path: '.env' });
/**
* Post-build plugin: inject modulepreload + stylesheet preload hints into
* dist/login.html matching the hashed assets referenced by dist/index.html.
*
* Why: `/login` is served as static HTML for the initial paint. When the
* user clicks "Sign in", the browser navigates to `/` which loads the SPA.
* Without this plugin, the SPA chunks only start downloading AFTER the SPA
* HTML has been parsed. With it, the browser starts fetching vendor-react,
* vendor-tanstack, the entry JS, and the stylesheet in parallel with the
* user typing their password — so the post-login paint feels instant.
*
* Hashes change every build; the plugin extracts them from index.html and
* rewrites login.html in-place so there is no manifest to maintain.
*/
function injectLoginPreloads(): Plugin {
return {
name: 'inject-login-preloads',
apply: 'build',
closeBundle: {
order: 'post',
sequential: true,
async handler() {
const dist = path.resolve(__dirname, 'dist');
const indexPath = path.join(dist, 'index.html');
const loginPath = path.join(dist, 'login.html');
let index: string;
let login: string;
try {
[index, login] = await Promise.all([
fs.readFile(indexPath, 'utf8'),
fs.readFile(loginPath, 'utf8'),
]);
} catch {
// One of the files doesn't exist (e.g. partial build). Skip.
return;
}
const links: string[] = [];
const entryMatch = index.match(
/<script\s+type="module"\s+crossorigin\s+src="([^"]+)"/,
);
if (entryMatch) {
links.push(
`<link rel="modulepreload" href="${entryMatch[1]}" crossorigin>`,
);
}
const preloadRe =
/<link\s+rel="modulepreload"\s+crossorigin\s+href="([^"]+)"/g;
for (const m of index.matchAll(preloadRe)) {
links.push(`<link rel="modulepreload" href="${m[1]}" crossorigin>`);
}
const cssMatch = index.match(
/<link\s+rel="stylesheet"\s+crossorigin\s+href="([^"]+)"/,
);
if (cssMatch) {
links.push(
`<link rel="preload" as="style" href="${cssMatch[1]}" crossorigin>`,
);
}
if (links.length === 0) return;
const open = '<!-- spa-preload:auto -->';
const close = '<!-- /spa-preload:auto -->';
const block = `${open}\n ${links.join('\n ')}\n ${close}`;
const next = login.includes(open)
? login.replace(
new RegExp(`${open}[\\s\\S]*?${close}`),
block,
)
: login.replace('</head>', ` ${block}\n </head>`);
if (next !== login) {
await fs.writeFile(loginPath, next, 'utf8');
}
},
},
};
}
export default defineConfig(() => {
return {
plugins: [react(), tailwindcss(), viteApiPlugin(), injectLoginPreloads()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
build: {
rollupOptions: {
output: {
// Split only the truly cross-route heavy vendors into long-lived
// chunks. `@base-ui` / `@floating-ui` used to live in a single
// vendor-baseui chunk; that forced every route to pay for Dialog +
// Select even though those primitives are only used on a minority
// of pages. Omitting them lets Rollup co-locate each primitive with
// its consuming route chunk, trimming the initial bundle.
manualChunks(id) {
if (
id.includes('node_modules/react/') ||
id.includes('node_modules/react-dom/') ||
id.includes('node_modules/react-router') ||
id.includes('node_modules/scheduler/')
) {
return 'vendor-react';
}
if (id.includes('node_modules/@tanstack')) {
return 'vendor-tanstack';
}
},
},
},
},
server: {
// HMR is disabled in AI Studio via DISABLE_HMR env var.
// Do not modifyâfile watching is disabled to prevent flickering during agent edits.
hmr: process.env.DISABLE_HMR !== 'true',
},
};
});