Медлительность в прошлом: Инструмент сборки Vite.js занял прочное место в экосистеме Javascript и продолжает бурное развитие в сторону TypeScript.
Вряд ли какой-либо язык программирования вызывает столько полярных мнений, как JavaScript. Если для одних разработчиков - это тот язык, который они выбрали, то у других от одного только его звучания волосы встают дыбом. Но здесь на сцену выходит Vite - новый проект команды Эвана Ю, основателя JavaScript-фреймворка Vue, - доказывает, что на JavaScript можно разрабатывать очень быстро.
По словам команды разработчиков, Vite (от французского "vite" - быстрый) призван заявить о себе в экосистеме JavaScript прежде всего скоростью и удобством разработки. Являясь сервером разработки и инструментом сборки, Vite сопровождает JavaScript-разработчиков от создания проекта до его выпуска. До сих пор большинство разработчиков полагались на webpack - осознанно или с помощью таких инструментов, как Create React App или Vue CLI (Command Line Interface). Но чем больше масштаб проекта, тем более медлительным становится сервер разработки webpack. Разработчики всё дольше и дольше ждут, пока сделанные изменения отобразятся в браузере. Накопленное раздражение выплеснулось в идею создания Vite. Выход стабильной версии 2.0 в середине февраля 2021 года ещё больше укрепил позиции Vite. С релизом версии 3 авторы подошли с огромным списком проектов, реализованных на базе Vite.
Проблемы webpack
Webpack, по сути, является так называемым бандлером. Он собирает все файлы JavaScript, CSS и изображений вместе и объединяет их в значительно меньшее количество отдельных частей. Если, например, вспомогательные методы находятся в другом файле, а вызов API в другом, то в готовой сборке они могут оказаться в одном длинном JavaScript-файле. Преимуществом этого подхода является сокращение времени загрузки для конечного пользователя: вместо тысяч файлов браузер загружает всего несколько. Однако, время работы пакета зависит от количества файлов.
При сборке несколько секунд - ещё сравнительно терпимо. Это становится проблематичным при разработке на сервере разработки, поскольку здесь также используется пакетирование webpack. Если разработчики меняют один файл, то webpack вынужден заново генерировать целые пакеты из десятков файлов. Поэтому так называемая горячая замена модулей, т.е. немедленная замена измененных компонентов в браузере без перезагрузки, быстро теряет свою остроту, когда время ожидания переваливает за десять секунд. Даже для запуска пустого React-приложения Vite требуется всего 430 миллисекунд, в то время как Create React App или webpack - более 12 секунд.
По меркам JavaScript, webpack - настоящий динозавр. С момента своего первого выпуска в 2012 г. он стал более зрелым и не имеет себе равных, хорошо справляясь с самыми крайними случаями. Например, так называемый "глубокий импорт" часто является причиной ошибок, возникающих в репозитории GitHub, в то время как webpack редко испытывает проблемы в таких сценариях. Но и размер конфигурации webpack демонстрирует гибкость и в то же время сложность, поэтому зачастую около 90% сценариев использования теряют в скорости и удобстве.
Всё указывает на скорость
По сути, Vite призван делать то же самое, что и webpack (а именно, разрабатывать JavaScript-проекты с продуктивной сборкой). Однако, он придерживается другой философии. В отличие от многих конечных пользователей, разработчики используют современные браузеры, и входящий в состав Vite сервер разработки использует это преимущество. Таким образом, модули ECMAScript могут быть напрямую интегрированы через тег <script type="module">, и любой современный браузер с поддержкой динамического импорта может их загрузить, интерпретировать и выполнить. Браузер преобразует каждый импорт в исходном коде JavaScript в HTTP-запрос, который затем попадает непосредственно на сервер разработки. С помощью флага --debug Vite выводит точный лог, в котором, помимо прочего, отображается разрешение (vite:resolve) HTTP-запросов:
vite:resolve 0ms react -> C:/dev/vite-example/node_modules/.vite/react.js?v=f9c662d4&es-interop +800ms
vite:resolve 0ms react-dom -> C:/dev/vite-example/node_modules/.vite/react-dom.js?v=f9c662d4&es-interop +9ms
vite:resolve 1ms ./index.css -> C:/dev/vite-example/src/index.css +3ms
vite:resolve 1ms ./App -> C:/dev/vite-example/src/App.jsx +2ms
vite:resolve 0ms /src/App.jsx -> C:/dev/vite-example/src/App.jsx +1ms
vite:load 7ms [fs] /node_modules/.vite/react.js?v=f9c662d4 +782ms
vite:resolve 0ms ./logo.svg -> C:/dev/vite-example/src/logo.svg +117ms
vite:resolve 0ms ./App.css -> C:/dev/vite-example/src/App.css +1ms
Если браузер запрашивает каждый файл по отдельности, то это приводит к снижению скорости даже при увеличении количества файлов. Чтобы избежать этого, Vite имеет возможность определять независимые эндпоинты и таким образом практиковать разделение кода. Например, если JavaScript-модуль появляется на подстранице, то он также требуется только при входе на эту подстраницу - только при необходимости Vite обрабатывает модуль и отправляет его браузеру. Это экономит время, прежде всего, потому, что модули разбираются и компилируются Vite по требованию, а не собираются заранее, как в webpack:
Наименование |
Путь |
Протокол |
Размер |
Время |
localhost |
/ |
http/1.1 |
145 B |
12 ms |
client |
/@vite/client |
http/1.1 |
17.2 kB |
11 ms |
main.jsx |
/src/main.jsx |
http/1.1 |
1.6 kB |
7 ms |
react.js?v=2a1b6b4a |
/node_modules/.vite/react.js |
http/1.1 |
850 B |
9 ms |
react-dom.js?v=2a1b6b4a |
/node_modules/.vite/react-dom.js |
http/1.1 |
2.4 MB |
105 ms |
index.css |
/src/index.css |
http/1.1 |
1.1 kB |
63 ms |
App.jsx |
/src/App.jsx |
http/1.1 |
6.5 kB |
70 ms |
localhost |
/ |
http/1.1 |
0 B |
3 ms |
Другой проблемой является сценарий водопада: с каждым запрошенным файлом, в худшем случае, добавляется множество дополнительных модулей, которые браузеру приходится запрашивать. Даже если он отвечает на эти запросы практически мгновенно, общие накладные расходы на HTTP приводят, в частности, к потере времени (см. список выше). Кроме того, на стороне браузера ограничено количество выполняемых запросов. Умная оптимизация, такая как упомянутое ранее разделение кода, позволяет избежать этого, используя кэширование браузера и объединяя зависимости в один файл при запуске сервера. Таким образом, браузер выполняет только один запрос на одну зависимость.
Интеллектуальное связывание зависимостей
В мире JavaScript модули ECMAScript - не единственный способ подключения файлов. В качестве альтернативы существуют такие методы, как синхронный CommonJS (через require(...) и module.exports), использование которого ограничено бэкендом JavaScript, или так называемое Universal Module Definition, которое призвано унифицировать другие методы и работает как в браузере, так и в бэкенде. Однако благодаря стандартизации, проведенной комитетом ECMA, модули ECMAScript считаются перспективными. Тем не менее, чтобы обеспечить совместимость зависимостей любого рода в проекте Vite, разработчики JavaScript должны адаптировать и оптимизировать их. В целях экономии времени Vite выполняет корректировку только для измененного файла package.json или lock.
Таким образом, оптимизируются не только все зависимости, не экспортирующие модули ECMAScript, но и те, точки входа которых имеют операторы импорта в более глубокие подмодули или другие зависимости. Например, в процессе оптимизации Vite все JavaScript-файлы Lodash - коллекции утилит с множеством функций, каждая из которых находится в своем подмодуле, - объединяются в один файл для разработки. Во время сборки происходит соответствующее разделение кода: В компиляцию попадают только те подмодули Lodash, которые действительно используются.
Легкие оптимизации файлов
Сервер разработки Vite не предоставляет файлы проекта в том виде, в котором они находятся на жестком диске при запросе. Это связано с тем, что необходимо не только обработать некоторые граничные случаи, которые пока не поддерживаются модулями ECMAScript, но и создать возможность для модификации файлов плагинами. Одним из примеров такого плагина является поддержка TypeScript: если браузер запрашивает файл .ts или .tsx, то он компилируется в JavaScript и возвращается. Стоит отметить, что Vite использует esbuild для трансляции TypeScript в JavaScript. Являясь одним из самых быстрых JavaScript-компоновщиков и транспиляторов, этот инструмент способен компилировать TypeScript в двадцать-тридцать раз быстрее, чем официальный компилятор tsc, но за счет некоторых более экзотических особенностей языка (см. рисунок к посту). Они иногда неправильно интерпретируются при сборке и приводят к ошибкам. Однако, в Esbuild описано, как сконфигурировать файл tsconfig, чтобы избежать этого. Кроме того, Vite предоставляет такую настройку непосредственно в своих шаблонах TypeScript.
Кроме того, зависимости в инструкциях импорта не поддерживаются браузерами. Это связано с тем, что после импорта не указывается ни абсолютный, ни относительный путь к файлу, а только имя пакета. Здесь также происходит оптимизация, чтобы путь был читаемым для браузера:
import { helloWorld } from '/node_modules/dependency-a/dist/dependency-a.js?v=1.2.1'
Кроме того, Vite уже предлагает множество других возможностей, таких как поддержка JSX (JavaScript XML), интеграция многочисленных CSS-препроцессоров, таких как SASS или PostCSS, и возможность использования модулей WebAssembly. Помимо официальных расширений для Vue или React, постоянно растущее количество плагинов от сообщества предоставляет готовые дополнительные функции.
Даже сайты, построенные на традиционных бэкендах, таких как Laravel, Ruby on Rails или Django, часто используют динамический контент с помощью JavaScript-компонентов в дополнение к рендерингу страниц на стороне сервера. Здесь Vite также может улучшить процесс разработки.
В отдельной главе документации рассказывается о том, как с помощью нескольких шагов по настройке Vite можно использовать в качестве сервера разработки для фронтенда. В основном эти шаги можно свести к настройке точки входа и правильной интеграции Vite и точки входа JavaScript в HTML-шаблон:
@if (__DEV__)
<!-- Development -->
<script type="module" src="http://localhost:5173/@vite/client"></script>
<script type="module" src="http://localhost:5173/index.js"></script>
@else
<!-- Produktion -->
<script type="module" src="dist/{{ $manifest['index.js']['file'] }}"></script>
<link href="dist/{{ $manifest['index.css']['file'] }}" rel="stylesheet" />
@endif
При разработке все JavaScript- и CSS-ссылки указывают на работающий сервер Vite (например, localhost:5173), в то время как в production среде они указывают на статические файлы в каталоге сервера (например, ./public/assets).
Функция HMR
В мире JavaScript функция Hot Module Replacement (HMR) стала одной из наиболее ценных. Как только изменение сохраняется, оно становится видимым в браузере в течение нескольких секунд - в лучшем случае. Пакетирование с помощью webpack гарантирует, что при большом количестве файлов "сразу" часто превращается в несколько секунд. Vite показывает, что это возможно и другим способом. Здесь производительность HMR не зависит от размера проекта.
При оптимизации и обработке файлов Vite создает стройный граф зависимостей. Таким образом, сервер разработки точно знает, какой файл в свою очередь использует другие. При изменении какого-либо файла Vite обходит свой граф, начиная с этого файла и заканчивая точкой входа. При этом проверяется, есть ли в метаданных import.meta файлов атрибут hot и реализуют ли они там собственный HMR-интерфейс Vite. В этом случае он уведомляет пользователя об изменении файла, что приводит к самостоятельному выполнению кода интерфейса и обработке изменения файла.
Если в процессе обхода Vite достигает точки входа и не находит такого интерфейса, то он просто перезагружает всю страницу в браузере. Однако это редкий случай, поскольку интерфейс import.meta.hot не является новым изобретением. Напротив, в webpack используется эквивалентное решение с помощью module.hot, с той лишь разницей, что разработчики webpack не стали опираться на import.meta, характерный для модулей ECMAScript. Таким образом, поддержка HMR для Vite уже присутствует во многих фреймворках.
Разработка и сборка с конфигурацией Rollup
Для создания продуктивных сборок используется Rollup - модульная альтернатива webpack, которая, по мнению Evan You, более перспективна и поэтому лучше подходит в качестве основы для Vite, чем webpack. Vite не только поддерживает множество существующих плагинов Rollup, но и может предложить более глубокую интеграцию с ними, выбрав конкретный инструмент сборки. Предопределенная конфигурация Vite означает, что большинство случаев использования уже решены без дополнительных настроек. Те же, кому требуется более сложный процесс сборки, могут изменить все конфигурации в соответствии с собственными требованиями и потребностями. Это объясняется тем, что в широкой экосистеме JavaScript часто теряется много времени на настройку проекта. Всем бы хотелось писать код, а не заниматься настройкой окружения.
Технически похожим инструментом является Snowpack - сервер разработки с функцией сборки, также основанный на модулях ECMAScript. Несмотря на всё сходство, есть и принципиальные различия. Из-за свободы выбора в Snowpack и концепции максимально узкой стандартной конфигурации, разработчикам приходится, например, специально настраивать пакетирование при продуктивной сборке. Для большинства пользователей это важно. Кроме того, Vite Rollup более глубоко интегрируется в качестве инструмента фиксированной сборки и предлагает таким образом больше возможностей.
Разнообразные возможности сборки
Процесс сборки - это не просто интеграция с Rollup. Vite включает в себя ряд других функций, которые делают сборку ещё более удобной. Например, CSS-код разбивается на куски - по аналогии с JavaScript-кодом, что позволяет автоматически разделять код. Вместе с генерируемыми Vite ссылками <rel="modulepreload" />
это заметно повышает скорость работы готового веб-приложения. Кроме того, процесс сборки оптимизирует цепочки зависимостей асинхронно загружаемого JavaScript-кода и тем самым предотвращает каскадные сетевые запросы.
Кроме того, Vite автоматически включает полифилл для динамического импорта модулей ECMAScript, чтобы обеспечить совместимость со старыми браузерами. Однако другие полифиллы не используются, и пользователи должны самостоятельно встраивать их по мере необходимости. Помимо создания обычных веб-страниц, в сборке реализована поддержка веб-страниц с несколькими точками входа и библиотеками. Также доступны стандартные функции, такие как переменные окружения.
Преждевременный выпуск Vite 1
В декабре 2020 года You опубликовал в репозитории Vite заявление о том, что он считает ошибками в разработке Vite 1 и каковы его планы на будущее Vite. Например, архитектура плагинов была гораздо менее модульной, чем планировалось, некоторые особенности Vue были фиксированными (вместо модульных), а многим интерфейсам не хватало избирательности. Он предложил несколько кардинальных изменений, но они вступали в противоречие с экосистемой, которая уже сформировалась вокруг первой версии Vite. По этой причине он обозначил состояние как "1.0 Release Candidate", прекратил работу над Vite 1 и продолжил вместе со своей командой работу над Vite 2. В Twitter You тем временем объявил, что Vite 1 устарела и разработчикам следует переходить на версию 2.
В настоящее время, судя по всему, в рамках разработки версии 2.x продвигаются те изменения, о которых он просил. API плагинов был переработан командой Vite, что позволило убрать или передать на аутсорсинг некоторые специфические функции. Конфигурация теперь более чётко разделена на серверы сборки и серверы разработки, производительность, как утверждается, повысилась, и было исправлено несколько незначительных проблем. Отзывы пользователей, полученные после выпуска бета-версии Vite 2 в январе 2021 года, очевидно, укрепили его видение будущего Vite.js. Теперь, когда доступна финальная версия, она будет интересна и тем, кто избегает бета-версий.
Значение для сообщества JavaScript
Изменения, внесенные в Vite 2, могут сделать этот инструмент настоящим приобретением для мира JavaScript. Уже сейчас, спустя несколько недель после выхода первой бета-версии, в сообществе регулярно появляются новые плагины и проекты. Помимо интеграции Vite в Ruby-on-Rails-проекты, есть и менее экзотические предложения в виде различных стартовых шаблонов, примеров проектов или небольших полезных плагинов, таких как автоматическое сжатие изображений. Этот рост свидетельствует о настроении сообщества, которое также позитивно реагирует в социальных сетях. Стремясь к независимости от фреймворков, разработчики React и Svelte также осваивают Vite. Об этом свидетельствуют не только некоторые проблемы в репозитории GitHub, но и волна учебников и руководств, которые сейчас заполонили Интернет (например, на платформе онлайн-публикаций Medium).
Vite 3 ещё больше ускоряет разработку
В версии 3 прирост скорости стал ещё более заметным и, похоже, способствует становлению инструмента. Очевидной тенденцией в мире JavaScript является скорость. Это заметно не только в Vite, но и в других проектах, например, в альтернативном, значительно более быстром компиляторе Bun, который уже привлёк к себе живое внимание в социальных сетях.
Следующая цель на подходе
Следующим этапом развития Vite является окончательная поддержка рендеринга на стороне сервера. После первоначального ажиотажа вокруг таких фронтенд-фреймворков, как React или Vue, растущая популярность серверного рендеринга является шагом к улучшению производительности JavaScript-сайтов. Клиентские фреймворки, в частности, создают большую нагрузку на время первоначальной загрузки, что, помимо прочего, приводит к ухудшению ранжирования в поисковых системах.
В конце января была опубликована Beta 50 - первая официальная реализация этой функции. Несмотря на то, что в сообществе уже есть примеры серверного рендеринга с использованием Vite, You стремится создать комплексное решение, которое должно оставаться верным своей основной задаче - скорости - за счёт умелого использования существующей архитектуры и функций. Текущая реализация пока носит экспериментальный характер, поэтому официальная документация просит проявлять осторожность и намекает на возможные изменения. Однако уже существуют полнофункциональные примеры проектов на базе Vue 3 или React.
Нетривиальность темы делает её частым вызовом, поэтому Vite имеет потенциал стать здесь настоящим спасением. Однако весь набор функций направлен не только на удобство и упрощение, но и позволяет создавать приложения, которые были бы невозможны в рамках webpack. Например, на примере VitePress команда впечатляюще демонстрирует, как можно использовать мощность Vite на практике, и предлагает идеи возможных вариантов применения этого инструмента.
VuePress и VitePress
В 2018 году команда Vue выпустила первую стабильную версию VuePress. Этот инструмент представляет собой комбинацию генератора статических страниц (SSG) и одностраничного приложения (SPA). На практике это означает, что в процессе сборки все заполненные контентом страницы преобразуются в статические HTML-файлы, которые затем при первом обращении к браузеру загружают JavaScript-логику и превращают веб-страницу в обычное одностраничное приложение. Судя по названию, это приложение основано на Vue.js 2, поэтому инструмент особенно популярен в сообществе Vue для блогов или технической документации. Основными преимуществами такой двухстраничной архитектуры являются лучшая оптимизация для поисковых систем и быстрое время первоначальной загрузки, аналогичное рендерингу на стороне сервера. Использование SPA позволяет получить дополнительные преимущества, а именно отзывчивую навигацию и возможность использования динамических компонентов.
Однако, поскольку VuePress также основан на webpack, ему также присущи вышеупомянутые проблемы, такие как медленная горячая замена модулей и неповоротливый сервер разработки. Поэтому команде Vue показалось очевидным создать на базе VitePress сестру VuePress. Благодаря параллельному развитию обоих проектов, проблемы или недостающие возможности, проявившиеся в VitePress, могут быть без бюрократических проволочек закреплены в Vite, чтобы обеспечить дополнительную ценность для множества других проектов.
Сумма принципиальных различий между Vue и VitePress невелика: вторая версия Vue становится третьей, Vite заменяет подструктуру webpack, больше внимания уделяется производительности. Это позволяет добиться дополнительных улучшений, например, отказаться от так называемой "двойной полезной нагрузки".
Двойная полезная нагрузка
Проблема статически сгенерированных страниц с функциональностью одностраничного приложения заключается в том, что содержимое, которое пользователь уже загрузил в статический HTML-файл, часто перезагружается JavaScript. Это означает, что при передаче данных между сервером и пользователем накапливается значительно больший объем информации, что особенно актуально в случае плохого сетевого соединения. Чтобы избежать такой двойной нагрузки, VitePress использует одну из особенностей Vue 3: статические элементы могут размещаться в компиляторе Vue 3. Это означает, что такие элементы из функции рендеринга заносятся в переменные и, таким образом, создаются только один раз - независимо от того, как часто будет рендериться страница или компонент.
VitePress распознает размещенные элементы при компиляции и удаляет их. При инициализации SPA-функции во фронтенде она затрагивает только те элементы, которые непосредственно затронуты изменением. Все статическое содержимое, которое не может быть изменено динамически, остается неизменным. Таким образом, VitePress решает проблему двойной полезной нагрузки и, как правило, достигает размеров файлов в несколько килобайт - время загрузки при этом очень мало. Благодаря тому, что VitePress уделяет внимание, казалось бы, незначительным проблемам, он может получить существенный импульс в целом и выделиться на фоне конкурентов.
Заключение
Инструменты разработки занимают всё больше места на сцене открытого кода, и не без оснований: те, кто регулярно использует JavaScript, часто замечают, насколько облегчилась их работа, только перейдя на более быстрый инструмент. Этот опыт значительно повышает популярность подобных проектов.
Потенциал Vite.js становится особенно очевидным благодаря таким проектам, как VitePress, и может привести к дальнейшему развитию сообщества JavaScript в ближайшем будущем - подобно тому, как это уже сделали другие замечательные технологии, такие как React, Electron или TypeScript. Универсальность делает этот инструмент привлекательным для значительной части JavaScript-разработчиков. С выходом третьего крупного релиза, инструмент уже прочно занял своё место в экосистеме Javascript. Сообщество вокруг Vite процветает и в ближайшем будущем может привести к появлению ещё большего числа новых разработок в JavaScript. Vite.js также является частью длинной традиции инструментов, которые показывают, что Typescript - это будущее Javascript. Всё больше современных фреймворков и библиотек по умолчанию пишутся на Typescript. Даже при полном переписывании существующих Javascript-проектов, например Vue.js 3 на Typescript, похоже, является первым выбором для значителной части разработчиков.