| 1 | import { reactRouter } from "@react-router/dev/vite"; |
| 2 | import { cloudflare } from "@cloudflare/vite-plugin"; |
| 3 | import tailwindcss from "@tailwindcss/vite"; |
| 4 | import { 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 | */ |
| 16 | function 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 | |
| 46 | export 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 | // Icons are shared by nearly every page, a few hundred bytes each: |
| 62 | // one file for all of them, not a request per icon. |
| 63 | { name: "icons", test: /[\\/]node_modules[\\/]lucide-react[\\/]/, priority: 2 }, |
| 64 | // The app's own components and helpers that several routes |
| 65 | // share: grouped by which routes use them, and small groups |
| 66 | // merged into their neighbours, so a page fetches a few files |
| 67 | // of them rather than one per module. A signed-in page fetched |
| 68 | // 124 files, most of them a kilobyte or two, each a request and |
| 69 | // a `modulepreload` to parse. Modules one route alone uses stay |
| 70 | // with that route. Packages are left to automatic splitting: a |
| 71 | // group over them merged what the editor needs with what the |
| 72 | // diagram renderers need, and every doc fetched both. |
| 73 | { name: "app", test: /[\\/]apps[\\/]web[\\/]app[\\/](components|lib)[\\/]/, minShareCount: 2, entriesAware: true, entriesAwareMergeThreshold: 24_000, priority: 1 }, |
| 74 | ], |
| 75 | }, |
| 76 | }, |
| 77 | }, |
| 78 | }, |
| 79 | }); |