2
votes

TLDR

How do you properly lazy load Vue components within a Laravel application, using Vue routing in addition to Laravel's own web routes?

Full story:

I have a Laravel application that is using Vue. Now that the application has grown considerably in size, I would like to lazy load the Vue components.

To do this, I have set up these files:

  • app.js
  • router.js
  • Test.vue
  • app.blade.php
  • home.blade.php
  • news.blade.php

If I import like: import Test from './components/Test';, then everything works fine, but the component isn't lazy loaded.

If I import like

const Test = resolve => {
    require.ensure(['./components/Test'], () => {
        resolve(require('./components/Test'));
    });
}

then, the component is lazy loaded. I can tell this because I am logging to the console. However, this seems to break all other JS and the CSS.

I can also see the new JS file created in the Network tab. It creates a 0.js file.

Based on this other question, I have also tried:

function loadView(view) {
    return () => import(/* webpackChunkName: "[request]" */ `./components/${view}.vue`)
}

Everything works fine here as well, but the component isn't lazy loaded.

app.js

import Vue from 'vue';
import router from './router.js';

new Vue({
    el: '#app',
    router
})

router.js

import Vue from 'vue';
import Router from 'vue-router';
// import Test from './components/Test'; // Everything works, but doesn't lazy load

// Lazy loads and logs to the console, but breaks all of the other JS and the CSS
const Test = resolve => {
    require.ensure(['./components/Test'], () => {
        resolve(require('./components/Test'));
    });
}

// Also tried this way, which works, but does not lazy load
// function loadView(view) {
//     return () => import(/* webpackChunkName: "[request]" */ `./components/${view}.vue`)
// }

Vue.use(Router);

export default new Router({
    routes: [
        {
            path: '/',
            components: {
                test: Test
                // test: loadView('Test') // 
            }
        },
        {
            path: '/news',
            components: {}
        }
    ],
    mode: 'history',
});

Test.vue

<template>
    <div>
        <h1>This is a test</h1>
    </div>
</template>

<script>
    export default {
    }
    console.log('hey, this is from test.vue');
</script>

<style lang="scss" scoped>
</style>

app.blade.php

  • Includes <div id="app"></div> within body

home.blade.php

  • Includes <router-view name="test"></router-view>

news.blade.php

  • Also includes <router-view name="test"></router-view>, just to test.

Update 1:

Based on this question: Lazy Loading Components not working with Vue Router, I have updated the loadView function, but am still not able to load the CSS.

function loadView(view) {
    return () => import(`./components/${view}.vue`)
}

Update 2:

Based on this question: vuejs lazy loading components without the router, I tried to import the component and set it to a constant:

const Test = () => import(
  /* webpackChunkName: "./js/Test" */ './components/Test'
)

The lazy loading works perfectly, but the CSS still doesn't load.

Update 3:

When using import Test from './components/Test';, I notice that the app.css file is compiled successfully to 560 KiB.

However, when using const Test = () => import(/* webpackChunkName: "./js/Test" */ './components/Test');, this same file fails to compile, and instead remains at 0 bytes. ????

1

1 Answers

2
votes

The problem here was with Webpack and not the Vue router. The routing was working fine, but as noted in Update 3, the app.css would compile to 0 bytes.

I noticed that this was a common problem that others had as well. The best solution that I found comes from a comment by Daljeet Singh, aka Ilamp, here: https://github.com/JeffreyWay/laravel-mix/issues/1914#issuecomment-628791041

To save you a click, here is what he did:

Install laravel-mix-merge-manifest

npm install laravel-mix-merge-manifest --save-dev

webpack.css.mix.js

const mix = require('laravel-mix');
require('laravel-mix-merge-manifest');

mix.sass('resources/sass/app.scss', 'public/css')
  .version();

if (!mix.inProduction()) {
  mix.sourceMaps();
}

mix.mergeManifest();

webpack.js.mix.js

const mix = require('laravel-mix');
require('laravel-mix-merge-manifest');

mix.react('resources/js/app.js', 'public/js')
  .extract([])
  .setPublicPath('public')
  .version();

if (!mix.inProduction()) {
  mix.sourceMaps();
}

mix.mergeManifest();

Update your package.json's scripts section to:

    "dev": "npm run development-js && npm run development-css",
    "development-js": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js --env.mixfile=webpack.js.mix",
    "development-css": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js --env.mixfile=webpack.css.mix",
    "prod": "npm run production-js && npm run production-css",
    "production-js": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js --env.mixfile=webpack.js.mix",
    "production-css": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js --env.mixfile=webpack.css.mix",
    "watch": "npm run development-js -- --watch & npm run development-css -- --watch",
    "watch-poll": "npm run watch -- --watch-poll",

Happy coding 🤓