Процесс: Создание Vue 3

Автор оригинала: Evan You
  • Перевод


Уроки, полученные от написания следующей основной версии Vue.js


Автор: Эван Ю (Evan You)


В течение прошлого года команда Vue работала над следующей основной (major) версией Vue.js, которую мы надеемся выпустить в первой половине 2020 года (эта работа продолжается на момент написания данной статьи). Идея новой основной версии Vue сформировалась в конце 2018 года, когда кодовой базе Vue 2 было около двух с половиной лет. Это может показаться не таким уж долгим периодом в жизни программного обеспечения, но идеи фронт-энда сильно изменились за этот период.


Два ключевых соображения привели нас к написанию новой (и переписанию старой) основной версии Vue: во-первых, доступность новых функций языка JavaScript в распространенных браузерах. Во-вторых, проблемы дизайна и архитектуры в текущей кодовой базе, выявленные с течением времени.


Зачем переписывать


Использование новых языковых функций


Со стандартизацией ES2015, язык JavaScript — формально известный как ECMAScript, сокращенно ES — получил значительные улучшения, и основные браузеры наконец начали оказывать достойную поддержку этим новым его дополнениям. Некоторые из этих улучшений языка, в частности, предоставили нам возможности значительно улучшить возможности Vue.
Наиболее примечательным среди них является Proxy, который позволяет фреймворку перехватывать совершаемые над объектами операции. Основная особенность Vue — это возможность следить за изменениями, сделанными в определяемом пользователем объекте состояния (state), и оперативно обновлять DOM. Vue 2 реализует эту реактивность, заменяя свойства объекта состояния геттерами и сеттерами. Переход на Proxy позволит нам устранить существующие ограничения Vue, такие как невозможность обнаружения добавления новых свойств к объекту, и обеспечить более высокую производительность.


Однако Proxy — это нативная функция, которая не может быть полностью заполифилена в старых браузерах. Чтобы использовать ее нам придется изменить диапазон поддерживаемых браузеров — большое изменение, которое может быть добавлено только в новой основной версии фреймворка.


Решение архитектурных проблем


В ходе поддержки Vue 2 мы накопили ряд проблем, которые трудно решить из-за ограничений существующей архитектуры. Например, компилятор шаблонов (templates) был написан таким образом, что делает правильную поддержку sourcemap очень сложной. Кроме того, хотя Vue 2 технически позволяет создавать высокоуровневые средства рендеринга, предназначенные для платформ, отличных от DOM, нам пришлось продублировать кодовую базу и большую часть кода, чтобы сделать это возможным. Для исправления этих проблем в текущей кодовой базе потребуются огромные рискованные рефакторинги, которые почти эквивалентны переписыванию.


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


Начальная фаза прототипирования


Мы начали прототипирование Vue 3 в конце 2018 года с предварительной целью проверки возможности решения этих проблем. На этом этапе мы в основном сосредоточились на создании прочной основы для дальнейшего развития.


Переключение на Typescript


Vue 2 был первоначально написан на простом ES. Вскоре после этапа создания прототипа мы поняли, что система типизации будет очень полезна для проекта такого масштаба. Проверка типов значительно снижает вероятность появления непреднамеренных ошибок при рефакторинге и помогает участникам более уверенно вносить нетривиальные изменения. Мы использовали продукт Facebook Flow type checker, потому что он может быть постепенно добавлен к существующему проекту на ES. Flow помог в некоторой степени, но мы не получили от него столько пользы, сколько ожидали; в частности, постоянные серьезные изменения в коде делали развитие проекта сложным. Поддержка интегрированных сред разработки также не была такой хорошей по сравнению, например, с глубокой интеграцией TypeScript с Visual Studio Code.


Мы также заметили, что пользователи все чаще используют Vue и TypeScript вместе. Чтобы поддержать эти варианты использования, мы должны были создавать и поддерживать объявления TypeScript отдельно от исходного кода, который использовал другую систему типов. Переход на TypeScript позволил бы нам автоматически генерировать файлы объявлений, облегчая бремя обслуживания.


Разделение (decoupling) внутренних пакетов


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


Настройка процесса RFC


К концу 2018 года у нас был рабочий прототип с новой системой реактивности и виртуальным рендерингом DOM. Мы испытали внутренние архитектурные улучшения, которые мы хотели сделать, но имели только черновики общих изменений API. Пришло время превратить их в конкретные конструкции.


