19 октября 2024 года завершился Vue Fes Japan 2024 — традиционное событие, которое собрало множество энтузиастов и экспертов в области веб-разработки, где рассказывали о будущем экосистемы вью. На этой конференции разработчик Кевин Денг подробно представил новый этап в эволюции фреймворка Vue — Vapor Vue. Ожидается, что Vapor Vue значительно повысит скорость работы фреймворка, делая его еще более эффективным и мощным инструментом для создания современных веб-приложений. Такой шаг в развитии не только обещает ускорение производительности, но и открывает новые горизонты для гибкости и адаптивности Vue в работе над сложными проектами. В данной статье мы расскажем о самом интересном в этой презентации.

Что такое Vapor

Vue Vapor представляет собой новый механизм рендеринга, который имеет следующие ключевые особенности:

  1. Прямая работа с нативными API DOM.
    Технология больше не полагается на виртуальный DOM, вместо этого напрямую вызывает нативные API DOM. Это позволяет уменьшить количество издержек, повышает производительность и снижает потребление памяти, поскольку сокращается количество промежуточных шагов и уменьшается нагрузка на систему.

  2. Уменьшение размера итогового бандла. 
    Этот подход уменьшает размер итогового бандла. При прямом использовании нативного API DOM исключается необходимость в коде для обработки виртуальных узлов DOM, что приводит к более компактному и быстрому загружаемому приложению, облегчающему использование, особенно для пользователей с медленным интернетом.

  3. Fine-grained rendering.
    Теперь, основываясь на системе реактивности vue, можно точно отслеживать изменения данных и обновлять только то, что действительно необходимо. Это позволяет избежать повторного рендеринга всего компонента и поиска измененных узлов виртуального DOM.

Уменьшение бандла 

Ранее я упоминал о важности уменьшения размера бандла. В результате использования прямого взаимодействия с нативными API DOM размер бандла сократился на впечатляющие 53,3% по сравнению со стандартным режимом виртуального DOM. Эти данные не учитывают размер самой системы реактивности, но даже без этого уменьшение уже является значительным и положительно сказывается на производительности приложения.

Ускорение производительности 

Если же говорить про скорость работы, то за абсолютное значение можно взять ванильный js, который будет представлять собой 100%. Дальше идёт текущий рекордсмен — Solid, следом идёт многим полюбившийся Svelte. Теперь же Vue Vapor работает наравне со Svelte, что весьма впечатляет. Он заметно быстрее, чем режим Vue vDOM и React с Jotai. Несмотря на то, что Vapor уже превосходит Vue vDOM и React, его еще можно улучшить, и команда стремимся сделать его еще лучше.

Результат получен с помощью js-framework-benchmark, и протестировано на MacBook Pro M1 Max
Результат получен с помощью js-framework-benchmark, и протестировано на MacBook Pro M1 Max

Vapor не является отдельным режимом

Vapor — по своей сути, лишь подмножество vDOM, что приводит к некоторым ограничениям. 

Текущий режим виртуального DOM (vDOM) поддерживает множество функций, таких как Options API, Composition API, пользовательские директивы, миксины и другие.

Vapor поддерживает исключительно Composition API и не включает Options API. Однако в будущем Options API может быть доступен через стороннюю библиотеку на основе отзывов пользователей. 

Для работы с Vapor необходимо использовать инструменты сборки, такие как Babel, так как компилятор Vapor сложен и не позволяет запускать код напрямую в браузере через CDN, в отличие от vDOM. Запуск в браузере замедлил бы процесс и увеличил размер бандла, снижая эффективность по сравнению с vDOM.

Кроме того, Vapor поддерживает только <script setup>, что исключает экспорт компонентов через обычный <script>. Это решение основано на признании <script setup> лучшей практикой для использования Composition API. Исключение Options API позволяет компилятору более эффективно оптимизировать код.

Место Vapor во Vue
Место Vapor во Vue

Как работает Vapor

Давайте разберём принципы Vue Vapor поэтапно.

Для начала упрощённо представим создание простого приложения-счётчика с использованием нативных DOM API. Сначала создаём `div`, заголовок и кнопку, после чего добавляем их в тело страницы. Затем объявляем переменную `count`, которая будет хранить текущее значение счётчика. После этого добавляем обработчик события для кнопки, который при каждом нажатии увеличивает значение `count` на единицу. Этот обработчик также вызывает функцию `render`, которая отвечает за обновление отображения счётчика на странице.

// Initialization
const container = document.createElement('div')
const label = document.createElement('h1')
const button = document.createElement('button')
button.textContent = 'Increase'




