Repository navigation
Expand file tree
/
Copy pathvite.config.ts
More file actions
371 lines (368 loc) · 16.3 KB
/
Copy pathvite.config.ts
File metadata and controls
371 lines (368 loc) · 16.3 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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
import { defineConfig, type Plugin } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import path from "path";
const host = process.env.TAURI_DEV_HOST;
// Dev-only fake backend (`src/dev/mock-backend/`). Injected as its own module
// script ahead of `main.tsx`, so it is installed before any app module calls
// `invoke()`. `apply: "serve"` keeps it out of every build; inside the Tauri
// window it sees `isTauri` and does nothing.
const mockBackend: Plugin = {
name: "atlas-mock-backend",
apply: "serve",
transformIndexHtml: () => [
{
tag: "script",
attrs: { type: "module", src: "/src/dev/mock-backend/install.ts" },
injectTo: "head",
},
],
};
export default defineConfig(() => ({
plugins: [react(), tailwindcss(), mockBackend],
resolve: {
alias: {
"@": path.resolve(import.meta.dirname, "./src"),
// Force the DOM-free build of `decode-named-character-reference`. Its
// package `browser` condition points at `index.dom.js`, which calls
// `document.createElement` at module scope. That module is a transitive
// dep of the remark/micromark markdown stack, which we run inside a Web
// Worker (markdown.worker.ts) — where `document` doesn't exist, so it
// threw `ReferenceError: Can't find variable: document`, killed the
// worker, and forced ALL markdown parsing onto the main thread (the
// fallback in markdown-cache.tsx). That congested the main thread during
// agent streaming + project switches. `index.js` is a table-based,
// DOM-free build with identical output.
"decode-named-character-reference": path.resolve(
import.meta.dirname,
"node_modules/decode-named-character-reference/index.js",
),
},
// Dedupe CodeMirror + Lezer. The lang packages (lang-json, lang-rust, …)
// are lazy-imported as separate chunks and each transitively imports
// @codemirror/{state,view,language} and @lezer/{common,highlight,lr};
// without dedup Rollup can ship two copies, and facets/extensions from
// one copy are invisible to an EditorView built from the other.
//
// NOTE (2026-09-06): "CodeMirror unstyled in production, fine in dev" was
// NOT this. It was the Tauri CSP: a nonce injected into index.html's
// inline <style> disables `style-src 'unsafe-inline'`, blocking every
// runtime-injected stylesheet. See the comment in index.html and
// `dangerousDisableAssetCspModification` in src-tauri/tauri.conf.json.
dedupe: [
"@codemirror/state",
"@codemirror/view",
"@codemirror/language",
"@codemirror/commands",
"@codemirror/search",
"@codemirror/autocomplete",
"@lezer/common",
"@lezer/highlight",
"@lezer/lr",
// Single pdfjs-dist instance: react-pdf re-exports `pdfjs`, and the
// worker is imported separately via `?url`. Two copies would mismatch
// the worker against the main-thread API version and fail to render.
"pdfjs-dist",
],
},
clearScreen: false,
// Pre-bundle the heavy dependency graphs into single ESM files served as
// one request, instead of letting Vite's dev server stream hundreds of
// tiny `node_modules/...` modules over individual HTTP roundtrips. The
// single biggest dev-mode startup speedup — `tauri dev` cold launch goes
// from "8 s to open devtools" territory down to a couple of seconds
// because the WebKit main thread isn't blocked on per-module fetches.
// Production builds ignore this; it's purely a dev-mode optimization.
optimizeDeps: {
include: [
"@codemirror/state",
"@codemirror/view",
"@codemirror/language",
"@codemirror/commands",
"@codemirror/autocomplete",
"@codemirror/search",
"@lezer/common",
"@lezer/highlight",
"@lezer/lr",
"react-markdown",
"remark-gfm",
"rehype-highlight",
"@base-ui/react/menu",
"@base-ui/react/popover",
"@base-ui/react/dialog",
"@base-ui/react/context-menu",
"@base-ui/react/tooltip",
// Pre-bundle the Tiptap stack so opening the Knowledge tab for
// the first time doesn't trigger Vite's "new dependencies
// optimized → reloading" cycle (which dumps editor state and
// looks like a full app reload to the user).
"@tiptap/core",
// NOTE: @tiptap/pm has no root export — it's only accessed via
// subpaths (@tiptap/pm/state, /view, etc.) which Vite picks up
// through the dep walker automatically. Including the bare name
// here errors with "Missing '.' specifier".
"@tiptap/react",
"@tiptap/starter-kit",
"@tiptap/extension-task-list",
"@tiptap/extension-task-item",
"@tiptap/extension-link",
"@tiptap/extension-placeholder",
"@tiptap/extension-typography",
"@tiptap/extension-highlight",
"@tiptap/extension-table",
"@tiptap/extension-table-row",
"@tiptap/extension-table-header",
"@tiptap/extension-table-cell",
"@tiptap/extension-code-block-lowlight",
"@tiptap/extension-mention",
"@tiptap/suggestion",
"tiptap-markdown",
"lowlight",
// Pre-bundle the graph view's renderer + physics stack for the
// same reason as Tiptap — first open of the Graph tab would
// otherwise trigger a "new dependencies optimized → reloading"
// cycle and dump in-progress state.
"pixi.js",
"matter-js",
// Pre-bundle the PDF stack so first open of a PDF tab doesn't trigger
// Vite's "new dependencies optimized → reloading" cycle.
"react-pdf",
"pdfjs-dist",
// Terminal stack — pre-bundle so first terminal open doesn't trigger a
// "new deps optimized → reload" cycle.
"@xterm/xterm",
"@xterm/addon-fit",
"@xterm/addon-webgl",
"@xterm/addon-unicode11",
],
// The worker is a separate ESM entry loaded via `?url`; pre-bundling it
// would rewrite its imports and break worker instantiation.
exclude: ["pdfjs-dist/build/pdf.worker.min.mjs"],
// Serve pre-bundled deps as soon as the scanner finishes instead of
// holding every request until the whole crawl ends. Only matters on a
// cold `node_modules/.vite` (first run, lockfile or config change).
holdUntilCrawlEnd: false,
},
build: {
// Skip gzipping all ~200 chunks just to print compressed sizes.
reportCompressedSize: false,
// Every chunk over Vite's 500 KB default is a lazy vendor group by design
// (pdf.worker 1.0 MB, mermaid, markdown, tiptap, xterm — see the groups
// below). The limit sits just above the largest so a NEW oversize chunk
// still warns.
chunkSizeWarningLimit: 1100,
// Vendor splitting so the initial chunk only holds what first paint
// needs (React + the chat panel). Heavy panel-specific vendors live in
// their own chunks and load on demand when the user opens a tab that
// pulls them in.
//
// PORTED from `rollupOptions.output.manualChunks` (a Rollup if-chain) to
// Rolldown's `codeSplitting.groups` when Vite 8 made Rolldown the bundler.
// The function form still "works" under Rolldown's compat shim, but it does
// NOT preserve exclusivity: measured on this repo, `clsx` was claimed by
// `vendor-pdf` and React's CJS build by `vendor-markdown` despite rules
// assigning both elsewhere, which made the entry statically import ~1 MB of
// lazy-panel vendors (rolldown#7473). `priority` is what fixes it — a
// higher-priority group matches first and its modules are REMOVED from
// lower-priority groups. Ordering alone cannot express that.
//
// Numbers are spaced by 10 so a rule can be slotted between two others
// without renumbering the file.
rolldownOptions: {
checks: {
// Five stores are imported dynamically by one module and statically by
// others (project-store from chat-store, git-store from editor-panel,
// …). Those `import()`s are deliberate cycle-breakers for module
// evaluation order, not code-splitting requests, so Rolldown's "this
// dynamic import won't make a chunk" notice is correct and irrelevant.
ineffectiveDynamicImport: false,
},
output: {
codeSplitting: {
groups: [
// Tiny shared utils, highest priority. `cn()` (clsx +
// tailwind-merge) is used all over the eager boot path AND by
// react-pdf. Left to a lower priority, clsx gets folded into
// `vendor-pdf`, so the entry imports a 200-byte function from a
// 425 KB chunk and preloads all of pdfjs before first paint —
// silently defeating the lazy `PdfViewer` boundary in
// center-panel.tsx. `\0vite/preload-helper` (the `__vitePreload`
// runtime) rides along for the same reason: it is needed eagerly,
// and wherever it lands the entry must import that chunk.
{
name: "vendor-utils",
priority: 100,
test: (id) =>
!id.endsWith(".css") &&
(id.includes("/node_modules/clsx/") ||
id.includes("/node_modules/tailwind-merge/") ||
id.includes("vite/preload-helper")),
},
// Exact package roots only — NOT a `/react/` substring (which
// matches @xyflow/react, @tiptap/react, react-markdown, …). Above
// vendor-markdown deliberately: react-markdown pulls React in, and
// at equal priority React's CJS build lands in `vendor-markdown`.
{
name: "vendor-react",
priority: 90,
test: (id) =>
!id.endsWith(".css") &&
(id.includes("/node_modules/react/") ||
id.includes("/node_modules/react-dom/") ||
id.includes("/node_modules/scheduler/")),
},
// `@codemirror/lang-*` is deliberately absent from every group, so
// each grammar keeps its own chunk. They are dynamically imported
// per-language in editor/lib/languages.ts; capturing them here
// collapses all 13 lazy boundaries into one chunk, so opening a
// JSON file would load every grammar. `@lezer/{common,highlight,lr}`
// are core runtime, not grammars, and stay with the editor chunk.
{
name: "vendor-codemirror",
priority: 80,
test: (id) =>
!id.endsWith(".css") &&
!id.includes("@codemirror/lang-") &&
(id.includes("@codemirror") ||
/@lezer\/(common|highlight|lr)\//.test(id)),
},
{
name: "vendor-xterm",
priority: 70,
test: (id) =>
!id.endsWith(".css") &&
(id.includes("@xterm") || id.includes("/xterm/") || id.includes("/xterm-")),
},
// Never route a STYLESHEET into a vendor JS chunk (every `test`
// here guards `.css` for this reason). `highlight.js/styles/
// github-dark.css` matched this rule once, which made the entry
// emit a bare `import "./vendor-markdown.js"` purely to attach that
// CSS — 554 KB of JS parsed before first paint for a theme file.
{
name: "vendor-markdown",
priority: 60,
// The `node_modules` guard is not decoration: these are substring
// tests, and `remark-`/`rehype-` are ordinary words to name a
// plugin after. `features/comms/lib/remark-comms-inline.ts`
// matched `remark-`, which pulled app source — and everything it
// imported, including the comms type module — into this vendor
// chunk, so the eagerly-loaded comms panel statically imported
// 560 KB of markdown it was carefully arranged to lazy-load.
test: (id) =>
!id.endsWith(".css") &&
id.includes("node_modules") &&
(id.includes("react-markdown") ||
id.includes("remark-") ||
id.includes("rehype-") ||
id.includes("shiki") ||
id.includes("highlight.js")),
},
{
name: "vendor-base-ui",
priority: 50,
test: (id) => !id.endsWith(".css") && id.includes("@base-ui"),
},
{
name: "vendor-tanstack",
priority: 40,
test: (id) => !id.endsWith(".css") && id.includes("@tanstack"),
},
{
name: "vendor-pdf",
priority: 30,
test: (id) =>
!id.endsWith(".css") &&
(id.includes("pdfjs-dist") || id.includes("react-pdf")),
},
{
name: "vendor-tauri",
priority: 20,
test: (id) => !id.endsWith(".css") && id.includes("@tauri-apps"),
},
// posthog-js is loaded with a dynamic import after the telemetry
// config IPC (see posthog-client.ts); a named group keeps it a
// recognisable lazy chunk instead of an anonymous hash.
{
name: "vendor-posthog",
priority: 20,
test: (id) => !id.endsWith(".css") && id.includes("/node_modules/posthog-js/"),
},
// Keep the heavy lazy-panel libs OUT of vendor-react. `@xyflow/react`
// (Canvas) and `@tiptap/react` (Knowledge) are only reached through
// lazy() panels, so they must land in their own lazy chunks — an
// earlier `/react/` substring match pulled them into the EAGER
// vendor-react chunk, loading ~500KB+ at startup.
{
name: "vendor-xyflow",
priority: 10,
test: (id) => !id.endsWith(".css") && id.includes("@xyflow"),
},
{
name: "vendor-tiptap",
priority: 10,
test: (id) => !id.endsWith(".css") && id.includes("@tiptap"),
},
],
},
},
},
},
server: {
port: 1420,
strictPort: true,
host: host || false,
hmr: host
? {
protocol: "ws",
host,
port: 1421,
}
: undefined,
watch: {
// Only the frontend (`src/`, index.html, the config files) is part of
// Vite's module graph; everything else in the repo is Rust, scripts,
// docs or build output. Without ignoring them, editing ANY such file
// while dogfooding Atlas on its own repo (e.g. tweaking `scripts/bump.sh` to
// watch the project git +/- update) makes Vite bounce the whole page.
ignored: [
"**/src-tauri/**",
"**/crates/**",
"**/landing/**",
"**/scripts/**",
"**/dist/**",
"**/target/**",
"**/.atlas/**",
"**/.git/**",
"**/*.sh",
"**/*.md",
],
},
// Pre-transform the boot critical-path the moment `tauri dev` starts the
// Vite server. Without this, Vite transforms each user-source module on
// first request, which on a 2400-module project shows up as a ~1.5 s
// "html → main-eval" gap on cold launch. Warmup hits the cache so the
// WebView's first request finds the module already transformed.
warmup: {
clientFiles: [
"./src/main.tsx",
"./src/App.tsx",
"./src/styles/globals.css",
"./src/features/layout/components/app-layout.tsx",
"./src/features/layout/components/center-panel.tsx",
"./src/features/layout/components/left-panel.tsx",
"./src/features/layout/components/right-panel.tsx",
"./src/features/layout/stores/layout-store.ts",
"./src/features/app/stores/app-store.ts",
"./src/features/app/components/welcome-screen.tsx",
"./src/features/projects/stores/project-store.ts",
"./src/features/chat/components/chat-panel.tsx",
"./src/features/chat/components/message-input.tsx",
"./src/features/chat/stores/chat-store.ts",
"./src/features/chat/lib/agents-api.ts",
"./src/components/titlebar.tsx",
"./src/components/command-palette.tsx",
"./src/components/atlas-icon.tsx",
],
},
},
}));