> Уверен, если бы вы хотели разобраться, вы легко смогли бы сделать это
Я специально указал на какие функции смотреть, а не на весь сгенерированый код.
> Можно также рассмотреть реальные кейсы, например, вот сравнение реализаций на разных фреймворках одного и того же проекта RealWorld — блогосоциальная сеть, как Medium или Хабр:
Совершенно бессмысленный бэнчмарк если смотреть на цифры и не понимать ключевые отличия в реализациях, например какие реализации используют code splitting, какие либы используются для client-server взаимодействия итд.
> По моему опыту, когда я заканчиваю писать приложение на Svelte, оно все еще меньше или приближается к только лишь исходному коду любого из фреймворков «большоей тройки».
Это если сравнивать с фреймворками «большой тройки». А если посмотреть на либы которые так же замарачиваются на размере, то есть всякие preact или hyperapp, которые не будут генерировать 3 различных пути исполнения для создания/обновления/удаления дом нодов.
Открываем пример на svelte https://svelte.technology/repl?version=2.8.1&demo=each-blocks, тыкаем снизу на кнопку "input" и смотрим на функции: create_main_fragment() и create_each_block(). И сразу становится очевидным что как только приложение будет более менее серьёзным, то кол-во кода которое генерирует "Магически исчезающий JS фреймворк" будет превышать размер фрэймворка, который был оптимизирован под размер генерируемого кода.
Очень любопытно было бы взглянуть на пример и в каком браузере это проявлялось. Чтоб вставка нодов в свг триггерила репэйнт в любом браузере — как-то не особо верится.
Единственное отличие во вставке будет только в случае если где-то сверху висит MutationObserver[1], тогда на каждом вызове `insertBefore()` будет вызываться `dispatchSubtreeModifiedEvent()`[2] и генерировать эвент. Но даже в таком случае разница в производительности будет практически незаметна.
Скорее всего у вас проблемы с производительности были из-за чего-то другого.
Даже не знаю точной даты, но точно ещё до того как хром форкнул вебкит.
Кстати, ещё интересный исторический факт — это то что в старых иешках и даже в старых версиях Edge'а, вставка элементов до того как у них отрендерены чилдрены была быстрее чем рендер вне документа и вставка.
Во всех браузерах такое поведение уже очень давно. Единственная ситуация в которой с фрагментом должно быть быстрее — это бэнчмарки с плоскими листами на 10к элементов запущеные в браузере с экстеншенами типа адблока.
Использование фрагментов добавляет лишь дополнительный оверхэд от того что приходится создавать этот фрагмент, вставлять в него и потом перебрасывать из него в нужный элемент. Никакого рефлоу не произойдёт от вставки элементов в дом узлы, которые находятся в документе, рефлоу будет отложен до того момента когда кто-нибудь не начнёт читать свойства, которые зависят от вычислений производимых во время рефлоу.
Возможно ускорит устранение отдельного lis прохода, но так же возможно что и наоборот, нужно как минимум тестировать на синтетических бэнчмарках. Не пробовал.
> Кстати в отличии от inferno, ivi и прочих этот алгоритм можно еще сильнее оптимизировать если не создавать новые временные массивы а переиспользовать один глобальный каждый раз при diff-е и уменьшить количество проходов — на первом проходе формируем тот временный массив а на обратном сразу можно предпринять перестановку элементов — не дожидаясь формирования конечного lis — то есть считываем свойство «prev» — если элемент равен текущему элементу в новом массиве то его не трогаем.
В 99% случаев в реальном приложении при апдэйте списков не придётся никого переставлять, поэтому усложнение предпроходов может сказаться на производительности более частых случаев. Переиспользование массива точно не даст никакого прироста.
> В Реакте для этого есть костыль с указанием key для элементов полученным из массивов
Все остальные костыли хуже.
> key не поможет при переносе элемента из одного массива в другой, из одного контейнера в другой и тд
При переносе дом узлов всё равно потеряется внутреннее состояние, которое невозможно никак иначе контроллировать. А там где с этим нет проблем flutter.io, там можно задавать глобальные ключи для переноса между контейнерами.
Это было в контексте «создавать свой язык шаблонизации и писать для него компилятор не входило в мои планы, когда я писал эту статью. Так что мы будем парсить голый HTML руками.»
В дискуссиях на тему vdom vs шаблонизаторы, под шаблонизаторами обычно подразумевают язык с набором ограничений, созданный специально для задачи описания пользовательских интерфейсов. youtu.be/mVVNJKv9esE?t=1217
> В вашем случае — да. В моей задаче (фреймворк для датагридов) — увы, нет. :(
Так вы же пытаетесь описать vdom в этой статье и парсер HTML ну совсем не имеет никакого отношения к vdom и лишь наоборот только вводит в заблуждение тех кто ещё не до конца понимает в чём заключается одна из его ключевых особенностей.
Когда термин «виртуальный дом» стал массово использоваться, были даже дискуссии среди вдом авторов на тему того что сам по себе термин совершенно не в тему, и предлагались идеи вроде «Value DOM».
> Зависит от того, что у вас на входе. Если больше чем сырой HTML вы получить не можете (мой случай) — придется парсить.
Для vdom'а на входе примитивная деревяшка жаваскрипт объектов, которая не требует каких-то особых шаблонизаторов или парсеров.
> Если вы имеете в виду immutable-структуры, то согласен. В противном случае не очень понимаю о чем речь.
Имею в виду что DOM объекты трогают лишь в случае если нужно производить какие-то изменения, для сравнения используют только виртуальный дом.
> Очень странно. LCS — первое, что пришло лично мне в голову.
Да, мне тоже когда-то давно это первое что пришло в голову, и моя первая реализация использовала LCS, но потом началась жёсткая борьба в попытках получить лучшие цифры в бэнчмарках и пришлось искать как можно ускорить всё это дело под большинство реальных кейсов.
> Таким образом innerHTML — как быстрый и грязный хак. Ненадежен в общем случае, имеет кучу сайд-эффектов, да и вообще — мы тут не в 9 классе школы чтобы так писать.
Самое ужасное последствие от использования innerHTML — это то что внутреннее состояние DOM элементов будет потеряно (цсс анимации, видео, аудио, позиция курсора в полях ввода, позиции скролла и много других состояний).
> Парсер HTML… Но упомянуть его определенно стоит, во многом потому что он нам понадобится для реализации VDOM.
Совершенно ненужная вещь, одна из основных фич vdom'а — это то что все узлы виртуального дома являются обычными жаваскрипт значениями и с ними можно работать так как душе угодно, никак не ограничивая себя примитивным шаблонизатором.
> Как я понимаю, React.js работает без парсера HTML в виду того, что его шаблоны (jsx/tsx) уже собираются в соответствующие вызовы создания нод.
Это не шаблоны, это обычный жаваскрипт и синтаксический сахар вокруг вызова `createElement`.
> По сути дела надо взять parent-нод, внутри которого отрисован наш HTML, взять массив всех его детей и сравнить его с аналогичным массивом виртуальных детей, который нам создал HTML-парсер.
Так работают медленные vdom реализации, все быстрые реализации производят все сравнения на vdom структурах.
> LCS — и есть самое сложное в VDOM-е, из-за чего многим он кажется страшной магией.
И подавляющее большинство vdom библиотек не замарачиваются и используют примитивный линейный алгоритм, из-за чего в том же реакте простая перестановка элемента может привести к N-1 `insertBefore` операций.
Да там ещё год назад пара людей пыталась им всё объяснять. На тему потери внутреннего состояния были очень долгие обсуждения (вроде автор snabbdom'а или какой-то другой либы безуспешно пытался это объяснять). Бывают просто разработчики, которым бесполезно что-то объяснять. Пару месяцев назад там этот чел, который пишет большую часть кода сейчас, опять продолжил говорить что ключи в реакте — это глупость :) Общаться с ними больше ни у кого просто нет никакого желания, они отлично всё понимают и у них всё замечательно. Лично я никому не рекомендую прикасаться к этому говну.
Я специально указал на какие функции смотреть, а не на весь сгенерированый код.
> Можно также рассмотреть реальные кейсы, например, вот сравнение реализаций на разных фреймворках одного и того же проекта RealWorld — блогосоциальная сеть, как Medium или Хабр:
Совершенно бессмысленный бэнчмарк если смотреть на цифры и не понимать ключевые отличия в реализациях, например какие реализации используют code splitting, какие либы используются для client-server взаимодействия итд.
> По моему опыту, когда я заканчиваю писать приложение на Svelte, оно все еще меньше или приближается к только лишь исходному коду любого из фреймворков «большоей тройки».
Это если сравнивать с фреймворками «большой тройки». А если посмотреть на либы которые так же замарачиваются на размере, то есть всякие preact или hyperapp, которые не будут генерировать 3 различных пути исполнения для создания/обновления/удаления дом нодов.
Открываем пример на svelte https://svelte.technology/repl?version=2.8.1&demo=each-blocks, тыкаем снизу на кнопку "input" и смотрим на функции:
create_main_fragment()иcreate_each_block(). И сразу становится очевидным что как только приложение будет более менее серьёзным, то кол-во кода которое генерирует "Магически исчезающий JS фреймворк" будет превышать размер фрэймворка, который был оптимизирован под размер генерируемого кода.Скорее всего у вас проблемы с производительности были из-за чего-то другого.
1. chromium.googlesource.com/chromium/blink/+/master/Source/core/dom/Node.cpp#1989
2. chromium.googlesource.com/chromium/blink/+/master/Source/core/dom/ContainerNode.cpp#242
Кстати, ещё интересный исторический факт — это то что в старых иешках и даже в старых версиях Edge'а, вставка элементов до того как у них отрендерены чилдрены была быстрее чем рендер вне документа и вставка.
Сначало случайно обнаружили это в эпоху когда пытались создать наиболее производительный vdom алгоритм github.com/localvoid/vdom-benchmark/issues/15#issuecomment-71692148, потом реактовцы добавили эту оптимизиацию github.com/sophiebits/innerhtml-vs-createelement-vs-clonenode, а сейчас наверное уже все повыкидывали этот хак :)
Можно вместо того чтобы рендерить в дом элемент, рендерить виртуальные ноды во фрагмент, а потом вставлять в дом элемент.
А ещё можно открыть исходники хрома и понять что на самом деле происходит при вставке элементов:
chromium.googlesource.com/chromium/blink/+/master/Source/core/dom/DocumentFragment.h#33
chromium.googlesource.com/chromium/blink/+/master/Source/core/dom/ContainerNode.cpp#166
chromium.googlesource.com/chromium/blink/+/master/Source/core/dom/ContainerNode.cpp#66
Возможно ускорит устранение отдельного lis прохода, но так же возможно что и наоборот, нужно как минимум тестировать на синтетических бэнчмарках. Не пробовал.
В 99% случаев в реальном приложении при апдэйте списков не придётся никого переставлять, поэтому усложнение предпроходов может сказаться на производительности более частых случаев. Переиспользование массива точно не даст никакого прироста.
Все остальные костыли хуже.
> key не поможет при переносе элемента из одного массива в другой, из одного контейнера в другой и тд
При переносе дом узлов всё равно потеряется внутреннее состояние, которое невозможно никак иначе контроллировать. А там где с этим нет проблем flutter.io, там можно задавать глобальные ключи для переноса между контейнерами.
hyperscript можно реализовать в десяток строк, используется в куче vdom либ для тех кто не любит jsx.
mithril.js.org/hyperscript.html
Так вы же пытаетесь описать vdom в этой статье и парсер HTML ну совсем не имеет никакого отношения к vdom и лишь наоборот только вводит в заблуждение тех кто ещё не до конца понимает в чём заключается одна из его ключевых особенностей.
Когда термин «виртуальный дом» стал массово использоваться, были даже дискуссии среди вдом авторов на тему того что сам по себе термин совершенно не в тему, и предлагались идеи вроде «Value DOM».
Для vdom'а на входе примитивная деревяшка жаваскрипт объектов, которая не требует каких-то особых шаблонизаторов или парсеров.
> Если вы имеете в виду immutable-структуры, то согласен. В противном случае не очень понимаю о чем речь.
Имею в виду что DOM объекты трогают лишь в случае если нужно производить какие-то изменения, для сравнения используют только виртуальный дом.
> Очень странно. LCS — первое, что пришло лично мне в голову.
Да, мне тоже когда-то давно это первое что пришло в голову, и моя первая реализация использовала LCS, но потом началась жёсткая борьба в попытках получить лучшие цифры в бэнчмарках и пришлось искать как можно ускорить всё это дело под большинство реальных кейсов.
Самое ужасное последствие от использования innerHTML — это то что внутреннее состояние DOM элементов будет потеряно (цсс анимации, видео, аудио, позиция курсора в полях ввода, позиции скролла и много других состояний).
> Парсер HTML… Но упомянуть его определенно стоит, во многом потому что он нам понадобится для реализации VDOM.
Совершенно ненужная вещь, одна из основных фич vdom'а — это то что все узлы виртуального дома являются обычными жаваскрипт значениями и с ними можно работать так как душе угодно, никак не ограничивая себя примитивным шаблонизатором.
> Как я понимаю, React.js работает без парсера HTML в виду того, что его шаблоны (jsx/tsx) уже собираются в соответствующие вызовы создания нод.
Это не шаблоны, это обычный жаваскрипт и синтаксический сахар вокруг вызова `createElement`.
> По сути дела надо взять parent-нод, внутри которого отрисован наш HTML, взять массив всех его детей и сравнить его с аналогичным массивом виртуальных детей, который нам создал HTML-парсер.
Так работают медленные vdom реализации, все быстрые реализации производят все сравнения на vdom структурах.
> LCS — и есть самое сложное в VDOM-е, из-за чего многим он кажется страшной магией.
Единственная известная мне реализация vdom'а которая использует LCS на сегодняшний день — это github.com/yelouafi/petit-dom. Я перестал использовать LCS для решения этой проблемы ещё 4 года назад, github.com/ivijs/ivi/blob/2c81ead934b9128e092cc2a5ef2d3cabc73cb5dd/packages/ivi/src/vdom/implementation.ts#L1366-L1593 здесь можете ознакомиться с моим решением этой проблемы.
И подавляющее большинство vdom библиотек не замарачиваются и используют примитивный линейный алгоритм, из-за чего в том же реакте простая перестановка элемента может привести к N-1 `insertBefore` операций.