Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

102 linesCodeBlameRaw
1import { reactRouter } from "@react-router/dev/vite";
2import { cloudflare } from "@cloudflare/vite-plugin";
3import tailwindcss from "@tailwindcss/vite";
4import { defineConfig, type Plugin } from "vite";
5import { randomUUID } from "node:crypto";
6
7/**
8 * The doc editor's chunk and everything it imports, so a doc's page can
9 * ask the browser to fetch them with the page (`<link rel="modulepreload">`,
10 * routes/workspace/folios/folio.tsx `links`) rather than after the page's
11 * own code has run and asked for them. The editor is only ever imported
12 * lazily (it runs in the browser alone), so no route lists it among its
13 * modules; this notes its files as the browser build writes them, for the
14 * server build that follows in the same run. In development there are no
15 * such files, and the page adds no links.
16 */
17function editorChunk(): Plugin {
18 const id = "virtual:g1t-editor-chunk";
19 let files: string[] = [];
20 return {
21 name: "g1t-editor-chunk",
22 resolveId(source) {
23 return source === id ? `\0${id}` : null;
24 },
25 load(source) {
26 return source === `\0${id}` ? `export default ${JSON.stringify(files)};` : null;
27 },
28 generateBundle(_options, bundle) {
29 if (this.environment.name !== "client") return;
30 const chunks = Object.values(bundle).filter((output) => output.type === "chunk");
31 const editor = chunks.find((chunk) => chunk.moduleIds.some((moduleId) => moduleId.replace(/\\/g, "/").endsWith("/app/components/folios/doc/editor.tsx")));
32 if (!editor) return;
33 // The chunk first, then what it statically imports, each once.
34 const seen = new Set<string>();
35 const walk = (fileName: string) => {
36 if (seen.has(fileName)) return;
37 seen.add(fileName);
38 const chunk = chunks.find((c) => c.fileName === fileName);
39 for (const imported of chunk?.imports ?? []) walk(imported);
40 };
41 walk(editor.fileName);
42 files = [...seen].map((fileName) => `/${fileName}`);
43 },
44 };
45}
46
47export default defineConfig({
48 define: { "import.meta.env.G1T_BUILD_ID": JSON.stringify(randomUUID()) },
49 plugins: [cloudflare({ viteEnvironment: { name: "ssr" } }), tailwindcss(), reactRouter(), editorChunk()],
50 resolve: {
51 tsconfigPaths: true,
52 },
53 build: {
54 rolldownOptions: {
55 output: {
56 codeSplitting: {
57 groups: [
58 // React and react-dom are CommonJS, which the bundler wraps in
59 // initialisers that a chunk calls while it loads. Left to
60 // automatic splitting, react-dom's landed in an app chunk that
61 // imports the chunk calling it, so that chunk ran first and
62 // threw `s is not a function`: tooltips, cards, menus and the
63 // spend pill never came up. In a chunk of their own, which
64 // imports nothing of the app's, they are always ready first.
65 { name: "react", test: /[\\/]node_modules[\\/](react|react-dom|scheduler|use-sync-external-store|react-is)[\\/]/, priority: 3 },
66 // The UI primitives behind shadcn's components (Radix, and the
67 // floating-ui that positions tooltips, cards and menus) set
68 // themselves up while they load, calling each other. Inside the
69 // app's chunks they could end up in two that import each other,
70 // and whichever a page loaded first would call into the other
71 // before it was ready. Together, apart from the app, they can't.
72 {
73 name: "ui",
74 test: /[\\/]node_modules[\\/](radix-ui|@radix-ui[\\/][^\\/]+|@floating-ui[\\/][^\\/]+|aria-hidden|react-remove-scroll[^\\/]*|use-callback-ref|use-sidecar|react-style-singleton|get-nonce|tslib|detect-node-es)[\\/]/,
75 priority: 3,
76 },
77 // Icons are shared by nearly every page, a few hundred bytes each:
78 // one file for all of them, not a request per icon.
79 { name: "icons", test: /[\\/]node_modules[\\/]lucide-react[\\/]/, priority: 2 },
80 // The app's own components and helpers that several routes
81 // share: grouped by which routes use them, and small groups
82 // merged into their neighbours, so a page fetches a few files
83 // of them rather than one per module. A signed-in page fetched
84 // 124 files, most of them a kilobyte or two, each a request and
85 // a `modulepreload` to parse. Modules one route alone uses stay
86 // with that route. Packages are left to automatic splitting: a
87 // group over them merged what the editor needs with what the
88 // diagram renderers need, and every doc fetched both.
89 {
90 name: "app",
91 test: /[\\/]apps[\\/]web[\\/]app[\\/](components|lib)[\\/]/,
92 minShareCount: 2,
93 entriesAware: true,
94 entriesAwareMergeThreshold: 24_000,
95 priority: 1,
96 },
97 ],
98 },
99 },
100 },
101 },
102});