Преимущества произвольных тегов, или как я отказался от HTML разметки

от автора

Уже какое то время использую/разрабатываю библиотеку MaskJS. Вначале использовал её только как движок для шаблонов, а со временем, она полностью заменила HTML. В статье расскажу какими преимуществами обладает компонентный подход в разработке приложений и данная реализация в частности. Если выделить по-пунктам, то мы получим приблизительно такой список:

  • Скорость
  • Обработчики тегов
  • Пре- и Пост-процессоры
  • IoC
  • Изоляция/Декомпозиция
  • Разметка — Модель — Код — Стили

Более подробно о самой библиотеке и примеры можете посмотреть здесь — libjs/Mask, a исходники тут — github/Mask

§ Скорость

  1. Производительность

    Сразу о ней, так как всегда первым вопросом, после длительного описания всех преимуществ, я слышу — «Ну хорошо, это у тебя реализовано через свой DOM Builder и все это вкусно, но наверное производительность страдает?!». Поэтому поспешу вас уверить, что данная библиотека даже быстрее, чем то, что вы сейчас используете. Извините за попсовое заявление, возможно я и ошибаюсь, но давайте углубимся.
    Разберем тест, который приводил уже ранее, jsperf.
    Замечу, что для меня, как для своих проектов, так и для работы важны Webkits JavaScriptCore и V8. Как видно из тестов — компиляция и рендеринг опережает другие решения во многих браузерах.
    Mustache здесь немного в преимуществе, так как он кэширует скомпилированный шаблон.

    Компиляция/Парсинг шаблона. Первый выигрыш

    Из шаблона мы строим JSON дерево вида {tagName:'div',attr:{...},nodes:[...]}. Моей первой идеей было подготавливать шаблоны для клиента — для дальнейшего var template = JSON.parse(serializedJsonDom), но когда я провел тесты, oказалось, что mask.compile в целевых движках быстрее JSON.parse — jsperf, а в других браузерах не сильно уступает последнему. Так что прекомпиляция отпадает уже из-за ненадобности.

    Построение DOM. Второй выигрыш

    Получив JSON дерево, мы строим DocumentFragment, который потом вставляем в «живой» DOM. Из теста видно, что и здесь производительность на уровне. Я оставил чистый .appendChild(documentFragment), чтобы показать, что вся соль в создании DocumentFragment. И что опять интересно, .innerHTML здесь тоже уступает в производительности (Chrome).

    Важным моментом также является, что мы изначально рендерим все в один заход. Разберем пример с jQuery виджетами. Как обычно все происходит:

    • в макете указываем контейнер <div id='myWidget'></div>
    • в javascript-e $('#myWidget').myWidgetInit(config);

    Огромным недостатком данного подхода является то, что мы создаем виджет в уже готовый DOM элемент. А как известно менять «живой» DOM вещь относительно дорогая. И при том мы должны признать, что часто у нас не один виджет в приложении.

    Вот собственно и получаем в итоге очень быстрый DOM Builder.

  2. Скорость разработки

    Более подробно это мы рассмотрим в следующих параграфах, собственно ради этого и затевалась эта статья. Здесь же просто отмечу, что произвольные теги существенно повышают скорость и удобство разработки. Рассмотрим данный макет:

    header > menuBar {     li target='item1' > 'Item1 Title'     li target='item2' > 'Item2 Title' } viewsManager {     view#item1 > carousel {         img src='img1.png';         img src='img2.png';     }     view#item2 > scroller {         'About Content'     }  } 

    Если у нас компоненты menuBar, viewsManager, scroller, carousel и slider уже готовы, то нам даже ничего в javascript-e дополнительно писать не надо, что бы меню и вьюшки переключались, что бы работал скролл, что бы картинки крутились. Разве не прелесть? И разве не для этого существует макет? Конечно многие виджеты умеют также самo-инициализироваться, но в основном это реализовано через Dom Ready Event и поиск/замену нужных тегов — все это дополнительный «overhead». Здесь же все проходит через этот самый DOM Builder. Вы можете вставить такой макет в любое время и он будет работать.

§ Обработчики тэгов

Это и есть наши компоненты. Builder встретив тег обработчика, проинициализирует объект класса, и передаст контекст рендеринга в этот обработчик — никакой магии. Если же никто под тегом не зарегистрирован — создаст элемент сам. Для более полной и удобной работы с компонентами имеется также небольшой «абстрактный и не только» класс Compo (Исходники, Документация)

§ Препроцессоры

Учитывая, что у нас древовидный render flow, мы получаем мощный инструмент воздействие на рендеринг и макет в целом. Препроцессорами я называю компоненты которые изменяют нижний макет. Таким образом мы как бы внедряемся нашим компонентом в макет — правим по усмотрению нижний шаблон или подменяем входные данные модели и продолжаем рендерить. Это очень удобный паттерн для построение разных макетов (layouts). Вот пример с MasterPages (asp.net привет):

layout:master#twoColumnLayout  {     div.wrap {         div.layoutLeft > placeholder#left;         div.layoutLeft > placeholder#right;     } } layout:view master='twoColumnLayout' {     left { /** content */ }     right { /** content */  } } 

Компонент #twoColumnLayout должен конечно дополнительно подгрузить свои стили, но об этом в другой раз.
Это очень простой пример, но здесь видно, как элементарно мы можем подставлять разные шаблоны нашему представлению. Пример реализации — layout.js

§ Постпроцессоры

Их также можно назвать декораторами. Здесь мы изменяем уже родителей, и не шаблон, так как он уже прорендерин, а непосредственно HTMLElement (мы помним, что работаем с DocumentFragment, так что все изменения безболезненны). Такой подход также является мощным помощником при проектировании. На примере биндингов:

div {     bind value='name';     bind value='status' attr='class'; } 

Здесь постпроцессор свяжет модель с элементом div.

var person =  { name: "Alex", status: "happy" } container.appendChild(mask.renderDom(template, person)); setTimeout(function(){ person.status = 'busy' ; person.name="Anonym" }, 1000); 

Код думаю понятный. А реализацию можно посмотреть здесь — github

§ Inversion of Control (Dependency Injection)

Извините за названия параграфа — название «уж больно сильно» нравится. Эх звучит то как… Не так мне сам паттерн нравится, как его название — чувствуется мощь. Простите за лирику, не удержался. Возвращаясь к нашей теме, можно сказать, что произвольные теги открывают нам горизонты новых или хорошо забытых старых паттернов.
В планах реализовать, например, фабрику редакторов свойств:

form {     propertyEditor value='name';     propertyEditor value='birthday';     propertyEditor value='age'; } 

А реализация будет выглядеть примерно так:

mask.registerHandler('propertyEditor', Class({     render: function(values, container, cntx){         var value = Object.getProperty(values, this.attr.value),               template;         switch(Object.typeOf(value)){             case 'string':                  template = Object.format('input type="text" value="#{%1}" > bind value="#{%1}" prop="value";',this.attr.value);                 break;            case 'datetime':                 template = Object.format('datePicker date="%1";', value.toString());                 break;            /** и так далее */         }         mask.renderDom(template, values, container, cntx);           }); }} 

Важным моментом здесь является то, что мы можем внедрятся в шаблон переопределив любой из тегов, даже тот же «DIV» — это открывает нам неограниченные горизонты для манипуляции с представлением, тестирования и прочего, что на ум придет.

§ Изоляция

Произвольные теги, или вернее их обработчики, поддаются хорошей изоляции. Как это и должно быть при блочной композиции, любой блок может быть выделен в отдельный проект (декомпозиция), разработан, протестирован и подключен обратно. Например, если из примера выше у нас нет компоненты «карусель» — мы закрываем основной проект, разрабатываем компонент который вращает своих детей (картинки), возвращаемся в основной проект и подключаем его. Наш загрузчик должен уметь подгружать все нужные компоненту ресурсы, такие как вспомогательные картинки и стили. О загрузчике я уже рассказывал, и потом хочу еще пару слов сказать, так как появилось несколько интересных идей. Но это в следующей статье.
Изоляция важна не только в процессе разработки, но и в архитектуре приложения. Это достигается через событийную модель.

§ Разметка — Модель — Код — Стили

Я очень люблю структуру вэб программирования (разметка — код — стили), поэтому старался придерживаться ее в MaskJS — максимально убрать логику из разметки сконцентрировавшись на макете и данных. Посмотрите на другие шаблонизаторы — циклы, условия, выражения и прочее. Мне кажется, намного приятнее иметь такoe (кода нет, есть только макет):

ul > list value='users' > li > '#{name}' 

Тег(компонент) list; возьмет массив "users" из модели переданную в шаблон, и продублирует свой шаблон li > "#{name}" N(users.length) раз. Все остальное можно также через компоненты реализовать, или воспользоватся уже готовым решением. Таким образом макет — макетом, код — кодом, а стили — стилями. Все это должно переплетаться по-минимуму. Конечно — это все субъективно, и возможно я в корне не прав, а вы знаете все намного лучше.

§ Уход от HTML

В силу всего выше сказанного, я полностью отказался от написания обычного HTML. В странице имеем тег скрипт с типом «mask/template» содержимое которого рендерим в DOM. Многие готовые библиотеки для удобности стараюсь оборачивать в компонент, как на пример — TimePicker

Прошу прощение, если кого-то обидел или где-то ошибся. Хотя в Chrome есть отличная проверка на орфографию, так с пунктуацией он не помогает, а наверное «ой как надо».

Удачного дня!

ссылка на оригинал статьи http://habrahabr.ru/post/157383/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *