@vueschool_io

vueschool.io

Vue 2 ย 

New Features and Migrating

3

๐ŸŽค

@danielkelly_io

@danielkellyio

Alabama, USA

Daniel Kelly

Lead Instructor @ Vue School

Full Stack developer (10 years)

Husband and Father

Workshop Overview

  • Intro to Vue 3
  • Benefits of Vue 3
  • Improvements to Existing Features
  • Removed Features
  • New Features
    • Composition API (overview)*
    • Teleport
    • State Driven CSS*
    • Other CSS Enhancements
    • Emits Declaration
    • Suspense
  • Changes in the Ecosystem
  • Migration

* backported to Vue 2.7

Intro to Vue 3

๐Ÿ‘‹

Pinia Experiment Begins

Nov, 2019

RFC Repo Created

Jan 14, 2019

Vue 3 Codebase Goes Public

Jan 3, 2020

Vue 3 Soft Launch
(One Piece)

Sep 18, 2020

Vue 3 New Default

Jan 20, 2022

Evan Announced Dev on Vite.js

Jan 12, 2020

๐Ÿ“ฃ

Vue 3 Development Announced

Feb 2018

v3

Volar

<script setup>

more!

Why Vue 3?

Benefits and why upgrade

Smaller and Faster

  • Initial render up to 55% faster
  • Memory usage down 54%
  • Updates up to 133% faster
  • Unused features are tree-shakeable (like transitions, nextTick, etc)

Faster === better conversions

A one-second site speed improvement can increase mobile conversions by up to 27%.

===

Write More Maintainable Code

  • Better logic re-use and organization with the Composition API *
  • Type-safety with improved TypeScript Support
    (TypeScript is fast becoming the norm for large scale JavaScript apps)
  • Other new features and API adjustments that make code more intuitive

* backported to Vue 2.7

More Maintainable Code is Good For Everyone!

โœ… Stakeholders
ย  ย  ย ย  Faster turn around times, less back and forth

โœ… Site Visitors/Customers
ย  ย  ย ย  Less bugs, more features faster

โœ… Developers

ย  ย  ย ย  Better DX, Iterate faster, spend less time debugging

Supported by the Latest and Greatest Libraries

  • Some of the newest 3rd party plugins, frameworks, etc only support Vue 3
  • ie. FormKit, Nuxt 3, Vuetify 3, etc

Much of the Same Syntax We Already Love โค๏ธ

  • Options API Still 100% Supported
  • Single File Components
    (script, template, style)
  • Declarative Template Syntax

Vue 2 EOL is Dec 2023

Improvements to Existing Features

Improvements to Existing Features

  • Multiple v-models (and more!)
  • No more reactivity caveats ๐ŸŽ‰
  • Fragments
  • Comprehensive $attrs

Multiple v-models

// MyInput.vue
<template>
<input 
  @input="$emit('input', $event.target.value)" 
  :value="value" 
/>
</template>
<script>
export default {
  props:{
    value: String
  }
}
</script>

Support v-model on a component by emitting input and accepting a value prop

v2

<MyInput v-model="name" />

Multiple v-models

// MyInput.vue
<template>
<input 
  @input="$emit('update:modelValue', $event.target.value)" 
  :value="modelValue" 
/>
</template>
<script>
export default {
  props:{
    modelValue: String
  }
}
</script>

Support v-model on a component by emitting update:modelValue and accepting a modelValue prop

v3

<MyInput v-model="name" />

Multiple v-models

// MyInput.vue
<template>
<input 
  @input="$emit('update:modelValue', $event.target.value)" 
  :value="modelValue" 
/>
<input 
  @input="$emit('update:email', $event.target.value)" 
  :value="title" 
/>
  <input 
  @input="$emit('update:password', $event.target.value)" 
  :value="title" 
/>
</template>
<script>
export default {
  props:{
    modelValue: String,
    email: String,
    password: String,
  }
}
</script>

Provide argument to v-model to specify any prop/update:[prop]

v3

<MyInput 
  v-model="name" 
  v-model:email="email"
  v-model:password="password"
/>

Multiple v-models

<MyComponent 
  :title="pageTitle" 
  @update:title="pageTitle = $event" 
/>

<!-- Shortand for Above -->
<MyComponent :title.sync="pageTitle" />

Multiple v-models replace .sync

v2

Multiple v-models

<MyComponent 
  :title="pageTitle" 
  @update:title="pageTitle = $event" 
/>

<!-- Shortand for Above -->
<MyComponent v-model:title="pageTitle" />

Multiple v-models replace .sync

v3

v-model anatomy

Custom v-model modifiers

<MyComponent v-model.capitalize="myText" />

Vue 3 also allows us to create custom modifiers for v-model

Custom v-model modifiers

<!-- MyComponent.vue-->
<script>
export default {
  props: {
    //...
    modelModifiers: {
      default: () => ({})
    }
  },
  created() {
    console.log(this.modelModifiers) // { capitalize: true }
  }
  //...
}
</script>

Check for them on the modelModifiers prop. Added modifiers will be a boolean true

<MyComponent v-model.capitalize="myText" />

Custom v-model modifiers

<!-- MyComponent.vue-->
<script>
export default {
  //... 
  methods: {
    emitValue(e) {
      let value = e.target.value
      if (this.modelModifiers.capitalize) {
        value = value.charAt(0).toUpperCase() + value.slice(1)
      }
      this.$emit('update:modelValue', value)
    }
  }
}
</script>

Then you can alter the emitted value based on the modifier

No More Reactivity Caveats

         Vue.set(this.frameworks, index, 'Vue')

v2

 this.frameworks[index] = "Vue"

v3

Setting a new array item

No More Reactivity Caveats

         Vue.set(this.framework, 'name', 'Vue')

v2

 this.framework.name = "Vue"

v3

Adding a new object property

No More Reactivity Caveats

         Vue.delete(this.framework, 'caveats')

v2

 delete this.framework.caveats

v3

Deleting an object property

No More Reactivity Caveats

Less Caveats === More Intuitive === Less Bugs

Fragments

aka. Multiple Root Elements

v2

<template>
  <div>...</div>
  <div>...</div>
</template>

โŒ

If you did this...

Fragments

aka. Multiple Root Elements

v2

<template>
  <div>...</div>
  <div>...</div>
</template>

You'd get an error like this!

Fragments

aka. Multiple Root Elements

v2

<template>
<div>
  <div>...</div>
  <div>...</div>
</div>
</template>

And have to wrap everything with a div

(which sometimes causes styling issues)

Fragments

aka. Multiple Root Elements

v3

<template>
  <div>...</div>
  <div>...</div>
</template>

It's no problem! ๐ŸŽ‰

Fragments

aka. Multiple Root Elements

v3

<template>
  <div>...</div>
  <div v-bind="$attrs">...</div>
</template>

And you can specify where to put the fall-through attributes

Comprehensive $attrs

v2

<template>
  <label>
    <input type="text" v-bind="$attrs" v-on="$listeners" />
  </label>
</template>
<script>
  export default {
    inheritAttrs: false
  }
</script>

Listeners separate from $attrs and if you wanted them to fall through you must remember to bind seperately

Comprehensive $attrs

v3

//MyInput.vue

<template>
  <label>
    <input type="text" v-bind="$attrs" />
  </label>
</template>
<script>
export default {
  inheritAttrs: false
}
</script>

Listeners combined with $attrs. Defined as functions prefixed with on

<MyInput id='my-input' @close="..."/>

$attrs === {
  id: 'my-input',
  onClose(){
    ...
  }
}

Comprehensive $attrs

v2

// MyInput.vue
<template>
  <label>
    <input type="text" v-bind="$attrs" />
  </label>
</template>
<script>
export default {
  inheritAttrs: false
}
</script>

Class and Style separate from $attrs

Given this component definition...

Comprehensive $attrs

v2

<MyInput id="my-id" class="my-class" />

Class and Style separate from $attrs

And used like this...

<label class="my-class">
  <input type="text" id="my-id" />
</label>

Would render this HTML

Comprehensive $attrs

v3

<MyInput id="my-id" class="my-class" />

Class and Style included with $attrs

And used like this...

<label>
  <input type="text" id="my-id" class="my-class" />
</label>

Would render this HTML

Questions?

๐Ÿ™‹๐Ÿพโ€โ™€๏ธ

Assignment # 1

๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ป

โฐ 20 mins

โŒ Removed Features

โŒ Removed Features

  • Functional Components
  • Filters
  • Keycode Modifiers
  • $children

Functional Components

<template functional>
  <component 
    :is="`h${props.level}`" 
    v-bind="attrs" 
    v-on="listeners"
  >
    <slot></slot>
  </component>
</template>

<script>
export default {
  props: ["level"],
};
</script>

v2

In v2, functional components provided a more performant alternative when component state was not needed

No $ prefix

Functional Components

<template>
  <component 
    :is="`h${props.level}`" 
    v-bind="$attrs" 
  >
    <slot></slot>
  </component>
</template>

<script>
export default {
  props: ["level"],
};
</script>

v3

In v3, the performance difference for stateful components is negligible so functional SFC's are removed

$ prefix

No functional

keyword

Functional Components

<script>
// note that h is imported from vue 
// instead of provided as an argument to the render function 
// (another diff between v2 and v3)
import { h } from "vue"; 

// functional components is a render function
const DynamicHeading = (props, context) => {
  return h(`h${props.level}`, context.attrs, context.slots);
};

