Search code examples
javascriptjsonvue.jslazy-loadinglottie

How can I correctly lazy load a json file in Vue.js to reduce bundle size?


I am using Vue.js and Laravel for my project and recently added two animations using a plugin named Lottie. Each animation is a component, and they both use an individual JSON file to animate a group of PNG images (similar to a PNG sequence). These two JSON files are stored locally in the project folder under the path /public/data/.

Firstly the JSON files are not being read unless I put in the absolute path (/users/username/documents/projectname/public/data/filename.json), is there no way I can get this to work just by using /data/filename.json?

Secondly, when I add the code below in my component, my JS files are compiled to separate chunks as expected:

const animationData = () =>
  import("/users/username/documents/projectname/public/data/filename.json");

I get the following error when the animation tries to run:

Invalid prop: type check failed for prop "data". Expected Object, got Function 

found in

---> <VueLottie>

However when I import my json file using a normal import in my component like below it works fine and shows the animation:

import animationData from "/users/username/documents/projectname/public/data/filename.json";

My animation components are both set up like this:

<template>
        <vue-lottie ref="lottie" loop autoplay :data="animationData" :height="400" :width="400"></vue-lottie>
</template>

<script>
    import vueLottie from "vue-lottie-ssr";
    import animationData from '/users/username/documents/projectname/public/data/filename.json'

    export default {
        name: 'animation',
        components: {
            vueLottie
        },
        data () {
            return {
                speed: 1,
                animationData
            }
        },
        computed: {
            lottie () {
                return this.$refs.lottie
            }
        }

    }
</script>

I have also tried getting the JSON file via an axios call when the component mounts, but the same error occurs.

Update

I updated my code so that each component is lazy loaded instead of the JSON file. Like so:

components: {
  WinAnimation: () => import("./WinAnimation.vue");

  LoseAnimation: () => import("./LoseAnimation.vue");
}

However now I'm getting the following error:

Unknown custom element: <win-animation> - did you register the component correctly? For recursive components, make sure to provide the "name" option.

Update 2

I realised why I was getting an error message. The correct way was to add the following at the top of my script inside the parent vue file.

const winAnimation = () => import("./WinAnimation.vue");
const loseAnimation = () => import("./LoseAnimation.vue");

and then inside export default {...} I forgot to add the names, so:

components: { winAnimation, loseAnimation }

Now my code has been split and my app.js file size has reduced by almost a half! :)


Solution

  • 1st - don't use vue-lottie library. If you take a look at the source code, the main and only thing which should be provided by this library is component src/lottie.vue (+ it's dependency lottie-web) but for some reason, NPM package also contains whole demo app including the demo JSON file (src/assets/pinjump.json)

    If you take a look at lottie.vue component, its just very little and very simple wrapper for lottie-web which provides main functionality. By getting rid of vue-lottie you will get following benefits:

    1. vue-lottie completely ignores one of the lottie-web options which is using path instead of animationData - documentation is not very clear here but I would guess that by providing path, the library will try download the animation data ad-hoc so you don't need to include it in your bundle. Worth trying imho...

    2. Loading animation data on demand

    • why are you using dynamic import on JSON file instead of dynamically importing whole component ? By making separate chunk on component level, dynamic chunk will include not only your json data but also lottie-web which is also not small. And Vue will handle loading of the component without any additional code changes...
    • if you still want to load on demand only your JSON data, you must understand that Webpack dynamic import (import(".....")) is returning Promise and lottie-web (and in turn vue-lottie) is expecting object. So you must do something like this:
    <script>
        import lottie from 'lottie-web';
    
        const animationData = () =>
           import("/users/username/documents/projectname/public/data/filename.json");
    
        export default {
          mounted () {
            animationData().then(function(data) {
              this.anim = lottie.loadAnimation({
                // other options 
                animationData: data
              })
            });
          }
        }
    </script>
    
    

    Update

    You should be always very careful when considering adding 3rd party components into your project. One more thing I'v noticed is that lottie-web has destroy() method in it's API. This indicates that it is creating some resources (DOM elements probably) which needs to be cleaned up. This is something vue-lottie component is not handling at all and can lead to nasty memory leaks in your app. You can read about the problem here