Мы знали, что должны были сделать это как можно раньше и тщательно. Широкое использование Vue означает, что серьезные изменения могут привести к огромным затратам на миграцию для пользователей и потенциальной фрагментации экосистемы. Чтобы пользователи могли предоставлять отзывы о критических изменениях, мы задействовали процесс RFC (Request for Comments) в начале 2019 года. Каждый RFC следует шаблону, разделы которого посвящены мотивации, деталям дизайна, компромиссам и принятию стратегии. Поскольку процесс проводится в репозитории GitHub с предложениями, представленными в виде пул реквестов, дискуссии разворачиваются в комментариях естественным образом.


Процесс RFC оказался чрезвычайно полезным, выступая в качестве основы для размышлений, которая заставляет нас полностью учитывать все аспекты потенциальных изменений, и позволяет нашему сообществу участвовать в процессе проектирования и отправлять хорошо продуманные feature requests.


Быстрее и меньше


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


Преодоление узкого места виртуального DOM


У Vue довольно уникальная стратегия рендеринга: он предоставляет синтаксис HTML- подобного шаблона, но компилирует шаблоны в функции рендеринга, которые возвращают виртуальные DOM-деревья. Фреймворк вычисляет, какие части фактического DOM обновлять, путем рекурсивного обхода двух виртуальных деревьев DOM и сравнения каждого свойства на каждом узле. Этот довольно грубый алгоритм, как правило, довольно быстр, благодаря продвинутым оптимизациям, выполняемым современными механизмами JavaScript, но обновления по-прежнему занимают много процессорного времени. Неэффективность особенно очевидна на шаблоне с, в основном, статическим содержимым и только несколькими динамическими привязками (bindings) — всё дерево DOM все еще должно быть рекурсивно пройдено, чтобы найти изменения.


К счастью, этап компиляции шаблона дает нам возможность выполнить статический анализ шаблона и извлечь информацию о динамических деталях. Vue 2 делает это в некоторой степени, пропуская статические поддеревья, но более сложную оптимизацию было сложно реализовать из-за чрезмерно упрощенной архитектуры компилятора. В Vue 3 мы переписали компилятор с правильным AST transform pipeline, который позволяет нам делать оптимизации во время компиляции в форме преобразующих (transform) плагинов.


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


Следующая полезная вещь состояла в том, чтобы избавиться от ненужных виртуальных обходов дерева DOM и сравнений свойств, что, как правило, дает наибольшую нагрузку при обновлении. Чтобы достичь этого, компилятор и среда выполнения (runtime) должны работать вместе: компилятор анализирует шаблон и генерирует код с советами по оптимизации, в то время как среда выполнения использует эти подсказки для выбора быстрых путей где возможно. Здесь работают три основные оптимизации:


Во-первых, на уровне дерева мы заметили, что структуры узлов остаются полностью статичными в отсутствие шаблонных директив, которые динамически изменяют структуру узлов (например, v-if и v-for). Если мы разделим шаблон (template) на вложенные «блоки», разделенные этими структурными директивами, структуры узлов внутри каждого блока снова становятся полностью статичными. Когда мы обновляем узлы в блоке, нам больше не нужно рекурсивно обходить дерево — динамические привязки внутри блока можно отслеживать в плоском массиве. Эта оптимизация позволяет избежать большей части накладных расходов виртуального DOM, уменьшив объем обхода дерева, который мы должны выполнить, на порядок.


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


В-третьих, на уровне DOM элементов компилятор также генерирует флаг оптимизации для каждого элемента с динамическими привязками на основе типа обновлений, которые он должен выполнить. Например, элемент с динамической привязкой класса и рядом статических атрибутов получит флаг, который указывает, что требуется только проверка класса. Среда выполнения соберет эти подсказки и выберет быстрые пути.


В совокупности эти методы значительно улучшили наши тесты ре-рендеринга: время исполнения Vue 3 иногда занимало менее одной десятой процессорного времени Vue 2.


Минимизация размера пакета


Размер фреймворка также влияет на его производительность. Это уникальная проблема для веб-приложений, поскольку ресурсы нужно загружать на лету, и приложение не будет интерактивным, пока браузер не проанализирует необходимый JavaScript код. Это особенно верно для одностраничных приложений. Хотя Vue всегда был относительно легким — размер среды выполнения Vue 2 составляет около 23 КБ в сжатом виде — мы заметили две проблемы:
Во-первых, не все используют все возможности фреймворка. Например, приложение, которое никогда не использует функции переходов (transitions), по-прежнему загружает и анализирует код, связанный с ними.


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