DynamicHeading.props = ["level"];

export default DynamicHeading;
</script>

v3

Or you can define functional components as plain functions

ย 

Filters

<template>
  <h1>Bank Account Balance</h1>
  <p>{{ accountBalance | currencyUSD }}</p>
</template>

<script>
  export default {
    //...
    filters: {
      currencyUSD(value) {
        return '$' + value
      }
    }
  }
</script>

v2

In v2, you could use filters to format data in the template

ย 

Custom syntax that involves:

  • a learning curve
  • and implementation costs

Filters

<template>
  <h1>Bank Account Balance</h1>
  <p>{{ accountInUSD }}</p>
</template>

<script>
  export default {
    //...
    computed: {
      accountInUSD() {
        return '$' + this.accountBalance
      }
    }
  }
</script>

v3

In v3, filters are removed. Just use a computed prop instead.

ย 

Global Filters

const app = createApp(App)

app.config.globalProperties.$filters = {
  currencyUSD(value) {
    return '$' + value
  }
}

v3

In v3, global filters can be replaced with globally defined methods

ย 

<template>
  <h1>Bank Account Balance</h1>
  <p>{{ $filters.currencyUSD(accountBalance) }}</p>
</template>

KeyCode Modifiers

v2

In Vue 2 you could use keycodes as modifiers for keyboard events

ย 


<!-- keycode 75 === 'k' -->
<input v-on:keyup.75="doThing" />

KeyCode Modifiers

v3

In Vue 3 you use the event's key value instead



<input v-on:keyup.k="doThing" />


<input v-on:keyup.k="doThing" />

KeyCode Modifiers

v3

For multi-word key names you'll use the kebab case



<input v-on:keyup.arrow-down="doThing" />

KeyCode Modifiers

v3

The keys for some punctuation marks can just be included literally.

(This excludes ", ', /, =, >, and . You can check these on the event in your handler)



<input v-on:keyup.,="commaPress" />

KeyCode Modifiers

KeyboardEvent.keyCode is now deprecated in browsers and no longer recommended for use. Therefore Vue 3 removes them.

ย 



<input v-on:keyup.k="doThing" />

$children

v2

<template>
  <div>
    <my-button>Change logo</my-button>
  </div>
</template>

<script>
import MyButton from './MyButton'

export default {
  components: { MyButton },
  mounted() {
    console.log(this.$children[0]) // VueComponent (MyButton)
  }
}
</script>

In Vue 2, you could access all a components child components via $children

ย 

$children

v3

In Vue 3, $children no longer exists. If you need access to a component via the parent you can use a template ref instead

ย 

<template>
  <div>
    <my-button ref="button">Change logo</my-button>
  </div>
</template>

<script>
import MyButton from './MyButton'

export default {
  components: { MyButton },
  mounted() {
    console.log(this.$refs.button) // VueComponent (MyButton)
  }
}
</script>

Questions?

๐Ÿ™‹๐Ÿพโ€โ™€๏ธ

Assignment #2&3

๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ป

โฐ 15 mins

Assignment #2&3

๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ป

โฐ 15 mins

Skip

10 Minute Break

โ˜•๏ธ๐Ÿ™†

โฐ 10 mins

๐ŸŽ‰ New Features

๐ŸŽ‰ New Features

  • Composition API *
  • Teleport
  • State Driven CSS *
  • Other CSS Enhancements
  • Emits Declaration
  • Suspense

* backported to Vue 2.7

Composition API

Code Organization

Logic Reuse

Improved

TypeScript Support

Composition API

Full Workshop Dedicated to CAPI

Composition API

โšก๏ธ Super Quick Overview

new Vue({
  data(){
    return {
      loading: false,
      count: 0,
      user: {}
    }
  },
  computed: {
    double () { return this.count * 2 },
    fullname () {/* ... */}
  },
  methods: {
    increment () { this.count++ },
    fetchUser () {/* ... */}
  }
})
import { ref, computed, watch } from 'vue'

No longer passing options INTO Vue

Instead we're getting reactive functions OUT OF Vue

const loading = ref(false)
const count = ref(0)
const user = reactive({})
new Vue({
  data(){
    return {
      loading: false,
      count: 0,
      user: {}
    }
  },
  computed: {
    double () { return this.count * 2 },
    fullname () {/* ... */}
  },
  methods: {
    increment () { this.count++ },
    fetchUser () {/* ... */}
  }
})

Define reactive data with

ref

or

reactive

const double = computed(()=> count.value * 2)
const fullname = computed(()=>({ /* ... */ }))
new Vue({
  data(){
    return {
      loading: false,
      count: 0,
      user: {}
    }
  },
  computed: {
    double () { return this.count * 2 },
    fullname () {/* ... */}
  },
  methods: {
    increment () { this.count++ },
    fetchUser () {/* ... */}
  }
})

Define derived data with

computed

const increment = ()=> count.value++
function fetchUser(){ /* ... */ }
new Vue({
  data(){
    return {
      loading: false,
      count: 0,
      user: {}
    }
  },
  computed: {
    double () { return this.count * 2 },
    fullname () {/* ... */}
  },
  methods: {
    increment () { this.count++ },
    fetchUser () {/* ... */}
  }
})

Define methods as

functions

Composition API

Full Workshop Dedicated to CAPI

You seem like a pretty advanced audience

Do you have any specific questions about the composition API?

Would you like to dive a little deeper into the Composition API?

(I can show you some examples in StackBlitz)

๐Ÿคซ

Don't tell anyone but I'll give you the content and exercises from our dedicated Composition API workshop too!

(copy and paste into chat)

Teleport

  • Allows us to keep markup inside a component but visually display it elsewhere in the DOM
  • Great for modal overlays

Teleport

Modal overlay without teleport usually relies on fixed positioning

<template>
  <button @click="open = true">Open Modal</button>

  <div v-if="open" class="modal">
    <slot></slot>
    <footer>
      <button @click="open = false">Close</button>
    </footer>
  </div>
</template>

<script>
  // ....
</script>

<style scoped>
.modal {
  @apply fixed;
}
</style>
  • Position fixed only works if an ancestor doesn't include `transform`, `perspective`, or `filter`
  • z-index issues

v2

Teleport

Append markup to any arbitrary element no matter where the component lives

<template>
  <button @click="open = true">Open Modal</button>

  <Teleport to="body">
    <div v-if="open" class="modal">
      <slot></slot>
      <footer>
        <button @click="open = false">Close</button>
      </footer>
    </div>
  </Teleport>
</template>

<script>
  // ....
</script>

<style scoped>
.modal {
  @apply absolute;
}
</style>

v3

Teleport Tips

`to` prop takes a CSS selector or an actual DOM node

<template>
   <!-- string query selector-->
  <Teleport to="body">
    <!--or actual element-->
  <Teleport :to="body">
</template>

<script>
export default {
  data() {
    return {
      body: document.querySelector("body"),
    };
  },
};
</script>

Teleport Tips

can combine <Teleport> with <Transition>

<template>
   <Teleport :to="body">
    <Transition>
      <div v-if="open" class="modal">
        //...
      </div>
    </Transition>
  </Teleport>
</template>
<style scoped>
.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
}
</style>

