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
Made with Slides.com