跪拜 Guibai
← All articles
Vite · Vue.js · Frontend Engineering

A Vite Plugin That Renames v-model to v-mortal — and Why That's a Compile-Time Win

By xiaohe0601 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Compile-time directive rewriting shows how teams can adopt internal DSLs or naming conventions without forking Vue or paying a runtime tax. The same AST-plus-MagicString pattern applies to any SFC transformation — linting, injecting defaults, or polyfilling missing props — and keeps the production bundle untouched.

Summary

A developer named his custom directive `v-mortal` after his own English name and built a Vite plugin that rewrites it to `v-model` at compile time. The plugin hooks into the `transform` phase with `enforce: 'pre'`, parses each `.vue` file with `vue/compiler-sfc`, walks the template AST to locate every `v-mortal` directive, and overwrites just the directive name using MagicString. Because the substitution happens before Vue's own compiler sees the code, there is no runtime overhead and no need to teach Vue about a new directive.

A companion TypeScript declaration file (`v-mortal.d.ts`) augments Vue's `GlobalDirectives` interface so that IDEs offer autocomplete for `v-mortal` exactly as they do for `v-model`. The whole setup is a compact demonstration of moving syntax sugar into the build toolchain rather than into the framework runtime.

The author explicitly warns against using this in production — it's a teaching example — but the underlying principle generalizes: push complexity into the compiler and toolchain to keep the runtime lean.

Takeaways
A Vite plugin with `enforce: 'pre'` runs before Vue's compiler, letting it rewrite template code that Vue never sees in its original form.
Parsing a `.vue` file with `vue/compiler-sfc`'s `parse()` and walking `template.ast` finds directives precisely, avoiding regex edge cases and false matches inside strings or comments.
MagicString overwrites only the targeted byte range (`v-mortal` → `v-model`) and preserves source maps, so downstream tooling stays accurate.
Augmenting `vue`'s `GlobalDirectives` interface in a `.d.ts` file gives the custom directive full IDE IntelliSense without any runtime registration.
The entire substitution carries zero runtime overhead because the transformed code is plain `v-model` by the time Vue processes it.
Conclusions

The technique decouples developer-facing syntax from framework-facing syntax — a team could use `v-myprop` everywhere and compile it to standard Vue directives, keeping code familiar without framework changes.

AST-based rewriting inside Vite's `transform` hook is underused for SFC conventions; this pattern works for any deterministic template transform (default props, accessibility attributes, lint auto-fixes) and leaves the runtime untouched.

The TypeScript declaration trick is the quiet enabler here: without it, the custom directive would compile correctly but feel broken in the editor, which is where most developers would reject it.

Concepts & terms
Vite `enforce: 'pre'`
A plugin ordering flag that forces a Vite plugin to run before other plugins (including the framework's own compiler), giving it first access to raw source code.
MagicString
A lightweight JavaScript library by Rich Harris for performing surgical string replacements while generating accurate source maps, commonly used in build-tool transforms.
vue/compiler-sfc parse()
Vue's official SFC parser that splits a `.vue` file into its `<template>`, `<script>`, and `<style>` blocks and produces an AST for each, enabling programmatic analysis and transformation.
GlobalDirectives type augmentation
A TypeScript module augmentation that extends Vue's `GlobalDirectives` interface so that custom directives (even compile-time-only ones) appear in IDE autocomplete as if they were built-in.
From the discussion

The only substantive exchange questions the plugin's premise: if the build step simply swaps v-mortal back to v-model, using v-model directly seems more straightforward. The reply acknowledges the question without resolving it.

Replacing v-mortal with v-model at compile time raises the question of why a developer wouldn't just write v-model in the first place — the indirection appears to add no clear benefit.
Featured comments
striveLei 1 likes

Since v-mortal gets replaced with v-model at build time, why not just use v-model directly?

xiaohe0601

Good question [look]

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