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