As of October 1, 2023, LINE has been rebranded as LY Corporation. Visit the new blog of LY Corporation here: LY Corporation Tech Blog

Blog


VueConf TO 2018 Recap

VueConf TO is North America's biggest Vue conference with attendees from all around the world, including Vue.js core team members, Nuxt.js, Vuetify.js and main library authors, along with expert speakers from the community. Vue is an open-source JavaScript framework for building user interfaces and single-page applications, which lower the barrier and complexity of frontend development. It's one of the most popular framework nowadays and has been widely used in LINE.

I'd like to share some of the sessions I attended: Vue 3.0, Light and lazy asynchronous pattern, and Advanced component patterns

Vue 3.0

Evan You is the creator of Vue. In his session, he introduced the upcoming Vue 3.0 which should become available sometime in 2019. By taking advantage of new abilities enabled by modern browsers, we can expect the transition from old one to Vue 3.0 to be smooth and see many improvements (although the changes will be "visible"). The following are highlights of the changes to come: fastersmaller, more maintainable code, easier to target native, and easier to use.

 Making it faster

New features are introduced in Vue 3.0 to make things faster: virtual DOM re-writeoptimized slot generation, static tree hoisting, static props hoisting, and proxy-based observation.

Virtual DOM re-writing

Virtual DOM being rewritten from the ground up will provide more compile-time hints to reduce runtime overhead. And the rewrite will include more efficient code to create virtual nodes.

The following is a simple Vue template.

<Comp></Comp>
<div>
  <span></span>
</div>

The following is a compiled output of the template above.

render() {
 const Comp = resolveComponent('Comp', this)
 return createFragment([
   createComponentVNode(Comp, null, null, 0 /* no children */),
   createElementVNode('div', null, [
     createElementVNode('span', null, null, 0 /* no children */)
   ], 2 /* single vnode child */)
 ], 8 /* multiple non-keyed children */)
}

From the output, we can see the following improvements:

  • Skip unnecessary condition branches
  • Easier for JavaScript engine to optimize
  • Monomorphic: an inline cache methods in JavaScript

Optimized slots generation

When a parent component re-renders, children also re-renders. With Vue 3, the parent and child can be re-rendered separately.This solved a legacy issue that has been around for a long time in Vue.

The following is a sample Vue template with dynamic content.

<Comp>
  <div>{{ hello }}</div>
</Comp>

The following compiled output shows that parent and child have been re-rendered separately.

render() {
 return h(Comp, null, {
   default: () => [h('div', this.hello)]
 }, 16 /* compiler generated slots */)
}

Static tree hoisting

Vue 3 is able to tell which component is static and then hoist it out in order to prevent patching entire tree and reducing the cost of rendering.

The following is a sample Vue template where the top half contains static content, and the bottom half contains dynamic content.

<div>
  <span class="foo">
    Static
  </span>
  <span>
    {{ dynamic }}
  </span>
</div>

The following is a compiled output of the template above. You can see how Vue 3.0 separates static content from dynamic content.

const __static1 = h('span', {
  class: 'foo'
}, 'static')
render() {
  return h('div', [
    __static1,
    h('span', this.dynamic)
  ])
}

Static props hoisting

With static props hoisting, Vue can skip patching nodes that does not change.

The following is a sample Vue template containing static properties.

<div id="foo" class="bar">
  {{ text }}
</div>

The following is a compiled output of the template above. You can see how the compiler hoists the static properties from patching operation.

const __props1 = {
 id: 'foo',
 class: 'bar'
}
render() {
 return h('div', __props1, this.text)
}

Proxy-based observation

Vue’s reactivity system is implemented by Object.defineProperty() with getter and setter,  due to browser compatibility and had some limitations. In Vue 3, the reactivity system will be re-written by Proxy, so you can detect property additions and deletions, array length, MapSet and even classes.

With a simple test of rendering 3,000 stateful component instances, we can see double the speed of rendering and half the memory used between Vue 2.5 and 3.0, as listed below.

Vue 2.5

  • JS Heap: 22.9MB–48.0MB of memory consumed
  • Rendering time: 294.5ms

Vue 3.0 Prototype

  • JS Heap: 11.2MB–22.8MB of memory consumed
  • Rendering time: 126.2ms

In order to continue to support IE11, Vue 3.0 will ship a build that supports both the old observation mechanism and the new proxy version.

Making it smaller

Vue 3.0 is Tree Shaking friendly and includes built-in components or utilities only if you use them.

Making it more maintainable

Vue 3.0 will be decoupled to the following packages, by functionality, to make maintenance easier and source contribution easier for newcomers.

  • compiler-core
  • observer
  • runtime-core
  • runtime-dom
  • runtime-test
  • scheduler
  • server-renderer
  • shared
  • vue
  • vue-compat

Vue 3.0 is also re-written in TypeScript, bringing the advantage of strong typed language and error checking. Also, the compiler has been re-written and provides source map with meaningful information instead of compiled code.

The following code shows a Vue component rewritten in TypeScript.

interface HelloProps {
  text: string
}
class Hello extends Component<HelloProps> {
  count = 0
  render() {
    return <div>
      {this.count}
      {this.$props.text}
    </div>
  }
}

Making it easier to target native

Vue 3.0 has exposed the runtime core to make it easier to use Vue with any platform (example: Web, Vue-Native)

The following code shows how you can use Vue on different platform.For your information, nodeOps contains a list of DOM methods such as appendChild() or insertChild(), and patchData contains the data you want to update.

