Easy lazy loading and code splitting in Vue.js

Vue.js is a great framework that will get your app running in just a few hours of work. However, importing components and packages without performance in mind, will make your bundle size go up real fast. Fortunately, Vue.js make it super easy to load your code on demand. Imagine having a large package or component that is only needed under specific use cases. For example, for one of my projects I had to implement video.js package, but given it's size and use case I wanted to load it on demand using lazy loading.

Putting Video.js into Vue components

Let's take a look at Video component below. This is where we import our VideoPlayer component which accepts one prop source. This prop simply passes the video URL. This component is being imported as a module and registered in a standard, non-dynamic manner.

<template>
  <VideoPlayer :source="videoUrl" />
</template>

<script>
  // Video.js
  import VideoPlayer from "@/components/VideoPlayer";
  export default {
    props: ["videoUrl"],
    components: {
      VideoPlayer,
    },
  };
</script>

If you look at the VideoPlayer component below you will see there is a lot of going on. This component imports video.js package along with package-specific CSS — these two are at least 500kb. That's heavy. Beside that, this component also initializes the video.js itself with basic options during mounted hook.

<template>
  <video class="video-js" ref="videoPlayer">
    <source :src="source" type="video/mp4" />
  </video>
</template>

<script>
  // VideoItem.js
  import videojs from "video.js";
  import "video.js/dist/video-js.min.css";

  export default {
    props: ["source"],
    data() {
      return {
        player: null,
        options: {
          controls: true,
        },
      };
    },
    mounted() {
      this.player = videojs(this.$refs.videoPlayer, this.options);
    },
  };
</script>

This would work. However, we don't want to bundle video.js everytime our application loads. We only need it in specific use cases, in this example when our app needs to load and play a video. Let's improve our code using dynamic imports.

Introducing dynamic imports

Let's look again at Video component. Thanks to the beauty of Vue.js, we can load this component using async factory function which returns a promise. This ensures our component is loaded only when it's needed and the result is cached for later use. This factory function also return an object with a bunch of useful properties, including loading states. In this example, I am using Spinner component which is displayed while the component is being loaded. You can also set other properties, like delay and timeout. Feel free to explore the full documentation here.

Final Video component looks like this.

<template>
  <VideoPlayer :source="videoUrl" />
</template>

<script>
  // Video.js
  import Spinner from "@/components/Spinner";

  const VideoPlayer = () => ({
    component: import("@/components/VideoPlayer"),
    loading: Spinner,
    delay: 200,
    timeout: 5000,
  });

  export default {
    props: ["videoUrl"],
    components: {
      VideoPlayer,
    },
  };
</script>

Hope this helps. Enjoy the Vue!