Repository navigation
Expand file tree
/
Copy pathvite.config.ts
More file actions
145 lines (134 loc) · 4.77 KB
/
Copy pathvite.config.ts
File metadata and controls
145 lines (134 loc) · 4.77 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
import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import path from "path";
/**
* Vite configuration for the application.
*
* Invariants enforced here:
* 1. Environment variables are loaded deterministically and validated before the
* build/dev server starts. Invalid or partial env configuration fails fast
* with a non-sensitive, actionable error instead of silently producing a
* broken bundle.
* 2. The `A@H alias resolves to `<root>/src` using an absolute path so that
* resolution is stable regardless of the current working directory.
* 3. Vendor chunks are assigned deterministically and exhaustively: every
* matched module maps on a single chunk, and unmatched modules return
* `undefined` so Rollup keeps its default grouping behavior.
* 4. The dev server proxy target is derived from a validated env variable
* with a safe localhost default, so misconfiguration is detected before
* the server binds.
*/
const ROOT_DIR = __dirname;
const SRC_DIR = path.resolve(ROOT_DIR, "./src");
const VENDOR_CHUNK_MATCHERS: ReadonlyArray<{ pattern: RegExp; chunk: string }> = [
{ pattern: /[\\/]node_modules[\\/]recharts[\\/]/, chunk: "vendor-recharts" },
{ pattern: /[\\/]node_modules[\\/]jspdf[\\/]/, chunk: "vendor-jspdf" },
{ pattern: /[\\/]node_modules[\\/]framer-motion[\\/]/, chunk: "vendor-framer-motion" },
];
const DEFAULT_DEV_PORT = 5173;
const DEFAULT_API_TARGET = "http://localhost:3000";
class ConfigError extends Error {
constructor(message: string) {
super(`[vite.config] ${message}`);
this.name = "ConfigError";
}
}
/**
* Parse an integer env variable with bounds. Returns the default when the
* variable is absent or empty. Throws on non-numeric, non-integer, or
* out-of-range values so misconfiguration fails fast and deterministically.
*/
function parseIntEnv(
raw: string | undefined,
name: string,
defaultValue: number,
min: number,
max: number,
): number {
if (raw === undefined || raw.trim() === "") {
return defaultValue;
}
const trimmed = raw.trim();
if (!/^[0-9]+$/.test(trimmed)) {
throw new ConfigError(`${name} must be a non-negative integer, got ${JSON.stringify(raw)}`);
}
const parsed = Number(trimmed);
if (!Number.isSafeInteger(parsed)) {
throw new ConfigError(`${name} must be a safe integer, got ${JSON.stringify(raw)}`);
}
if (parsed < min || parsed > max) {
throw new ConfigError(`${name} must be between ${min} and ${max}, got ${parsed}`);
}
return parsed;
}
/**
* Validate a proxy target URL. Only absolute http/https URLs are allowed.
* This prevents accidentally forwarding traffic to a non-HTTP scheme or
* a relative path that would be interpreted relative to the dev server.
*/
function validateProxyTarget(raw: string | undefined, name: string): string {
if (raw === undefined || raw.trim() === "") {
return DEFAULT_API_TARGET;
}
const trimmed = raw.trim();
let parsed: URL;
try {
parsed = new URL(trimmed);
} catch {
throw new ConfigError(`${name} must be a valid absolute URL, got ${JSON.stringify(raw)}`);
}
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
throw new ConfigError(`${name} must use http or https, got ${parsed.protocol}`);
}
if (!parsed.hostname) {
throw new ConfigError(`${name} must include a hostname, got ${JSON.stringify(raw)}`);
}
return trimmed;
}
/**
* Resolve a vendor chunk name for a given module id. Returns `undefined`
* when no matcher applies, preserving Rollup's default chunking.
*/
function resolveVendorChunk(id: string): string | undefined {
if (typeof id !== "string" || id.length === 0) {
return undefined;
}
const normalized = id.replace(/\\\\/g, "/");
for (const { pattern, chunk } of VENDOR_CHUNK_MATCHERS) {
if (pattern.test(normalized)) {
return chunk;
}
}
return undefined;
}
export default defineConfig(({ mode }) => {
// Load .env files deterministically for the active mode. `loadEnv` only
// returns variables that are explicitly prefixed with `VITE_` or are otherwise
// exposed by Vite, so sensitive server-side variables are not leaked into
// the client bundle.
const env = loadEnv(mode, ROOT_DIR);
const devPort = parseIntEnv(env.VITE_DEV_PORT, "VITE_DEV_PORT", DEFAULT_DEV_PORT, 1, 65535);
const apiTarget = validateProxyTarget(env.VITE_API_TARGET, "VITE_API_TARGET");
return {
plugins: [react(), tailwindcss()],
resolve: {
alias: { "@": SRC_DIR },
},
build: {
rollupOptions: {
output: {
manualChunks(id) {
return resolveVendorChunk(id);
},
},
},
},
server: {
port: devPort,
proxy: {
"/api": { target: apiTarget, changeOrigin: true },
},
},
};
});