import { createRenderer } from '@vue/runtime-core'
const { render } = createRenderer({
 nodeOps,
 patchData
})

Making your life easier

Vue 3.0 has extracted the observer module, allowing you to use the Reactivity API as the following.

import { observable, effect } from 'vue'
const state = observable({
 count: 0
})
effect(() => {
 console.log(`count is: ${state.count}`)
}) // count is: 0
state.count++ // count is: 1

Vue 3.0 also provides better debugging capabilities, enabling you to trace what really happened under mutable framework. The following code shows you how you can attach the renderTriggered() function, for providing debug information when the component is re-rendered.

const Comp = {
  render(props) {
    return h('div', props.count)
  },
  renderTriggered(event) {
    debugger
  }
}

Experimental Hooks API

We use Mixins to share functions between two components in Vue. Hooks is a more of an explicit concept for content sharing between components proposed and implemented in React. The Vue team loves the idea of this but their research is ongoing to find a way to integrate it with Vue.

Experimental time slicing support

When you have lots of components that are all trying to re-render at the same time, everything will be on hold, especially on slow devices. Time slicing support will make your rendering acceptable on slow devices. Although the rendering is asynchronous, you will see the complete result at once.

Light and lazy asynchronous pattern

In this section the Vue core team member Eduardo introduce lazy asynchronous pattern for Vue. It all started from webpack code splitting plugin — SplitChunksPlugin, so we can have async load pattern easily like below:

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

const aPackage = () => import('a_package')
const utils = () => import('./utils.js')

There is also Prefetch. It will prefetch chunks when browser is idle, this is not yet supported on some major browsers like Safari. Do take compatibility into consideration.

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

Another thing is Preload which preloads resources that have higher priority.

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

Lazyload component for VUE

Reusing the same pattern for Vue components is easy, as shown below.

import Calendar from '@/components/Calendar.vue'

To make a Vue component as a lazyload component, transform it into a promise as shown below.

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

Here is an example of Vue router which can separate each component by lazyload pattern. The lazyload component is loaded dynamically when you enter the specified page.

const Home = () => import('@/views/Home')
const User = () => import('@/views/User')
const routes = [
  { path: '/', component: Home },
  { path: '/users/:id', component: User },
]

Here is an example of importing a lazyload component into another component.

// App.vueconst Search = () => import('@/components/Search')export default {  components: { Search }}

We can check the dist directory, inside the library directory, and see that the lazyload components have been transformed into to chunks.

Dynamic imports with expressions

By using the following schema, let webpack decide how to create chunks. It's a very good pattern for certain usage, such as demo page or storybook.

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

Dynamic imports with expressions can be combined with computed properties.

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

Do not use import statements like the following.

import(myComponentPath)

You better hint webpack as much as you can, as shown below.

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

Analyzing your app

You can analyze your app by using the Coverage option in Chrome devTool.

We analyzed LINE Today as an example. As you can see from the message in the screenshot below, 64% of JavaScript code (1.6 MB of 2.6 MB) is not used in the first view. There are a lot of improvements yet to be made, including this one; not loading unnecessary resources in the first view.

To use lazyload, we need to have error handling block as shown below.

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

We need to handle loading also, as shown below.

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

From Vue 2.3.0+ async component factory is provided so you can aggregate all details in one place:

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

Advanced component patterns

In this session provided by Adam Wathan, he introduced different patterns to wrap logic into a Vue component by using slot-scope, which is different from what we used to wrap whole logic into a separate package. (From Vue 2.6 this name has changed to v-slot and the old name will be deprecated from 3.0)

Here is an example of wrapping an intersectionObserver into a Vue component and exposed by an enteredViewport.

<template>
 <div>
  <slot :entered-viewport="enteredViewport"></slot>
 </div>
</template>
<script>
 export default {
   data() {
     return {
       enteredViewport: false
     }
   },
   mounted() {
     const observer = new IntersectionObserver(([entry], observer) => {
       if (entry.intersectionRatio > 0) {
         this.enteredViewport = true
         observer.disconnect()
       }
     })
     observer.observe(this.$el)
   }
 }
</script>

To gain reactivity, use enteredViewport with slot-scope.

<with-entered-viewport>
	<feature-section
		slot-scope="{ enteredViewport }"
		:class="{
			'slide-in-end': enteredViewport
		}"
		class="slide-in-from-left"
		icon="cog"
	>
		<template slot="title">Fully automated</template>
		<template slot="body">Simply give us your boss's email ...</template>
	</feature-section>
</with-entered-viewport>
<with-entered-viewport>
  <feature-section
    slot-scope="{ enteredViewport }"
    :class="{
      'slide-in-end': enteredViewport
    }"
    class="slide-in-from-left"
    icon="cog"
  >
    <template slot="title">Fully automated</template>
    <template slot="body">Simply give us your boss's email ...</template>
  </feature-section>
</with-entered-viewport>

So, we can easily reuse the with-entered-viewport component everywhere. See this repository for the demo Adam shared with us at the conference.

Summary

The speakers of VueConf TO conference were from the Vue core team and library authors. It was a great opportunity for me to get the latest information and suggestion for different toolset. Moreover, I had a chance to view the codebase from some famous corporations, learning of the libraries they had chosen and code architecture. I took the chance to see if our approach at LINE was following the best practice from such companies. I also talked to some developers from small to big companies who were also facing similar issues who had more than two frameworks implemented in their current project; it is difficult to throw away the current implementation. I was able to learn how to maintain more than two frameworks together and migrate to the latest one.