Extremely Light, Lazy Asynchronous Patterns for Vue Apps

Paris 17th May 2019

GitHub icon
Twitter icon

credits to @callumacrae

Eduardo

San Martin Morote

GitHub icon

🌍 Vue core team

πŸ‘¨β€πŸ’» Freelance

πŸ“Paris

Prototyping is fun

ReBuild plugins in Vue

Code Splitting

You should split your code

It all starts with webpack

Dynamic Import

import('a_package').then(module => module.default)
import('./utils.js').then(module => module.log)
const aPackage = () => import('a_package')
const utils = () => import('./utils.js')

Configure Webpack

Sean / Tobias / Johannes

Me

type="module"

<script type="module">
  const utils = () => import('./utils.js')

  utils().then(({ log }) => {
    log('hey')
  })
</script>

Lazy-Loading components

✨Anywhere

import Calendar from '@/components/Calendar.vue'
const Calendar = () => import('@/components/Calendar.vue')

Per-Page

// router.js
const Home = () => import('@/views/Home')
const User = () => import('@/views/User')

const routes = [
  { path: '/', component: Home },
  { path: '/users/:id', component: User },
]

in-componenT

// App.vue
const Search = () => import('@/components/Search')

export default {
  components: { Search }
}

Chunks

Search.vue

The real deal

βœ‚οΈ vendors

πŸ† Online

🐎 Fast 3G

🐒 Slow 3G

Don't lazy load what is needed on initial render

Safe Lazy load Recipee πŸ₯˜

  • Local static component registration
  • Lazy load routes

PROFIT πŸ”₯

Webpack
​Magic Comments

regular

import('LoginModal')

Prefetch

import(/* webpackPrefetch: true */ 'LoginModal')
<link rel="prefetch" href="login-modal-chunk.js" />

Preload

import(/* webpackPreload: true */ 'ChartingLibrary')
<link rel="preload" href="chunk.js" />

⚠️ Prefetch / Preload

// SomePage.vue
() => import(/* webpackPrefetch: true */ 'LoginModal')
() => import(/* webpackPreload: true */ 'ChartingLibrary')
const SomePage = () => import('./SomePage.vue')

Only works for lazy loaded chunks:

Dynamic Components

in-template

<component :is="dynamicComponent"/>
// inside of a component method

// local or global component
this.dynamicComponent = 'UserCard'

// component descriptor
// import UserCard from './UserCard'
this.dynamicComponent = UserCard

In-Render functions

export default {
  render (h) {
    return h(this.dynamicComponent)
  },
  methods: {
    changeDisplayedComponent () {
      this.dynamicComponent = 'UserCard'
      // or
      this.dynamicComponent = UserCard
    }
  }
}

✨Anywhere

import Calendar from '@/components/Calendar.vue'
const Calendar = () => import('@/components/Calendar.vue')

Dynamic Lazy components

<component :is="dynamicComponent"/>
// inside of a component method
this.dynamicComponent = () => import('./UserCard')
render(h) { return h(this.dynamicComponnet) }

Dynamic Lazy components

<component :is="() => import('./UserCard')"/>
render(h) {
  return h(() => import('./UserCard'))
}

πŸ™…β€β™‚οΈ DON'T ❌

Dynamic Lazy components

<component :is="dynamicComponent"/>
// inside of a component method
this.dynamicComponent = () => import('./UserCard')
render(h) { return h(this.dynamicComponnet) }

YES βœ…

Dynamic Imports with Expressions

import(`@/components/async/${name}.vue`)

let webpack creates chunks πŸ‘Œ

Careful...

import('@/components/' + myComponentPath) // 😱 ☠️

your app

Provide Webpack a more strict filter

import('@/components/' + myComponentPath) // 😱 ☠️
import(`@/components/Lazy${name}.vue`) // πŸ™Œ
import(`@/components/async/${name}.vue`) // πŸ’―

Lazy loading Dynamic Components

USage

<ExamplePreview name="counter"/>

dynamic components + computed = πŸ‘Œ

computed: {
  demoComponent() {
    // make demoComponent depend on name
    const name = this.name
    return () =>
      import(`@docs/examples/${name}/index.js`)
  }
}
<component :is="demoComponent"/>

Analyzing your app

Monster

when your app is unmaintainable but you still care

vue ui

Lazy Load

  • Local components registration
  • Always lazy load routes
  • Lazy load anything not need up-front

Dynamic import expressions

  • As strict as possible
  • Prefetch flag (can wait)
  • Preload (needed now)

Handling Errors

❌ Error

// in a component method
this.error = null
return import('./Component.vue').catch(err => {
  this.error = err
})
<p class="danger" v-if="error">{{ error }}</p>

⏳ LOading

this.error = null
this.pending = true
return import('./Component.vue').then(module => {
  this.isPending = false
  return module
}).catch(err => this.error = err)
<p class="danger" v-if="error">{{ error }}</p>
<p class="info" v-else-if="isPending">Loading...</p>

⏳ Delay loading message

<div>
  <p v-if="error">Error: {{ error.message }}</p>
  <p v-else-if="isPending && isDelayElapsed">Loading...</p>
  <component v-else :is="dynamicComponent" />
</div>
fetchUsers() {
  this.error = null
  this.isPending = true
  this.isDelayElapsed = false
  import('./Component.vue')
    .then(m => { this.dynamicComponent = m.default })
    .catch(error => { this.error = error })
    .finally(() => { this.isPending = false })
  setTimeout(() => {
    this.isDelayElapsed = true
  }, 200)
}

Async component factory

const AsyncComponent = () => ({
  component: import('./MyComponent.vue'),
  loading: LoadingComponent,
  error: ErrorComponent,
  delay: 200,
  timeout: 3000
})

πŸ’ Vue-promised

<Promised
  :promise="componentPromise"
  #combined="{ isPending, data, error }"
>
  <p v-if="error">
    {{ error.message}}
  </p>
  <p v-else-if="isPending">Loading component</p>
  <component
    v-else
    :is="data.default"
  />
</Promised>

πŸ‘‰ Github repo

Vuex

Patreons πŸ™Œ

patreon.com/posva

Thanks! πŸ––

GitHub icon

Extremely Light, Lazy Asynchronous Patterns for Vue Apps

By Eduardo San Martin Morote

Extremely Light, Lazy Asynchronous Patterns for Vue Apps

We keep shipping more and more features in our Web applications, and as a result, we ship heavier apps that take more time to load. So we use bundlers like webpack to split our application code into multiple bundles and load them asynchronously. As a result, we end up having asynchronous code pretty much everywhere in our apps. This means that we need to handle unexpected errors that were otherwise impossible, like network ones, handle loading state and make sure the application is able to recover from these errors. During this talk, we will take a look at different patterns about handling asynchronous request correctly in Vue applications in order to make our Apps feel light, fast and reliable everywhere.

  • 3,066