В идеале пользователь должен иметь возможность отбрасывать код для неиспользуемых функций фреймворка во время сборки — алгоритм, также известный как tree-shaking — и платить только за то, что используется. Это также позволило бы нам добавлять функции, которые часть наших пользователей сочла бы полезными, без добавления ненужного бремени для остальных.


В Vue 3 мы достигли этого, переместив большинство глобальных API и внутренних хелперов в экспортируемые ES модули. Это позволяет современным упаковщикам статически анализировать зависимости модуля и удалять код, связанный с неиспользованным экспортом. Компилятор шаблона также генерирует tree-shaking дружественный код, который импортирует хелперы для функции, только если эта функция фактически используется в шаблоне.


Некоторые части фреймворка никогда не могут быть "стрясены", потому что они необходимы для любого приложения. Мы называем меру этих обязательных частей базовым размером. Базовый размер Vue 3 составляет около 10 КБ в сжатом виде — это менее половины размера Vue 2, несмотря на добавление многочисленных новых функций.


Решение проблемы масштабирования


Мы также хотели улучшить способность Vue обрабатывать крупномасштабные приложения. Наш первоначальный дизайн Vue был ориентирован на низкий барьер для входа и плавное обучение. Но поскольку Vue стал более широко распространенным, мы узнали больше о потребностях проектов, которые содержат сотни модулей и поддерживаются десятками разработчиков в течении длительного времени. Для проектов такого типа критически важна система типов, такая как TypeScript, и возможность чистой организации многократно используемого кода, и возможности Vue 2 в этих областях была далеко не идеальной.


На ранних этапах планирования архитектуры Vue 3 мы пытались улучшить интеграцию TypeScript, предлагая встроенную поддержку для разработки компонентов с использованием классов (Class API). Трудность состояла в том, что многие из возможностей языка, которые мы хотели задействовать, чтобы сделать классы пригодными к использованию, например, атрибуты классов и декораторы, не были еще стандартизированы и могли изменяться, прежде чем официально стать частью JavaScript. Сложность и неопределенность заставили нас усомниться в том, действительно ли добавление Class API было оправдано, поскольку оно не предлагало ничего, кроме немного лучшей интеграции TypeScript.


Мы решили исследовать другие способы решения проблемы масштабирования. Вдохновленные React Hooks, мы подумали об использовании низкоуровневых API-интерфейсов реактивности и жизненного цикла компонентов, чтобы обеспечить более легковесный способ создания логики компонентов, называемый Composition API. Вместо определения компонента путем указания длинного списка параметров, Composition API позволяет пользователю также свободно создавать, составлять и повторно использовать логику компонентов с состоянием (statefull components), как при написании простой функции, обеспечивая при этом превосходную поддержку TypeScript.


Нам очень понравилась эта идея. Хотя Composition API был разработан для решения конкретной категории проблем, технически возможно использовать его только при разработке компонентов. При обсуждении Vue 3 мы стали рассуждать, что Composition API могло бы заменить Options API в будущем релизе. Это привело к массовому не очень положительному отклику со стороны членов сообщества, который послужил нам ценным уроком о необходимости четкого определения долгосрочных планов и намерений, а также понимания потребностей пользователей. Выслушав отзывы нашего сообщества, мы полностью переработали предложение, пояснив, что Composition API будет дополнять Options API. Это понравилось сообществу больше, и мы получили много конструктивных предложений.


В поисках баланса


Среди пользователей Vue, насчитывающих более миллиона разработчиков, есть новички с базовыми знаниями HTML/CSS, профессионалы, переходящие с jQuery, ветераны, переходящие с других фреймоворков, бэкенд-инженеры, ищущие фронтэнд решение, и архитекторы программного обеспечения. Разнообразие типов разработчиков соответствует разнообразию вариантов использования: некоторые разработчики могут захотеть добавить интерактивность в уже существующие приложения, в то время как другие могут работать над проектами с быстрым временем разработки, но не очень легкими в обслуживании; архитекторам, возможно, придется иметь дело с крупномасштабными, многолетними проектами и меняющейся командой разработчиков на протяжении всего жизненного цикла проекта.


