Олег Марков
Понимание и работа с компилятором Vue
Введение
Если вы пишете интерфейсы на Vue, наверняка сталкивались с такими понятиями как шаблоны, рантайм, рендер-функции и SFC (Single-File Components). За всеми этими привычными механизмами находится важная “деталь под капотом” — компилятор Vue. Это ключевая часть, которая преобразует ваши шаблоны в исполняемый JavaScript-код, выполняемый уже в браузере.
В этой статье разберём, как устроен компилятор Vue, как с ним можно взаимодействовать, какие этапы он проходит, и зачем вообще необходим этот процесс. Я покажу вам примеры, дам рекомендации по оптимизации производительности и расскажу главные нюансы, связанные с компиляцией шаблонов и использованием рантайма.
Как устроен компилятор Vue
Компилятор Vue отвечает за преобразование шаблонов (обычно записанных в HTML-подобном синтаксисе) в рендер-функции на JavaScript. Этот процесс осуществляется на этапе сборки или «на лету» в браузере, если вы не собираете ваш проект заранее. Давайте взглянем на основные этапы работы компилятора.
Основные этапы компиляции
Процесс компиляции делится на три крупных этапа:
Парсинг (Parsing)
Ваш шаблон переводится в промежуточное AST (Abstract Syntax Tree) — древообразную структуру, которая описывает, из каких элементов состоит компонент.Трансформация (Transformation)
AST модифицируется: добавляются обработчики событий, вычисляются директивы (v-for
,v-if
и т.д.), оптимизируются узлы.Генерация кода (Code Generation)
На основе AST формируется JavaScript-функция, которая при запуске создает виртуальное DOM-дерево.
Посмотрите на схематичный разбор:
// Исходный шаблон
<div>{{ message }}</div>
// После компиляции — результат:
function render(ctx) {
// ctx — прокси-компонента
return h('div', null, ctx.message)
}
Как видите, ваша разметка превращается в вызов функции, которую интерпретирует рантайм Vue.
Online и Offline-компиляция
Компиляция шаблонов в Vue бывает двух типов:
Runtime Compilation (компиляция в рантайме)
Шаблон компилируется прямо в браузере “на лету”. Такой подход менее производительный и увеличивает размер бандла, но полезен для сценариев, где шаблоны неизвестны заранее (например, CMS или конструкторах).Pre-Compilation (предварительная компиляция)
Шаблоны компилируются во время сборки (чаще всего через tools вродеvue-loader
для webpack илиvite-plugin-vue
для Vite). В результате в браузер попадает только JavaScript с уже готовыми рендер-функциями.
Советую использовать pre-compilation для production-приложений. Это делает приложения быстрее и уменьшает их размер.
Single-File Components (SFC) и роль компилятора
Когда вы работаете с .vue
файлами — Single-File Components — компилятор Vue принимает на себя задачу извлечения шаблона из блока <template>
, его обработки и преобразования в рендер-функцию.
В связке с такими инструментами как vue-loader
, процесс примерно таков:
vue-loader
выделяет блоки<template>
,<script>
,<style>
;<template>
отправляется в компилятор Vue (например,@vue/compiler-sfc
);- Результат — JS-код с функцией рендера, замещающий исходный шаблон.
Вот пример, чтобы вам было проще понять:
<template>
<button @click="increment">{{ count }}</button>
</template>
<script>
export default {
data() {
return { count: 0 }
},
methods: {
increment() {
this.count++
}
}
}
</script>
После компиляции template
превращается примерно в:
function render(ctx, cache, props, setup, data, options) {
// Функция создает элемент button и подключает событие click
return h('button', { onClick: options.increment }, [ toDisplayString(data.count) ])
}
Настройка и использование компилятора Vue
Вы не работаете с компилятором напрямую при обычной разработке, потому что интеграция обычно настраивается в сборщиках (Vite, Webpack). Тем не менее, знания об этом этапе помогут вам глубже понимать, как работает ваш проект.
Пример настройки с Vite
Смотрите, как можно подключить и использовать компилятор Vue с Vite:
- Установите зависимости:
npm install vue@next @vitejs/plugin-vue
- Настройте Vite в
vite.config.js
:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
})
Компилятор будет использоваться автоматически для обработки .vue
файлов.
Использование sfc компилятора напрямую
Иногда бывает полезно скомпилировать шаблон в рендер-функцию прямо в коде. Для этого используйте пакет @vue/compiler-dom
или @vue/compiler-sfc
.
import { compile } from '@vue/compiler-dom'
const template = '<p>{{ greeting }}, {{ name }}!</p>'
const { code } = compile(template)
console.log(code)
// Выведет JS-реализацию рендер-функции этого шаблона
Это бывает полезно для написания собственных инструментов, тестирования или изучения того, как Vue ведёт “под капотом”.
AST и плагины трансформации
AST (Abstract Syntax Tree) — центральная точка, где можно влиять на процесс компиляции. Можно писать плагины-трансформаторы, которые изменяют AST, например, для кастомных директив, оптимизации под специфику приложения, или автоматического внедрения определённых паттернов.
Почему важно знать про AST?
- Позволяет проводить статический анализ шаблонов;
- Помогает выявлять потенциальные ошибки ещё до запуска приложения;
- Открывает путь к кастомизации и расширению функциональности Vue через плагины.
Пример анализа AST с помощью @vue/compiler-dom
:
import { baseParse } from '@vue/compiler-dom'
// Базовый парсинг шаблона
const ast = baseParse('<div v-if="isVisible">Hello</div>')
console.log(ast)
// Покажет дерево разбора, где виден узел с v-if
Отличие Vue 2 и Vue 3 в компиляции
В Vue 2 процесс компиляции сильно отличался. Шаблон обычно компилировался на лету в браузере, а для сборки production был отдельный пакет без компилятора (vue.runtime.js
). Рендер-функции были доступны, но работать с ними было чуть сложнее.
В Vue 3 произошёл значительный рефакторинг:
- Компилятор вынесен в отдельные пакеты (
@vue/compiler-dom
,@vue/compiler-sfc
); - Поддержка новых синтаксических конструкций (например, );
- Больший упор на pre-compile и tree-shaking;
- Улучшенная обработка типов (особенно если в проекте используется TypeScript).
Если вы начали с Vue 3 — принимайте как данность, что шаблоны чаще всего не компилируются на лету. Но если переходите с Vue 2, то стоит быть внимательнее к разнице между runtime-only и full build пакетами.
Runtime + Compiler vs. Runtime only
Vue поставляется в разных вариантах сборки:
Runtime + Compiler (
vue.global.js
,vue.esm-browser.js
)
Позволяет передавать шаблоны как строки в функциюcreateApp
. Подходит для legacy-проектов или динамических шаблонов.Runtime only (
vue.runtime.global.js
,vue.runtime.esm-browser.js
)
Более лёгкая сборка, не содержит компилятора, работает только с готовыми рендер-функциями. Идеал для production.
Вот как это выглядит на практике:
// Возможность передачи строки-шаблона (работает только с Runtime + Compiler)
const app = Vue.createApp({
template: '<span>Hello, world</span>'
})
// Если у вас Runtime only, используйте render-функцию
const app2 = Vue.createApp({
render() {
return h('span', null, 'Hello, world')
}
})
Если вы попробуете использовать строку-шаблон с runtime-only версией, получите ошибку типа "Failed to mount component: template or render function not defined".
Встраиваемые и пользовательские шаблонные компиляторы
Иногда разработчики хотят разработать собственный препроцессор шаблонов. Например, поддерживать JSX или Markdown вместо обычного HTML. Для этого можно воспользоваться открытым API компилятора SFC. Используйте @vue/compiler-sfc
для парсинга, модификации, сборки или обработки блоков <template>
.
Пример парсинга и изменения SFC:
import { parse, compileTemplate } from '@vue/compiler-sfc'
// Исходный компонент
const sfcContent = `
<template>
<h1>{{ title }}</h1>
</template>
<script>
export default {
data() {
return { title: "Hi" }
}
}
</script>
`
const parsed = parse(sfcContent)
const templateBlock = parsed.descriptor.template
// Компилируем шаблон
const renderResult = compileTemplate({
source: templateBlock.content,
filename: 'MyComponent.vue'
})
console.log(renderResult.code)
// Вы увидите готовую рендер-функцию
Это открывает широкие горизонты для инструментов статического анализа, линтинга или расширения фреймворка.
Диагностика и оптимизация производительности
Понимая, как шаблоны превращаются в рендер-функции, вы можете писать более эффективные компоненты.
Как можно влиять на оптимизацию:
- Используйте pre-compile (см. выше);
- Избегайте сложных вычислений прямо в шаблонах;
- Используйте директиву
v-once
, если элемент или блок не изменяется; - Старайтесь вносить тяжелые вычисления на уровень computed;
- Помните про ключи
key
в циклах — компилятор их оптимизирует; - Используйте инструменты анализа (Vue Devtools, Vite plugin inspect).
Влияние директив на компиляцию
Директивы вроде v-if
, v-for
, v-slot
напрямую влияют на структуру AST. Например, вложенный v-for
превращается в циклы на уровне рендер-функции.
Пример с v-for
:
<ul>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</ul>
Компиляция этого шаблона даст примерно такой результат на уровне рендер-функции:
function render(_ctx, _cache) {
return h('ul', null, _ctx.items.map(item =>
h('li', { key: item.id }, item.name)
))
}
Если не указать :key
, компилятор выдаст предупреждение, потому что это нарушает эффективное сравнение виртуального DOM.
А как посмотреть, что сгенерировал компилятор?
С помощью командной строки — можно использовать пакет @vue/compiler-dom
для генерации JS-функции из шаблона:
npx @vue/compiler-dom --code "<div>{{ msg }}</div>"
Или сделать то же самое в node.js, как я показывал выше.
Возможные ошибки и диагностика
Ошибки компиляции шаблонов — не редкость. Люди часто сталкиваются с:
- Неправильным синтаксисом (например, забыли закрыть тег);
- Использованием переменных не из контекста компонента;
- Использованием неподдерживаемых HTML/JSX-конструкций;
- Неуникальными ключами в циклах.
Внимательно читайте сообщения об ошибках в консоли сборщика — они часто подробно описывают причину.
Заключение
Понимание внутреннего устройства компилятора Vue и его роли в создании приложения помогает не только быстрее решать проблемы и оптимизировать проект, но и открывает возможности для продвинутой кастомизации, работы с новым синтаксисом или разработки собственных инструментов. Используя pre-compilation, правильно настраивая сборку и следя за структурой шаблонов, вы получаете максимальную производительность и гибкость. Не бойтесь экспериментировать с AST, использовать разные плагины и инструменты компилятора — экосистема Vue это отлично поддерживает.
Частозадаваемые технические вопросы
1. Как проверить, какую версию сборки Vue использует мой проект — с компилятором или только runtime?
Ответ:
Проверьте способ импорта Vue в вашем bundler (например, в webpack или Vite). Если импортируется 'vue'
, то обычно используется версия с компилятором. Можно добавить в рантайме лог:js
console.log(Vue.compile) // Если функция определена, у вас версия с компилятором
Кроме того, для Vite/webpack посмотрите в настройках alias, каким файлом замещается vue.
2. Что делать, если шаблон недоступен (например, поступает с сервера) и нужно компилировать его на лету?
Ответ:
Используйте версию Vue, включающую компилятор (runtime + compiler). Затем вызывайте Vue.compile
для преобразования строки в функцию:
js
const render = Vue.compile('<span>{{ message }}</span>');
new Vue({ data: { message: 'Hi' }, render }).$mount('#app');
В Vue 3 используйте отдельный пакет @vue/runtime-dom
и @vue/compiler-dom
.
3. Как добавить поддержку нового синтаксиса или кастомных директив на этапе компиляции?
Ответ:
Вам нужен доступ к AST через @vue/compiler-dom
или @vue/compiler-sfc
. Используйте хуки трансформации. Подробно про AST API читайте в документации.
4. Почему появляется ошибка "Failed to mount component: template or render function not defined"?
Ответ:
Это происходит, если вы используете runtime-only версию Vue, но не скомпилировали шаблон в рендер-функцию. Проверьте, чтобы сборщик преобразовывал шаблоны в рендер-функции, либо подключайте версию с компилятором.
5. Как просмотреть сгенерированную рендер-функцию моего компонента?
Ответ:
Используйте @vue/compiler-dom
или @vue/compiler-sfc
для совмещения шаблона в JS-функцию:
js
import { compile } from '@vue/compiler-dom'
const { code } = compile('<div>{{ msg }}</div>')
console.log(code)
Это полезно для изучения процесса или для отладки.