diff --git a/vite.config.js b/vite.config.js index e499635..44da18d 100644 --- a/vite.config.js +++ b/vite.config.js @@ -119,6 +119,14 @@ export default defineConfig({ // which precaching would undo. They're served via the // `sticker-library` runtimeCaching rule below instead. globIgnores: ['**/stickers/**'], + // Note: we deliberately do NOT raise `maximumFileSizeToCacheInBytes` + // here. The default 2 MiB cap is a useful regression alarm — if + // a future change pushes any chunk past it, the build fails loudly + // and we know to either split further or investigate why a chunk + // grew. Earlier this file overrode the cap to 5 MiB because the + // pre-split bundle was 2.32 MB; that override was removed once + // `build.rollupOptions.output.manualChunks` (below in this config) + // brought every chunk under the default cap. runtimeCaching: [ { urlPattern: /^https:\/\/cdn\.huggingface\.co\/.*/i, @@ -198,5 +206,95 @@ export default defineConfig({ '/exports': { target: 'http://localhost:3001', changeOrigin: true }, }, }, - build: { outDir: 'dist' }, + build: { + outDir: 'dist', + // ──────────────────────────────────────────────────────────────── + // Manual chunk splitting + // ──────────────────────────────────────────────────────────────── + // + // Why we split rather than ship one bundle + // ───────────────────────────────────────── + // 1. Workbox precache cap. The PWA plugin's default + // `maximumFileSizeToCacheInBytes` is 2 MiB. A single bundle + // containing Konva + react-konva + @huggingface/transformers + + // react + filerobot + everything else easily clears 2.3 MB, + // which fails the precache step. Splitting keeps every chunk + // comfortably under the cap without needing to override it. + // + // 2. Browser cache reuse across deploys. When app code changes + // (the common case during active development), only the + // `index` chunk's hash flips; vendor chunks (Konva, React, + // transformers) keep their old hash and stay cached in users' + // browsers. That means returning users only re-download the + // relatively-small app chunk, not the 1-MB-each library + // chunks that haven't changed. + // + // 3. Parallel download. Browsers can fetch multiple chunks + // concurrently on HTTP/2, so loading 4 × 600 KB chunks is + // typically faster than 1 × 2.3 MB chunk on broadband, even + // before considering caching. + // + // What goes where + // ─────────────── + // transformers/ @huggingface/transformers \u2014 the in-browser + // ML runtime used for background removal. + // Biggest single dependency. Only the + // inference engine is bundled here; the + // actual ML model weights stream from the + // huggingface CDN at runtime (see + // `transformers-models` / `transformers-lfs` + // runtimeCaching rules above for offline + // re-use). + // konva/ konva + react-konva + use-image \u2014 the canvas + // stack. All three are needed together + // because react-konva wraps konva and + // use-image wraps it for React consumption. + // Grouping prevents one from being inlined + // into a chunk that doesn't otherwise need + // the others. + // react-vendor/ react + react-dom \u2014 stable, every visitor + // needs them, perfect for long-term caching. + // filerobot/ react-filerobot-image-editor \u2014 mid-size, + // used only when the user opens the advanced + // image editor. Worth its own chunk so the + // rest of the app doesn't pay for it. + // index/ Everything else: app code, react-select, + // styled-components, zod, uuid, plus all + // the small utilities. This stays the + // "main" chunk that changes with each deploy. + // + // What we deliberately did NOT split + // ────────────────────────────────── + // The smaller deps (react-select, styled-components, zod, uuid, + // @emotion/is-prop-valid) stay in the index chunk on purpose. + // Pulling them out would create chunks under 50 KB each, which + // adds HTTP overhead (per-request connection cost, separate + // hash entries in the precache manifest) without meaningfully + // helping cache reuse. The "many small chunks" antipattern is + // worse than a moderately-sized index chunk that includes the + // long tail. + // + // If a future change makes one of these grow significantly (say, + // a styled-components major upgrade that doubles its size), it + // becomes worth pulling into its own chunk \u2014 but only then. + // + // Maintenance: when adding a new heavy dependency + // ─────────────────────────────────────────────── + // 1. Build and check `dist/assets/` for which chunk it landed in. + // 2. If it bloated the `index` chunk past comfort (rule of thumb: + // if index alone is > 1 MiB), add a new entry to manualChunks + // targeting that package name. + // 3. The 2 MiB workbox cap acts as the loud alarm \u2014 if you forget, + // the build fails and tells you exactly which chunk overflowed. + rollupOptions: { + output: { + manualChunks: { + 'transformers': ['@huggingface/transformers'], + 'konva': ['konva', 'react-konva', 'use-image'], + 'react-vendor': ['react', 'react-dom'], + 'filerobot': ['react-filerobot-image-editor'], + }, + }, + }, + }, });