Архитектура Vue постоянно формировалась учитывая эти потребности, поскольку мы стремимся найти баланс между различными компромиссами. Лозунг Vue «прогрессивный фреймворк», заключает в себе многоуровневую структуру API, которая является результатом этого процесса. Новички могут наслаждаться плавным обучением с использованием одного CDN скрипта, шаблонов на основе HTML и интуитивно понятного Options API, в то время как эксперты могут заниматься сложными проектами с полнофункциональным интерфейсом командной строки (CLI), функциями рендеринга и Composition API.


Нам еще предстоит проделать большую работу, чтобы реализовать наше видение — самое главное, обновить вспомогательные библиотеки, документацию и инструменты для обеспечения плавного перехода на новую версию. Мы будем усердно работать в ближайшие месяцы, и мы ждем — не дождемся посмотреть, что сообщество создаст с помощью Vue 3.


image

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 13

    +2
    Лично меня Vue 2 устраивает вполне, и перспектива переезда на Vue 3 мне совсем не нравилась, по соображениям экономии времени. А оставаться на Vue 2 конечно можно было бы, но отставать по версиям не хочется из стратегических соображений. Прочитав это перевод, а заодно и оригинал (перевод на Хабре всегда вызывает опасения), наконец-то стало ясно зачем Vue 3 вообще нужен и что даст. Первые статьи о третьей версии, вызвавшие большие споры, были не совсем убедительны. Сейчас все стало на места. Задумки у команды отличные.
    Надо отдать должное Иван — отличный популяризатор. Это было заметно по отличной документации к первым версиям. И это заметно по этой статье. Я часто натыкаюсь на его ответы на форумах, статьи и подкасты. Заметил, что он очень тактичный и внимательный человек. И организовал очень сильную команду. В общем кажется VueJS 3 будет хорош.

    P.S. У автора Эван. Иван звучит лучше ИМХО. Локализация.
      0
      Я бы даже сказал, что один из столбов, на котором держится Vue, это персональные качества (personality) Эвана
      0

      В прошлых выпусках дайджеста веба находил занятный материал описывающий пример использования нового Composition API:
      https://webdevblog.ru/upravlenie-sostoyaniem-state-s-pomoshhju-composition-api/

        0

        Получается что основной проблемой будет отсутствие поддержки старых браузеров.
        Факт неприятный сам по себе, но с течением времени времени будет становиться все менее актуальным.

          –1
          Прокси пока не лучшее решение для производительности bugzilla.mozilla.org/show_bug.cgi?id=1172313
            0
            5лет назад сиё было, сейчас они вровень с __proto__
              0
              Баг все еще открыт, так что вероятно все еще медленные. По поводу __proto__ интересно. В v8 делали оптимизации, но там все равно больше переходов из native в js по сравнению с __proto__, хотя по производительности вполне возможно сопоставимо.
              +2
              Это не актуально для фронта, вам нужны миллионы синхронных операций в цикле на каждое действие пользователя чтобы хоть как-то ощутить то, что Proxy медленнее, то бишь никто и никогда этого не почувствует. Только вот то, что позволяют делать Proxy, дает шикарные возможности для полноценной реактивности, что позволяет писать супер компактный, понятный код, а это куда важнее сферического кота в вакуме и того, что в бесконечных циклах Proxy будет медленнее getters.
                0
                Зависит от сферы деятельности. Для большинства — не актуально, а в каком-нибудь декларативном WebGL — очень даже. По поводу реактивности согласен — сам писал микрошаблонизаторы на прокси, когда это было слишком медленно для серьезных проектов. Что до компактности — тут мне кажется Svelte впереди планеты всей.
                  0
                  А такие вещи как WebGL, Canvas и не требуют реактивности, там совсем другая специфика и другие понятия жизненных циклов и рендеринга кадров. Эти штуки из другой оперы и им как бы по барабаны для всякие реакты, свелты, редаксы, мобиксы, прокси, геттеры/сеттеры и т.д и т.п.
                    0
                    Зависит от интерфейса, вот вам пример github.com/sveltejs/gl
                      0
                      Это уже балавство)
                        0
                        Это просто пример, вот более серьезный и зрелый проект aframe.io Не все что предоставляет декларативный интерфейс в итоге рендерится в DOM. Да и в любом случае тут либо писать про производительность и ее показывать по сравнению с другими либо не писать вообще.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое