From b45c99be3b55878beceb882a03bd937210304fd3 Mon Sep 17 00:00:00 2001 From: Florian Sylvain Date: Thu, 22 Oct 2026 10:15:32 +0200 Subject: [PATCH] fix: reduced SPA build chunk sizes --- Taskfile.yml | 2 +- web/vite.config.ts | 4 ++++ web/vite/vendorChunks.spec.ts | 33 +++++++++++++++++++++++++++++++++ web/vite/vendorChunks.ts | 21 +++++++++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 web/vite/vendorChunks.spec.ts create mode 100644 web/vite/vendorChunks.ts diff --git a/Taskfile.yml b/Taskfile.yml index dc3a66b..9ae8188 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -231,7 +231,7 @@ tasks: desc: Type-check and build the SPA into {{.DIST_DIR}} dir: web deps: [web:install] - sources: [src/**/*, public/**/*, index.html, vite.config.ts, tsconfig*.json, package.json, bun.lock] + sources: [src/**/*, public/**/*, vite/**/*, index.html, vite.config.ts, tsconfig*.json, package.json, bun.lock] generates: ['../{{.DIST_DIR}}/index.html'] cmds: - bun run build diff --git a/web/vite.config.ts b/web/vite.config.ts index 35c89d5..5e8ded6 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -6,11 +6,15 @@ import vueDevTools from 'vite-plugin-vue-devtools' import ui from '@nuxt/ui/vite' import { renewUi } from './src/theme/ui.config' import { lucideSubset } from './vite/lucideSubset' +import { vendorChunk } from './vite/vendorChunks' export default defineConfig({ build: { outDir: '../internal/infrastructure/assets/dist/', emptyOutDir: true, + rollupOptions: { + output: { manualChunks: vendorChunk }, + }, }, server: { proxy: { diff --git a/web/vite/vendorChunks.spec.ts b/web/vite/vendorChunks.spec.ts new file mode 100644 index 0000000..201bf57 --- /dev/null +++ b/web/vite/vendorChunks.spec.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from 'vitest' +import { vendorChunk } from './vendorChunks' + +describe('vendorChunk', () => { + it.each([ + ['/app/node_modules/prosemirror-state/dist/index.js', 'prosemirror'], + ['/app/node_modules/prosemirror-view/dist/index.js', 'prosemirror'], + ['/app/node_modules/@tiptap/core/dist/index.js', 'tiptap'], + ['/app/node_modules/markdown-it/index.mjs', 'markdown'], + ['/app/node_modules/vue/dist/vue.runtime.esm-bundler.js', 'vue'], + ['/app/node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js', 'vue'], + ['/app/node_modules/vue-router/dist/vue-router.mjs', 'vue'], + ['/app/node_modules/pinia/dist/pinia.mjs', 'vue'], + ['/app/node_modules/reka-ui/dist/index.js', 'reka'], + ['/app/node_modules/@vueuse/core/index.mjs', 'ui-utils'], + ['/app/node_modules/@floating-ui/dom/dist/floating-ui.dom.mjs', 'ui-utils'], + ['C:\\app\\node_modules\\@tiptap\\core\\dist\\index.js', 'tiptap'], + ])('puts %s in the %s chunk', (id, chunk) => { + expect(vendorChunk(id)).toBe(chunk) + }) + + it('leaves application code and unknown packages to the default splitting', () => { + expect(vendorChunk('/app/src/views/EditorView.vue')).toBeUndefined() + expect(vendorChunk('/app/node_modules/some-lib/index.js')).toBeUndefined() + expect( + vendorChunk('/app/node_modules/@nuxt/ui/dist/runtime/components/Button.vue'), + ).toBeUndefined() + }) + + it('does not match packages that merely start with a group name', () => { + expect(vendorChunk('/app/node_modules/vue-demi/lib/index.mjs')).toBeUndefined() + }) +}) diff --git a/web/vite/vendorChunks.ts b/web/vite/vendorChunks.ts new file mode 100644 index 0000000..bb471c6 --- /dev/null +++ b/web/vite/vendorChunks.ts @@ -0,0 +1,21 @@ +const groups: Array<[string, RegExp]> = [ + [ + 'prosemirror', + /[\\/]node_modules[\\/](prosemirror-[^\\/]+|orderedmap|rope-sequence|w3c-keyname)[\\/]/, + ], + [ + 'markdown', + /[\\/]node_modules[\\/](markdown-it|entities|mdurl|uc\.micro|linkify-it|punycode\.js)[\\/]/, + ], + ['tiptap', /[\\/]node_modules[\\/](@tiptap|linkifyjs)[\\/]/], + ['vue', /[\\/]node_modules[\\/](vue|@vue|vue-router|pinia)[\\/]/], + ['reka', /[\\/]node_modules[\\/]reka-ui[\\/]/], + ['ui-utils', /[\\/]node_modules[\\/](@internationalized|@floating-ui|@vueuse|@iconify)[\\/]/], +] + +export function vendorChunk(id: string): string | undefined { + for (const [name, pattern] of groups) { + if (pattern.test(id)) return name + } + return undefined +}