Teleport Tips

Can have multiple teleports active at one time.

<Teleport to="#modals">
  <div>A</div>
</Teleport>
<Teleport to="#modals">
  <div>B</div>
</Teleport>
<div id="modals">
  <div>A</div>
  <div>B</div>
</div>

Teleport Tips

Can dynamically disable. Useful to conditionally display inside component

<Teleport :disabled="isMobile">
  ...
</Teleport>

Teleport Tips

ย The teleport `to` target must already exist in the DOM when the component using <Teleport> is mounted.

  • Ideally, this is an element outside the Vue app.
  • When targeting elements rendered by Vue, ensure it's mounted first

Questions?

๐Ÿ™‹๐Ÿพโ€โ™€๏ธ

Assignment #4

๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ป

โฐ 25 mins

Lunch

๐Ÿ”

โฐ 25 mins

Lunch Break

๐Ÿฝ๏ธ

โฐ 25 mins

State Driven CSS

and more!

State Driven CSS

In v2, inline styles were the only option for dynamic CSS rules

<template>
<div 
  class="swatch"
  :style="{ backgroundColor: color }"
></div>
</template>
<script>
export default{
  data(){
    return {
      color: "red"
    }
  }
}
</script>

v2

State Driven CSS

Re-use would mean re-declaring the inline style

<template>
<div 
  class="swatch"
  :style="{ backgroundColor: color }"
></div>
  
<div 
  class="swatch"
  :style="{ backgroundColor: color }"
></div>
</template>
<script>
export default{
  data(){
    return {
      color: "red"
    }
  }
}
</script>

v2

State Driven CSS

In v3, you can bind CSS values directly in the style block

v3

<template>
<div>
  <div class="swatch"></div>
  <div class="swatch"></div>
</div>
</template>
<script>
export default{
  data(){
    return { color: "red" }
  }
}
</script>

<style scoped>
  .swatch{
    background-color: v-bind(color);
  }
</style>

State Driven CSS

Defines a hashed CSS custom property on the root component element

(or on each element if no root)

v3

<template>
<div>
  <div class="swatch"></div>
  <div class="swatch"></div>
</div>
</template>
<script>
export default{
  data(){
    return { color: "red" }
  }
}
</script>

