Что такое DiffHTML.js?
DiffHTML — эта утилита для патчинга (частичного изменения) DOM-дерева. Она умеет находить разницу между существующим DOM-деревом и HTML-строкой, между двумя деревьями. В результате будут произведены только те изменения, которые реально имеют место быть. Те элементы которых не было — вставятся, атрибуты которые были реально изменены — изменятся, и только они. Остальные элементы останутся без изменений.
Зачем это?
Просто чтобы не трогать те элементы в которых не было изменений. Это в теории дешевле, чем ре-рендерить полностью все дерево.
Как же React.JS?
React делает почти тоже самое, но у DiffHTML есть существенный козырь — эта библиотека по-умолчанию не требует практически никакой инфраструктуры вокруг себя. Ни сборки, ни специальных трансформаций в реакт-объекты. Вы просто можете сделать следующее:
diff.innerHTML(document.body, '<h1>Hello world!</h1>');
И объект появится в DOM-дереве. Далее…
diff.innerHTML(document.body, '<h1 class=”title”>Hello world!</h1>');
И только атрибут class будет добавлен. Просто добавим параграф:
diff.innerHTML(document.body, '<h1 class=”title”>Hello world!</h1><p>Dear, World!</p>');
В общем идея очень простая и в то же время достаточно мощная.
Минусы (готовность для продакшена)
- Проект молодой, поэтому здесь баги — это нормально
- Проблемы с навешиванием событий (старые события автоматически не удаляются)
- Нет большого количества информации вокруг этой библиотеки
- Нет бенчмарков
Плюсы
- В теории, быстрей простой перерисовки всех элементов внутри контейнера
- Есть middleware, можно контролировать проецес патчинга
- Есть подобие Virtual DOM, можно писать React-like шаблоны
- Меньше проводить времени с точечными модификациями DOM-дерева вручную
- Отзывчивый разработчик
Где применять?
Лично я вижу сферы применения:
- В старом коде, работающем на базе традиционных шаблонов и
el.innerHTML
вставки, можно добиться ускорения производительности - Для сложных SaaS виджетов, где важен размер подключаемых библиотек
- Для pet-проектов, где React избыточен, но на Vanilla.JS уже не хочется
А ToDo?
Есть ToDo, но как мне показалось — код сильно избыточен, поэтому я сделал свое:
Мой ToDo (DiffHTML, Babel DiffHTML tag transformer, Redux)
Вывод
Библиотека сырая, но она мне нравится так как проста и не требует радикальных изменений в давно привычном подходе обновлять все внутри контейнера. Для продакшена брать уж точно не стоит, но попробовать можно. Даже просто для того чтобы проверить как оно работает, и может быть помочь с поиском багов.
Будет очень кстати, если кто то возьмется померять производительность.
» Github: github.com/tbranyen/diffhtml
» Issues: github.com/tbranyen/diffhtml/issues
Спасибо за чтение!
Update: в комментариях подсказали, что еще есть аналог — morphdom (и форк).
Update 2: в одном из комментариев продолжил идею об уходе от работы напрямую с DOM к работе исключительно с шаблонами. Пересобрать HTML-строку всего приложения из шаблонов не так дорого, и пусть DiffHTML посчитает разницу и внесет изменения в DOM.
Поделиться с друзьями
profesor08
Значит эта утилита годится только для замены текущего dom дерева на новое, пришедшее из ajax ответа? Хотелось бы увидеть результаты тестов производительности с и без утилиты.
jMas
Можно еще UI компоненты делать, на подобие React / Riot (https://github.com/jmas/htmldiff-todo/blob/master/src/ui.js). Бенчмарки самому интересны.
jMas
Померял на реальном сложном куске HTML — DiffHTML показал такие результаты:
На однородном глубоком дереве из DIV лучше всего работает стандартный
el.innerHTML
,morphdom()
— средний результат, аdiffhtml.innerHTML
— хуже всего.profesor08
Чем больше DOM деревья и сильнее отличаются, тем менее эффективным будет difhtml. Это сразу понятно было, но надежда была на некую магию. Но для мелких кусочков годится, ведь его же можно и отдельно для некоторых элементов применять?
jMas
Как раз на большом реальном дереве получилось очень быстро, быстрей чем обычный innerHTML. Попробую разобраться почему. За ссылку на бенч отдельное спасибо.
jMas
Нужно принять во внимание, что холодная вставка (пустой контейнер) будет скорей всего всегда хуже. В своем бенче у меня было 1000 операций вставки подряд одного и того же дерева.
nuit
>Как оказалось, не так просто подготовить объективный бенчмарк. Если есть идеи как оценить объективно — пишите.
Если не лень, можете спортировать этот бэнчмарк https://github.com/localvoid/uibench-react/
Вот пример результатов этого бэнча: https://cdn.rawgit.com/localvoid/6715c4b23eadc460112e671b4add3710/raw/907901966dd0473f1026d1ff25e244a022eb5ab1/uibench_results.html
В бэнчмарках уи либ невозможно получить какую-то одну цифру по которой можно сказать что какая-то одна библиотека быстрее другой, нужно рассматривать кучу различных кэйсов.
Riim
Аналог: morphdom и мой форк с поддержкой встроенного svg, сохранением позиции фокуса и гарантированным переиспользованием существующих уникальных (с id, key — в моём форке) элементов (оригинальный morphdom всё ещё имеет несколько кейсов типа этого).
jMas
Спасибо за наводку. Мне очень интересна эта тема.
spmbt
Получается, что идея — в экономии некоторых if() в jQuery, лишь выбраны некоторые пороги узнавания диффов? Например, третий пример:
Если так, то, действительно, наличие условных инструментов модификации DOM постоянно требуется. Но как в DiffHTML напишется аппенд к любому содержимому (innerHTML) блока $temp? Наверное, надо вводить метасимволы типа "*" и дальнейшие правила разбора шаблона (второго параметра).Не проще ли написать сразу плагин к jQuery для условной манипуляции DOM? Преимущество в том, что не создаётся новый инструмент, а расширяется давно известный, дополняются лишь правила. Не будет, например, перестановки параметров. Если смущает увесистость jQuery, то у меня есть функция на 1.5K, которая делает элементы условной манипуляции и при этом заменяет основные функции jQuery (аналогичные микролибы — $dom.js, balalaika и другие).
И объём DiffHTML — 80К, что подсказывает, что туда вошли много правил, парсер шаблонов, частично исполняющие роли jQuery. Плагин был бы легче. Другими словами, хотел бы сказать, что оформленные идеи, работающие в коде — конечно, хорошо, но описанное в Гитхабе API наводит на мысли, что к частичным целям можно идти другими и более спрямлёнными путями.
jMas
Я наверное поясню для чего это все. При помощи подобных библиотек можно абстрагироваться от манипуляции DOM, а перенести работу с шаблонами на уровень текстовых строк-шаблонов.
Да, подход не идеален, и здесь есть много не решенных вопросов, но практика показывает, что чем больше работы с DOM, тем сложней вносить апдейты в DOM-структуры (например переезжать с одного UI-фреймверка на другой). Все грубо говоря жестко прибито к селекторам и определенной вложенности.
Вы можете взглянуть на UI часть ToDo, возможно уловите мою мысль.
TimsTims
А что насчет исполнения/добавления javascript-кода/функций в пришедших данных?
jMas
Автор предлагает делать это инлайновыми
onclick=${onButtonClick}
, пример тут. Но с событиями навешенными другими способами могут возникнуть проблемы.Jabher
А почему было не сделать обертку над incremental-dom?
jMas
Демается, что это было бы хорошо, но я прошел мимо лишь потому, что нет встроенного транслятора в incremental-dom объекты. А может быть есть?
vird
tl;dr не используйте ни diffhtml, ни morphdom. Они даже на синтетике не могут показать хорошие результаты.
1. Открываем http://mathieuancelin.github.io/js-repaint-perfs/ в режиме инкогнито дабы исключить влияние плагинов браузера. (Прим. есть и более сложные способы получить более аккуратные результаты)
2. Chrome devtools. Sources. Press Escape. Rendering. FPS meter. Включаем.
3. Открываем http://mathieuancelin.github.io/js-repaint-perfs/diffhtml/index.html на разрешении 1920*1080. (Дабы потом результаты не расходились с таблицей ниже)
4. Ждем 10 сек устаканивания fps.
ВНИМАНИЕ. На каждый новый тест закрываем все вкладки инкогнито и открываем снова.
Не удовлетворяемся результатами т.к. библиотека развивается. Может что-то пофиксили.
1. Скачиваем содержимое http://mathieuancelin.github.io/js-repaint-perfs/diffhtml/index.html (chrome сохранить страницу целиком)
2. Забрасываем в папку dbmon (diffHTML)_files https://raw.githubusercontent.com/tbranyen/diffhtml/master/dist/diffhtml.js
3. Патчим до https://gist.github.com/vird/ff236a38b6b72e23a9e87c88e38760cd
4. Запускаем по инструкции выше.
Получаем тот же fps.
morphdom, честно, было лень патчить и проверять.
Замечания к методике тестирования и результатам принимаются.
P.s. Есть еще todomvc benchmark. Кто бросит ссылку на большую сборную солянку 20+, тому отдельное спасибо. Ато на http://todomvc.com/ только примеры.
vird
BTW. На хабре еще упоминались
Starche
Как жаль, что у riot такой низкий показатель. Мне прям очень нравится эта библиотечка. Буду знать, что для перегруженных UI придётся от неё отказываться
nuit
dbmon для riot'а — это бэст кэйс, на других кэйсах там всё гораздо печальнее. API у риота вроде нормальное, впаривать они тоже умеют (всякая ложь типа минимальное кол-во дом операций итп), но вот если взглянуть на исходники, то реализация полное говно :)
jMas
У меня наоборот
MacBook Pro i5 / Chrome
Да, оказывается, на деле у DiffHTML показатели средние по больнице. Спасибо за наводку на бенчмарк.
vird
Вы тестировали неоптимизированный angular. Попробуйте
http://mathieuancelin.github.io/js-repaint-perfs/angular/opt.html
http://mathieuancelin.github.io/js-repaint-perfs/angular-track-by/
vird
Вторая ссылка по недосмотру битая
http://mathieuancelin.github.io/js-repaint-perfs/angular-track-by/
CodeViking
Какие планы у автора касательно своего проекта? Планирует ли автор предлагать библиотеку как инструмент равный любому популярному фреймворку или это будет фича из разряда пониже (к примеру underscore.js)?
jMas
Хороший вопрос, пока ничего по этому поводу от автора не слышал. Но судя по инструментарию (транформер html в подобие Virtual DOM), автор предлагает DiffHTML как возможную альтернативу Rect.JS, например. Ну это лично у меня сложилось такое мнение.