DiffHTML.js — утилита для патчинга DOM



    Что такое 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.
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 25

      0
      Значит эта утилита годится только для замены текущего dom дерева на новое, пришедшее из ajax ответа? Хотелось бы увидеть результаты тестов производительности с и без утилиты.
        0
        Можно еще UI компоненты делать, на подобие React / Riot (https://github.com/jmas/htmldiff-todo/blob/master/src/ui.js). Бенчмарки самому интересны.
          +1
          Померял на реальном сложном куске HTML — DiffHTML показал такие результаты:
          el.innerHTML ... 742ms
          diffhtml.innerHTML() ... 136ms
          morphdom() ... 2023ms
          

          На однородном глубоком дереве из DIV лучше всего работает стандартный el.innerHTML, morphdom() — средний результат, а diffhtml.innerHTML — хуже всего.

          Листинг бенчмарка
          import * as diffhtml from 'diffhtml';
          // import morphdom from 'morphdom';
          
          var rootEl = document.getElementById('root');
          
          var tpl = `...`;
          
          // console.profile("diffhtml.innerHTML()");
          
          var fn = function () { return Math.ceil(Math.random()*1000); };
          var start = window.performance.now();
          
          for (var i=0,ln=1000; i<ln; i++) {
            // str += tpl;
            // V1 = innerHTML
            // rootEl.innerHTML = tpl//tpl.replace(/\{rnd\}/ig, fn);
          
            // V2 = diffhtml.innerHTML
            diffhtml.innerHTML(rootEl, tpl);
          
            // V3
            // morphdom(rootEl, tpl);
          }
          
          // console.profileEnd();
          console.log(Math.ceil(window.performance.now() - start));
          


          Как оказалось, не так просто подготовить объективный бенчмарк. Если есть идеи как оценить объективно — пишите.
            0
            Чем больше DOM деревья и сильнее отличаются, тем менее эффективным будет difhtml. Это сразу понятно было, но надежда была на некую магию. Но для мелких кусочков годится, ведь его же можно и отдельно для некоторых элементов применять?
              0
              Как раз на большом реальном дереве получилось очень быстро, быстрей чем обычный innerHTML. Попробую разобраться почему. За ссылку на бенч отдельное спасибо.
                0
                Нужно принять во внимание, что холодная вставка (пустой контейнер) будет скорей всего всегда хуже. В своем бенче у меня было 1000 операций вставки подряд одного и того же дерева.
                +1
                >Как оказалось, не так просто подготовить объективный бенчмарк. Если есть идеи как оценить объективно — пишите.

                Если не лень, можете спортировать этот бэнчмарк https://github.com/localvoid/uibench-react/
                Вот пример результатов этого бэнча: https://cdn.rawgit.com/localvoid/6715c4b23eadc460112e671b4add3710/raw/907901966dd0473f1026d1ff25e244a022eb5ab1/uibench_results.html

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

              Аналог: morphdom и мой форк с поддержкой встроенного svg, сохранением позиции фокуса и гарантированным переиспользованием существующих уникальных (с id, key — в моём форке) элементов (оригинальный morphdom всё ещё имеет несколько кейсов типа этого).

                0
                Спасибо за наводку. Мне очень интересна эта тема.
                0
                Получается, что идея — в экономии некоторых if() в jQuery, лишь выбраны некоторые пороги узнавания диффов? Например, третий пример:
                var $temp = $('h1.title', document.body);
                if($temp.length && $temp.html() =='Hello world!')
                    $temp.after('<p>Dear, World!</p>');
                
                Если так, то, действительно, наличие условных инструментов модификации DOM постоянно требуется. Но как в DiffHTML напишется аппенд к любому содержимому (innerHTML) блока $temp? Наверное, надо вводить метасимволы типа "*" и дальнейшие правила разбора шаблона (второго параметра).

                Не проще ли написать сразу плагин к jQuery для условной манипуляции DOM? Преимущество в том, что не создаётся новый инструмент, а расширяется давно известный, дополняются лишь правила. Не будет, например, перестановки параметров. Если смущает увесистость jQuery, то у меня есть функция на 1.5K, которая делает элементы условной манипуляции и при этом заменяет основные функции jQuery (аналогичные микролибы — $dom.js, balalaika и другие).

                И объём DiffHTML — 80К, что подсказывает, что туда вошли много правил, парсер шаблонов, частично исполняющие роли jQuery. Плагин был бы легче. Другими словами, хотел бы сказать, что оформленные идеи, работающие в коде — конечно, хорошо, но описанное в Гитхабе API наводит на мысли, что к частичным целям можно идти другими и более спрямлёнными путями.
                  0
                  Я наверное поясню для чего это все. При помощи подобных библиотек можно абстрагироваться от манипуляции DOM, а перенести работу с шаблонами на уровень текстовых строк-шаблонов.
                  Да, подход не идеален, и здесь есть много не решенных вопросов, но практика показывает, что чем больше работы с DOM, тем сложней вносить апдейты в DOM-структуры (например переезжать с одного UI-фреймверка на другой). Все грубо говоря жестко прибито к селекторам и определенной вложенности.
                  Вы можете взглянуть на UI часть ToDo, возможно уловите мою мысль.
                  0
                  А что насчет исполнения/добавления javascript-кода/функций в пришедших данных?
                    0
                    Автор предлагает делать это инлайновыми onclick=${onButtonClick}, пример тут. Но с событиями навешенными другими способами могут возникнуть проблемы.
                    0
                    А почему было не сделать обертку над incremental-dom?
                      0
                      Демается, что это было бы хорошо, но я прошел мимо лишь потому, что нет встроенного транслятора в incremental-dom объекты. А может быть есть?
                      +2
                      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.
                      ВНИМАНИЕ. На каждый новый тест закрываем все вкладки инкогнито и открываем снова.

                      В статье:
                      diffhtml          14-17
                      morphdom          11-17
                      
                      mainstream:
                      (тут должен быть jquery)
                      vanilla-optimized 31-33
                      React             31-34
                      Backbone          25-27
                      Angular           28-31
                      Angular 2.0 Alpha 38-40
                      vuejs             35-36
                      ember              0-1 (нужен тест с последней версией, не верю)
                      Aurelia           33-36 ( http://jdanyow.github.io/aurelia-dbmonster/ )
                      
                      hipsters:
                      riot              21-23
                      Elm               32-36
                      vidom             41-44
                      Monkberry         43-45
                      Inferno           40-43
                      frzr              40-42
                      моя поделка       40-43
                      
                      вне конкурса:
                      canvas            60
                      


                      Software:
                      Chrome 52.0.2743.116 m.
                      Windows 7 64bit
                      
                      Hardware:
                      CPU : AMD A10-7850K (не самый плохой процессор  https://www.cpubenchmark.net/high_end_cpus.html      5548 где-то сзади в high end)
                      GPU : AMD 7970      (не самая плохая видеокарта http://www.videocardbenchmark.net/high_end_gpus.html 5246 где-то top 42)
                      А теперь представьте как всё это будет тормозить на чём-то по-слабее
                      


                      Не удовлетворяемся результатами т.к. библиотека развивается. Может что-то пофиксили.
                      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/ только примеры.
                        0
                        BTW. На хабре еще упоминались

                        matreshka.js  26-28
                        Angular light 35-38
                        
                          0
                          Как жаль, что у riot такой низкий показатель. Мне прям очень нравится эта библиотечка. Буду знать, что для перегруженных UI придётся от неё отказываться
                            +1
                            dbmon для riot'а — это бэст кэйс, на других кэйсах там всё гораздо печальнее. API у риота вроде нормальное, впаривать они тоже умеют (всякая ложь типа минимальное кол-во дом операций итп), но вот если взглянуть на исходники, то реализация полное говно :)
                            0
                            У меня наоборот

                            diffhtml ... 19-20
                            Angular ... 12-14
                            

                            MacBook Pro i5 / Chrome

                            Да, оказывается, на деле у DiffHTML показатели средние по больнице. Спасибо за наводку на бенчмарк.
                          +1
                          Какие планы у автора касательно своего проекта? Планирует ли автор предлагать библиотеку как инструмент равный любому популярному фреймворку или это будет фича из разряда пониже (к примеру underscore.js)?
                            0
                            Хороший вопрос, пока ничего по этому поводу от автора не слышал. Но судя по инструментарию (транформер html в подобие Virtual DOM), автор предлагает DiffHTML как возможную альтернативу Rect.JS, например. Ну это лично у меня сложилось такое мнение.
                            0
                            тест с последней версией Ember (2.10)
                            http://mathieuancelin.github.io/js-repaint-perfs/ember/

                            Only users with full accounts can post comments. Log in, please.