<style scoped>
  .swatch{
    background-color: v-bind(color);
  }
</style>

State Driven CSS

Can use dot notation to target nested data (must use quotes)

v3

<template>
<div class="swatch"></div>
<div class="swatch"></div>
</template>
<script>
export default{
  data(){
    return {
      colors:{ primary: "red" }
    }
  }
}
</script>

<style scoped>
  .swatch{
    background-color: v-bind('color.primary');
  }
</style>

State Driven CSS

v-bind must the entire value

v3

<template>
<div class="swatch"></div>
<div class="swatch"></div>
</template>
<script>
export default{
  data(){
    return {
      colors:{ primary: "red" },
      width: 100
    }
  },
  computed:{
    widthInPixels(){ return this.width + 'px'}
  }
}
</script>

<style scoped>
  .swatch{
    background-color: v-bind('color.primary');
    width: v-bind(width)px; /* โŒ This won't work */
    width: v-bind(widthInPixels);
  }
</style>

Vue Specific CSS Selectors

v3

<style scoped>
/* Style child components */
.a :deep(.b) {}
  
/* Style slot content */
:slotted(div) {}  

/* Quickly define a global rule */
:global(.red) {}
</style>

<style>
/* Same as :global in scoped tag above */
.red{}
</style>

Questions?

๐Ÿ™‹๐Ÿพโ€โ™€๏ธ

Assignment #5

๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ป

โฐ 15 mins

Emits Option

Declare Component Events

Emits Option

Emits scattered throughout component

<!-- DataSender.vue -->
<template>
  <div>
    <button @click="sendData">Send Data</button>
  </div>
</template>

<script>
export default {
  methods: {
    sendData() {
      this.$emit('sending-start')
      
      // send data to API

      this.$emit('sending-complete')
    }
  }
}
</script>

v2

Emits Option

Declare all events with emits option

<!-- DataSender.vue -->
<template>
  <div>
    <button @click="sendData">Send Data</button>
  </div>
</template>

<script>
export default {
  emits:['sending-start', 'sending-complete'],
  methods: {
    sendData() {
      this.$emit('sending-start')
      
      // send data to API

      this.$emit('sending-complete')
    }
  }
}
</script>

v3

Emits Option

Benefits of Emit Option

  • It documents component events in a single place
<script>
export default {
  emits:['sending-start', 'sending-complete'],
  //...
}
</script>

Important for devs and tooling

Emits Option

Benefits of Emit Option

  • When using TypeScript events appear in intellisense results

Emits Option

Benefits of Emit Option

  • Makes the removal of $listeners possible
  • Why? because listeners for declared events are removed from the `$attrs`
  • This also makes means no more .native
<MyButton @click.native="doThing" />

Emits Option

Benefits of Emit Option

  • Documents component events in a single place
  • When using TypeScript events appear in intellisense results



    ย 
  • Listeners for declared events are removed from the `$attrs`
  • Can validate emit payload

Emits Option

Validate event payload

<!-- DataSender.vue -->
<!--...-->
<script>
export default {
  // define as object to use validation
  emits:{
    // null is no validation
    'sending-start': null,
    
    // or provide function to validate
    // return truthy for valid, falsy for invalid
    'sending-complete'(payload){
      return !!(typeof payload.duration === 'number' && payload.response);
    }
  },
  methods: {
    sendData() {
      this.$emit('sending-start')
      
      // send data to API

      this.$emit('sending-complete', {
        duration: 2,
        // response: 'how are you'
      })
    }
  }
}
</script>

v3

Emits Option

Validate event payload with TypeScript

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  emits: {
    addBook(payload: { bookName: string }) {
      // perform runtime validation
      return payload.bookName.length > 0
    }
  },
  methods: {
    onSubmit() {
      this.$emit('addBook', {
        bookName: 123 // Type error!
      })

      this.$emit('non-declared-event') // Type error!
    }
  }
})
</script>

v3

Questions?

๐Ÿ™‹๐Ÿพโ€โ™€๏ธ

Assignment #6

๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ป

โฐ 20 mins

Assignment #6

๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ป

โฐ 20 mins

Skip

Suspense

What is Suspense?

Admittedly a little difficult to understand if you've never heard of it before

Suspense

Orchestrate async dependencies

Without Suspense

With Suspense

With Suspense

ย 

(with more control at component level)

AKA - eliminate popcorn loading

Suspense

In Vue 2 you'd have local loading data in
the component

<!-- PostsList.vue -->
<script>
export default{
  data(){
    return {
      loading: true,
      posts: null
    }
  },
  async created(){
    const res = await fetch("https://myapi.com/posts")
    this.posts = await res.json()
    this.loading = false
  }
}
</script>
<template>
<div>
  <AppSpinner v-if="loading"/>
  <div v-else>...</div>
