vue-router-lazy-loading

HTML

<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app">
  <router-link to="/">Home</router-link>
  <router-link to="/welcome">Welcome</router-link>
  <router-link to="/hello">Hello</router-link>

  <router-view></router-view>
</div>

<template id="loading">
  <transition name="loading">
    <div class="loading">
      <div class="spinner"></div>
    </div>
  </transition>
</template>

<template id="loader">
  <div v-if="isLoading" :is="component"></div>
</template>

CSS

.loading-enter-active,
.loading-leave-active {
  transition: opacity .5s
}

.loading-enter,
.loading-leave-to {
  opacity: 0
}

.loading {
  display: flex;
  position: fixed;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  justify-content: center;
  align-items: center;
  background-color: rgba(0, 0, 0, 0.3);
}

.spinner {
  border: 16px solid #f3f3f3;
  /* Light grey */
  border-top: 16px solid #3498db;
  /* Blue */
  border-radius: 50%;
  width: 60px;
  height: 60px;
  animation: spin 2s linear infinite;
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

JavaScript

// pages
const Home = {
  template: '<div>Home</div>'
}
const Welcome = {
  template: '<div>Welcome to Your Vue.js App</div>'
}
const Hello = {
  template: '<div>Hello World!!</div>'
}

// https://webpack.js.org/guides/code-splitting/
// { path: '/', component: () => import(/* webpackChunkname: 'home' */'./Home.vue') }
const router = new VueRouter({
  routes: [{
      path: '/',
      component: () => new Promise(resolve => setTimeout(() => resolve(Home), 1000))
    },
    {
      path: '/welcome',
      component: () => new Promise(resolve => setTimeout(() => resolve(Welcome), 2000))
    },
    {
      path: '/hello',
      component: () => new Promise(resolve => setTimeout(() => resolve(Hello), 3000))
    }
  ]
})

const Loading = {
  template: '#loading'
}
const Loader = {
  name: 'loader',
  template: '#loader',
  props: {
    component: Object
  },
  data() {
    return {
      isLoading: false
    }
  },
  mounted() {
    document.body.appendChild(this.$el)
  },
  destroyed() {
    this.$el.remove()
  },
  methods: {
    start() {
      this.isLoading = true
    },
    stop() {
      this.isLoading = false
    }
  }
}

class LoadingHelper {
  constructor(component, delay) {
    this.loading = false
    this.delay = delay

    const Ctor = Vue.extend(Loader)
    this.loader = new Ctor({
      el: document.createElement('div'),
      propsData: {
        component,
        delay
      }
    })
  }

  get isLoading() {
    return this.loading
  }

  start() {
    this.loading = true

    setTimeout(() => {
      if (this.isLoading) {
        this.loader.start()
      }
    }, this.delay)
  }

  stop() {
    this.loader.stop()
    this.loading = false
  }
}

const VueLoading = {
  install(Vue, {
    router,
    component = Loading,
    delay = 200
  } = {}) {

    const loading = new LoadingHelper(component, delay)

    if (router) {
      router.beforeEach((to, from, next) => {
        loading.start()
        next()
      })
      router.beforeResolve((to,...