Beyond defineModel and defineProps
Compiler macros such as defineProps and defineModel look like normal function calls but do not exist at runtime at all, the Vue compiler transforms them away while building the code. The dedicated Vue Macros plugin goes further and adds its own macros for reactivity, prop forwarding and conditional compilation, with clear tradeoffs in tooling and predictability.
Table of Contents
- 1. What a compiler macro actually is
- 2. Native compiler macros in script setup
- 3. defineModel: two way binding without boilerplate
- 4. defineExpose and defineOptions in detail
- 5. The Vue Macros plugin: scope and installation
- 6. Reactivity Transform and $ref in historical context
- 7. Defining your own macros with Vue Macros
- 8. Limits, tooling cost and when to skip it
- 9. Native macros vs. the Vue Macros plugin compared
- 10. Summary
- 11. FAQ
1. What a compiler macro actually is
A compiler macro in Vue looks like an ordinary function call but does not exist at runtime at all. The Vue compiler recognizes specific function names such as defineProps or defineEmits inside a script setup block and replaces them with real, executable JavaScript code during compilation. At runtime in the browser these functions do not exist, they are purely compile time constructs that give developers a declarative, terse syntax without needing an extra import.
The key difference from a normal function is that a compiler macro has access to static information from the surrounding source code, such as TypeScript types that are long gone at runtime. defineProps<{ title: string }>() only works because the compiler reads the generic type parameter at compile time and generates a runtime props declaration from it, a normal function could not do that, since TypeScript types no longer exist after compilation.
Vue itself already ships several native compiler macros without needing an extra plugin. The separate community project Vue Macros goes further and adds additional, unofficial macros that require extra Vite or Webpack plugins. This distinction between native and plugin based compiler macros is central to understanding what works out of the box in every Vue 3 project and what requires additional dependencies.
2. Native compiler macros in script setup
Vue ships a fixed set of native compiler macros in the script setup context: defineProps, defineEmits, defineExpose, defineOptions, defineSlots, and, since Vue 3.4, defineModel. These macros need no import because the compiler specifically recognizes them, a regular import would actually cause an error, since the compiler explicitly checks that these names are only used as direct calls in the file's top level scope.
That restriction is intentional: a compiler macro cannot be called inside a condition, a loop, or a nested function, because the compiler has to analyze the call statically at compile time without actually executing the code. Anyone trying to call defineProps conditionally inside an if block gets a compile error, because that kind of dynamic behavior is incompatible with the static nature of a compiler macro.
// Native Vue compiler macros — no import required
// The compiler recognizes these names inside <script setup>
const props = defineProps<{
title: string
variant?: 'primary' | 'secondary'
}>()
const emit = defineEmits<{
(e: 'submit', payload: { id: number }): void
(e: 'cancel'): void
}>()
// defineOptions replaces the old export default { name: ... } block
defineOptions({
name: 'ConfirmDialog',
inheritAttrs: false,
})
// Wrong: macros cannot be called conditionally
// if (someFlag) {
// defineProps<{ foo: string }>() // compile error
// }
3. defineModel: two way binding without boilerplate
Before Vue 3.4, a two way binding with v-model on a child component required a prop and a matching emit event, usually modelValue and update:modelValue, plus a computed value connecting the two. The defineModel macro condenses that recurring boilerplate into a single line and returns a ref that automatically fires the matching update event on change, without needing to hand build the prop emit coupling.
defineModel also supports named models for multiple independent two way bindings on the same component, plus a required and default modifier similar to defineProps. For form components that bind several values at once, for example a date range with a start and an end date, this macro noticeably reduces the amount of repetitive code compared to the manual prop emit solution.
// DateRangePicker.vue — two independent two-way bindings via defineModel
const startDate = defineModel<string>('start', { required: true })
const endDate = defineModel<string>('end', { required: true })
function setToday() {
const today = new Date().toISOString().slice(0, 10)
startDate.value = today
endDate.value = today
}
// Parent usage:
// <DateRangePicker v-model:start="range.from" v-model:end="range.to" />
4. defineExpose and defineOptions in detail
Because script setup exposes no public interface by default, every local variable is only visible internally within the component, the macro defineExpose exists specifically to selectively expose chosen methods or values to a template ref in the parent. Without defineExpose, a child component accessed through a template ref is practically useless, since the parent component would otherwise only ever see an empty object.
defineOptions adds back options that used to live in the classic Options API object, most notably the component name, which matters for recursive components and for display in Vue DevTools. Without defineOptions, the component name has to be guessed from the file name, leading to inconsistencies when files are renamed or moved. Together, both macros cover the gaps left by dropping the classic Options API object inside script setup.
5. The Vue Macros plugin: scope and installation
Beyond the native macros, there is the community project Vue Macros, a Vite or Webpack plugin that extends the script setup compiler with additional, unofficial macros. These include defineProp for individual, reactive props as separate refs, $ as shorthand for computed values, and experimental extensions such as conditional JSX compilation. Vue Macros is installed via npm install -D unplugin-vue-macros and registered as a Vite plugin.
It is important to draw this line clearly: everything Vue Macros provides is not an official part of Vue itself, but an additional compilation stage that runs before the actual Vue compiler. Concretely that means any project using Vue Macros carries an extra build dependency, and editor tooling like Volar only understands this extra syntax correctly with matching extensions.
// vite.config.ts — enabling the Vue Macros plugin
import { defineConfig } from 'vite'
import Vue from '@vitejs/plugin-vue'
import VueMacros from 'unplugin-vue-macros/vite'
export default defineConfig({
plugins: [
VueMacros({
plugins: {
vue: Vue(),
},
}),
],
})
// Component.vue — using the non-official defineProp macro
// One reactive prop as its own ref, instead of destructuring defineProps()
const title = defineProp<string>('title')
6. Reactivity Transform and $ref in historical context
An important chapter in the history of Vue compiler macros is Reactivity Transform, an experimental feature that tried, with macros like $ref, $computed and $$, to make the explicit .value on refs unnecessary. The Vue core team removed Reactivity Transform again in Vue 3.4 as an officially experimental feature, arguing that implicit ref resolution required too much compiler magic and hurt the readability of code that looked like plain variables at first glance but behaved like reactive refs at runtime.
The Vue Macros plugin still supports this syntax for teams that deliberately want it, but outside the official Vue core. Anyone starting a new project today should be aware of this history: $ref syntax is no longer a standard Vue feature, it is a Vue Macros specific extension that needs additional editor support and still requires explanation when onboarding new team members.
7. Defining your own macros with Vue Macros
Vue Macros lets advanced users define their own compiler macros, for example for project specific patterns that repeat across many components. A typical example is a macro that automatically wraps a function with logging code, based on a configuration evaluated at compile time, without repeating that logic manually in every single component.
This capability is powerful but comes with responsibility: a custom compiler macro changes the code that actually runs in a way that is not visible in the editor without explicitly inspecting the compiled output. For smaller teams or projects with high turnover, the effort of maintaining and documenting custom macros is often bigger than the benefit over a simple, explicit utility function without compiler magic.
8. Limits, tooling cost and when to skip it
The biggest practical downside of compiler macros, whether native or from the Vue Macros plugin, is the dependency on correct editor tooling. Without the matching Volar extension, the TypeScript language server does not interpret defineProps and defineModel correctly, leading to false error messages in the editor even when the actual build passes without errors. With additional, unofficial macros from the Vue Macros plugin this problem gets worse, since less common editor extensions sometimes do not know this syntax at all.
A second downside concerns discoverability for new team members. Native Vue compiler macros are widely known knowledge by now and well documented officially, whereas additional Vue Macros extensions such as defineProp or custom, project specific macros require extra onboarding. The recommendation for most teams: use native compiler macros consistently, and adopt the Vue Macros plugin only for individual, clearly justified cases, not as a general default for every new project.
9. Native macros vs. the Vue Macros plugin compared
The following table places native Vue compiler macros against the additional Vue Macros plugin, to make the decision for or against the extra dependency easier.
| Criterion | Native Compiler Macros | Vue Macros Plugin | Recommendation |
|---|---|---|---|
| Extra dependency | None, part of Vue itself | unplugin-vue-macros required | Prefer native macros |
| Editor support | Volar supports it by default | Partially limited | Check editor support before using |
| Onboarding new developers | Well documented, widely known | Requires extra knowledge | Only with clear team consensus |
| Feature scope | defineProps, defineModel, defineExpose | Additionally defineProp, $, custom macros | Only with concrete additional need |
The table makes clear that native compiler macros are sufficient for most Vue 3 projects. The Vue Macros plugin is mainly worth it for teams that deliberately want to evaluate experimental syntax or that want to standardize very specific, recurring patterns via their own macros.
Mironsoft
Vue 3, script setup and modern component architecture
Not sure if Vue Macros makes sense for your project?
We assess whether native compiler macros are enough for your Vue project or whether the Vue Macros plugin offers a concrete benefit, including an editor tooling check and a migration plan for existing components.
Compiler macro audit
Review existing script setup code for clean macro usage
defineModel migration
Migrate old prop emit bindings to defineModel
Tooling check
Verify Volar compatibility and editor support before adoption
10. Summary
Vue Macros, understood as an umbrella term for compiler macros in Vue, range from the native, official macros such as defineProps, defineModel and defineExpose to the separate community plugin that enables additional, unofficial syntax such as defineProp and custom, project specific macros. Native macros are sufficient for the vast majority of Vue 3 projects and require no additional build dependency.
The additional Vue Macros plugin is only worth it when a team deliberately wants to evaluate experimental syntax or wants to standardize recurring, project specific patterns through custom compiler macros. In every case it remains important to check whether editor tooling, above all Volar or the Vue Language Server, correctly understands the macros used, otherwise false error messages appear despite an error free build.
Vue Macros and compiler macros, the essentials at a glance
Native macros
defineProps, defineEmits, defineModel, defineExpose, defineOptions, usable without any import.
defineModel since 3.4
Replaces manual prop emit coupling for v-model in a single line.
Vue Macros plugin
unplugin-vue-macros adds defineProp, $, and custom macros, with tooling cost.
Reactivity Transform
Removed from Vue core, lives on only as a Vue Macros extension.