</div>
</template>

v2

Suspense

Somewhere higher in the higharchy would use the component and others like it

<!-- ParentComponent.vue -->
<template>
<div>
  <PostsList />
  <UsersList />
  <CommentsList />
</div>
</template>

v2

Suspense

Let's take the same component and

modify it for Vue 3

<!-- PostsList.vue -->
<script>
export default{
  data(){
    return {
      loading: true,
      posts: null
    }
  },
  async created(){
    const res = await fetch("https://myapi.com/posts")
    this.posts = await res.json()
    this.loading = false
  }
}
</script>
<template>
<div>
  <AppSpinner v-if="loading"/>
  <div v-else>...</div>
</div>
</template>

v2

Suspense

In Vue 3 you can get rid of all that loading state in the component

<!-- PostsList.vue -->
<script>
export default{
  data(){
    return {
      posts: null
    }
  },
  async created(){
    const res = await fetch("https://myapi.com/posts")
    this.posts = await res.json()
  }
}
</script>
<template>
<div>
  <div>...</div>
</div>
</template>

v3

Suspense

Then return an async setup function
instead

<!-- PostsList.vue -->
<script>
export default{
  async setup(){
    const res = await fetch("https://myapi.com/posts")
    const posts = await res.json()
    return { posts }
  },
}
</script>
<template>
<div>
  <div>...</div>
</div>
</template>

v3

Don't worry too much about how setup works right now

Suspense

With script setup you can use a top level await

<!-- PostsList.vue -->
<script setup>
const res = await fetch("https://myapi.com/posts")
const posts = await res.json()
</script>
<template>
<div>
  <div>...</div>
</div>
</template>

v3

Don't worry too much about how setup works right now

Suspense

Then in the parent you use suspense to show a

single loader once all promises have resolved

<!-- ParentComponent.vue -->
<template>
<div>
  <Suspense>
    <!-- Put all the async components 
         inside the default slot -->
    <template #default>
      <PostsList />
      <UsersList />
      <CommentsList />
    </template>
    
    <!-- And your loader 
         in the fallback slot -->
    <template #fallback>
      <AppSpinner/>
    </template>
  </Suspense>
</div>
</template>

v3

Suspense

Also works with asyncComponents

(different chunk from main js bundle)

<script>
import { defineAsyncComponent } from 'vue'

export default {
  components: {
    AdminPage: defineAsyncComponent(() =>
      import('./components/AdminPageComponent.vue')
    )
  }
}
</script>

<template>
  <Suspense>
    <template #default> <AdminPage /> </template>
    <template #fallback> <AppSpinner/> </template>
  </Suspense>
</template>

v3

Suspense

Total control by mixing async setup and local loading state

<!-- ParentComponent.vue -->
<template>
<div>
  <Suspense>

    <template #default>
      <!-- has async setup -->
      <PostsList /> 
      <!-- has async setup -->
      <UsersList /> 
      <!-- handles own loading state -->
      <CommentsList /> 
    </template>
    
    <template #fallback>
      <AppSpinner/>
    </template>
  </Suspense>
</div>
</template>

v3

Suspense

Does it do error handling?

Suspense Recap

  • Eliminates "Popcorn loading"
  • Less boilerplate in components (no loading state)
  • Handle loading state at top level
  • Can still handle loading state in component if desired for total control

Suspense Word of Warning

Suspense is technically an experimental feature

Suspense Word of Warning

But Nuxt 3 uses in production... so I wouldn't

worry about Vue.js core making breaking changes

Questions?

๐Ÿ™‹๐Ÿพโ€โ™€๏ธ

Assignment #7

๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ป

โฐ 20 mins

5 Minute Break

โ˜•๏ธ๐Ÿ™†

โฐ 5 mins

Migrate to Vue 3

Why Migrate to Vue 3

  • All the great new features we've already talked about!ย 
  • (Many of these are back-ported to 2.7^)
  • Get continued support and updates (security patches, future new features, etc)

What if I can't migrate?

How do I Migrate to V3?

How do I Migrate to V3?

The Official Migration guide provides:

  • A list of breaking changes complete with examples of prior behavior, new behavior, and a strategy for migrating

Breaking Changes

Example of Filter

Notable Breaking Changes

Besides the breaking changes already seen throughout the workshop, there are a few more worthy of noting

๐Ÿ”–

Notable Breaking Changes

Async compnent syntax changed

<!-- Vue 2 -->
<script>
export default{
  components:{
    MyAsyncComponent: () => import('./MyAsyncComponent.vue')
  }
}
</script>

<!-- Vue 3 -->
<script>
import {defineAsyncComponent} from "vue"
export default{
  components:{
    MyAsyncComponent: defineAsyncComponent(
      () => import('./MyAsyncComponent.vue')
    )
  }
}
</script>

Watch on Arrays

<script>
export default{
  data(){
    return {
      food: ['Hamburger', 'Hotdog', 'Spaghetti', 'Taco']
    }
  },
  watch:{
    // Vue 2 - will fire when array replaced, order changed, item added, etc
    // Vue 3 - will only fire when array is replaced
    food(){
      console.log('food changed')
    },
    
    // Vue 3 - must provide for handler to fire on order changed, item added, etc
    food:{
      handler(){
        console.log('food changed')
      },
      deep: true
    }
  }
}
</script>

Notable Breaking Changes

Global API Treeshaking

<!-- Vue 2 -->
<script>
import Vue from 'vue'

Vue.nextTick(() => {
  // something DOM-related
})
</script>

<!-- Vue 3 -->
<script>
import { nextTick } from 'vue'

nextTick(() => {
  // something DOM-related
})
</script>

Notable Breaking Changes

Global API Treeshaking

<!-- Vue 2 -->
<script>
import Vue from 'vue'

Vue.nextTick(() => {
  // something DOM-related
})
</script>

<!-- Vue 3 -->
<script>
import { nextTick } from 'vue'

nextTick(() => {
  // something DOM-related
})
</script>

Notable Breaking Changes

Also Vue.version

<!-- Vue 2 -->
<script>
import Vue from 'vue'
console.log(Vue.version)
</script>

<!-- Vue 3 -->
<script>
import { version } from 'vue'
console.log(version)
</script>

Custom Directives Hooks

// Vue 2
Vue.directive('highlight', {
  bind(el, binding, vnode) {
    el.style.background = binding.value
  }
})

// Vue 3
const app = Vue.createApp({})

app.directive('highlight', {
  beforeMount(el, binding, vnode) {
    el.style.background = binding.value
  }
})

Notable Breaking Changes

custom directive hooks mirrors component lifecycle hooks

Custom Directives Hooks

Notable Breaking Changes

  • created - new! This is called before the element's attributes or event listeners are applied.
  • bind โ†’ beforeMount
  • inserted โ†’ mounted
  • beforeUpdate: new! This is called before the element itself is updated, much like the component lifecycle hooks.
  • update โ†’ removed! There were too many similarities to updated, so this is redundant. Please use updated instead.
  • componentUpdated โ†’ updated
  • beforeUnmount: new! Similar to component lifecycle hooks, this will be called right before an element is unmounted.
  • unbind -> unmounted

Notable Breaking Changes

All breaking changes can be found at:

https://v3-migration.vuejs.org/breaking-changes/

How do I Migrate to V3?

ย 

  • A list of breaking changes complete with examples of prior behavior, new behavior, and a strategy for migrating
  • A list of New Framework-level Recommendations

The Official Migration guide provides:

How do I Migrate to V3?

Vue - Official

New Framework Level Recomendations

  • Vue Core v2 โžก v3
    โœ… Yes, this is the priority

๐ŸŸฅ

๐ŸŸจ

๐ŸŸจ

๐ŸŸฅ

  • Options โžก Composition
    โžก๏ธ Recommend using CAPI going forward as needed
  • Vue CLI โžก Vite
    ๐Ÿ Sooner or later
  • Vuex โžก Pinia
    ๐Ÿ Sooner or later

Difficulty

๐ŸŸฉ Easy

๐ŸŸจ Medium

๐ŸŸฅ Hard

  • Router, Devtools, test-utils

๐ŸŸฉ

๐ŸŸฉ

  • Vetur โžก Volar
    โœ… Yes for VS Code, super easy

Resource for Other Migrations

How do I Migrate to V3?

ย 

  • A list of breaking changes complete with examples of prior behavior, new behavior, and a strategy for migrating
  • A list of New Framework-level Recommendations
  • Migration build

The Official Migration guide provides:

What is the Migration Build?

  • @vue/compat
  • a build of Vue 3 that supports Vue 2 API's (mostly)
  • Usage of features that have changed or been deprecated in Vue 3 will emit runtime warnings.
  • Used for migrating from Vue 2 to Vue 3

Install Vue 3 and Migration Build

npm install vue@3.2.45
npm install @vue/compat

Step

1

Alias vue to @vue/compat

Step

2

// vue.config.js (vue-cli)
module.exports = {
  chainWebpack: config => {
    config.resolve.alias.set('vue', '@vue/compat')

    config.module
      .rule('vue')
      .use('vue-loader')
      .tap(options => {
        return {
          ...options,
          compilerOptions: {
            compatConfig: {
              MODE: 2
            }
          }
        }
      })
  }
}

Vue Docs also contains example configs for:

  • Plain Webpack
  • Vite

Vue CLI

Handle compile time errors