button.addEventListener('click',increase)


let count = 0
const render = () => {
    label.textContent = `Count: ${count}`
}


function increase() {
    count++
    render() // Re-render
}
render() // Initial Reneder


document.body.append(container)
container.append(label, button)

Теперь, если использовать `@vue/reactivity`, мы можем сделать `count` реактивным. Достаточно обернуть `count` в `ref` и обновлять его через `count.value++` в функции увеличения. Ранее мы вызывали функцию рендеринга вручную, чтобы обновить страницу, но теперь можно использовать функцию `effect`, в которую внутрь поместим функцию рендеринга. Благодаря этому функция рендеринга автоматически срабатывает при изменении реактивного `count`.

import {effect, ref} from '@vue/reactivity'


// Initialization
const container = document.createElement('div')
const label = document.createElement('h1')
const button = document.createElement('button')
button.textContent = 'Increase'




button.addEventListener('click',increase)
let count = ref(0)


effect(()=>{
      label.textContent = `Count: ${count.value}`
})
function increase() {
    count.value++
}
document.body.append(container)
container.append(label, button)

Это простая демонстрация работы с реактивностью, но представьте, что есть и другие функции, например, уменьшение. При  использовании этих функций нужно было бы следить за вызовами функции рендеринга каждый раз, что могло бы привести к путанице. Также простое перерисовывание всего замедлило бы производительность.

Используя реактивность, мы можем больше не беспокоиться о том, где нужны обновления, или вручную вызывать функцию рендеринга. Это и есть основная идея Vue Vapor: с помощью `@vue/reactivity` отслеживать изменения данных и автоматически обновлять только те DOM-узлы, которым это действительно необходимо.

Вспомним на минутку React, а точнее, его концепцию рендеринга. Её можно представить формулой UI = fn(state), где state и ui представляют собой снапшоты в определенный момент времени. Когда состояние изменяется, создается новый снапшот UI, который сравнивается с нативными DOM-узлами.

В отличие от этого, в Vue в режиме виртуального DOM (vDOM) UI также остается снапшотом, но состояние является реактивным. При изменениях состояния Vue повторно запускает функцию рендеринга для создания нового снапшота UI, что похоже на поведение React.

Однако в Vue Vapor ни состояние, ни UI больше не являются снапшотами. Функция рендеринга выполняется только один раз, создавая один объект UI. Когда состояние меняется, мы не создаём новый DOM-объект, а работаем с уже существующим: напрямую меняем его атрибуты. Это более действенный метод.

Внутри снапшот неизменяем, но в Vapor объект UI изменяем внутри, хотя кажется неизменным снаружи. Это ключевое отличие между Vapor и моделью виртуального DOM.

Vapor SFC Compilation

Теперь, когда мы поняли основной принцип Vapor, рассмотрим пример компиляции однофайлового компонента (SFC) с использованием Vapor. SFC относится к файлам .vue и практически идентичен SFC в режиме vDOM.

Основное отличие состоит в атрибуте `vapor`, добавленном к тегу script, указывающем, что это компонент Vapor.

<script setup vapor>
import { ref } from 'vue'


const count = ref(0)


function increase() {
  count.value++
}
</script>


<template>
  <h1>Count: {{ count }}</h1>
  <button @click="increase">Increase</button>
</template>

Рассмотрим скомпилированный код SFC поэтапно:

import { ref } from 'vue'
import { delegate, delegateEvents, renderEffect, setText,
         template } from 'vue/vapor'


const t0 = template('<h1>')
const t1 = template('<button>Increase')
delegateEvents('click')


export default {
  setup() {
    const count = ref(0)
    function increase() {
      count.value++
    }


    const n0 = t0()
    const n1 = t1()
    delegate(n1, 'click', () => increase)
    renderEffect(() => setText(n0, 'Count: ',
                               count.value))
    return [n0, n1]
  },
}

Сначала шаблон компилируется в отдельные фрагменты, а обработка событий перенаправляется на корневой узел. Это уменьшает количество слушателей событий и улучшает производительность.

Затем мы определяем переменную `count` и функцию `increase` внутри `setup`.

Последние строки кода обрабатывают основную логику рендеринга. Мы связываем обработчик событий с узлом и используем `renderEffect` для отслеживания любых обновлений данных.

После этого при необходимости обновлений выполняется только код внутри `renderEffect`.

Структура сборки 

Структура Vapor отличается гибкостью благодаря следующему процессу:

1. Общая логика вызовов для однофайлового компонента (SFC): сначала используется верхнеуровневый плагин Vite, который вызывает компилятор SFC.

2. Компилятор SFC извлекает тег шаблона и передаёт его компилятору Vapor, который затем компилирует его в JavaScript код.

Внутренний процесс включает:

  • Фазу разбора: содержимое разбирается в абстрактное синтаксическое дерево (AST).

  • Фазу трансформации: AST преобразуется в промежуточное представление (IR).

  • Фазу генерации: IR преобразуется в JavaScript код, который является конечным результатом компиляции.

иллюстрация работы компиляции vapor
иллюстрация работы компиляции vapor

IR (Intermediate Representation) — это промежуточное представление, используемое Vapor. Оно выступает связующим звеном между шаблоном и финальным JavaScript кодом.

IR определяет ключевые действия, такие как создание фрагментов шаблона, привязка событий, вставка или добавление DOM-узлов, настройка атрибутов или свойств и другие.

Это означает, что независимо от используемого синтаксиса шаблона, если он может генерировать целевой IR в соответствии со стандартами Vapor, мы можем скомпилировать его в код для выполнения Vapor.

IR может быть адаптирован для различных синтаксисов шаблонов, таких как шаблоны Vue, Svelte, JSX и другие, что упрощает поддержку различных синтаксисов шаблонов.

Нужно ли переходить на Vapor сразу же полностью?

Ответ — не обязательно. Компоненты Vapor будут совместимы с режимом vDOM и наоборот. Конечно, будет поддержка и чистого режима Vapor.

Это позволяет осуществлять переход более плавно, без необходимости мгновенного перевода всего проекта на Vapor. Можно сначала мигрировать критически важные для производительности компоненты на Vapor, оставив остальные в режиме vDOM

Подводя итоги

  • Vapor — это новое начало для Vue. Было переписано почти всё с нуля.  

  • Vapor является подмножеством режима vDOM Vue, с акцентом на упрощение.  

  • Vapor на текущий момент сильно оптимизирует производительность и размер сборки.  


НЛО прилетело и оставило здесь промокод для читателей нашего блога:

-15% на заказ любого VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.

Комментарии (10)


  1. karrakoliko
    07.11.2024 09:33

    Технология больше не полагается на виртуальный DOM, вместо этого напрямую вызывает нативные API DOM. Это позволяет уменьшить количество издержек, повышает производительность и снижает потребление памяти, поскольку сокращается количество промежуточных шагов и уменьшается нагрузка на систему

    Но ведь несколько лет назад, когда завозили виртуальный DOM, рассказывали что именно эти проблемы он и призван решить?


    1. DDroll
      07.11.2024 09:33

      "Я соврал" - (с)Джон Матрикс, кф "Коммандо"


    1. nin-jin
      07.11.2024 09:33

      Вам постоянно врут. И даже сейчас наврали, утверждая, что ловля всех событий в одной точке эффективней, чем точечная ловля событий в разных точках. Фактически они на JS переизобретают то, что в браузерах уже реализовано на C++.


    1. gmtd
      07.11.2024 09:33

      Вряд ли когда-то про эти проблемы говорили
      VDOM решает другую проблему - более понятный и структурированный код, лучший DX, особенно на больших проектах


    1. kpbsod
      07.11.2024 09:33

      Главное же здесь — это продать. Тогда это было модно и продавалось. Сейчас в моде отказ от этого. В будущем, уверяю, что-то ещё придумают)


      1. nin-jin
        07.11.2024 09:33

        Я всё жду, когда же, наконец, придумают виртуализацию рендеринга.


  1. gmtd
    07.11.2024 09:33

    Непонятно, это перевод или авторская статья?
    Где ссылка на оригинал?


    1. YukinoKingu Автор
      07.11.2024 09:33

      Перевод, а если быть точнее адаптации презентации - https://talks.sxzz.moe/2024-10-vue-fes-japan/1.
      ссылка, как обычно, указана в шапке.


  1. AcckiyGerman
    07.11.2024 09:33

    Каждые пару лет добавлять и убирать программных сущностей по вкусу, раз в три года

    Было переписано почти всё с нуля.

    ведь просто работающий фреймворк это же скучно. А так люди заняты обслуживанием, обновлением, переписыванием. Но пока Vue отстаёт в переливании пустого в порожнее от Ангуляра, который давеча 18 версию разменял.


    1. gmtd
      07.11.2024 09:33

      А так люди заняты обслуживанием, обновлением, переписыванием.

      Ну, вообще-то, это его личный проект
      Отдельный от Vue