2

I'm using Laravel, Vue, Vite, and Inertia. For UI I'm using Vuetify and for some reason when I load the page I can see that there are 500 + requests in the network tab, most of them are loading some Vuetify components that I don't need at all. How can I make it so it loads only component that are used in the app?

This is my main.js file :

/* eslint-disable import/order */
import layoutsPlugin from '@/plugins/layouts'
import vuetify from '@/plugins/vuetify'
import VueToast from 'vue-toast-notification'
import 'vue-toast-notification/dist/theme-default.css'
import { createInertiaApp, Link } from '@inertiajs/vue3'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'
import { loadFonts } from '@/plugins/webfontloader'
import './../styles/@core/template/index.scss'
import '@styles/styles.scss'
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import { ZiggyVue } from '../../vendor/tightenco/ziggy/dist/vue.m'
import 'sweetalert2/dist/sweetalert2.min.css'

loadFonts()
createInertiaApp({
    title: title => `${title}`,
    resolve: name => resolvePageComponent(`./pages/${name}.vue`, import.meta.glob('./pages/**/*.vue')),
    setup({ el, App, props, plugin }) {
        // eslint-disable-next-line vue/component-api-style
        const vueApp = createApp({ render: () => h(App, props) })

        vueApp.use(plugin)
            .use(ZiggyVue, Ziggy)
            .use(VueToast, {
                position: 'top',
                pauseOnHover: true,
            }).provide('toast', vueApp.config.globalProperties.$toast)
            .use(vuetify)
            .use(createPinia())
            .use(layoutsPlugin)
            .use(Link)
            .mount(el)
        
        return vueApp
    },
    progress: {
        color: '#4B5563',
    },
})

And vite.config.js:

import { fileURLToPath } from 'url'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import DefineOptions from 'unplugin-vue-define-options/vite'
import { defineConfig } from 'vite'
import Pages from 'vite-plugin-pages'
import Layouts from 'vite-plugin-vue-layouts'
import vuetify from 'vite-plugin-vuetify'
import laravel from 'laravel-vite-plugin'

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/js/main.js'],
            refresh: true,
        }),
        vue({
            template: {
                transformAssetUrls: {
                    base: null,
                    includeAbsolute: false,
                },
            },
        }),
        vueJsx(),

        // https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vite-plugin
        vuetify({
            styles: {
                configFile: 'resources/styles/variables/_vuetify.scss',
            },
        }),
        Pages({
            dirs: ['./resources/js/pages'],
        }),
        Layouts({
            layoutsDirs: './resources/js/layouts/',
        }),
        Components({
            dirs: ['resources/js/@core/components', 'resources/js/views/demos'],
            dts: true,
        }),
        AutoImport({
            eslintrc: {
                enabled: true,
                filepath: './.eslintrc-auto-import.json',
            },
            imports: ['vue', 'vue-router', '@vueuse/core', '@vueuse/math', 'pinia'],
            vueTemplate: true,
        }),
        DefineOptions(),
    ],
    define: { 'process.env': {} },
    resolve: {
        alias: {
            '@core-scss': fileURLToPath(new URL('./resources/styles/@core', import.meta.url)),
            '@': fileURLToPath(new URL('./resources/js', import.meta.url)),
            '@themeConfig': fileURLToPath(new URL('./themeConfig.js', import.meta.url)),
            '@core': fileURLToPath(new URL('./resources/js/@core', import.meta.url)),
            '@layouts': fileURLToPath(new URL('./resources/js/@layouts', import.meta.url)),
            '@images': fileURLToPath(new URL('./resources/images/', import.meta.url)),
            '@styles': fileURLToPath(new URL('./resources/styles/', import.meta.url)),
            '@configured-variables': fileURLToPath(new URL('./resources/styles/variables/_template.scss', import.meta.url)),
            '@axios': fileURLToPath(new URL('./resources/js/plugins/axios', import.meta.url)),
            '@validators': fileURLToPath(new URL('./resources/js/@core/utils/validators', import.meta.url)),
            'apexcharts': fileURLToPath(new URL('node_modules/apexcharts-clevision', import.meta.url)),
        },
    },
    build: {
        chunkSizeWarningLimit: 5000,
    },
    optimizeDeps: {
        exclude: ['vuetify'],
        entries: [
            './resources/js/**/*.vue',
        ],
    },
})

Network tab: enter image description here enter image description here

resources/js/plugins/vuetify/index.js:

import { createVuetify } from 'vuetify'
import { VBtn } from 'vuetify/components'
import defaults from './defaults'
import { icons } from './icons'
import theme from './theme'

// Styles
// eslint-disable-next-line import/no-unresolved
import '@core-scss/template/libs/vuetify/index.scss'
import 'vuetify/styles'

export default createVuetify({
    aliases: {
        IconBtn: VBtn,
    },
    defaults,
    icons,
    theme,
})

defaults.js:

