How to deal with @nextcloud/vue bloat?

I’m currently migrating my app from my own vue components to the components from @nextcloud/vue.

Being mostly done, i took a look at the compiled files and noticed that they’re a lot bigger than before. Even after dropping some dependencies like jQuery it’s still ~90% more than before. I’ve tried using chunks to load most of it only when it’s actually needed, but even with that the main app.js is still 2.6Mb from 1.5mb before. The sidebar alone now needs 2.7Mb after it was included in the app.js before.

I’m already using webpack to uglify the Js, so that won’t help me.

Looking at the js files i can see that there is a lot of stuff in there that i don’t use but that is loaded by the components by default. (e.G. Datepicker, tons of translations etc.) Is there any way to strip that?

I also see that the files still contain CSS, while i thought that that would be moved to my app.css file during compilation. Is there any way to archive that? My webpack config contains the following section to extract CSS:


                {
                    test: /\.s?css$/,
                    use : [
                        {loader: 'vue-style-loader'},
                        {
                            loader : MiniCssExtractPlugin.loader,
                            options: {
                                esModule: true
                            }
                        },
                        {
                            loader : 'css-loader',
                            options: {
                                esModule: true,
                                modules : 'global',
                                url     : {
                                    filter: (url) => {
                                        return url.indexOf('/apps/passwords/') === -1;
                                    }
                                }
                            }
                        },
                        {
                            loader : 'sass-loader',
                            options: {
                                sassOptions: {
                                    sourceMap  : !production,
                                    outputStyle: production ? 'compressed':null
                                }
                            }
                        },
                        {
                            loader : 'sass-resources-loader',
                            options: {
                                resources: [
                                    `${__dirname}/src/scss/Partials/_variables.scss`
                                ]
                            }
                        }
                    ]
                },

Edit: I found the reason for the gigantic sidebar js file. Apparently i once loaded NcSelect from @nextcloud/vue instead of specifying the path which loaded all Nextcloud components including 800kb of emojis. Now my sidebar “only” needs 633kb. That still doesn’t fix my bloated app.js

Some additional reading on GitHub:

1 Like

Thanks for the tickets. The treemap in the first ticket was actually helpful in some way.

Here is what i did so far to reduce my app.js size:

  • I included the Webpack Bundle Analyzer to get an overview of what contributes the most to size in my app.js (But don’t pack the report files into your appstore release, it’s just wasted space there)
  • Always import components directly, never import like this: import {NcButton} from '@nextcloud/vue'. This would load all components, not just the ones you need.
  • Used the Vue Async Components functionality to load NcAppNavigation* and NcBreadcumb* separately since they are gigantic. You can even create a mini placeholder / loading state component that Vue shows until the actual component is loaded. That helps to prevent content jumping around and you can show something to let the user know it’s loading.
  • I replaced @nextcloud/moment with dayjs. I only use two functions of moment, so 800kb is too much for that. The downside is that i had to manually add import statements for every locale. Maybe there is a better way to do that idk, but at least it’s smaller now.
  • Loading the material icons asynchronously does nothing. They are small and don’t add much. Just make sure to load them individually like import FolderIcon from 'vue-material-design-icons/Folder'; so you don’t load all at once.

These things are all workarounds for the actual problem, but at least they should help users with slow servers on on mobile connections.

I appreciate that the Nextcloud Devs seem to be aware of the size issue and have looked into it a bit, but i still can’t understand how you see a 180kb app navigation item and think “Yeah, no problem, we can ship it like that”. At least please include a warning in the documentation.

@Daphne It may be a bit rude to include you just like that, but maybe you could make sure that the “Usage” section here: https://nextcloud-vue-components.netlify.app/ contains some info that developers know that the components may be unexpectedly large and there are strategies to counter that.

@marcelrobitaille here is a discussion, that might fir with the PR on the cookbook app. You are more experienced here. Maybe you can either add value here or make use of the provided information from here? (Idk if this is already implemented in the PR.)

@mdw You could have a look at this PR for Nextcloud Cookbook. Like you with NcSelect, I noticed that a single component was importing a bunch of libraries that we didn’t actually use. In my case, NcActionInput had many large date-picker dependencies, but we never used it as a date picker. My (hopefully) temporary solution to that was to “fork” NcActionInput and strip out all the unused functions. I removed the imports of the big libraries and removed the code that switches the input to a date picker.

I also started loading moment.js locales dynamically. I first ignore all locales with a line in webpack.config.js:

new webpack.IgnorePlugin({ resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/ }),

then I load the user’s locale only when the page loads:

const locale = document.documentElement
    .getAttribute("data-locale")
    .replace("_", "-")
    .toLowerCase()

// `en` is the default locale and cannot be dynamically imported. Will 404
// https://github.com/moment/moment/issues/3624
;(locale === "en"
    ? Promise.resolve()
    : import(`moment/locale/${locale}.js`)
).then(() => moment.locale(locale))

It would be really amazing if they could upstream this, especially now that I know that at least two apps are noticing the large bundle size. I think components like NcActionInput should dynamically load their dependencies based on how they’re used. I started working on a PR for that, but I haven’t had a lot of time for it recently.

2 Likes

That is amazing. I didn’t know you could import like this with webpack.

Since i’m using dayjs i don’t even need to ignore anything, i just need the import with a variable and webpack does the rest.

Thanks a lot

2 Likes