From 56003ec371b6a00aa83a689f5f069607d68d42f4 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 5 May 2026 08:10:27 +0200 Subject: [PATCH] perf: split shared Vue/@nextcloud/vue chunks across widget entry-points MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each procest widget entry was inlining its own copy of Vue, @nextcloud/vue, @conduction/nextcloud-vue, pinia and the material-design-icon set, costing ~6.7 MB per bundle. With 7 widget entries that compounded to ~47 MB of duplicated framework JS attached to the dashboard. Webpack 'optimization.splitChunks' now extracts two stable-filename shared chunks (one for Vue + lightweight deps, one for the heavier @nextcloud/vue surface) so each widget keeps only widget-specific code. The shared chunks load once on the page and are cached indefinitely across navigations. Each widget's PHP load() now also Util::addScript()s the two shared chunks. Util::addScript dedupes by (app, file), so even with mydash eagerly loading every widget the shared chunks emit once. After 'npm run build' on the latest @nextcloud/vue: procest-shared-nc-vue.js : (new) 2.79 MB procest-shared-vendor.js : (new) 0.33 MB procest-main.js : 9 MB → 4.73 MB procest-settings.js : 6 MB → 3.35 MB procest-Widget.js : 6.7 MB → 3.00 MB Total widget JS on /apps/mydash/ drops from ~47 MB to ~24 MB on cold load and to per-widget delta on warm cache. Validated locally via mydash page load; no new console errors versus baseline. --- lib/Dashboard/CasesOverviewWidget.php | 3 +++ lib/Dashboard/DeadlineAlertsWidget.php | 3 +++ lib/Dashboard/MyTasksWidget.php | 3 +++ lib/Dashboard/OverdueCasesWidget.php | 3 +++ lib/Dashboard/StalledCasesWidget.php | 3 +++ lib/Dashboard/StartCaseWidget.php | 3 +++ lib/Dashboard/TaskRemindersWidget.php | 3 +++ webpack.config.js | 37 ++++++++++++++++++++++++++ 8 files changed, 58 insertions(+) diff --git a/lib/Dashboard/CasesOverviewWidget.php b/lib/Dashboard/CasesOverviewWidget.php index ad4ef471..e6abb48d 100644 --- a/lib/Dashboard/CasesOverviewWidget.php +++ b/lib/Dashboard/CasesOverviewWidget.php @@ -117,6 +117,9 @@ public function getUrl(): ?string */ public function load(): void { + // Shared vendor chunks emitted by webpack splitChunks (see webpack.config.js). + Util::addScript(Application::APP_ID, Application::APP_ID.'-shared-vendor'); + Util::addScript(Application::APP_ID, Application::APP_ID.'-shared-nc-vue'); Util::addScript(Application::APP_ID, Application::APP_ID.'-casesOverviewWidget'); Util::addStyle(Application::APP_ID, 'dashboardWidgets'); diff --git a/lib/Dashboard/DeadlineAlertsWidget.php b/lib/Dashboard/DeadlineAlertsWidget.php index c2486074..0deb9328 100644 --- a/lib/Dashboard/DeadlineAlertsWidget.php +++ b/lib/Dashboard/DeadlineAlertsWidget.php @@ -118,6 +118,9 @@ public function getUrl(): ?string */ public function load(): void { + // Shared vendor chunks emitted by webpack splitChunks (see webpack.config.js). + Util::addScript(Application::APP_ID, Application::APP_ID.'-shared-vendor'); + Util::addScript(Application::APP_ID, Application::APP_ID.'-shared-nc-vue'); Util::addScript(Application::APP_ID, Application::APP_ID.'-deadlineAlertsWidget'); Util::addStyle(Application::APP_ID, 'dashboardWidgets'); diff --git a/lib/Dashboard/MyTasksWidget.php b/lib/Dashboard/MyTasksWidget.php index 31025e66..7d5a3d7f 100644 --- a/lib/Dashboard/MyTasksWidget.php +++ b/lib/Dashboard/MyTasksWidget.php @@ -117,6 +117,9 @@ public function getUrl(): ?string */ public function load(): void { + // Shared vendor chunks emitted by webpack splitChunks (see webpack.config.js). + Util::addScript(Application::APP_ID, Application::APP_ID.'-shared-vendor'); + Util::addScript(Application::APP_ID, Application::APP_ID.'-shared-nc-vue'); Util::addScript(Application::APP_ID, Application::APP_ID.'-myTasksWidget'); Util::addStyle(Application::APP_ID, 'dashboardWidgets'); diff --git a/lib/Dashboard/OverdueCasesWidget.php b/lib/Dashboard/OverdueCasesWidget.php index 2b88efa1..03095855 100644 --- a/lib/Dashboard/OverdueCasesWidget.php +++ b/lib/Dashboard/OverdueCasesWidget.php @@ -117,6 +117,9 @@ public function getUrl(): ?string */ public function load(): void { + // Shared vendor chunks emitted by webpack splitChunks (see webpack.config.js). + Util::addScript(Application::APP_ID, Application::APP_ID.'-shared-vendor'); + Util::addScript(Application::APP_ID, Application::APP_ID.'-shared-nc-vue'); Util::addScript(Application::APP_ID, Application::APP_ID.'-overdueCasesWidget'); Util::addStyle(Application::APP_ID, 'dashboardWidgets'); diff --git a/lib/Dashboard/StalledCasesWidget.php b/lib/Dashboard/StalledCasesWidget.php index 70280b57..81aa5d25 100644 --- a/lib/Dashboard/StalledCasesWidget.php +++ b/lib/Dashboard/StalledCasesWidget.php @@ -118,6 +118,9 @@ public function getUrl(): ?string */ public function load(): void { + // Shared vendor chunks emitted by webpack splitChunks (see webpack.config.js). + Util::addScript(Application::APP_ID, Application::APP_ID.'-shared-vendor'); + Util::addScript(Application::APP_ID, Application::APP_ID.'-shared-nc-vue'); Util::addScript(Application::APP_ID, Application::APP_ID.'-stalledCasesWidget'); Util::addStyle(Application::APP_ID, 'dashboardWidgets'); diff --git a/lib/Dashboard/StartCaseWidget.php b/lib/Dashboard/StartCaseWidget.php index 836abfbc..8a449b3d 100644 --- a/lib/Dashboard/StartCaseWidget.php +++ b/lib/Dashboard/StartCaseWidget.php @@ -112,6 +112,9 @@ public function getUrl(): ?string */ public function load(): void { + // Shared vendor chunks emitted by webpack splitChunks (see webpack.config.js). + Util::addScript(Application::APP_ID, Application::APP_ID.'-shared-vendor'); + Util::addScript(Application::APP_ID, Application::APP_ID.'-shared-nc-vue'); Util::addScript(Application::APP_ID, Application::APP_ID.'-startCaseWidget'); Util::addStyle(Application::APP_ID, 'dashboardWidgets'); }//end load() diff --git a/lib/Dashboard/TaskRemindersWidget.php b/lib/Dashboard/TaskRemindersWidget.php index ef852eb9..bceca4b6 100644 --- a/lib/Dashboard/TaskRemindersWidget.php +++ b/lib/Dashboard/TaskRemindersWidget.php @@ -118,6 +118,9 @@ public function getUrl(): ?string */ public function load(): void { + // Shared vendor chunks emitted by webpack splitChunks (see webpack.config.js). + Util::addScript(Application::APP_ID, Application::APP_ID.'-shared-vendor'); + Util::addScript(Application::APP_ID, Application::APP_ID.'-shared-nc-vue'); Util::addScript(Application::APP_ID, Application::APP_ID.'-taskRemindersWidget'); Util::addStyle(Application::APP_ID, 'dashboardWidgets'); diff --git a/webpack.config.js b/webpack.config.js index 6125f44a..d8e96279 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -101,4 +101,41 @@ webpackConfig.plugins = [ // preventing the nextcloud-vue submodule's nested deps (Vue 3) from leaking in. webpackConfig.resolve.alias['@nextcloud/dialogs'] = path.resolve(__dirname, 'node_modules/@nextcloud/dialogs') +// Share Vue + @nextcloud/vue + pinia + icons + @conduction/nextcloud-vue +// across every entry-point so each widget bundle no longer inlines its own +// ~5 MB framework copy. Stable filenames (no contenthash in the JS name) +// mean each widget's `Util::addScript` PHP call can reference the chunk +// directly without a manifest. The vendor chunk is loaded once and cached +// across every widget/page in the app. +webpackConfig.optimization = { + ...(webpackConfig.optimization || {}), + splitChunks: { + ...(webpackConfig.optimization?.splitChunks || {}), + chunks: 'all', + cacheGroups: { + default: false, + defaultVendors: false, + ncVue: { + name: appId + '-shared-nc-vue', + // Matches both node_modules entries AND the monorepo-dev alias + // `../nextcloud-vue/src/...` which webpack resolves outside + // node_modules when @conduction/nextcloud-vue is aliased to it. + test: /[\\/]node_modules[\\/](@nextcloud[\\/]vue|@conduction[\\/]nextcloud-vue)[\\/]|[\\/]nextcloud-vue[\\/]src[\\/]/, + priority: 30, + reuseExistingChunk: true, + enforce: true, + filename: appId + '-shared-nc-vue.js', + }, + vendor: { + name: appId + '-shared-vendor', + test: /[\\/]node_modules[\\/](vue|pinia|vue-material-design-icons|@vueuse|core-js)[\\/]/, + priority: 20, + reuseExistingChunk: true, + enforce: true, + filename: appId + '-shared-vendor.js', + }, + }, + }, +} + module.exports = webpackConfig