export default {
    IconBtn: {
        icon: true,
        size: 'small',
        color: 'default',
        variant: 'text',
        VIcon: {
            size: 24,
        },
    },
    VAlert: {
        density: 'comfortable',
        VBtn: {
            color: undefined,
        },
    },
    VAvatar: {
    // ℹ️ Remove after next release
        variant: 'flat',
    },
    VBadge: {
    // set v-badge default color to primary
        color: 'primary',
    },
    VBtn: {
    // set v-btn default color to primary
        color: 'primary',
    },
    VChip: {
        elevation: 0,
    },
    VPagination: {
        activeColor: 'primary',
        density: 'comfortable',
        size: '32',
    },
    VTabs: {
    // set v-tabs default color to primary
        color: 'primary',
        VSlideGroup: {
            showArrows: true,
        },
    },
    VTooltip: {
    // set v-tooltip default location to top
        location: 'top',
    },
    VList: {
        VListItem: {
            activeColor: 'primary',
        },
    },
    VCheckbox: {
    // set v-checkbox default color to primary
        color: 'primary',
        density: 'comfortable',
        hideDetails: 'auto',
    },
    VRadioGroup: {
        color: 'primary',
        hideDetails: 'auto',
    },
    VRadio: {
        hideDetails: 'auto',
    },
    VSelect: {
        variant: 'outlined',
        color: 'primary',
        hideDetails: 'auto',
    },
    VRangeSlider: {
    // set v-range-slider default color to primary
        color: 'primary',
        density: 'comfortable',
        thumbLabel: true,
        hideDetails: 'auto',
    },
    VRating: {
    // set v-rating default color to primary
        density: 'compact',
        activeColor: 'warning',
    },
    VProgressCircular: {
    // set v-progress-circular default color to primary
        color: 'primary',
    },
    VSlider: {
    // set v-slider default color to primary
        color: 'primary',
        hideDetails: 'auto',
    },
    VTextField: {
        variant: 'outlined',
        color: 'primary',
        hideDetails: 'auto',
    },
    VAutocomplete: {
        variant: 'outlined',
        density: 'comfortable',
        color: 'primary',
        hideDetails: 'auto',
    },
    VCombobox: {
        variant: 'outlined',
        color: 'primary',
        hideDetails: 'auto',
    },
    VFileInput: {
        variant: 'outlined',
        color: 'primary',
        hideDetails: 'auto',
    },
    VTextarea: {
        variant: 'outlined',
        density: 'comfortable',
        color: 'primary',
        hideDetails: 'auto',
    },
    VSwitch: {
    // set v-switch default color to primary
        color: 'primary',
        hideDetails: 'auto',
    },
}
Aleksandar Đokić
  • 2,118
  • 17
  • 35
  • Typically these files are compiled into a single piece by, e.g. Webpack. – miken32 Aug 02 '23 at 15:25
  • Any idea why it's all sass files? Not sure why a browser would load those except from source maps. Are there unused Vuetify components that are not sass files? When you do a text search in the dist directory for one of the sass files, i.e. `VTab.sass`, what files come up? – Moritz Ringler Aug 05 '23 at 15:25
  • @MoritzRingler https://prnt.sc/D9qvbPRhiNiX this is what pops up – Aleksandar Đokić Aug 06 '23 at 20:08
  • @MoritzRingler i added one more screen shot of network. there are not only sass files – Aleksandar Đokić Aug 06 '23 at 20:10
  • Ok, so the SASS files come from the `styles.configFile` option - since you are rebuilding styles with custom parameters, the sass sources have to be loaded. The mjs files come from the `optimizeDeps.exclude`. They are only loaded individually in the dev server, when building the project, they will be packed into a single file. So that all makes sense. But I can't find an indicator why tree shaking wouldn't work. Can you name some of the components that are imported even though you are not using them?Are you sure they are not used in other components that you are using? – Moritz Ringler Aug 06 '23 at 22:40
  • Yes, i'm milion percent sure that i NEVER load some of the components. Example is VColorPicker lets say. I never use that in my app or In any of pages. @MoritzRingler – Aleksandar Đokić Aug 07 '23 at 08:37

2 Answers2

2

In your resources/js/plugins/vuetify/index.js, you import VBtn through vuetify/components:

import { VBtn } from 'vuetify/components'

This loads node_modules/vuetify/lib/components/index.mjs, which exports all Vuetify components. The unused components will be removed by treeshaking when building for production, but not when running the dev server.

When you load VBtn directly from vuetify/components/VBtn, the other files will not be loaded anymore:

import { VBtn } from 'vuetify/components/VBtn'

This is also described in the documentation:

Although treeshaking is automatically applied during production builds, it is advantageous to import components by specifying their full path in development mode. For instance, using vuetify/components/VBtn instead of vuetify/components ensures that the compiler loads fewer components, thus optimizing performance.

Moritz Ringler
  • 9,772
  • 9
  • 21
  • 34
0

Automatic treeshaking with Vuetify

You haven't shown your /plugins/vuetify file, but the following import statements will import all components (whether you use them or not), and should be removed if you want treeshaking.

import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'

The other requirement, which you already meet, is to register vite-plugin-vuetify in vite.config.js

import vue from '@vitejs/plugin-vue'
import vuetify from 'vite-plugin-vuetify'

export default {
  plugins: [
    vue(),
    vuetify(),
  ],
}
yoduh
  • 7,074
  • 2
  • 11
  • 21