Мы снова написали самый быстрый JS-фреймворк UI

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

Шаг 1. Определить задачу.

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

Шаг 2. Выработать идеальное решение любой ценой.

Не совсем так, конечно. Просто найти простой и понятный способ, предполагающий, что все пойдет именно так, как вам надо.

Шаг 3. Отбросить все эти наработки и заново сформулировать изначальный вопрос.

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

Solid на сервере

SolidJS — это JS библиотека, наподобие React, Vue или Angular, которая спроектирована для эффективной отрисовки Web UI. Она обеспечивает все современные наборы возможностей, включая отличную поддержку TypeScript, Granular Reactivity, Async Concurrency и JSX/размеченных шаблонных литералов. Кроме того, SolidJS уже три года является наиболее производительной библиотекой отрисовки в браузере и занимает верхние строчки бенчмарков. 

Итак, каков же следующий рубеж Solid? На данный момент к одной из наиболее актуальных и очевидных тем можно отнести реализацию отрисовки на стороне сервера. Это уже весьма сложная задача. Сначала мне нужно найти самый быстрый серверный отрисовщик и разобраться, как он работает. После недолгих поисков я выяснил, что это MarkoJS. Он используется в продакшене компанией eBay, которая реализовала на сервере решение для JS, значительно обходящее по производительности любую другую библиотеку. 

У них также был один из немногих известных мне наборов тестов SSR (server side rendering) с интересным многообразием реализаций. Это означало, что стартовая точка найдена…

Тестирование на сервере

Итак, я клонировал репозиторий (исходный код здесь) и выполнил 2 простых теста: цветовой миксер и страницу результатов поиска по типу eCommerce. Вы уже могли видеть их ранее на сайте MarkoJS.

Производительность SSR для результатов поисков
Производительность SSR для цветового миксера

Я реализовал образцы, произвел сборку…и… результаты оказались печальны, даже очень. Я использовал среды DOM на стороне сервера (JSDOM, BasicHTML), и они не приблизились к показателям даже самых медленных библиотек, оказавшись примерно в 10 раз медленнее.

Я рассмотрел несколько подходов, среди которых был Heresy SSR Андреа Джаммарки, и понял, что хоть он и может сработать для нисходящей библиотеки шаблонов, но окажется абсолютно бессмысленным для реактивной. Причина была не только в имитированном DOM, но также в том, что мне нужно было разобраться с реактивным графом.

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

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

В конечном счете до меня дошло, что нужно опираться на преимущества Solid. Будучи наибыстрейшим клиентским отрисовщиком, он уже сократил разрыв в производительности при отрисовке на сервере. Я понял, что могу использовать те же ресурсы, что использовал для задержки (suspense) на клиентской стороне, чтобы потоком отправлять значения с сервера клиенту, который будет выполнять отрисовку после первой оболочки. 

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

А самое главное то, что исчезло ограничение, и я смог сосредоточиться на ускоренном синхронном рендеринге.

А затем…

Я написал новый компилятор и среду выполнения, которые нужным образом настроил. После этого повторил тесты и… достиг 3-го места! Достойно. Мне удалось срезать сотни лишних миллисекунд тут и там, но при этом я понимал, что все равно ограничен.

Дело в том, что Marko и Svelte — это языки, которые могут анализировать грамматику и не имеют лишних оболочек функций, которые нужно было бы выполнять. К тому же их шаблоны не являются JSX и в отличие от них принимают не любые значения, а только им нужные.

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

Как выяснилось, производительность SSR кроется не в изяществе решения, так как особого ума здесь не требуется. В браузере мы прибегаем к подобным ухищрениям, чтобы избежать операций DOM. На сервере же речь идет о том, насколько быстро вы сможете объединять строки. 

Это еще не конец…

Я уже смирился с тем, что не достигну реальной производительности, когда вдруг получил пул-реквест от одного из участников, который гласил: “Повышение производительности escapeHTML в 10 раз!”. В это было трудно поверить. При создании функции выхода я использовал комбинацию подходов Svelte и Marko, поэтому считал, что уже, итак, достиг неплохого результата.

Оказалось же, что она может быть еще быстрее. Я обновил библиотеку и после устранения узкого места функции выхода производительность Solid буквально взлетела.

Производительность SSR для результатов поиска
Производительность SSR для цветового миксера

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

А дальше Next?

Вообще-то нет. Я лично не собираюсь создавать библиотеку Next.js для Solid, но буду рад, если кто-то этим займется. Сегодня же я просто хочу показать результаты, которых добился благодаря содействию открытого сообщества. 

Я прекрасно понимаю, что Solid не останется лидером по скорости при работе на сервере. Об этом мне говорит личный опыт участия в команде MarkoJS (меня настолько впечатлила их работа, что я присоединился). Кроме того, тесты, подобные использованным здесь, не отражают должным образом сложность гидратации, но мне нравится по возможности поднимать планку.

Год назад после написания статьи о создании самых быстрых JS-фреймворков UI мной был запланирован ряд дополнений, которые на тот момент еще не были доработаны. Сегодня же в Solid есть поддержка потоковой задержки SSR с загрузкой данных и разделением кода, параллельная отрисовка и переходы (в бета), а также Realworld Demo. Помимо этого, в нем присутствуют инструменты CLI, REPL и сторонние библиотеки для i18n. Дополнение происходит постоянно.

Тем не менее нам как сообществу еще многое предстоит сделать. На этой неделе мы достигли 4,000 звезд на GitHub и 75,000 загрузок на NPM. Производительность и бенчмарки — это далеко не все показатели, но пока что-то в корне не изменится, я продолжу расширять границы.

Исходный код для бенчмарка, использованного в этой статье, находится в форке Isomorphic UI Benchmarks здесь:

ryansolid/isomorphic-ui-benchmarks
This repo includes multiple benchmarks for various UI libraries. Each benchmark is designed to measure rendering…github.com

ryansolid/solid
A declarative, efficient, and flexible JavaScript library for building user interfaces. — ryansolid/solidgithub.com

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Ryan Carniato: How we wrote the Fastest JavaScript UI Framework, Again!

Предыдущая статьяКак предварительно обработать данные и текстовые сообщения из социальных сетей
Следующая статьяАбстракции с нулевой стоимостью* в Kotlin