DEV Community

Cover image for Collection of Vue Macros in Vue.js 3.3
Eduard Krivanek for Bitovi

Posted on

Collection of Vue Macros in Vue.js 3.3

Many developers (myself included) were excited when Vue.js version 3.3 introduced several functionalities from Vue Macros, the collection of proposed additions to Vue.js that extend its basic functionalities with syntactic sugar.

In this post, you will learn about Vue Macros that became a part of Vue version 3.3 and how you can use them if you are not yet on version 3.3.

What is Vue Macros?

Let's go straight to the source! The official website claims the following:

Vue Macros is a library that implements proposals or ideas that have not been officially implemented by Vue. That means it will explore and extend more features and syntax sugar to Vue.

Looking at its GitHub repo, the library is well maintained. Moreover, Vue Macros supports Vue from version 2.7. To install Vue Macros, you need to run npm i -D unplugin-vue-macros, configure vite.config.ts, and the tsconfig.json as is installation suggests to start using them.

Which Macros do I want to use, you ask? Let’s look into Macros introduced as part of Vue version 3.3.

Macro defineModels

Prior to v3.3, when it came to component two-way  binding with v-model, we had to implement props and emits in the child component to receive or notify the parent component about a data change. You usually end up with the following example:

    // MySearch.vue

    <template>
      <div> 
        <button
          v-for="data in store.getLoadedElements"
          :key="data.title"
          @click="() => onInput(data)"
        >
         {{ data.title }}
        </button>
      </div>
    </template>

    <script setup lang="ts">
    // some additional code

    const store = useStore();

    const props = defineProps({
      modelValue: {
         type: Element,
         required: false
      }
    });

    const emits = defineEmits<{
      (e: "update:modelValue", value: Element): void;
    }>();

    function onInput(element: Element) {
      emit('update:modelValue', element)
    }

    // some additional code
    </script>
Enter fullscreen mode Exit fullscreen mode

The defineModels macro is a syntactic sugar by which you can avoid implementing two-way binding for using v-model on the component level instead of the default Vue.js implementation. You can achieve the above logic with the following syntax:

    // MySearch.vue

    <template>
      <div> 
        <button
         v-for="data in store.getLoadedElements"
         :key="data.title"
         @click="modelValue = data"
        >
         {{ data.title }}
        </button>
      </div>
    </template>

    <script setup lang="ts">
    // some additional code

    const store = useStore();
    const { modelValue } = defineModels<{ modelValue: Element }>();

    // some additional code
    </script>
Enter fullscreen mode Exit fullscreen mode

Vue.js 3.3 recently received the defineModels enhancement for two-way binding under the name defineModel, which has the exact same functionality.

You will find more information in our blog post: New in Vue.js 3.3: Two-Way Binding With defineModel Macro. However, opting for the defineModel functionality in Vue.js 3.3, as it is still an experimental feature, you have to modify your vite.config.ts in the following way:

    plugins: [
      vue({
        script: {
          defineModel: true
          // ^^ enables the feature
        }
      })
    ]
Enter fullscreen mode Exit fullscreen mode

Macro definePropsRefs

Reactive props destructuring was quite a requested feature from the Vue.js community. It’s safe to say the developer was excited about the feature released in Vue 3.3. However, the older Vue.js version still receives the following error message when trying to destructure props.

macrodefineprops

Fortunately, the macro definePropsRefs allows us property destructuring by writing the following code:

    const { showModal, inputValue } = definePropsRefs({
      showModal: {
        type: Boolean,
        required: false
      },
      inputValue: {
        type: String,
        required: false,
        default: ""
      }
    });
Enter fullscreen mode Exit fullscreen mode

Using Vue Macros are much better developer experience than destructuring properties before version 3.3:

    const { showModal, inputValue } = toRefs(
      withDefaults(
        defineProps({
          showModal: {
            type: Boolean,
            required: false
          },
          inputValue: {
            type: String,
            required: false,
            default: ""
          }
       }),
       {
         showModal: false,
         inputValue: ""
       }
      )
    )
Enter fullscreen mode Exit fullscreen mode

If you want to use defineProps an experimental feature in Vue version 3.3, you have to opt-in with the following configuration:

    // vite.config.js
    plugins: [
      vue({
        script: {
          propsDestructure: true
          // ^^ enables the feature
        }
      })
    ]
Enter fullscreen mode Exit fullscreen mode

Macro defineEmit

Using defineEmits wasn’t such a bad experience. You declared and used emits in the following way:

    const emit = defineEmits<{
      (e: 'foo', id: number): void
      (e: 'bar', name: string): void
    }>()

    emit('foo', 10);
    emit('bar', '10'');
Enter fullscreen mode Exit fullscreen mode

Using defineEmit from Vue Macros, you get the advantage of declaring individual emits in the following way:

    <script setup>
    // Declare emit
    const foo = defineEmit<number[]>('foo')

    // Infer emit name from variable name
    const bar = defineEmit<string[]>()

    // Emit array of strings
    const test = defineEmit<string[][]>() 

    foo(10)
    bar('10')
    test(['test1', 'test2'])
    </script>
Enter fullscreen mode Exit fullscreen mode

Vue 3.3 brought a little bit of syntactic sugar, working with defineEmits in the following way:

    const emit = defineEmits<{
      foo: [id: number]
      bar: [name: string]
    }>()
Enter fullscreen mode Exit fullscreen mode

Fortunately, there is no opting-in for this feature, so you can use it right away!

Macro defineSlots

When working with scoped slots in Vue.js, one problem we faced was not knowing what properties were available on the parent component. Here is the following example before Vue version 3.3:

    <!-- <ChildComponent> template -->
    <div>
      <slot :text="greetingMessage" :count="1"></slot>
    </div>

    // ------------------------------------------

    <!-- <ParentComponent> template -->
    <MyComponent v-slot="slotProps">
      {{ slotProps.text }} {{ slotProps.count }}
    </MyComponent>
Enter fullscreen mode Exit fullscreen mode

A developer may access slotProps.anything and receive an error only during the runtime.

Vue.js 3.3 introduced defineSlots, which use types slots, eliminating the parent component of accessing non-existing props from the child. Here is the following example:

    <!-- <ChildComponent> -->

    <template>
      <div v-for="item of items" :key="item">
        <h2>Name</h2>

        <!-- default slot -->
        <slot name="itemNameSlot" :item-name="item">
           {{ item }}
        </slot>
      </div>
    </template>

    <script setup lang="ts">
    const items = ref<string[]>(["item1", "item2", "item3"]);

    defineSlots<{
      itemNameSlot: (props: { itemName: string }) => any;
    }>();
    </script>

    // ----------------------------

    <!-- <ParentComponent> -->

    <template>
      <main>
       <Test>
         <template #itemNameSlot="{ itemName }">
           <a :href="`/someurl/${itemName}`"> 
             {{ itemName }} - click 
           </a>
         </template>
       </Test>
      </main>
    </template>
Enter fullscreen mode Exit fullscreen mode

If you replace the itemName with a different value in the #itemNameSlot="{ itemName }" in the parent component, you will receive the following error:

macrodefineslots

Fortunately, there is already a macro for the same functionality with the same name - defineSlots from Vue Macros if you are not yet on version 3.3.

Summary

Vue.js 3.3 brought new features that developers were waiting for. The community has already implemented most of these features in a package called Vue Macros. It is a small but convenient collection of utils that can be used even on Vue version 2.7.

Top comments (0)