Step

3

  • Errors will display in the terminal when running `npm run serve`
  • Is recommended to handle all compile time errors first

Handle compile time errors

Step

3

  • Errors will display in the terminal when running `npm run serve`
  • Is recommended to handle all compile time errors first

Set compiler to Vue 3 mode

Step

4

module.exports = {
  //...
  compilerOptions: {
    compatConfig: {
      MODE: 2,
      MODE: 3
    },
  },
}

Means the Vue compiler should expect all compiler level code to be written as per Vue 3 specification

Set compiler to Vue 3 mode

Step

4

module.exports = {
  //...
  compilerOptions: {
    compatConfig: {
      MODE: 2,
      MODE: 3
    },
  },
}

Means the Vue compiler should expect all compiler level code to be written as per Vue 3 specification

More on compatConfig in a minute!

Update dependencies like Vue Router and Vuex

Step

5

Official dependencies will have their own migration guides. Others may or may not. (This is where things can get a little dicey, but the ecosystem has definitely progressed)

Run app and go through console warnings

Step

6

  • Deprecations will show in console
  • Can filter warnings in console to concentrate on one type of change at a time

ID

Explanation

ย Link to docs

Run app and go through console warnings

Step

6

  • Once you've handled all instances of a feature ID you can turn compatibility for it off
  • All your tests involving that feature should now pass and be 100% compatible with Vue 3
// main.js
import { configureCompat } from 'vue'

// disable compat for certain features
configureCompat({
  COMPONENT_V_MODEL: false,
  FEATURE_ID_B: false
})

Run app and go through console warnings

Step

6

  • you could also do the same config at the component level
  • Use case for this? ๐Ÿคท Maybe you could migrate one component at a time as opposed to one feature ID at a time
export default {
  compatConfig: {
     COMPONENT_V_MODEL: false,
     FEATURE_ID_B: false
  }
  // ...
}

Run app and go through console warnings

Step

6

  • Can deploy to production with partial migration (warnings still in console)
  • Minimal performance hit
  • HOWEVER, I recommend a git branch for complete migration and deploy all together
feature/vue-3-migration

and maybe a commit per feature ID?

Run app and go through console warnings

Step

6

  • when all console warnings are gone you'll end up with a compatConfig that's disabled all applicable feature ID's
  • but you'll have one feature ID left to handle: TRANSITION_CLASSES
configureCompat({
  COMPONENT_V_MODEL: false,
  GLOBAL_MOUNT_CONTAINER: false,
  GLOBAL_MOUNT: false,
  GLOBAL_SET: false,
  //... 
})

Update transition class names (no console warning)

Step

7

  • You can do a project-wide search in IDE for .*-enter and .*-leave CSS class names
    ย 
  • Replace ย  ย  ย  ย  ย  ย  ย  ย  ย  ย ย  with
    ย 
  • Replace ย  ย  ย  ย  ย  ย  ย  ย  ย  ย ย  with
.v-enter
.v-enter-from
.v-leave
.v-leave-from

Update transition class names (no console warning)

Step

7

  • add TRANSITION_CLASSES to compatConfig
configureCompat({
  // ...
  TRANSITION_CLASSES: false
})

Update transition class names (no console warning)

Step

7

  • once all the IDs have had compatibility turned off that's the equivalent of changing the compatConfig.mode to 3
configureCompat({
   MODE: 3
})

Switch to Vue 3 proper

Step

8

  1. uninstall @vue/compat
  2. remove any compat config
  3. remove alias of vue to @vue/compat
// package.json
"@vue/compat": "^3.1.0",


// vue.config.js
module.exports = {
  chainWebpack: (config) => {
    config.resolve.alias.set("vue", "@vue/compat");

    config.module
      .rule("vue")
      .use("vue-loader")
      .tap((options) => {
        return {
          ...options,
          compilerOptions: {
            compatConfig: {
              MODE: 3,
            },
          },
        };
      });
  },
};

import { 
  createApp, 
  configureCompat 
} from "vue";

import App from "./App.vue";

configureCompat({
  COMPONENT_V_MODEL: false,
  GLOBAL_MOUNT_CONTAINER: false,
  GLOBAL_MOUNT: false,
  GLOBAL_SET: false,
  //... 
})

1

2

3

Questions?

๐Ÿ™‹๐Ÿพโ€โ™€๏ธ

HOMEWORK!

๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ป

Do you accept this challenge? ๐Ÿ’ช

Assignment #8

๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ป

โฐ 30 mins

Questions?

๐Ÿ™‹๐Ÿพโ€โ™€๏ธ

General Q&A

โ“

โฐ 15 minutes

Thank You!

๐Ÿ™

Vue 3 New Features

By Daniel Kelly

Vue 3 New Features

  • 1,270