Сколько нужно времени, чтобы просто вывести на экран большой список, используя современные фреймворки?


Список на 2000 строк ReactJS AngularJS Raw HTML SAPUI5 $mol
Появление списка 170 ms 420 ms 260 ms 1200 ms 50 ms
Обновление всех его данных 75 ms 75 ms 260 ms 1200 ms 10 ms

Напишем нехитрое приложение — личный список задач. Какие у него будут характеристики?


ToDoMVC ReactJS AngularJS PolymerJS VanillaJS $mol
Размер ( html + js + css + templates ) * gzip 322 KB 326 KB 56 KB 20 KB 23 KB
Время загрузки 1.4 s 1.5 s 1.0 s 1.7 s 0.7 s
Время создания и удаления 100 задач 1.3 s 1.7 s 1.4 s 1.6 s 0.5s

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


Синхронная параллельная загрузка ресурсов


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



Клуб именованных велосипедистов


Здравствуйте, меня зовут Дмитрий Карловский и я… руководитель группы веб-разработки компании SAPRUN. Наша компания занимается преимущественно внедрением и поддержкой продуктов SAP в ведущих компаниях России и ближнего зарубежья. Сам SAP — огромная сложная система, состоящая из множества компонент.


Один из таких компонент — веб фреймворк SAPUI5, предназначенный для создания одностраничных приложений. Это — типичный представитель коробочных фреймворков, то есть таких, которые предоставляют вам не только архитектуру, но и богатую библиотеку виджетов. И как любой коробочный фреймворк, данный подвержен страшной болезни современности — ожирению.


Старый, толстый, мрачный гусь


Проявляется ожирение во всём: огромные объёмы кода из изысканной немецкой пасты; неповоротливые виджеты, еле-еле двигающие списки на 100 элементов; развесистые деревья классов, в дебрях которых заблудится даже лесной эльф. Всё это приводит к достаточно длительной разработке, а время — деньги.


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


Нам требовался более эффективный инструмент, позволяющий малой кровью создавать конкурентоспособные масштабные кроссплатформенные приложения, поэтому мы решились на страшное — переизобрести колесо — собственный веб фреймворк с говорящим названием $mol. Разработанный с нуля, он вобрал в себя множество свежих идей, о которых и пойдёт дальнейшее повествование.


Реактивное программирование


Изобретённое 50 лет назад, оно только недавно добралось до мира пользовательских интерфейсов в вебе. Причём добралось в достаточно куцем "push" виде: вы описываете некоторую последовательность действий, на вход подаёте некоторые данные, и эти действия, последовательно применяются к каждому элементу данных. Однако, такой подход приводит ко сложностям при реализации ленивых и динамически меняющихся вычислений.


$mol же построен на "pull" архитектуре, где инициатором любых действий выступает потребитель результата этих действий, а не источник данных. Это позволяет рендерить лишь те части приложения, что попадают в видимую область; создавать лишь те объекты, что требуются для рендеринга в текущий момент; запрашивать с сервера лишь те данные, что требуются для созданных объектов.


$mol насквозь пропитан "ленивыми вычислениями" и автоматическим освобождением ресурсов. Вы можете всего одной строчкой закешировать результат выполнения функции и не беспокоиться об инвалидации и очистке этого кеша — модуль $mol_atom сам отследит все зависимости и выполнит всю рутинную работу.


const source = new $mol_atom( ( next? : number )=> next || Math.ceil( Math.random() * 1000 ) )

const middle = new $mol_atom( ()=> source.get() + 1 )

const target = new $mol_atom( ()=> middle.get() + 1 )

console.assert( target.get() === source.get() + 2 , 'Target must be calculated from source!' )
console.assert( target.get() === target.get() , 'Value must be cached!' )

source.push( 10 )

console.assert( target.get() === 12 , 'Target value must be changed after source change!' )

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


Синхронное программирование


Нет ничего проще, чем синхронное программирование. Код получается коротким, понятным и вы можете свободно использовать все возможности языка по управлению потоком исполнения (if, for, while, switch, case, break, continue, throw, try, catch, finally).


К сожалению, JS — однопоточный язык, поэтому, для обеспечения конкурентного исполнения множества задач, код приходится писать асинхронный, что порождает множество проблем: начиная лапшой из мелких функций и заканчивая ненадёжной обработкой исключительных ситуаций. node-fibers позволяет писать синхронный код не блокируя системный поток, но работает только в NodeJS. async/await/generators позволяют создавать асинхронные функции, которые могут вызывать друг друга синхронно, но из-за несовместимости с обычными синхронными функциями, приходится чуть ли не все функции делать асинхронными. Кроме того, для них требуется специальная поддержка со стороны браузера или транспиляция в адскую машину состояний.


Модель реактивности же, используемая в $mol, позволяет элегантно абстрагировать код от асинхронности. Посмотрите, например, на исходный код Куайна из начала статьи:


content() {

    const paths = [
        '/mol/app/quine/quine.view.tree' ,
        '/mol/app/quine/quine.view.ts' ,
        '/mol/app/quine/quine.view.css' ,
        '/mol/app/quine/index.html' ,
    ]

    const sources = paths.map( path => {
        return $mol_http_resource.item( path ).text()
    } )

    const content = sources.map( ( source , index )=> {
        const header = `# ${ paths[ index ] }\n`
        const code = '```\n' + source.replace( /\n+$/ , '' ) + '\n```\n'
        return `${ header }\n${ code }`
    } ).join( '\n' )

    return content
}

Тут вы видите вполне себе синхронную генерацию содержимого страницы. Однако, системный поток не блокируется, а загрузка всех 4 файлов происходит параллельно. При этом, пока идёт загрузка данных, вместо них выводится индикатор загрузки. Формируется он автоматически, избавляя разработчика и от этой головной боли тоже.


Компонентное программирование


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


Конструктор LEGO содержит множество самых разнообразных деталей, но любые из них стыкуются вместе благодаря стандартизированному соединительному интерфейсу. В $mol в роли такого интерфейса выступают свойства. Когда родительский компонент создаёт дочерний, он переопределяет у того ряд свойств, настраивая его поведение под свои требования. А благодаря реактивности, риск что-либо непреднамеренно сломать в дочернем компоненте — минимален.


Вы можете легко и просто заменить какое-либо свойство моком, чтобы протестировать логику работы компонента; задать всем свойствам константные значения для проверки вёрстки; указать дочернему компоненту использовать какое-либо свойство родительского, и связать через него несколько дочерних компонент вместе.


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


В примере с Куайном используется компонент $mol_pager, рисующий типичную страничку с заголовком в шапке, скроллящимся телом и подвалом:


$mol_pager $mol_viewer
    childs /
        < header $mol_viewer
            childs < head /
                < titler $mol_viewer
                    childs /
                        < title
        < bodier $mol_scroller
            childs < body /
        < footer $mol_viewer
            childs < foot /

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


$mol_app_quine $mol_pager
    head /
        < logo $mol_icon_refresh
        < titler
    body /
        < texter $mol_texter
            text < content     footer null

С голой грудью на амбразуру


Представьте, что вам достался старый проект, о котором вы слышали ровным счётом ничего. Всё, что у вас есть — репозиторий с исходными кодами. Документации либо нет, либо она уже давно потеряла былую актуальность. А вам нужно починить какой-нибудь назойливый баг. Ещё вчера.


Допустим, перед вами вот это не хитрое приложение:


Типичное бизнес-приложение


Тут слева вы видите список заявок на закупки, а справа — подробности по выбранной заявке: кому, что, когда и на какую сумму. И всё бы хорошо, да вот только дата поставки выводится в формате ISO8601 "YYYY-MM-DD", а не в привычном для целевой аудитории "MM/DD/YYYY". Кто мы такие, чтобы навязывать заказчику международные стандарты? Нет, так дело не пойдёт и нужно срочно исправить, но с чего начать, куда копать?


Единственная зацепка — DOM элемент, куда выводится дата. Возможно DOM инспектор сможет помочь найти какие-либо зацепки, которые позволят вам выйти на исполнителя:


Типичное бизнес-приложение кишками наружу


Что за больной психопат мог придумать столь длинные идентификаторы элементам? И почему они такие странные? Словно бы являются JS кодом… А что если..


Содержимое объекта, который вытягивается за кишки


Скопировав идентификатор в консоль вы с удивлением обнаруживаете, что данный код не просто рабочий, но и возвращает какой-то объект, подозрительно напоминающий визуальный компонент: он является экземпляром класса $mol_viewer и хранит в себе ссылку на DOM элемент с которого вы и начали своё расследование.


Тут вы подмечаете, странную закономерность: все поля именуются либо нормально, но хранят в себе функции, либо хранят не функции, но именуются со скобками в конце. Похоже, это ружьё тут тоже висит не просто так — вы пробуете вызвать у объекта метод objectOwner() и получаете ожидаемый результат — ссылку на компонент выше по иерархии:


Содержимое владельца находится на расстоянии вытянутой руки


Уже лучше ориентируясь в происходящем, вы возвращаетесь к жертве неправильной локализации и окидываете взглядом её немногочисленные поля, ни одно из которых не содержит заветной даты. Ваше внимание привлекает поле с говорящим названием childs, которое содержит некую функцию возвращаемым значением которой может быть именно то, что вы ищите.


Метод возвращает дату поставки


Ага! У вас есть подозреваемый. Щёлкнув правой кнопкой по функции вы в два счёта находите место её определения:


Исходный и сгенерированный код создания компонента


Перед вами код создания вложенного компонента, явно сгенерированный роботом. На это указывает странный путь к файлу и короткий комментарий, судя по всему, послуживший исходником для генератора. А найденная вами функция childs — не более чем посредник, передающий управление функции content компонента-владельца. Продолжая движение по цепочке улик, вы поднимаетесь всё выше, распутывая клубок интриг в высших эшелонах власти, пока, наконец, не выходите на истинного преступника под именем $mol_app_supplies.root(0).detailer().position(0).supplyDate():


Настоящий исполнитель этого зверского преступления


Дело за малым — направиться по указанному адресу, внести необходимые изменения и проверить их. Но с чего начать, куда копать?


Вы выкачиваете репозиторий и обнаруживаете в корне проекта package.json. Логично предположить, что это NodeJS проект, а значит нужно установить зависимости:


> npm install --depth 0

Type 'npm start' to start dev server or 'npm start {relative/path}' to build some package.
.
+-- body-parser@1.15.2
+-- compression@1.6.2
+-- concat-with-sourcemaps@1.0.4
+-- express@4.14.0
+-- mam@1.0.0
+-- portastic@1.0.1
+-- postcss@5.2.4
+-- postcss-cssnext@2.8.0
+-- source-map-support@0.4.3
`-- typescript@2.0.3

Зависимостей не очень много, так что ставятся они все в течении минуты. Вы подмечаете, что в проекте активно используется транспиляция: скрипты пишутся на typescript, стили обрабатываются postcss, а для отладчика генерируются source-maps.


Cудя по подсказке, для запуска локального сервера, нужно выполнить очевидную команду:


> npm start

22:23 Built mol/build/-/node.deps.json
22:23 Built mol/build/-/node.js
22:23 Built mol/build/-/node.test.js

$mol_build.root(".").server() started at http://127.0.0.1:8080/

Дальнейшие шаги не менее очевидны — открытие указанного адреса приводит вас к списку пакетов вида:


Список файлов в корне проекта


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


При этом вы подмечаете, что первое открытие приложения происходит несколько секунд, а повторные заходы уже отрабатывают мгновенно. Что за тормоза на пустом месте? Открытие консоли проясняет ситуацию:


$mol_build.root(".").server() started at http://127.0.0.1:8080/

mol> git fetch & git log --oneline HEAD..origin/master
> git fetch & git log --oneline HEAD..origin/master
jin> git fetch & git log --oneline HEAD..origin/master

23:00:23 Built mol/app/supplies/-/web.css
23:00:27 Built mol/app/supplies/-/web.js
23:00:27 Built mol/app/supplies/-/web.locale=en.json
23:00:41 Built mol/app/todomvc/-/web.css
23:00:45 Built mol/app/todomvc/-/web.js
23:00:45 Built mol/app/todomvc/-/web.locale=en.json

Ага, при первом заходе в приложение происходит сборка пакетов для него. Все скрипты в один файл, все стили — в другой, тексты — в третий. Вы перезапускаете сервер, открываете браузер и проверяете эту теорию:


Первая загрузка 4 секунды, вторая - пол секунды


Так и есть — грузятся всего 4 файла, причём, подозрительно малого объёма в сравнении с другими популярными фреймворками: все скрипты умещаются в 30 килобайт с учётом сжатия. Чёрная магия, не иначе. В 30 килобайт даже отдельно взятая jQuery не помещается, а ведь эта библиотека — основа большинства фреймворков. Вы смотрите в сгенерированный пакет web.js и офигеваете ещё сильнее, ведь код даже не минифицирован! Совсем ничего святого!


Что ж, хватит развлекаться, пора провести исправительные работы. Вы открываете positioner.view.ts и видите там следующую картину:


namespace $.$mol {
    export class $mol_app_supplies_positioner extends $.$mol_app_supplies_positioner {

        position() {
            return null as $mol_app_supplies_domain_supply_position
        }

        productName() {
            return this.position().name()
        }

        price() {
            return this.position().price()
        }

        quantity() {
            return this.position().quantity().toString()
        }

        cost() {
            return this.position().cost()
        }

        supplyDate() {
            return this.position().supplyMoment().toString( 'YYYY-MM-DD' )
        }

        divisionName() {
            return this.position().division().name()
        }

        storeName() {
            return this.position().store().name()
        }

    }
}

Как-то бедновато. Где лапша? Где фрикадельки? Всё, что делают эти 8 методов — это преобразуют хитросплетения данных доменной модели в свойства модели интерфейсной. Чтобы понять как данные выводятся, вы идёте по единственному видимому отсюда пути — зажимаете CTRL и щёлкаете по базовому классу, что приводит вас к тому самому генерированному коду, расположенному во '-/view.tree/positioner.view.tree.ts':


/// $mol_app_supplies_positioner $mol_carder
namespace $ { export class $mol_app_supplies_positioner extends $mol_carder {

    /// heightMinimal 68
    heightMinimal() {
        return 68
    }

    /// productLabel @ \Product
    productLabel() {
        return this.text( "productLabel" )
    }

    /// productName     productName() {
        return ""
    }

    /// productItem $mol_labeler 
    ///     title < productLabel 
    ///     content < productName
    @ $mol_mem()
    productItem( next? : any , prev? : any ) {
        return ( next !== void 0 ) ? next : new $mol_labeler().setup( __ => { 
            __.title = () => this.productLabel()
            __.content = () => this.productName()
        } )
    }
// ...

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


  • При объявлении компонента сначала указывается имя его класса, а потом имя базового класса.
  • Внутри объявления комбинация имени и значения создают функцию, которая возвращает это значение.
  • В качестве значения можно указать число, либо строку, если предварить её "обратной косой чертой". Эта черта ассоциируется у вас с экранированием данных. По всей видимости всё, что идёт после неё не будет разбираться генератором, а будет вставлено как строка.
  • Если поставить "собачку", то текст пропадёт из кода, а вместо него будет вставлено получение его по ключу. По всей видимости именно на этом и основана генерация файла с текстами, которую вы подметили, когда игрались со сборкой проекта.
  • В качестве значения можно указать имя другого компонента и тогда функция будет возвращать соответствующий экземпляр. При этом можно перегрузить свойства вложенного компонента, своими свойствами. Угловая скобка, очевидно, показывает направление движения данных.

Вроде бы всё просто, но не понятно только зачем было вводить какой-то свой формат, если то же самое в typescript занимает не сильно больше места. Вы открываете исходный positioner.view.tree в надежде увидеть там что-то ещё.


$mol_app_supplies_positioner $mol_carder
    heightMinimal 68

    content < grouper $mol_viewer childs /

        < mainGroup $mol_rower childs /

            < productItem $mol_labeler
                title < productLabel @ \Product
                content < productName 
            < costItem $mol_labeler
                title < costlabel @ \Cost
                content < coster $mol_coster
                    value < cost $mol_unit_money
                        valueOf 0
- ...

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


Воодушевлённый тем, как легко вы распутываете клубок внутренней архитектуры, вы берётесь за правки. Можно было бы просто поменять формат вывода в функции supplyDate и на этом закрыть дело:


        supplyDate() {
            return this.position().supplyMoment().toString( 'MM/DD/YYYY' )
        }

Но это бы лишь отсрочило решение настоящей проблемы — формат не зависит от установленной локали. А ведь чуть раньше вы выяснили, что локализация текстов уже вполне себе поддерживается. Вы возвращаетесь к positioner.view.tree.ts:


    /// productLabel @ \Product
    productLabel() {
        return this.text( "productLabel" )
    }

Погрузившись в text() вы доходите до места, где задаётся язык:


    export class $mol_locale extends $mol_object {

        @ $mol_mem()
        static lang( next? : string ) {
            return $mol_state_local.value( 'locale' , next ) || 'en'
        }

Ага, чтобы получить текущий язык, нужно выполнить:


$mol_locale.lang()

Вы выполняете этот код в консоли и убеждаетесь, что он действительно работает.


Осталось создать функцию, которая бы по идентификатору языка возвращала формат представления даты. Но где её разместить? Нужно создать отдельный модуль.


По аналогии с другими модулями вы создаёте новый по адресу mol/dateFormat/dateFormat.ts со следующего вида содержимым:


namespace $ {

    export const $mol_dateFormat_formats : { [ key : string ] : string } = {
        'en' : 'MM/DD/YYYY' ,
        'ru' : 'DD.MM.YYYY' ,
    }

    export function $mol_dateFormat() {
        return $mol_dateFormat_formats[ $mol_locale.lang() ] || 'YYYY-MM-DD'
    }

}

Только одно не понятно — ни в одном файле нет ни import, ни require. Как же система узнает, что этот файл нужно включить в пакет приложения? Не попадают же в пакет вообще все файлы? Чтобы проверить эту гипотезу вы перезагружаете приложение и пытаетесь вызвать свежесозданную функцию из консоли:


$.$mol_dateFormat() // Uncaught TypeError: $.$mol_dateFormat is not a function

Ну не может же оно само понимать какой модуль нужен, а какой — нет? Или может? Вы добавляете использование функции в приложение:


        supplyDate() {
            return this.position().supplyMoment().toString( $mol_dateFormat() )
        }

Перезагрузив страницу, вы с удивлением обнаруживаете, что приложение не только не упало, но и вывело дату в локализованном формате:


Вывод даты в американском формате


Вы переименовываете файл в dateFormat2.ts — всё работает. Переименовываете директорию в dateFormat2 — снова ошибка. Переименовываете функцию в $mol_dateFormat2 — снова работает. Всё становится ясно — при обращении ко глобальной функции/классу/переменной с таким странным именованием происходит поиск пути, соответствующего частям имени. И если находится такая директория — подключаются скрипты из неё.


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


Безудержное чаепитие


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


  • Все объекты доступны по ссылкам из глобальной области видимости, а не спрятаны в замыканиях. Это позволяет разработчику легко и просто исследовать внутреннее состояние приложения.


  • Для долгоживущих объектов автоматически генерируются уникальные человекопонятные идентификаторы, которые одновременно являются и "javascript-путями" до них из глобальной области видимости, что гарантирует их уникальность.


  • Изменения всех состояний логируются, с указанием идентификаторов объектов, что позволяет в точности понять, где что произошло. Например, если вы включите вывод логов всех сообщений, в идентификаторах которых есть подстрока "task", то, при завершении задачи в ToDoMVC, вы увидите следующие сообщения:

> $mol_log.filter('task')
< "task"
12:27:36 $mol_state_local.value("task=1476005250333") push Object {completed: true, title: "Hello!"} Object {completed: false, title: "Hello!"}
12:27:36 $mol_app_todomvc.root(0).taskCompleted(0) obsolete
12:27:36 $mol_app_todomvc.root(0).taskTitle(0) obsolete
12:27:36 $mol_app_todomvc.root(0).taskCompleted(0) push true false
12:27:36 $mol_app_todomvc.root(0).taskRow(0).completer().DOMTree() obsolete
12:27:36 $mol_app_todomvc.root(0).taskRow(0).DOMTree() obsolete

  • Пространства имён в рантайме однозначно соответствуют структуре директорий в репозитории. Это гарантирует отсутствие конфликтов и даёт чёткое понимание как человеку, так и машине, где искать исходные файлы.


  • Весь код псевдосинхронен и разбит на небольшие функции, что упрощает его понимание. В следующем примере происходит неблокирующий запрос файла с текстами на нужном языке. Обратите внимание на полезный стектрейс, который не доступен при использовании "обещаний", "стримов" и тому подобных абстракций.

Псевдосинхронный код с полезным стектрейсом


Прыжок без парашюта


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


Итак, вы работаете в компании ACME (а если не работаете, то основываете свою) и вам нужно реализовать веб-приложение для гиковского социального блога HabHub. Для начала, вам нужно просто загружать с гитхаба статьи и показывать их единой лентой.


Первым делом вы устанавливаете необходимое программное обеспечение: Git, WebStorm, NodeJS и NPM.


Далее вы выкачиваете репозиторий со стартовым проектом MAM:


git clone https://github.com/eigenmethod/mam.git ./mam && cd mam

Содержит он лишь общие для всех пакетов конфиги:


  • .idea — настройки для WebStorm: форматирование кода, статические проверки, запуск локального сервера.
  • .editorconfig — настройки для других редакторов.
  • .gitignore — указывает какие файлы git должен игнорировать.
  • .pms.tree — указывает какой пакет из какого репозитория выкачивать. Пакеты выкачиваются сборщиком автоматически по необходимости.
  • package.json — настройки для NPM.
  • tsconfig.json — настройки для TypeScript компилятора.

Открыв проект в WebStorm, вы запускаете локальный сервер, кнопкой "Start" на панели инструментов, либо, если вы предпочитаете другой редактор, выполнив в консоли:


npm start

Далее вы создаёте для приложения директорию acme/habhub и кладёте в неё index.html, который будет служить точкой входа в ваше приложение:


<!doctype html>
<html style=" height: 100% ">

<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />

<script src="-/web.js"></script>
<script src="-/web.test.js"></script>
<link rel="stylesheet" href="-/web.css" />

<body mol_viewer_root="$acme_habhub">

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


Чтобы создать упомянутый компонент, вы создаёте файл ./acme/habhub/habhub.view.tree:


$acme_habhub $mol_viewer

После чего открываете http://localhost:8080/acme/habhub/ и убеждаетесь, что загружается чистая страница, а в консоли нет ни одной ошибки — это значит, что все необходимые файлы успешно сгенерировались и загрузились, а тесты не выявили проблем.


В Багдаде всё чисто


Язык описания компонент


view.tree — мощный и лаконичный декларативный язык описания компонент, позволяющий собирать одни компоненты из других, как из кубиков LEGO. Выучив этот не хитрый язык, любой верстальщик может создавать гибкие переиспользуемые компоненты, которые легко интегрируются в другие, без традиционного "натягивания вёрстки на логику". Вся логика пишется в отдельном файле view.ts и как правило не требует изменений во view.tree, что позволяет программисту и верстальщику работать над одними и теми же компонентами, не мешая друг другу. Это достигается за счёт намеренного ограничения: вы не можете просто взять и вставить div в нужном месте. view.tree требует, чтобы вы использовали компоненты и (самое главное!) каждому из них давали уникальные имена. Фактически $mol_viewer просто создаст div при рендеринге в DOM, но в перспективе рендеринг может быть в графический холст, нативные компоненты или даже в excel файл.


Типичный сценарий создания компонента верстальщиком выглядит так (на примере компонента показывающего ненавязчивый лейбл над блоком):


Сперва он пишет демо-компоненты, которые являются примерами использования реализуемого компонента:


- Label over simple text
$mol_labeler_demo_text $mol_labeler
    title @ \Provider
    content @ \ACME Provider Inc.

- Label over string form field
$mol_labeler_demo_stringer $mol_labeler
    title @ \User name
    content $mol_stringer
        hint < hint @ \Jack Sparrow
        value > userName \

Потом, собственно реализует его:


$mol_labeler $mol_viewer
    childs /
        < titler $mol_viewer
            childs /
                < title
        < contenter $mol_viewer
            childs /
                < content null

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


[mol_labeler_titler] {
    color: var(--mol_skin_passive_text);
    font-size: .75rem;
}

Многих смущает необычный синтаксис. То же самое можно было бы написать используя более привычный синтаксис XML:


<!-- Label over string form field -->
<component name="mol_labeler_demo_stringer" extends="mol_labeler">

    <L10n name="title">User name</L10n>

    <mol_stringer name="content">

        <hint>
            <String name="hint">Jack Sparrow</String>
        </hint>

        <value name="userName">
            <String />
        </value>

    </mol_stringer>

</component>

Но он весьма громоздкий; во главе угла у него типы компонент, а не их имена в контексте родительского компонента; некоторые символы в строках требуют замены на xml-entities; велик соблазн просто скопипастить кусок вёрстки, без компонентной декомпозиции. Всё это приводит к осложнению работы с кодом и его поддержки, и поэтому в $mol используется именно синтаксис Tree, оптимально подходящий для задачи.


Небольшая шпаргалка по view.tree:


Объявление/использование компонента состоит из 3 частей:


  1. Имя компонента/свойства
  2. Имя базового компонента
  3. Список (пере)определяемых свойств

$ — префикс имён компонент. Данный префикс используется везде, кроме css, где он не допустим.


\ — с этого символа начинаются сырые данные. Содержать они могут любые символы (кроме символа конца строки), без какого-либо экранирования. Чтобы встравить несколько строк, нужно добавить символ \ перед каждой.


@ — вставленный между именем свойства и сырыми данными, он указывает вынести текст в файл с локализованными строками.


/ — объявляет список. Вставляйте элементы списка на отдельных строках с дополнительным отступом.


* — объявляет словарь. Сопоставляет текстовым ключам произвольные значения. Ключ не может содержать пробельные символы.


< — односторонне связывание (не путать с одноразовым). Указывает, что свойство слева (принадлежащее компоненту слева) должно брать значение из свойства справа (принадлежащее определяемому компоненту).


> — двустороннее связывание (не путать с обработчиками событий). Указывает, что в качестве свойства слева, должно быть взято свойство справа.


# — произвольный ключ. Указывает, что первым параметром свойство принимает некоторый ключ


Числа, логические значения и null выводятся как есть, без каких-либо префиксов.


Складываем кирпичики


Разобравшись в языке view.tree вы продолжаете пилить социальный блог. Прежде всего вы решаете, что у вас будет типичная раскладка страницы в виде шапки и скроллящейся области. Для этого вы используете готовый компонент $mol_pager:


$acme_habhub $mol_pager
    title \HabHub
    body /
        \Hello HabHub!
    footer null

Шапка и контент


Отлично! В теле страницы должны быть статьи. Статьи на GitHub пишутся в формате markdown, поэтому вы добавляете пару примеров статей, используя компонент для визуализации markdown — $mol_texter:


$acme_habhub $mol_pager
    title \HabHub
    body < gisters /
        < gister1 $acme_habhub_gister
            text                 \# Hello markdown!
                                \*This* **is** some content.
        < gister2 $acme_habhub_gister
            text                 \# Some List
                                \* Hello from one!
                \* Hello from two!
                \* Hello from three!
    footer null

$acme_habhub_gister $mol_texter

[acme_habhub_gister] {
    margin: 1rem;
}

Несколько демо карточек в теле


Супер! Теперь вы убираете жёсткий код и оставляете лишь формулу создания карточки статьи по её номеру:


$acme_habhub $mol_pager
    title \HabHub
    body < gisters /
    gister# $mol_texter
        text < gistContent#     footer null

Пришло время загрузить данные. Вы создаёте файл habhub.view.ts и пишете несколько мантр, которые позволят вам переопределить поведение уже созданного компонента:


namespace $.$mol {

    export class $acme_habhub extends $.$acme_habhub {

    }

}

Прежде всего вы описываете формат в котором от сервера приходят статьи:


interface Gist {
    id : number
    title : string
    body : string
}

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


uriSource() {
    return 'https://api.github.com/search/issues?q=label:HabHub+is:open&sort=reactions'
}

А теперь вы задаёте свойство, которое будет возвращать собственно данные, делая запрос к серверу через модуль $mol_http_resource_json, предназначенный для работы с json-rest ресурсами:


gists() {
    return $mol_http_resource_json.item<{ items : Gist[] }>( this.uriSource() ).json().items
}

Далее вы формируете карточки для показа статей по числу этих статей через свойство gister#, которое вы объявили ещё во view.tree:


gisters() {
    return this.gists().map( ( gist , index ) => this.gister( index ) )
}

gister# обращаясь к gistContent# передаёт ему тот же ключ, что передан и ему, так что осталось лишь задать, как по номеру статьи сформировать её содержимое:


gistContent( index : number ) {
    const gist = this.gists()[ index ]
    return `#${ gist.title }\n${ gist.body }`
}

В результате у вас получается следующего вида презентатор:


namespace $.$mol {

    interface Gist {
        id : number
        title : string
        body : string
    }

    export class $acme_habhub extends $.$acme_habhub {

        uriSource(){
            return 'https://api.github.com/search/issues?q=label:HabHub+is:open&sort=reactions'
        }

        gists() {
            return $mol_http_resource_json.item<{ items : Gist[] }>( this.uriSource() ).json().items
        }

        gisters() {
            return this.gists().map( ( gist , index ) => this.gister( index ) )
        }

        gistContent( index : number ) {
            const gist = this.gists()[ index ]
            return `#${ gist.title }\n${ gist.body }`
        }

    }

}

Код в целом тривиальный и в тестировании не нуждается: uriSource возвращает константу, правильность обращения gists к стороннему модулю проверит typescript компилятор, gisters тривиален и опять же проверяется компилятором, и только gistContent содержит нетривиальное формирование строки, поэтому вы пишете на него тест в habhub.test.ts:


namespace $.$mol {
    $mol_test({

        'gist content is title + body'() {

            const app = new $acme_habhub

            app.gists = ()=> [
                {
                    id : 1 ,
                    title : 'hello' ,
                    body : 'world' ,
                }
            ]

            $mol_assert_equal( app.gistContent( 0 ) , '# hello\nworld' )

        }

    })
}

Перезагрузив страницу, вы обнаруживаете в консоли:


Неправильно формируется текст


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


Ожидание статей с GitHub


Статьи с GitHub


Блеск! Проверив, своё маленькое приложение на корректность, вы с чувством полного удовлетворения коммитите изменения и идёте в столовую праздновать столь быстрое завершение задачи, предполагающее неблокирующие запросы, визуализацию markdown и ленивый рендеринг...


Что это тут выпирает?


Кое-что всё же омрачает вашу радость и вам приходится вернуться к отладчику — страница довольно долго открывается пытаясь отрендерить сразу все статьи.


Долгий рендеринг страницы


Каждый $mol_texter является наследником от $mol_lister, который умеет лениво рендерить вертикальные списки, дорендеривая их по мере прокрутки. Поэтому при открытии страницы статьи рендерятся не целиком, а лишь некоторое число первых блоков. Чтобы и сами $mol_texter исключались из рендеринга, когда точно не влезают в видимую область, достаточно их тоже засунуть в $mol_lister:


$acme_habhub $mol_pager
    title \HabHub
    body /
        < lister $mol_lister
            rows < gisters /
    gister# $mol_texter
        text < gistContent#     footer null

Быстрый рендеринг страницы


Ленивый рендеринг


Работает ленивый рендеринг просто и железно. Любой компонент может предоставить информацию о своей минимальной высоте через свойство heightMinimal. Например, $mol_texter_rower указывает минимальную высоту в 40 пикселей, меньше которых он занимать не сможет, независимо от содержимого, css правил и ширины родительского элемента. Компонент $mol_scroller отслеживает позицию скроллинга и устанавливает свойство $mol_viewer_heightLimit контекста рендеринга таким образом, чтобы гарантированно накрыть видимую область (позиция скроллинга плюс высота окна). Контекст автоматически передаётся всем отрендеренным внутри компонентам и доступен в них через this.context().$mol_viewer_heightLimit(). Используя всю эту информацию, компонент $mol_lister рассчитывает сколько элементов списка нужно отрендерить, чтобы гарантированно накрыть видимую область. Так как все упомянутые свойства реактивны, то при изменении состава элементов, позиции скроллинга и размеров окна, происходит автоматический дорендеринг недостающих или удаление лишних элементов списка.


Именно за счёт ленивого рендеринга $mol и оказывается лидером в тестах производительности. Без него, производительность $mol была бы на уровне Angular. Кто-то может возразить, что это не честно. Однако, это не менее честно, чем Virtual DOM в React, позволяющий не делать то, что можно не делать. При этом ускорение в обоих случаях достаётся почти бесплатно, без километров хрупкой логики, описывающей когда и что нужно делать, а когда и что — не нужно.


Ленивый рендеринг позволяет быстро показывать пользователю экран, практически независимо от объёмов выводимых данных. Так как часть компонент не рендерится, то и данные для них не запрашиваются, что позволяет и грузить их лишь по мере надобности, ничего не меняя для этого в слое отображения. Единственный скользкий момент — если прокрутить страницу сразу в самый конец списка, то таки придётся подождать полного рендеринга всех элементов. Но это достаточно редкий случай в повседневном использовании, ведь благодаря фильтрам и сортировке, куда проще сделать так, чтобы нужные данные оказались наверху, чем вручную искать их огромном списке.


Исключительные ситуации


Всё бы хорошо, но после последних оптимизаций куда-то пропал индикатор загрузки. Вы открываете DOM-инспектор и видите там следующую картину:


Список нулевой высоты


Блок $acme_habhub.root(0).lister() в который выводится список статей не смог отрендериться так как список статей ещё не загружен, поэтому для него автоматически был установлен атрибут mol_viewer_error с типом ошибки в качестве значения. Для типа ошибки $mol_atom_wait по умолчанию рисуются бегущие полоски. Но вот беда, в отсутствии содержимого этот блок схлопнулся до нулевой высоты, и поэтому не видно индикатора загрузки. Самое простое решение — задать для этого блока минимальную высоту:


[acme_habhub_lister] {
    min-height: 1rem;
}

Список небольшой высоты


Но что если загрузка оборвётся или произойдёт её какая-либо ошибка?


Индикатор ошибки


Так дело не пойдёт! Надо сообщить пользователю что пошло не так. Вы могли бы просто перехватить исключение в gisters() и нарисовать вместо списка статей сообщение об ошибке. Но это достаточно типовой код, который удобнее вынести в отдельный компонент, который бы принимал некоторое свойство, и если при его вычислении происходила бы ошибка — не просто падал, а показывал сообщение пользователю. Именно так и работает $mol_statuser:


$acme_habhub $mol_pager
    title \HabHub
    body /
        < statuser $mol_statuser
            status < gisters /
        < lister $mol_lister
            rows < gisters /
    gister# $mol_texter
        text < gistContent#     footer null

Гламурное сообщение об ошибке


Как можно заметить, особое внимание в $mol уделено толерантности к ошибкам. Если какой-то компонент упал, то только он и выйдет из строя, не ломая всё остальное, не зависящее от него, приложение. А если источник проблемы устранён, то и компонент следом возвращается к нормальной работе.


Так как код на $mol в подавляющем большинстве случаев синхронен, то и try-catch работает как полагается. Но что если данные ещё не загружены и за ними нужно сходить на сервер? Это самая натуральная исключительная ситуация для синхронного кода. Поэтому, модуль загрузки данных делает как полагается асинхронный запрос, но вместо немедленного возврата данных (которых ещё нет), кидает специальное исключение $mol_atom_wait, которое раскручивает стек до ближайшего реактивного свойства, которое его перехватывает и запоминает в себе. А когда данные придут, то это свойство будет вычислено повторно, но на этот раз вместо исключения, будут уже синхронно возвращены данные. Таким не хитрым способом достигается абстрагирование кода всего приложения от асинхронности отдельных операций, без необходимости выстраивать цепочки обещаний и превращения половины функций в "асинхронные" (async-await).


Тут же стоит отметить элегантную магию, доступную во всех современных браузерах благодаря Proxy API. В общем случае, при обращении к реактивному свойству, в котором сохранён объект исключения, это самое исключение бросается незамедлительно. Но если поддерживается Proxy API, то возвращается лишь прокси, который бросает исключение, при попытке взаимодействия с результатом. Это позволяет продолжить выполение кода, отложив "синхронизацию" до момента, когда возвращаемые данные реально понадобятся.


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


@ $mol_mem()
greeting() {

    const config = $mol_http_resource_json.item( './config.json' ).json()
    // Запущен асинхронный запрос, а в config помещён Proxy

    const profile = $mol_http_resource_json.item( './profile.json' ).json()
    // Запущен асинхронный запрос, а в profile помещён Proxy

    // В этот момент исполнение будет остановлено, а в свойство greeting будет помещено исключение $mol_atom_wait
    const greeting = config.greeting.replace( '{name}' , profile.name ) 

    // Сюда исполнение уже не дойдёт
    return greeting
}

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


@ $mol_mem()
greeting() {

    const config = $mol_http_resource_json.item( './config.json' ).json()
    // В config помещён json, полученный с сервера

    const profile = $mol_http_resource_json.item( './profile.json' ).json()
    // В profile помещён json, полученный с сервера

    const greeting = config.greeting.replace( '{name}' , profile.name ) 
    // greeting будет вычислен на основе config и profile 

    // Наконец, дошли до конца и вернули актуальное значение
    return greeting
}

Свистелки и блестелки


Как можно было заметить, $mol содержит всё необходимое, чтобы просто взять и начать делать приложение. Не нужно ничего конфигурировать, а в комплекте идёт библиотека стандартных адаптивных компонент, содержащая как тривиальные компоненты типа $mol_filler, который выводит небезызвестный "Lorem ipsum", так и комплексные компоненты, типа $mol_grider, который предназначен для отображения огромных таблиц с плавающими заголовками.


При рендеринге DOM-элементу устанавливается атрибут с именем класса компонента, а также именами всех классов-предков. Это позволяет, например, задать для всех кнопок общие стили:


[mol_clicker] {
    cursor: pointer;
}

А потом для какого-то конкретного типа кнопки перегрузить их:


$mol_clicker_major $mol_clicker

[mol_clicker_major] {
    background: var(--mol_skin_accent);
    color: var(--mol_skin_accent_text);
}

Кроме того, в соответствии с методологией БЭМ для всех вложенных компонент устанавливаются контекстно-зависимые атрибуты вида my_signup_submitter, где my_signup — имя класса владельца, а submitter — имя свойства, в которое объект сохранён:


$my_signup $mol_pager
    body /
        < submitter $mol_clicker_major
            childs /
                < submitterLabel @ \Submit

[mol_signup_submitter] {
    font-size: 2em;
}

Такая логика работы позволяет избавить разработчика от необходимости вручную дописывать к каждому dom-элементу "css-классы" и поддерживать порядок в их именовании. В то же время, она даёт высокую гибкость при композиции компонент — всегда можно как-то по особенному стилизовать конкретный компонент в конкретном контексте его использования, без риска сломать что-то в других местах.


Так как один и тот же компонент может использоваться в совершенно разных местах, в совершенно разных приложениях, выполненных в совершенно разных дизайнах, то критически важно, чтобы компонент мог адекватно мимикрировать под общий дизайн приложения. Основным аспектом этой мимикрии являются цвета. Поэтому, как минимум стандартные компоненты, не содержат в себе никакой цветовой информации, вместо этого беря её из глобальных констант. В $mol эти константы сгруппированы в модуле $mol_skin. Реализуя своё приложение, вы можете переопределить эти константы и все компоненты перекрасятся в соответствии с ними:


:root {
    --mol_skin_base: #c85bad;
}

Гламурный дизайн


Если вам не хватит стандартных констант — вы всегда можете завести свои. Это позволит тем, кто будет использовать ваши компоненты, так же просто интегрировать их в свой дизайн, как и стандартные.


Экстренное торможение


$mol весьма прост и гибкок, однако, как и любой фреймворк, он имеет и некоторые жёсткие рамки, которые поощряют "хорошие" практики и препятствуют "плохим". Что такое "хорошо", а что такое "плохо", зависит от целей. И для $mol они ставились такие:


  • Создание быстрых приложений. С быстрым приложением работать — одно удовольствие. Как конечному пользователю, так и изначальному разработчику.
  • Быстрое создание приложений. Это даёт не только удешевление производства, но и больше времени на другие этапы: от согласования, до внедрения.
  • Создание кроссплатформенных приложений. Веб платформа как ничто лучше подходит для этих целей.
  • Долгосрочная поддержка созданных приложений. Она не должна превращаться в снежный ком из костылей и заплаток.
  • Минимизация багов в созданных приложениях. Они и по репутации больно бьют, и внедрение затягивают.
  • Создание компактных приложений. Чем меньше кода, тем быстрее он стартует, тем меньше в нём багов, тем быстрее его писать.
  • Создание межпроектной кодовой базы. Это и профессионала ускоряет, и новичку позволяет быстрее влиться в процесс.
  • Расспараллеливание разработки приложений. Горизонтальная и вертикальная декомпозиция, позволяют большему числу людей работать над одним приложением используя наработоки друг друга, что приводит к ускорению поставки новых версий.

По итогу, на текущий момент можно выделить следующие свойства $mol, которые в других фреймворках либо не встречаются вообще, либо встречаются, но в несколько куцем виде:


  • Минимум конфигурирования — только несколько простых соглашений и максимальная автоматизация.
  • Микромодульность — приложение собирается из множества маленьких модулей. Нет строго выделенного ядра.
  • Автоматические зависимости между модулями — детектирование зависимости по факту использования и автоматическое включение зависимостей в пакет при сборке.
  • Многоязычные модули — нет какого-то выделенного языка, все языки равноправны при поиске зависимостей и сборке пакета.
  • Статическая типизация — по возможности используется TypeScript для исходников и промежуточных файлов.
  • Множество приложений и библиотек в одной кодовой базе — сборка любого модуля как независимого пакета для деплоя куда бы то ни было.
  • Полная реактивность — автоматическое обнаружение и эффективная актуализация зависимостей между состояниями.
  • Синхронный код, но неблокирующие запросы — в том числе и параллельные запросы, когда это возможно.
  • Полная ленивость — ленивая отрисовка, ленивая инициализация свойств, ленивая загрузка данных, ленивая сборка.
  • Контроль жизненного цикла объектов — автоматическое уничтожение при утрате зависимостей от него.
  • Высокая компонуемость — легко соединять даже те компоненты, которые написаны без оглядки на настраиваемость.
  • Толерантность к ошибкам — исключительные ситуации не приводят к нестабильной работе приложения.
  • Кроссплатформенность — модуль может содержать разные версии кода под разные окружения и для разных окружений собираются отдельные пакеты.
  • Ориентация на исследование рантайма — везде есть "хлебные крошки" помогающие найти концы.
  • Человекопонятные идентификаторы объектов — генерируются автоматически на основании имён свойств, которые ими владеют.
  • Логирование изменения всех состояний — поддерживается фильтрация по содержимому идентификатора.
  • Пространства имён вместо изоляции — простой доступ из консоли к любому состоянию, пространства имён соответствуют расположению модулей в файловой системе.
  • Автогенерация BEM-атрибутов — не нужно вручную прописывать классы, имена в CSS гарантированно соответствуют именам в JS/TS/view.tree, поддерживается наследование.

Болванс Чик


На этом наша история… не заканчивается. Возможно именно вы станете соавтором её продолжения, привнесёте с собой свежих идей, а может даже и сочных запросов на слияние. На этом распутье у вас есть несколько дорог:


Поделиться с друзьями
-->

Комментарии (120)


  1. nazarpc
    31.10.2016 09:10
    +3

    Бенчмарки, конечно, странные. Меня привлекло что какая-то (не важно какая) библиотека может в принципе быть быстрее платформы. Я решил немного исправить бенчмарк: https://github.com/eigenmethod/mol/pull/62
    Прирост получился ~20x в Firefox Nightly и ~10x в Chromium Nightly.


    Теперь в Chromuim даже повторный рендеринг $mol вдвое медленнее обычного нативного рендеринга:)


    1. vintage
      31.10.2016 09:18

      Фактически вы тут замеряете время установки свойства innerHTML и всё.


      1. nazarpc
        31.10.2016 09:22
        +4

        Я с другими инструментами не знаком:) Был бы Polymer — его бы тоже оптимизировал, но с прочими предоставленными не работал, так что извините.


        Как по мне — так производительность фреймворка это на самом деле количество оверхеда, который он привносит. То есть он не может быть быстрее платформы. Понятно, что в более сложных ситуациях вам может быть не очень удобно всё писать без фреймворка, но это не отменяет того факта, что ни $mol, ни React не могут обогнать платформу, которую они используют под капотом.


        1. vintage
          31.10.2016 09:40
          +1

          Добавьте Полимер, сравним :-)


          Вы тут сделали 2 оптимизации:


          1. Заменили установку обработчиков событий на делегирование. Это не сильно влияет на результат. Да и оптимизацию эту можно применить к любому фреймворку. А SAPUI5 использует её по умолчанию. Как видно, ему это слабо помогает.


          2. Убрали setTimeout. Из-за чего, стало замеряться лишь время генерации html и установки свойства innerHTML. Без формирования DOM, без раскладки элементов и без собственно их отображения.

          image


          А про ускорение по сравнению с платформой никто и не говорит, но вот наивная реализация на фреймворке вполне может быть быстрее наивной реализации на ванилле.


          1. nazarpc
            31.10.2016 09:49
            +1

            Да, если вы действительно хотите отрендерить, тогда вместо setTimeout() лучше container.offsetHeight, это синхронно вызовет нужные эффекты. Но если на чистоту, то и $mol не рендерит весь список:) Похожий механизм ленивого рендеринга есть и в Polymer: https://elements.polymer-project.org/elements/iron-list


            Писать бенчмарк для Polymer ленюсь)


            1. vintage
              31.10.2016 10:06

              Лучше всё же setTimeout. offsetHeight доведёт дело лишь до layout, но не до painting.


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


              Неужели на полимере так сложно реализовать столь простое приложение? ;-)


              1. nazarpc
                31.10.2016 10:31
                +1

                А зачем отрисовка? Пусть отрисовывается позже, это ведь только часть страницы, в реальности вы же не будете после инициализации каждого виджета делать отрисовку, впрочем, как и раскладку (если ориентироваться на производительность, то обоих должно быть минимум).


                Не сложно, а именно лень.


                1. vintage
                  31.10.2016 10:45

                  В реальности, наиболее важным показателем является время между изменением данных и тем моментом, когда пользователь уже может взаимодействовать с интерфейсом. Идеально, если всё это время (включая отрисовку) укладывается в 16мс, что даёт 60 кадров в секунду без дропов.


          1. babylon
            31.10.2016 18:12

            Либо я не выспался, либо нездоров. Как реализовано делегирование в представленных примерах?


            1. vintage
              31.10.2016 18:28

              Тут мы обсуждали вот этот патч.


        1. nuit
          31.10.2016 10:35
          +7

          >Как по мне — так производительность фреймворка это на самом деле количество оверхеда, который он привносит. То есть он не может быть быстрее платформы.

          Хорошо что хоть кто-то это понимает :) Являясь мэйнтэйнером пары бэнмарков меня просто поражает различная глупость на которую люди идут ради того чтобы выиграть бэнчмарки, а потом демонстрировать всем какие они молодцы. С некоторыми «оптимизациями» вроде «ленивого» рендера успешно боролся в бэнчмарках, но всё же приходится закрывать глаза на некоторые «оптимизации» из-за которых библиотеки начинают демонстрировать на различных кэйсах чудо производительность.

          Весь этот бэнчмаркетинг уи либ — это полнейший трэш.


          1. nazarpc
            31.10.2016 10:45
            +2

            На самом деле нужно просто обозначить, а что мы, собственно, меряем. Ибо в зависимости от выбранных метрик получатся совершенно разные результаты. Ещё сложнее то, что нужно учитывать взаимодействие с пользователем, к примеру, рендер первого скрина за 100 мс и рендер интерактивной страницы за ещё 1500 мс может быть предпочтительно полному рендеру интерактивной страницы за 1000 мс, если она всё это время белая без содержимого.


            Короче, неблагодарное дело вот это всё без четких критериев:)


          1. vintage
            31.10.2016 11:10

            И зачем вы боретесь с "ленивым рендером"? Конечному пользователю совершенно не важно, рендерится ли что-то в области, которую он не видит, или нет.


            Может расскажете поподробней про упомянутый "треш" и "глупые оптимизации", чтобы я мог это всё учесть?


            1. nuit
              31.10.2016 11:33
              +2

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

              >Может расскажете поподробней про упомянутый «треш» и «глупые оптимизации», чтобы я мог это всё учесть?

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


              1. vintage
                31.10.2016 11:49

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


                Не распишете вкратце как работает этот "умный ресайклинг"? И что за бенчмарк у вас?


                1. nuit
                  31.10.2016 12:13

                  >Куда важнее какую производительность показывает именно идеоморфный код, который вы скорее всего напишите с использованием заданного фреймворка.

                  Я тоже так считал, когда несколько лет назад преследовал цель сделать самую быструю реализацию виртуального дома+компонент, добавил кучу различных оптимизаций в ущерб примитивного API. Но правда в том что в 95% случаев мне совершенно не нужно выжимать каждую микросекунду, пытаться сделать так что на апдэйте будет происходить как можно меньше лишних операций, теперь я понимаю что хочу писать невероятно тупой код, и в реальноых приложениях ничего не будет тормозить, если я не буду делать совсем глупых вещей. А ленивый рендер обычно уместен только в паре компонент вроде дататэйблов, и его не так уж сложно добавить.

                  Я так же начинал с умной pull-based реактивности с каждым обсёрвбл значением, для списков чуть посложнее специальные эвенты чтобы небыло тысяч листенеров на кэйсах с фильтраций, ну чтобы прям совсем никаких лишних операций небыло :) И может я конечно недостаточно хороший разработчик, но вобщем писать реальное приложение в таком стиле для меня достаточно сложно, особенно отлаживать потом какие-то странные баги в моём коде.

                  >Не распишете вкратце как работает этот «умный ресайклинг»? И что за бенчмарк у вас?

                  Когда отрабатывает trackBy алгоритм при обновлении чилдрен листов, каждую ноду можно как-то уникально идентифицировать, и вот когда происходит удаление компоненты, вместо примитивного списка, используется map в который по уникальному идентификатору помещается удалённая компонента. А когда потом с таким же идентификатором она вставляется обратно, то из пула вынимается нужная компонента, в которой ничего не нужно обновлять, просто сделать `insertBefore`. Такой кэйс возможен только в коряво сделаном бэнчмарке, вроде моего :)

                  https://localvoid.github.io/uibench/


                  1. vintage
                    31.10.2016 15:37

                    В случае $mol, быстрый показ экрана — это скорее побочный эффект от ленивой архитектуры, чем первоцель. Печально, что всё обсуждение свелось к калибровке линеек.


                    Взять, к примеру, приложение из начала статьи: https://eigenmethod.github.io/mol/app/supplies/
                    Число закупок и число позиций в каждой закупке может быть как большим так и маленьким., но (благодаря ленивости) показываться экран будет одинаково быстро. При этом разработчику не придётся судорожно "не так уж сложно добавлять" ленивый рендеринг, когда вдруг выяснится, что у 1% клиентов число позиций в заказе необычайно высоко.


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


                    Фактически в $mol неотключаемый "trackBy". Любой компонент имеет уникальный идентификатор. И это на самом деле классно, ибо нет ничего плохого в том, что для переноса элемента из начала в конец достаточно просто перенести узел, а не перерендеривать весь список.


                    Так что бенчмарк в этом смысле как раз таки не коряв. Добавил в него $mol и особых звёзд он не хватает. Несколько напрягает в нём то, что он требует простановки классов. Для $mol в них нет необходимости — он и так проставляет кучу атрибутов для стилизации.


                    1. nuit
                      31.10.2016 15:57

                      >Фактически в $mol неотключаемый «trackBy». Любой компонент имеет уникальный идентификатор.

                      Еслиб работал «trackBy», то бэнчмарк бы не показывал красный флажок в «Preserve State», тк не происходит перестановка дом элементов и внутреннее состояние теряется.

                      >Несколько напрягает в нём то, что он требует простановки классов.

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


                      1. vintage
                        31.10.2016 16:11

                        Ну так там реализация, которая "трекает" по индексу, а не по идентификатору из исходных данных. Вот и получается, что компоненты стоят на месте и меняют данные внутри.


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


                        1. nuit
                          31.10.2016 16:26

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

                          Я обычно всё время вырубаю sCU оптимизацию когда бэнчмаркаю всех, и отношусь к этому бэнчу как будто это приходят данные с сервера, поэтому у всех жаваскрипт объектов не сохраняется identity, и лишь только есть параметр «id» по которому можно как-то связывать данные, и если библиотека не умеет в таком случае сохранять внутреннее состояние дом нодов, то моё отношение к такой библиотеке сразу сильно портится :) Во всех популярных УИ либах с этим нет никаких проблем.

                          >Как минимум можно убрать тесты на простановку классов.

                          Можно добавить опцию `disableChecks` и не будет никаких проверок :)

                          https://eigenmethod.github.io/mol/perf/uibench/?disableChecks=true


                          1. vintage
                            31.10.2016 18:24

                            Я имел ввиду, что реализация на $mol там в лоб — компоненты создаются по индексу. Иначе бы пришлось заводить дополнительный словарь для маппинга идентификаторов на данные. Ведь "сервер" у вас возвращает данные в виде таблицы, а не словаря, как было бы правильнее.


                            Проверок-то может и не будет, да вот сам бенчмарк завязан на имена классов и падает. Плюс стили к ним привязаны.


                            1. nuit
                              31.10.2016 18:34

                              >Я имел ввиду, что реализация на $mol там в лоб — компоненты создаются по индексу.

                              А сорри, не понял сразу :)

                              >Проверок-то может и не будет, да вот сам бенчмарк завязан на имена классов и падает. Плюс стили к ним привязаны.

                              Точно, там же всякая фигня с проверкой на scu итп, где вынимаются по класснэймам.

                              Вобщем я бы с радостью забил на всю эту тему бэнчмарков и не хочу уже прикасаться к этой помойке, особенно там сейчас такой говнокод из-за того что она развивалась эволюционным путём :)

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


                              1. vintage
                                31.10.2016 18:38

                                Не, я её зря добавлял что-ли? Пусть будет. :-)


                1. youlose
                  31.10.2016 12:21

                  «Где-то ленивый рендеринг достаётся почти бесплатно и грех им не пользоваться, а где-то надо шаманить с бубном и всё-равно выходит криво, из-за чего его применяют лишь в крайних случаях»
                  Ну тогда надо сравнивать хотя бы ленивую реализацию с кривой ленивой реализацией другого инструмента.
                  Я вот у себя открыл первый тест и там для $mol, лениво рендерятся элементы, а для angularjs целиком, хотя там можно поставить angular-vs-repeat который покажет совершенно другие цифры.

                  У меня в хроме из-за чего-то $mol рендерит все элементы и результаты не такие как у автора ($mol медленнее ангуляра) — скриншот
                  А в firefox он рендерит всего 60 элементов, а вот ангуляр все 2 тысячи, хотя плагином выше подобный результат элементарно сделать.


                  1. vintage
                    31.10.2016 15:59

                    Сравнивать надо всё же идеоморфный код, а не специально оптимизированный под конкретный бенчмарк. Поэтому я рекомендую смотреть в сторону ToDoMVC Benchmark.


                    angular-vs-repeat годится лишь для крайне ограниченного числа случаев, когда у нас список однотипных элементов фиксированной высоты. Но таки да, в данном бенчмарке он бы сгодился и позволил бы Ангуляру ударить в грязь лицом не в синтетическом бенчмарке, а лишь в реальном приложении, где всё будет не так радужно. Банальное изменение высоты элемента через стили и всё поехало.


                    Результаты не такие, потому, что вы промотали список в конец, а потом перезагрузили страницу. Так как при загрузке страницы список нулевой высоты, то позиция скроллинга не восстанавливается (стандартные компоненты восстанавливают своё состояние при перезагрузке). По хорошему, тут нужно просекать, что скролл не переместился и сбрасывать сохранённое значение на актуальное. Завёл на это дело задачу.


                    1. youlose
                      31.10.2016 16:40

                      «Сравнивать надо всё же идеоморфный код, а не специально оптимизированный под конкретный бенчмарк.»
                      Мне кажется что вы немного неверно видите суть бенчмарков фреймворков, для них важны скорость решения задачи, безопасность, удобство поддержки и.т.п., а не оптимизированность какой-то одной фичи, которая очень редко используется (я вот angular-vs-repeat всего несколько раз использовал, хотя у меня есть проекты которые я уже по не скольку лет поддерживаю), да и вообще вывод нескольких тысяч элементов одного типа на страницу обычно говорит о проблеме с дизайном и юзабилити сайта, это не совсем программистская задача.

                      «angular-vs-repeat годится лишь для крайне ограниченного числа случаев, когда у нас список однотипных элементов фиксированной высоты.»

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


                      1. vintage
                        31.10.2016 18:37

                        Разной, но фиксированной. Вы обязаны предрассчитать высоту элемента программно и не дай бог его высота окажется не такой — у вас всё начнёт скакать и прыгать. А ещё он теряет инерцию при скроллинге пальцем.


          1. inoyakaigor
            31.10.2016 12:32
            +2

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


  1. Odrin
    31.10.2016 12:40
    +5

    Извините, но пихать в id dom-элемента строковое представление вызова функции, еще и не уникальное — это жесть. Именование_модулей_тоже_вызывает_сомнения, читаемость кода очень плохая.


    1. vintage
      31.10.2016 16:01

      Вообще-то уникальное.
      ВыСчитаетеТакоеИменованиеСильноЛучше?


      1. bromzh
        31.10.2016 19:24
        +1

        Ну именование всё же дело вкуса, но почему id?


        Помню, JSF страдало такой болезнью: если не задать id, JSF сгенерит свой, а если задать — то добавит перед ним id родительских компонентов через двоеточие. Надо ли говорить, что это только запутывает, когда пытаешься работать с jsf-компонентами извне. А получить реальное значение id в DOM совсем нетривиально.


        Вот если мне нужно компоненту в $mol задать определённый id, как это сделать?


        1. vintage
          31.10.2016 19:33

          А зачем вам это делать?


          1. bromzh
            31.10.2016 21:06

            Как минимум для:


            <label for="name">Name</label>
            ...
            <input type="text" id="name" name="name>

            Другой пример — якоря


            <h1 id="foo">Foo</foo>
            <a href="#foo">go to foo</a>


            1. vintage
              31.10.2016 22:06

              Жёсткий хардкод идентификаторов плохо уживается со сборкой приложений из компонент. Для подписей к полям, я бы рекомендовал использовать $mol_labeler.


              Якоря плохо уживаются с SPA приложениями, на которые ориентирован $mol. В общем случае, вы не можете гарантировать, что к моменту перехода по якорю, у вас уже отрендерен элемент с нужным идентификатором (банально, ждём загрузки данных). Поэтому интерактив со скроллингом к нужным частям страницы, реализуется программно. $mol пока не предоставляет обобщённого решения для подобных задач. Но вручную задаваемые идентификаторы тут слабо помогут.


              Впрочем, задать свой id, как и любой другой атрибут возможность есть. Но лучше ею не пользоваться.


              1. justboris
                31.10.2016 22:48

                В вашем примере у текстового поля тега label вообще нет.


                А это важно как для семантики, так и просто для удобства: при клике на label фокус станет в поле ввода автоматически, пользователям проще целиться.


                1. vintage
                  01.11.2016 09:40
                  +2

                  Отлично, вы обнаружили проблему в компоненте. Что нужно сделать?


                  1. Создать issue с предложением сделать, чтобы этот компонент рендерился в элемент label.
                  2. Не ждать, пока это сделает кто-то, а сделать это самому и предложить pull-request.
                  3. Создать свой собственный компонент, как наследника от стандартного, но редрерящегося в label.
                  4. Написать в комментариях, что фреймворк ни на что не годен, так как там есть столь фундаментальный архитектурный просчёт.

                  Семантика для SPA не особо важна, хотя и не лишняя. А вот доступность — да, желательна. Правда я не видел ещё ни одного человека, который бы фокусировал поле ввода кликом по подписи к нему. Единственное полезное применение label — группировка стандартного чекбокса и текста к нему. И то, это скорее костыль. По логике, чекбокс — это частный случай кнопки, которая переключает состояние между несколькими. Код типа такого:


                  <label> <input type="checkbox"/> Subscribe to subscription </label>

                  фактически создаёт эту самую кнопку, только замысловатым образом. Поэтому предпочтительным является создание кнопки с текстом внутри и стилизация её под чекбокс, если в этом есть необходимость, а не рендеринг стандартного чекбокса с хитрыми манипуляциями для замены его на дизайнерский. А если действительно волнует доступность, а не только лишь на словах, как обычно, то достаточно добавить wai-aria аттрибуты.


                  Но вернёмся к исходной теме. Мой тезис в том, что ручная простановка идентификаторов несёт вреда куда больше, чем пользы. Вы привели 2 контрпримера. Первый из которых не требует идентификаторов вообще. Второй ориентирован на веб-страницы, а не веб-приложения. Есть ещё идеи, зачем вам может потребоваться вручную проставлять идентификаторы элементам?


                  1. justboris
                    01.11.2016 12:04
                    +1

                    Отлично, вы обнаружили проблему в компоненте. Что нужно сделать?

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


                    Для создания issue на Github неплохо сначала начать пользоваться фреймворком, чего я делать не собираюсь.


                    Семантика для SPA не особо важна, хотя и не лишняя.

                    Второй ориентирован на веб-страницы, а не веб-приложения.

                    То есть вы хотите сказать, что когда форма на веб-страничке становится достаточно сложной, чтобы повесить на нее ярлык, "веб-приложение", то можно тут же забить на правила семантической верстки и реализовывать всю функциональность на js?


                    1. vintage
                      01.11.2016 12:49
                      -3

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


                      Нет никакой "семантической вёрстки". Есть лишь инструменты, которые определённым образом решают определённые задачи. Если ваша задача состоит в том, чтобы ваша "страничка" работала без JS, то вы выбираете инструменты, встроенные в браузер. Если у вас такой задачи не стоит, то ваш выбор инструментов значительно расширяется.


                      1. justboris
                        01.11.2016 13:16
                        +2

                        Лучше была бы аналогия:
                        "Я не собираюсь пользоваться спорт-каром у которого нет руля и я не могу им управлять"


                        1. vintage
                          02.11.2016 08:16
                          -3

                          Скорее уж "я не собираюсь пользоваться спорт-каром, у которого есть ABS". Задача фреймворка — не удовлетворение любых хотелок, а обепечение определённых характеристик. Цели $mol описаны в конце статьи.


                  1. bromzh
                    01.11.2016 12:48
                    +2

                    Отлично, вам указали на проблему. Что надо сделать?


                    1) Сказать человеку, чтобы составлял на гитхабе issue
                    2) Сказать человеку, чтобы делал пул-реквест
                    3) Сказать, чтобы сам делал компонент, потому что
                    4) Попытаться исправить ошибку


                    У вас практически в каждом компоненте присутствуют косяки, я бы устал составлять issues.
                    Но даже если я их и составлю, я знаю какая будет реакция: не нужно, у меня лучше. Я привёл примеры использования id, что вы ответили? "Не нужно, это контрпримеры. А даже если и нужно, то т.к. мой фреймворк не умеет, то тоже не нужно." Поэтому вы называете свой фреймворк "идеальным"?


                    Вы постоянно жалуетесь на кривые web-стандарты — формы не те, label не тот, id не нужен. Так пишите письма в w3c со своей аргументированной позицией, пусть сделают как надо.


                    Касательно того же чекбокса, почему вы сами не проставили нужные атрибуты у элементов? Ок, пускай у вас чекбокс — это button. Ну так и добавьте в неё type="button" role="checkbox". И используйте стандартный атрибут checked. Или вы без issue на гитхабе не сможете это?


                    1. vintage
                      02.11.2016 08:53
                      -3

                      Один человек считает это критической ошибкой. Другой — минорной особенностью. Как им друг с другом договориться?


                      1. Создать отдельную issue посвящённую этому компоненту и обсудить там все аспекты его поведения. В результате получить хороший компонент, который можно будет использовать в различных случаях.
                      2. Кричать, что фреймворк ни на что не годен под статьёй с описанием его общих принципов, аргументируя тем, что "в каждом компоненте есть косяки".

                      Есть косяки — излагайте. Желательно в виде issue. Комментарии на Хабре — так себе таск-трекер. И пока не напишешь — не узнаешь "не нужно, есть решение лучше" или "спасибо за идею, уже внедряем". Но это ведь не интересно, правда? Куда интересней устроить срач, приписывая оппоненту слова, которые он не говорил.


                      У вас очень наивное представление о W3C :-) Веб не изменится в одночасье, а приложения нужно делать ещё вчера, на технологиях, выпущенных несколько лет назад.


                      На последок, открою вам страшный секрет: у меня всего 2 руки, одна голова, в сутках 24 часа, а заниматься улучшениями можно бесконечно. И нет, обеспечение доступности — это не только и не столько расстановка атрибутов, сколько проверка как реагируют на твой код различные пользовательские агенты.


                  1. saksmt
                    01.11.2016 22:36
                    +1

                    >> Правда я не видел ещё ни одного человека, который бы фокусировал поле ввода кликом по подписи к нему

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

                    >> Второй ориентирован на веб-страницы, а не веб-приложения.

                    В чём по вашему принципиальная разница?


                    1. vintage
                      02.11.2016 09:04

                      Как минимум в том, что веб-страница — это данные, которые дополняются интерфейсом. А веб-приложение — это интерфейс, который загружает данные.


                      1. saksmt
                        02.11.2016 10:27

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

                        UPD: и хабру тоже (якорь в комментах)


                        1. vintage
                          02.11.2016 10:50

                          В обоих случаях происходит программная (через JS) прокрутка. Например, на Хабре нет элемента с идентификатором "first_unread".


                          1. saksmt
                            02.11.2016 10:56

                            На хабре — нет, на гитхабе оно тоже не совсем полностью js. Там судя по вёрстке js только «user-content-» префикс добавляет.

                            Зато вполне есть «comment_9889944» (ваш комментарий) и оно действительно работает как якорь без всякого js


                            1. vintage
                              02.11.2016 11:22

                              И? Факт в том, что стандартного поведения недостаточно даже для этих недоприложений :-)


                              1. saksmt
                                02.11.2016 21:46

                                Вполне достаточно для: хабра, LOR-а. Почти достаточно для гитхаба. И это если не сильно задумываться


                                1. vintage
                                  03.11.2016 08:04

                                  Всё это не веб-приложения. Мы о чём спорим-то?


                                  1. saksmt
                                    03.11.2016 08:32

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


                                    1. vintage
                                      03.11.2016 09:44
                                      -1

                                      А по вашему это не так и они сначала грузят веб-клиент, который уже загружает данные?


                                      1. saksmt
                                        03.11.2016 21:09

                                        Гитхаб вроде вполне себе SPA, если вы это подразумеваете.


                                        1. vintage
                                          03.11.2016 21:53
                                          -1

                                          Да нет, не SPA. Там при переходах грузится новая страница, которая и заменяет старую. Бестолковейшее применение AJAX.


                                          1. arvitaly
                                            03.11.2016 22:15

                                            > Бестолковейшее применение AJAX.
                                            А как же избавление от нужды грузить core-компоненты системы? Я знаю, конечно, один способ, версионировать загруженную платформу и в куки записывать версию, тогда сервер может отдавать лишь нужную часть, но ajax попроще будет.


                                            1. vintage
                                              03.11.2016 23:49

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


                                              1. arvitaly
                                                04.11.2016 23:27

                                                Если не делать бандлы, то количество сетевых запросов возрастает резко, если делать, то обновление возможно только целиком. Да и сеть не весь вопрос, загрузка ядра на клиента тоже может быть затратным мероприятием.
                                                А для приложения с миллионами придирчивых пользователей уж точно.
                                                P.S. Я замечаю изменения, и вообще, именно с этого и начались клиентские изменения в сайтах, они не меняют experience, зато экономят ресурсы и компании и клиента.


                                          1. saksmt
                                            03.11.2016 22:18

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


                                            1. vintage
                                              04.11.2016 00:04

                                              Выглядит он примерно так:


                                              $( document ).on( 'a[href]' , 'click' , event => $.get( this.href + '?_pjax' ).then( html => document.body.innerHTML = html ) )

                                              Плюс фальшивый индикатор загрузки и прочие мелочи.


                                              1. saksmt
                                                04.11.2016 04:02

                                                Сильно в этом сомневаюсь, особенно учитывая скорость работы.


                                                1. vintage
                                                  04.11.2016 09:04

                                                  Ну зачем мне вас обманывать? Мы же не в детском саду :-)


                                                  image


                  1. MetaDriver
                    02.11.2016 08:18

                    Есть ещё идеи, зачем вам может потребоваться вручную проставлять идентификаторы элементам?
                    Насчёт ручной вставки оснований не нашёл, но таки есть идеи автогенерации айдишников, но по бизнес-логике приложения, а не фреймворка.

                    Например, недавно генерил idы для элементов списка сгруппированного по указанному (в рантайме) критерию. Реализация была на ангуларе через вложенные ng-repeat'ы, соответственно при клике по кнопке на элементе списка нужно было вызвать функцию, которая бы его (элемент) для начала однозначно идентифицировала через передаваемый ей из разметки (ангулар жеж!) параметр. При том, хотелось элементы идентифицировать «быстро-вычислимо», типа как по индексу в массиве, а не путём линейного поиска по itemId в двумерном js-масииве. В итоге по скорострельности победила реализация с генерацией айдишника по правилу _{{ ext$index }}_{{ int$index}}, с передачей оного в качестве параметра. Как-то так.

                    Есть ещё пример недавний (тоже ангуларский). В этот раз я купился на скорость доступа по айдишнику к DOM-элементам: по задачке нужно было при скролле проверять на каждом событии позицию элемента относительно окна приложения. Если кто не знает забыл (ох jQuery-поколение) все DOM-элементы с айдишниками напрямую доступны аки поля объекта window. И таки факт — работает очень, очень быстро.


                    1. vintage
                      02.11.2016 09:09

                      Вам не кажется, что у вас "бизнес-логика" как-то уж слишком привязана к DOM, который относится к слою отображения? Уверен, вашу задачу можно было бы решить куда лучше, не пересекая слои.


              1. bromzh
                31.10.2016 23:20
                +2

                Для подписей к полям, я бы рекомендовал использовать $mol_labeler.

                Здорово, только мне нужна не просто подпись, а именно label. Надеюсь, разницу вы знаете? Ведь многие программы для людей с ограниченными возможностями опираются на эту связь (label — input), да и при клике на label связанное поле фокусируется (средствами браузера, а не js), что удобно. Особенно для всяких чекбоксов/радиокнопок.


                К слову о чекбоксах: зачем было их делать кнопками? Ведь для этого есть нативный input[type=checkbox]. В вашей чудо-реализации из-за того что используется кнопка (а не input+label), нельзя, например, выделить текст чекбокса. Плюс, эта самая кнопка идёт с дефолтным типом, а, значит, вас ожидает сюрприз, если такой чекбокс окажется внутри тэга form.


                Кстати, в примерах я нигде не нашёл, чтобы вы при создании форм использовали этот одноимённый стандартный тэг. Может поэтому вы и не знаете об этих проблемах? И поэтому ваши "формы" не субмитятся по нажатию enter, как это делают обычные формы.


                И сдаётся мне, это только вершина айсберга...


                1. vintage
                  01.11.2016 10:00

                  Про чекбокс я ответил чуть выше. Почему вы хотите выделять текст у чекбокса, но не хотите выделять текст у кнопок?


                  Касательно кнопок и форм: формы в html тоже имеют один неприятный просчёт в дизайне — они не могут быть вложенными. Из-за этого приходится городить костыли при реализации. При этом всё, что они дают — некоторое достаточно тривиальное поведение к которому всё-равно потом нужно прикручивать сбоку свою логику. В случае SPA компонент для создания форм, конечно же нужен, и текущая реализация мне и самому не очень нравится, но он точно не должен наследовать ограничения html-form.


                  1. bromzh
                    01.11.2016 12:23

                    Почему вы хотите выделять текст у чекбокса, но не хотите выделять текст у кнопок?

                    Хочу, но не могу. А почему вы решили, что не хочу? С нормальными чекбоксами такой проблемы нет, но у вас она появилась. Так что вы не только наследуете ограничения, но и распространяете их туда, где их не было. Восхитительно!


                    1. vintage
                      01.11.2016 12:32

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


                      Однако, для пользователя выделение текста на кнопках (и чекбоксах в том числе) — это баг. Он пытается кликнуть, но рука соскочила и кнопка зачем-то выделилась.


                      Есть хорошее правило: интерактивные элементы интерфейса не должны выделяться, но должны фокусироваться. А выделяться должен обычный, не интерфейсный текст.


                      1. bromzh
                        01.11.2016 13:05

                        Не надо думать за меня.


                        Чекбокс — это поле с галкой. Текст около него — некое пояснение. Иногда этого текста бывает много, он может быть многострочный. Иногда нужно его выделить и скопировать. Причём, при выделении связанного текста чекбокс всё равно сработает.


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


                        В первом случае текст не относится к интерфейсу, он вне чекбокса, но может быть с ним связан. Во втором случае, текст — это часть элемента, его нельзя вынести. Именно поэтому стандартное поведение такое, какое есть. Оно следует вашему правилу, а вы — не совсем.


                        1. vintage
                          02.11.2016 10:04

                          Вы считаете нормальным, чтобы при выделении текста чекбокс переключался?


                          Всё же стоит различать область взаимодействия и пояснительный текст. Вам не кажется странным кликать по параграфу текста, чтобы произвести какое-либо действие?


              1. saksmt
                01.11.2016 22:41

                >> Впрочем, задать свой id, как и любой другой атрибут возможность есть. Но лучше ею не пользоваться.

                Никогда не надейтесь на условные соглашения, тем более в enterprise, где любой случайный не очень опытный юниор может таким образом создать вам баг на 10+ человеко-часов


                1. vintage
                  02.11.2016 10:11

                  От ССЗБ никакой фреймворк не спасёт :-) $mol никак не привязывается к идентификаторам элементов. Они вставляются в DOM лишь как "хлебные крошки", помогающие исследовать рантайм.


    1. inoyakaigor
      31.10.2016 18:41
      +1

      Поддерживаю. Если уж и хочется что-то запихнуть в атрибуты, то для этого придумали data-* либо свои собственные можно придумать, например, mol-*


      1. vintage
        31.10.2016 18:51

        Ну так свои и придуманы: mol_*


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


        1. SPAHI4
          01.11.2016 09:00

          IDE уже умеют в умный рефакторинг, кое-где и с переводом из camelCase в snake_case


          1. vintage
            01.11.2016 09:03

            Некоторые IDE для некоторых языков в некоторых случаях. Зачем усложнять соглашения и требовать IDE, если можно без этого обойтись?


    1. Holix
      02.11.2016 15:24

      Вкусовщина. Лично для меня горбатый стиль остался в далёком прошлом, там же где пылятся всякие паскали, си++ и виндовсы. И именно идентификаторы через подчёркивание читаются легче, так как подчёркивание больше походит на пробел и соответственно облегчает чтение. А вот слитная запись для чтения трудна.


      Сами сравните: ГорбатоПлотноеИмя или простое_для_чтения_обозначение.


  1. handicraftsman
    31.10.2016 18:42

    Можно ли использовать $mol без nodejs? Я, например, использую ruby.


    1. vintage
      31.10.2016 18:46

      Для сборки в любом случае нужна нода. После сборки создаётся web.js, который можно грузить прямо в браузере.


      1. handicraftsman
        31.10.2016 18:48

        В общем, без ноды не обойтись (njs в разы тяжеловеснее того-же ruby. Да и на heroku такое сложно залить)

        А на ruby/python портировано вообще когда-нибудь будет?


        1. vintage
          31.10.2016 18:54

          На heroku нода замечательно запускается.


          Нет, тут же используется typescript и postcss, которые написаны на js. Портировать их — проще застрелиться. :-)


          1. handicraftsman
            01.11.2016 16:23

            Ну, можно сделать обёртку-враппер вместо портирования. Именно так в ruby работает coffeescript.


            1. vintage
              02.11.2016 10:13

              Но для исполнения всё-равно нужна будет нода. Я не очень понимаю, что будет делать этот "враппер".


  1. saksmt
    31.10.2016 20:12
    +3

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

    UPD: да прибудут со мной минусы.


    1. handicraftsman
      01.11.2016 16:35

      Согласен с уровнем знания английского.
      `Builded` убил.


    1. vintage
      02.11.2016 10:15

      Где вы тут увидели обилие спецсимволов на строку?


      Поможете нам с английским? :-)


      1. saksmt
        02.11.2016 10:49
        +1

        Ровно весь формат Tree, ровно каждый ваш идентификатор начинается с "$" это не говоря уже о «глобальном» пространстве имён, которое тоже "$", что в результате заставляет писать нечто такое: `$.$`. Основная проблема в том, что все эти спецсимволы новы для того, кто начинает пользоваться вашим фреймворком. В той же скале почти для каждого символа такого рода есть вполне человеческое имя метода, да и каждый новый фреймворк обычно не привносит более 1 метода названного через спецсимвол на десяток классов + те, что есть уже давно зафиксированы в сообществе. Например, есть `::` для слияния списков (и их разбора, но это отдельная тема), есть `->` для создания кортежей и есть фреймворк akka, который из часто используемого привносит только `!` (отправить сообщение) и `?` («спросить» сообщение). При этом для кортежей можно использовать `Tuple2(a, b)`, для отправки сообщения `tell(message)`, для «вопроса» — `ask`, собственно для списков тоже есть метод (вот только я уже забыл его название). В вашем же случае вы просто вбрасываете охапку этих спецсимволов без каких-либо альтернатив и надеетесь, что кому-то будет легко это запомнить.

        Можно, например, для начала выучить неправильные глаголы и осознать что суффикс «er» предназначен для обозначения объекта выполняющего действие и нет никакого практического смысла пихать его к каждому компоненту.


        1. vintage
          02.11.2016 11:14

          Во view.tree используется всего 8 спецсимволов. Код без спецсимволов воспринимается сложнее (я пробовал). Ограниченный набор наглядных спецсимволов позволяет быстрее ухватывать суть. Возможность писать и так и сяк привносит лишь бардак и не даёт никаких преимуществ.


          Нечто такое $.$ писать не нужно. $ по умолчанию эквивалентен глобальному пространству имён. Его пришлось добавить исключительно для совместимости с NodeJS. Вы посмотрите в коде, нигде $.$ не используется. Сами '$' — совсем не новы. Код на некогда популярном jQuery ими утыкан чуть менее, чем полностью.


          Впрочем, если отставить в сторону эти "фу, мне не нравится, я так не привык", то как бы вы реализовали view.tree? Вот возьмём, например, следующий код:


          $mol_clicker $mol_viewer
              tagName \button
              enabled true
              event * click > eventClick null
              attr *
                  disabled < disabled false
                  tabindex \0

          Как бы вы его переписали на "более удобном языке"?


          Это соглашение об именовании такое — заканчивать презентеры на "er", что даёт похожие на англоязычные имена.


          1. saksmt
            02.11.2016 22:43
            +1

            «Всего 8» — это не «всего». Запомните магическое число 7 — это максимум однотипной не очень понятной ерунды которую среднестатистический человек может держать в голове, соответственно идеально — 3-4. Они не наглядны, ибо нигде раньше в таком контексте не были использованы. Возможность писать «и так и сяк» должна быть по крайней мере до тех пор пока сообщество (а не вы, что важно) не примет один из стандартов.

            >> Нечто такое `$.$` писать не нужно.

            Оно в статье есть? — Есть. Значит-таки иногда оказывается нужно.

            >> Код на некогда популярном jQuery ими утыкан чуть менее, чем полностью.

            И вы хотите сказать, что это хорошо?

            >> Впрочем, если отставить в сторону эти «фу, мне не нравится, я так не привык», то как бы вы реализовали view.tree?

            Да хоть бы и так (https://gist.github.com/anonymous/d05bf00e33fa6e23141178e0aa524cea):
            ```scala
            class MyView[ElementType](val e: TypedTag[ElementType], val clickHandler: (Event) => Any)
            extends View
            with MySuperBehavior {

            val child = div()

            override lazy val getTemplate = {
            e(
            enabled := true,
            onclick := clickHandler,
            onmouseover :+= { println(s«Someone moved mouse over ${e.tagName}») }
            // In reality would be some sort of template method, so there won't be any `getTemplate` methods
            getMySuperModifiers()
            )(
            p(«HEADER PART»),
            child,
            p(«FOOTER PART»)
            )
            }

            override def renderChild(view: View): Unit = {
            child.children.foreach(grand => child.removeChild(grand))
            view.getTemplate.applyTo(child)
            }
            }
            ```
            Пример, очевидно, не рабочий. Если хочется посмотреть на подобную штуку поподробнее — google://udash+scala.js

            >> Это соглашение об именовании такое — заканчивать презентеры на «er», что даёт похожие на англоязычные имена.

            Эти имена не правильны, большинства этих английских слов либо нет, либо они значат не то, что вы ожидаете. Лучше бы уж тогда по Java косили с повсеместным "-able" оно хотя бы переводится более-менее адекватно, да и по смыслу значительно больше подходит. И ещё раз для закрепления: clicker — «тот, кто кликает», clickable — «то, что по чему можно кликнуть».


            1. vintage
              03.11.2016 11:19
              -2

              Знаю я эту байку про волшебное число 7, но она тут совершенно не к месту.


              Сообщество никогда не придёт к консенсусу. Слишком оно разнородно. Поэтому даже плохой стандарт лучше, его полного отсутствия. Тем не менее, обозначения я постарался подобрать такие, чтобы вызывать правильные ассоциации: \ ассоциируется с сырыми данными (экранирование), / ассоциируется со вложенной коллекцией (файловые системы), # ассоциируется с идентификатором, * со множественным выбором и так далее. Число этих обозначений намеренно минимизировано и каждое вводилось лишь при крайней необходимости, после долгого обдумывания. Если сообщество предложит обозначения по лучше, то я с радостью изменю генератор для его поддержки.


              Если бы вы внимательно читали статью, а не бегло выискивали к чему бы прицепиться, то заметили бы, что через $.$ было только одно обращение, да и то было в контексте "вы выполнили в консоли такой-то код — сработало". Для совместимости с NodeJS весь код и так пишется внутри неймспейса $, так что его указывать не нужно. По умолчанию $ эквивалентен глобальному неймспейсу, так что через консоль опять же его можно не указывать. Единственное исключение — если вы зачем-то подключили какую-нибудь библиотеку типа jQuery, которая объявляет глобальную переменную $ со всеми вытекающими.


              И вы хотите сказать, что это хорошо?

              Нет, я говорю, что в этом нет ничего необычного в мире JS.


              Далее вы приводите код, который делает совершенно не то, что предложенный мной. Имеет в 3 раза больший объём. Гораздо меньшую гибкость. 12 спецсимволов (против 5 в моём примере). Несколько десятков разных синтаксических конструкций (против 7 в моём примере). Вы действительно считаете эту портянку предпочтительней?


              Если интересно, представленный выше мною код, транслируется в следующий:


              namespace $ { export class $mol_clicker extends $mol_viewer {
              
                  /// tagName \button
                  tagName() {
                      return "button"
                  }
              
                  /// enabled true
                  enabled() {
                      return true
                  }
              
                  /// eventClick null
                  @ $mol_mem()
                  eventClick( next? : any , prev? : any ) {
                      return ( next !== void 0 ) ? next : <any> null
                  }
              
                  /// event * click > eventClick
                  event() {
                      return $mol_merge_dict( super.event() , {
                          "click" : ( next? : any , prev? : any )=> <any> this.eventClick( next , prev ) ,
                      } )
                  }
              
                  /// disabled false
                  disabled() {
                      return false
                  }
              
                  /// attr * 
                  ///     disabled < disabled 
                  ///     tabindex \0
                  attr() {
                      return $mol_merge_dict( super.attr() , {
                          "disabled" : ()=> <any> this.disabled() ,
                          "tabindex" : ()=> <any> "0" ,
                      } )
                  }
              
              } }

              "clicker" в данном случае — сокращение от "clickable presenter". Можно было бы ввести суффикс типа "_view" или "View", но тогда получаются совсем стрёмные имена вида "$mol_grid_view_row_view" и "$mol_gridView_rowView". Совсем без суффиксов — получается путаница между фабриками визуальных компонент и данных для них. Например, свойство "row" может возвращать данные для строчки таблицы, а "rower" — компонент, который эту строчку визуализирует.


              1. saksmt
                03.11.2016 22:11
                +1

                Эта «байка» к месту ровно всегда и не пытайтесь искать себе оправдания.

                Сообщество сойдётся на максимум 3 стандартах из которых мейнтейнеру и нужно выбирать, а не думать, что у кого-то "#" начнёт ассоциироваться с идентификатором, а не комментарием. Касательно остальных ваших я согласен разве что с экранированием, да и то относительно.

                Как вы думаете почему я первую половину статьи прочитал, а остальную бегло просмотрел и начал «выискивать»? Всегда задавайте себе такой вопрос, когда кто-либо относительно аргументировано начинает критиковать — обычно это значит, что вы что-то не учли или не просчитали либо при написании/проектировании, либо при выборе target группы. Так вот, начал я к "$" докапываться потому что у меня возникло ощущение, что я читаю статью о php/bash/perl, а не о typescript. А про спецсимволы я свою позицию уже объяснил.

                Вам это не говорит о том, что код не понятен? Кхм, мой пример имеет в разы большую гибкость, вплоть до выбора типа элемента + оба возможных метода расширения функционала: наследование и композиция элементов. Уж не знаю как вы считали (генерики с лямбдами учли небось?), но для скала разработчика там всего 2 спец символа и те вполне понятны, особенно если уточнить, что ":" просто суффикс, ибо "=" не перегрузить, а именованых вараргов в отличие от питона не завезли. Касательно конструкций — там лишь наследование, генерики, свойства, методы и лямбды — ничего сверхестественного или непонятного. И да, я считаю эту «портянку» ЗНАЧИТЕЛЬНО более предпочтительной, а для тех, кому трудновато пока освоиться с нестандартным синтаксисом, но очень понравилась идея — рекомендую Binding.scala, что выглядит как реакт, с той лишь разницей, что оно действительно реактивно.

                И вот моя попытка номер 2 в осознании вашего кода (https://gist.github.com/anonymous/d56c6665ab6fbea6c162f2f76abbc983):

                // literally rewrote
                def clickableElement(text: Modifier): TypedTag[Button] =
                button(
                enabled := true,
                onclick := { false },
                disabled := false,
                tabIndex := 0
                )(text)

                // More generic way:
                def button(text: => Modifier): TypedTag[Button] =
                typedTag[Button](«button»)(
                enabled := true,
                onclick := { false },
                disabled := false,
                tabIndex := 0
                )(text)

                // My thoughts about what it was…
                class Button extends View {
                protected def enabled = true
                protected def disabled = false
                protected def clickHandler: (Event) => Boolean = { e => false }
                protected def tabIndex = 0

                abstract protected def text: Modifier

                def render = button(
                enabled := { this.enabled || !disabled }
                onclick := clickHandler
                tabIndex := { tabIndex }
                )
                }

                >> «clicker» в данном случае — сокращение от «clickable presenter»…
                Вы уж определитесь с тем, что это такое: вью, презентер (кстати рекурсивный каламбур по вашей логике получается :) ) или компонент и с тем, для чего вы ставите эти суффиксы как следствие. Я вам предложил суффикс который бы отделял компоненты от полей. И чем вас так не устроил вариант "$mol.rowInGridView"


                1. vintage
                  04.11.2016 12:25
                  -2

                  Думаю вам стоит несколько расширить свой кругозор.


                  Нет никакой рациональной причины фрагментировать сообщество. Наличие 3 несовместимых стандарта, не идёт на пользу ни продукту, ни сообществу.


                  Это нормально, что один и тот же символ в разных контекстах вызывает разные ассоциации. Для формирования правильных ассоциаций — достаточно прочитать краткое описание и пролистать примеры использования.


                  Целью статьи было поделиться свежими идеями, позволяющими повысить общую эффективность разработки, а не отлизать как можно большему числу жоп :-) Всегда найдутся те, кто не поняли, не привыкли, не заинтересовались. Особенно на Хабре, где куда ни плюнь — попадёшь в матёрого хайлоад бигдата компутерсаенс специалиста.


                  Но вернёмся в конструктивное русло. $ используется для того, чтобы обозначить глобальное пространство имён. Именно благодаря ему, сборщик быстро находит зависимости. И именно благодаря ему, вы можете быстро найти все упоминания компонента в любом типе файлов (даже в readme.md), с использованием любого текстового редактора. Исключение только одно — css. Хотя, возможно стоит это пофиксить. Правда селекторы тогда получатся не очень привлекательными: [\$mol_clicker]. Но от единообразия больше пользы.


                  Для $mol разработчика, в коде view.tree тоже нет ничего нового :-) Однако, скалу знает не так уж много людей. А хотят на ней программировать и тем более верстать — ещё меньше. Так что оба языка одинаково бесконечно далеки от мейнстрима на данный момент. Но если view.tree можно изучить за пару часов вдоль и поперёк, то на скалу придётся потратить существенно больше времени. Не знаю, зачем вы привели первых два варианта, которые опять же делают совершенно не то, но вот последний уже ближе к истине.


                  Подозреваю, вы не дочитали до того момента, где объясняется, почему вместо описания класса на TypeScript был введён специальный язык. Он позволяет описывать класс в иерархической форме, при этом не теряя в гибкости, позволяя перегружать любой аспект его поведения. У вас же, последнем примере, прямая калька с TypeScript класса. Попробуйте реализовать аналог $mol_pager:


                  $mol_pager $mol_viewer
                      childs /
                          < header $mol_viewer
                              childs < head /
                                  < titler $mol_viewer
                                      childs /
                                          < title
                          < bodier $mol_scroller
                              childs < body /
                          < footer $mol_viewer
                              childs < foot /

                  Который разворачивается в:


                  namespace $ { export class $mol_pager extends $mol_viewer {
                  
                      /// titler $mol_viewer childs / < title
                      @ $mol_mem()
                      titler( next? : any , prev? : any ) {
                          return new $mol_viewer().setup( obj => { 
                              obj.childs = () => [].concat( this.title() )
                          } )
                      }
                  
                      /// head / < titler
                      head() {
                          return [].concat( this.titler() )
                      }
                  
                      /// header $mol_viewer childs < head
                      @ $mol_mem()
                      header( next? : any , prev? : any ) {
                          return new $mol_viewer().setup( obj => { 
                              obj.childs = () => this.head()
                          } )
                      }
                  
                      /// body /
                      body() {
                          return [] as any[]
                      }
                  
                      /// bodier $mol_scroller childs < body
                      @ $mol_mem()
                      bodier( next? : any , prev? : any ) {
                          return new $mol_scroller().setup( obj => { 
                              obj.childs = () => this.body()
                          } )
                      }
                  
                      /// foot /
                      foot() {
                          return [] as any[]
                      }
                  
                      /// footer $mol_viewer childs < foot
                      @ $mol_mem()
                      footer( next? : any , prev? : any ) {
                          return new $mol_viewer().setup( obj => { 
                              obj.childs = () => this.foot()
                          } )
                      }
                  
                      /// childs / 
                      ///     < header 
                      ///     < bodier 
                      ///     < footer
                      childs() {
                          return [].concat( this.header() , this.bodier() , this.footer() )
                      }
                  
                  } }

                  Вариант $mol.rowInGridView плох тем, что его имя вылезает за пределы модуля $mol.gridView.


                  1. saksmt
                    04.11.2016 16:20

                    >> Думаю вам стоит несколько расширить свой кругозор.

                    Дело не в конкретном числе, а именно в малом количестве — я не просто так подметил, что в реальности это число ближе к 4. В целом это больше говорит о KISS. Т.е. никто не будет на самом деле сидеть и считать «а не привысило ли число абстрактной ерунды в в этой штуковине число 7?». Кстати не очень понимаю зачем в той статье упомянуты дизайнеры — мне всегда казалось, что у них число 3 более важно (максимальное число переходов, максимальное число шрифтов/цветов...)

                    >> Нет никакой рациональной причины фрагментировать сообщество.
                    У вас нет выбора «фрагментировать своё сообщество или нет» у вас выбор «будет у меня фрагментированное сообщество или его не будет совсем». Сомневаюсь что найдётся много разработчиков (не студентов, которым время девать некуда и не юниоров, которым надо просто «побольше выучить, чтоб казаться про»), которые бы стали копать вашу документацию и разбираться в тонкостях новых способов разметки, если им нужно решение здесь и сейчас, это даже для нелегаси проектов не прокатит.

                    >> Это нормально, что один и тот же символ в разных контекстах вызывает разные ассоциации.
                    Нет, это скорее исключение. "!=" везде «не равно» (haskell не в счёт) и "+" везде плюс. Не надо усложнять и так не простую жизнь разработчика новыми значениями для привычных вещей.

                    >> Целью статьи было поделиться свежими идеями
                    Я в самом первом комментарии написал, что идея хороша, а вот реализация подкачала.

                    >> Всегда найдутся те, кто не поняли, не привыкли, не заинтересовались
                    Естественно. Вопрос в количестве — и чем больше нового вы привносите (прямо скажем за зря) тем больше их будет.

                    >> $ используется для того, чтобы обозначить глобальное пространство имён. Именно благодаря ему, сборщик быстро находит зависимости.
                    Вы сейчас про typescript или про префиксы своих компонентов пишете?

                    >> Для $mol разработчика, в коде view.tree тоже нет ничего нового
                    Есть принципиальная разница: для scala-разработчика в udash и Binding.scala нет кардинально нового, а для ts-разработчика (и тем более js) в $mol сликом много нового.

                    >> Но если view.tree можно изучить за пару часов вдоль и поперёк, то на скалу придётся потратить существенно больше времени
                    Зависит от того насколько глубоко нужно изучить скалу и что уже знает изучающий.

                    >> Подозреваю, вы не дочитали до того момента, где объясняется, почему вместо описания класса на TypeScript был введён специальный язык.
                    Либо я плохо искал, либо у вас нет этого объяснения. У вас есть только сравнение с xml-подобным синтаксисом. Не самое убедительное кстати.

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

                    >> Попробуйте реализовать аналог
                    https://gist.github.com/anonymous/17043c00b00117eb7af266df9cd7b802:

                    /**
                    * param header Reactive property binding
                    * param content Reactive property binding
                    * param footer Reactive property binding
                    * param attrs Attributes
                    */
                    case class Page(
                    private val header: Property[Element] = Property[Element],
                    private val content: Property[Element] = Property[Element],
                    private val footer: Property[Element] = Property[Element],
                    private val attrs: Modifier*
                    ) {
                    def render = div(attrs: _*)(
                    header,
                    content,
                    footer
                    )

                    def apply(
                    header: Property[Element] = this.header,
                    content: Property[Element] = this.content,
                    footer: Property[Element] = this.footer,
                    modifiers: Modifier* = this.attrs
                    ) = copy(
                    header = header,
                    content = content,
                    footer = footer,
                    modifiers = modifiers
                    )
                    }

                    val myContent = Property(p(«hello»).render)
                    val mainPage = Page(style := MainPage.styles)(
                    header = Property(h2(«Hello»))
                    content = myContent
                    )

                    // application in body

                    body(
                    onclick :+= { myContent.set(p(«world»)) }
                    mainPage.render
                    )

                    >> Вариант $mol.rowInGridView плох тем, что его имя вылезает за пределы модуля $mol.gridView.
                    $mol.grid.row.RowView


                    1. vintage
                      05.11.2016 11:40
                      -2

                      Ну так KISS и является основным принципом при разработке $mol и view.tree в частности. Меньше синтаксических конструкций фиг сделаешь. Разве что prop @ \value можно заменить на l10n#prop \value, но тогда будут сложности с перегрузкой отдельных текстов, ведь все тексты будут находиться в одном свойстве, но по разным ключам.


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


                      Но поддержка нескольких синтаксисов не уменьшает порог входа. Ведь для входа нужно не только суметь что-то написать, но и уметь понимать, что написано. А если половина кода написана в одном синтаксисе, а половина в другом, то для полноценного входа нужно выучить оба.


                      Введение отдельного языка view.tree — вынужденная мера, чтобы уменьшить сложность, а не самоцель. Без него, пришлось бы либо писать кучу плоского кода на TypeScript, либо отказываться от таких плюшек как "контроль жизненного цикла" и "человекопонятные идентификаторы" и фигачить кучу кода на TSX, либо фигачить кучу ещё менее понятного кода на XML (синтаксис-то хоть из знаком, но необходимость осваивания новых идиом никто не отменял).


                      В середине главы "С голой грудью на амбразуру" как раз приводится иллюстрация того, как view.tree позволяет объявлять плоский класс, при этом не теряя наглядности иерархии:


                      $mol_app_supplies_positioner $mol_carder
                          heightMinimal 68
                      
                          content < grouper $mol_viewer childs /
                      
                              < mainGroup $mol_rower childs /
                      
                                  < productItem $mol_labeler
                                      title < productLabel @ \Product
                                      content < productName 
                                  < costItem $mol_labeler
                                      title < costlabel @ \Cost
                                      content < coster $mol_coster
                                          value < cost $mol_unit_money
                                              valueOf 0
                      - ...

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


                      В вашем коде потерялись следующие свойства: head, body, foot, позволяющие изменить содержимое соответствующих блоков, не меняя сами блоки. Кроме того, потерялся вложенный блок titler, который по умолчанию выводится в header, и который выводит внутри себя свойство title, которое есть у всех компонент. Содержимое свойства title корневого элемента, кстати, выводится в заголовке окна автоматически. А так как любой компонент может быть корневым, то это свойство изначально есть у всех компонент. И опять же, у вас получилось много запутанного плоского кода, вместо лаконичного иерархичного декларативного на view.tree.


                      + в контексте URL ассоциируется с пробелом, в контексте версий ассоциируется с открытым сверху диапазоном. = в контексте URL ассоциируется с разделителем ключ-значение, в контексте языка программирования с присвоением, в контексте SQL — со сравнением. Так вот #во многих контекстах ассоциируется с идентификатором, поэтому и была выбрана на роль свободного идентификатора. И, кстати, пока мы тут спорили и уже запомнили, что # — это идентификатор в контексте view.tree, так что никаких сложностей в идентификации идиомы этот символ у вас уже не вызовет :-)


                      Вот вы жалуетесь, что реализация подкачала. Но что вы предлагаете? Перевести весь код на scala? Вы действительно думаете, что это повысит популярность фреймворка в мире, где половина сидит на Angular, половина переходит на React (что бы это ни значило), а большая половина поддерживает легаси на jQuery? Или вы предлагаете скрестить ужа с ежом — собрать химеру из JXS и Angular, чтобы и тем и другим было проще перейти, но по факту получить лишь вопросы в духе "ну и зачем ещё один велосипед, который делает то же, что и реакт?!"?


                      Касательно сборщика — его логика работы не завязана на конкретные языки. В одном модуле могут быть файлы css, js, ts, view.tree и другие в различных пропорциях. При этом зависимость может подтянуться в любом из них. Например, вы можете использовать в css селектор вида [my_support_grid], который приведёт к загрузке модуля my/support, который проставит в body этот самый атрибут my_support_grid, если детектирует поддержку css-grid в браузере. Ну а если в приложении ни одно правило не завязалось на my_support_grid, то соответствующий модуль загружен не будет.


                      $mol.gridView и $mol.grid.row.RowView опять же имеют разные пространства имён. Тогда уж $mol.grid.view и $mol.grid.row.view. Но тут получается, что все презентеры будут иметь одно и то же имя класса, но в разных неймспейсах, что не очень удобно в работе — в них легко запутаться.


                      1. saksmt
                        05.11.2016 17:01

                        Про первые четыре абзаца:

                        Вы JSX видели? Видели чем он по факту является? Так вот я предлагаю вам не городить свои велосипеды и не писать свой парсер (который к слову будет иметь баги, каким бы крутым разработчиком вы не были), а взять уже готовое решение — JSX, mustache, nunjucks или в крайнем случае написать свой DSL (не уверен на счёт TS, но в котлине или скале оно делается совсем не сложно)

                        >> Вот вы жалуетесь, что реализация подкачала. Но что вы предлагаете? Перевести весь код на scala?
                        Боже упаси, нет конечно. Не думайте, что я упоротый и пытаюсь воткнуть любимую скалу повсеместно. Это были лишь примеры того, что не обязательно велосипедировать и вполне можно обойтись простеньким DSL-ем.

                        >> но по факту получить лишь вопросы в духе «ну и зачем ещё один велосипед, который делает то же, что и реакт?!»?
                        Сомневаюсь, что кто-либо будет задавать такие вопросы. В конце концов вы пилите один из немногих на самом деле реактивных фреймворков + насколько я понял ваши идеи — это сотворить современный extJS «с блекджеком...»

                        >> В вашем коде потерялись следующие свойства: head, body, foot, позволяющие изменить содержимое соответствующих блоков, не меняя сами блоки
                        Они через конструктор передаются.

                        >> Кроме того, потерялся вложенный блок titler, который по умолчанию выводится в header, и который выводит внутри себя свойство title, которое есть у всех компонент.
                        Представьте себе ситуацию, когда кто-то изменит одновременно header, titler и title и после этого начнёт удивляться что же пошло не так. Нельзя давать слишком много гибкости. Вилкой тоже можно суп съесть, но зачем.

                        >> Касательно сборщика
                        Вам не кажется более логичным использовать как разделитель точку? И это всё равно не объясняет зачем вам "$"

                        >> $mol.gridView и $mol.grid.row.RowView опять же имеют разные пространства имён. Тогда уж $mol.grid.view и $mol.grid.row.view
                        $mol.grid.GridView и $mol.grid.row.RowView


                        1. vintage
                          06.11.2016 13:28
                          -2

                          Вы JSX видели? Видели чем он по факту является?

                          Код вида


                                  return <div>
                                      <div id="header">
                                          <div id="titler">{ this.title }</div>
                                      </div>
                                      <div id="bodier">{ this.body }</div>
                                      <div id="footer">{ this.foot }</div>
                                  </div>

                          транслируется в


                                  return R.createElement("div", null, 
                                      R.createElement("div", {id: "header"}, 
                                          R.createElement("div", {id: "titler"}, this.title)
                                      ), 
                                      R.createElement("div", {id: "bodier"}, this.body), 
                                      R.createElement("div", {id: "footer"}, this.foot));

                          Это совершенно не кастомизируемый код. Вы не можете извне убрать футер, добавить в шапку кнопку, да даже просто добавление атрибута потребует полного переписывания метода render. Чтобы сделать компонент достаточно гибким вам придётся раздербанить один большой render, на множество мелких методов:


                          class Pager {
                              title() {
                                  return ''
                              }
                              head() {
                                  return [ this.titler ] as any[]
                              }
                              body() {
                                  return [] as any[]
                              }
                              foot() {
                                  return [] as any[]
                              }
                              titler() {
                                  return <div id="titler">{ this.title }</div>
                              }
                              header() {
                                  return <div id="header">{ this.head }</div>
                              }
                              bodier() {
                                  return <div id="bodier">{ this.body }</div>
                              }
                              footer() {
                                  return <div id="footer">{ this.foot }</div>
                              }
                              childs() {
                                  return [ this.header , this.bodier , this.footer ] as any[]
                              }
                              render() {
                                  return <div>{ this.childs }</div>
                              }
                          }

                          Но при таком использовании смысла в XML нет ровным счётом никакого. Какая разница писать ли


                          <div id="bodier">{ this.body }</div>


                          или же


                          new Div({ id : 'bodier' , childs : this.body })


                          ? Кода получается примерно столько же, гибкость одинаковая, возможностей второй вариант даёт больше, а костылей и транспиляторов во втором варианте меньше. view.tree же делает это "раздербанивание" автоматически. К TSX это не прикрутить. Но можно прикрутить ко внешнему XML — точно так же разбирать его и генерировать TypeScript класс. Но как я уже говорил, наглядность у XML хуже, а кода больше, плюс костыли нужны для словарей, значениями которых могут быть не только строки.


                          mustache, nunjucks

                          Это вообще шаблонизаторы. На каждый чих перегененрировать HTML, который потом парсить, навешивать обработчики и вставлять в DOM — не очень эффективно. Это помимо описанных выше проблем с гибкостью.


                          или в крайнем случае написать свой DSL (не уверен на счёт TS, но в котлине или скале оно делается совсем не сложно)

                          view.tree — и есть DSL. Он предельно прост, декларативен и эффективно решает все задачи связанные с описанием и композицией компонент. С ним может работать как простой верстальщик, который не умеет в JS/TS/scala, так и программист, который своими хитрыми скриптами никак не задевает верстальщика.


                          Сомневаюсь, что кто-либо будет задавать такие вопросы.

                          Вы чуть выше задаёте эти вопросы. "Зачем view.tree, если есть jsx/mustache/nunjucks/dom-builder, которые делают то же самое". В том-то и дело, что view.tree — не шаблонизатор, а язык для описания и композиции компонент. Сами компоненты могут быть вообще никак не связаны с DOM.


                          ваши идеи — это сотворить современный extJS «с блекджеком...»

                          Единственное сходство с ExtJS — и тот и другой предоставляют базовый набор готовых компонент. Это делает любой компонентный фреймворк.


                          Они через конструктор передаются.

                          Через конструктор передаются сами блоки header, bodier, footer, а не их содержимое. И каждый раз при использовании нужно вручную создавать каждый из этих блоков. Во view.tree вы можете перегрузить как сам блок (bodier), так и только лишь его содержимое (body). Во втором случае будет сгенерирован дефолтный bodier, в который и будет вставлено содержимое.


                          Представьте себе ситуацию, когда кто-то изменит одновременно header, titler и title и после этого начнёт удивляться что же пошло не так.

                          И что же там может пойти не так, например?


                          Нельзя давать слишком много гибкости.

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


                          https://github.com/wmira/react-panel/blob/master/src/panel.js
                          https://github.com/react-bootstrap/react-bootstrap/blob/master/src/Panel.js
                          https://github.com/Theadd/react-panels/blob/master/src/jsx/panel.js
                          https://github.com/pivotal-cf/pivotal-ui/blob/development/library/src/pivotal-ui-react/panels/panels.js


                          Это аналоги десятистрочного $mol_pager на реакте.


                          Особенно мне нравится этот чудесный пример использования:


                          <Panel className="bg-neutral-10" header={<h2>Custom Title</h2>} actions={<div><button>Go</button><button>Stop</button></div>}>
                           Base Panel with custom header and actions
                          </Panel>

                          Вам не кажется более логичным использовать как разделитель точку?

                          Символ подчёркивания — единственный символ, который можно одинаково использовать хоть в JS, хоть в CSS, хоть ещё где. К тому же, благодаря этому, имя класса всегда содержит полный путь к нему (а не только последнюю его часть), что используется для генерации аттрибутов, идентификаторов и просто удобно при дебаге.


                          И это всё равно не объясняет зачем вам "$"

                          Чтобы минимизировать ложноположительные срабатывания. Токены, начинающиеся на $, достаточно уникальны.


                          $mol.grid.GridView и $mol.grid.row.RowView

                          Уже лучше, но как-то костыльно — для каждого класса отдельный неймспейс. Тогда уж $mol.grid.GridView и $mol.grid.RowView, но какой должен сгенерироваться из них класс/атрибут для стилизации в css?


                          1. saksmt
                            08.11.2016 22:32

                            То, что вы не правильно поняли концепт реакта не говорит о том, что он плохо расширяется. Это говорит лишь о том, что если вы пилите компонент панели, то всё, что она о себе должна знать — это то, что у неё возможно (nullable) есть шапка, тело и футер, а вот что в них будет решат уже они сами (собственно в моём примере на scala-tags+udash это и реализовано).


                            К TSX это не прикрутить

                            Вы в этом уверены? Насколько я понял tsx/jsx транслируется примерно в такие вызовы (псевдокод): tagName[T](children: List[T], attributes: Map[String, Object]): T


                            Но как я уже говорил, наглядность у XML хуже, а кода больше

                            Это лично ваше мнение.


                            view.tree — и есть DSL

                            view-tree — это DSL на базе самописного формата. Я же имел ввиду именно DSL на базе стандартных возможностей языка, хоть в JS это и не популярно.


                            Во view.tree вы можете перегрузить как сам блок (bodier), так и только лишь его содержимое (body). Во втором случае будет сгенерирован дефолтный bodier, в который и будет вставлено содержимое.

                            Не могу себе представить кейс, когда это было бы действительно необходимо и не решалось бы несложной композицией других компонентов. Так что это похоже на побочную фичу — т.е. мне это представляется как: вы сначала написали всё это добро, а потом осознали что оно ещё "вот так" умеет, хотя может я и не прав.


                            Это вообще шаблонизаторы

                            Да, но их можно было бы преобразовать при очень большом желании. + nunjucks умеет в транспиляцию насколько я помню.


                            Единственное сходство с ExtJS — и тот и другой предоставляют базовый набор готовых компонент. Это делает любой компонентный фреймворк.

                            Я бы сказал расширенный набор компонент. И на самом деле таких фремворков сейчас как-то не видно. В лучшем случае есть набор не связанных сторонних компонент от сомнительных вендоров. У вас же виден прицел на быстрое клепание enterprise-форм, что сильно выделяет среди остальных (в хорошем смысле)


                            И что же там может пойти не так, например?

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


                            Иначе у вас неизбежно будет получаться вот такой вот говнокод

                            Первый это чьё-то поделие (не в обиду автору), количество звёзд говорит само за себя.
                            Второй — обёртка над бутстрапом никакой жести там кроме самого бутстрапа (способов его конфигурирования) нет.
                            Третий — пример идеального проектирования (ИМХО) и полного понимания что такое композиция элементов и как её нужно использовать.
                            Четвёртый — пример того как не надо воспринимать композицию элементов.
                            И ни один из них не говорит о "неизбежности говнокода", как и о недостаточной гибкости реакта.


                            Это аналоги десятистрочного $mol_pager на реакте.
                            Разве что первый.

                            Символ подчёркивания — единственный символ, который можно одинаково использовать хоть в JS, хоть в CSS, хоть ещё где

                            "ё" тоже можно одинаково везде использовать, но-таки не стоит. Лучше ввести несложные правила замены, тогда семантически везде будет одно и то же.


                            Чтобы минимизировать ложноположительные срабатывания. Токены, начинающиеся на $, достаточно уникальны.

                            В ts же вроде как аннотации есть, зачем тогда так извращаться?


                            Уже лучше, но как-то костыльно — для каждого класса отдельный неймспейс

                            Ровно до тех пор, пока не появятся другие классы в неймспейсе, например, CustomerDao и CustomerView в mycompany.search.customer


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

                            Сомнительный момент для, например, mycompany_search_customer_results_card_subdivision_account_identification_place_registration и человека, который разрабатывает форму лицевого счёта. И поверьте, в SID бывает ещё и не такое...


                            но какой должен сгенерироваться из них класс/атрибут для стилизации в css?

                            FQCN, до тех пор, пока не задано иное явно.


                            1. vintage
                              10.11.2016 09:57

                              Это говорит лишь о том, что если вы пилите компонент панели, то всё, что она о себе должна знать — это то, что у неё возможно (nullable) есть шапка, тело и футер, а вот что в них будет решат уже они сами (собственно в моём примере на scala-tags+udash это и реализовано).

                              Боюсь это работает только в простейших случаях. И то вам придётся создавать отдельные компоненты для каждого блока вида "шапка в такой-то панели". Чуть более сложный компонент — и вы сталкиваетесь с описываемыми мной проблемами в полный рост. Простой пример — вы реализуете компонент "диалог", который по хорошему должен инкапсулировать в себе "панель", но с вашим подходом "по концепту реакта" вы не сможете абстрагироваться от типа панели, так ка на вход принимаете "компонент подвала для такой-то панели".


                              Вы в этом уверены? Насколько я понял tsx/jsx транслируется примерно в такие вызовы (псевдокод): tagName[T](children: List[T], attributes: Map[String, Object]): T

                              Я в предыдущем сообщении привёл привёл код, который генерируется. Зачем вы фантазируете?


                              Это лично ваше мнение.

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


                              view-tree — это DSL на базе самописного формата. Я же имел ввиду именно DSL на базе стандартных возможностей языка, хоть в JS это и не популярно.

                              Стандартными возможностями языка TypeScript нельзя иерархически объявлять свойства — только в плоском виде.


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

                              Представьте себе компонент "календарик". На тулбаре у него есть кнопки "предыдущий месяц", "следующий месяц" и "текущая дата". Вам нужен точно такой же, но с перламутровыми пуговицами кнопками "предыдущий месяц", "следующий месяц" и "ближайшая дата отчёта". Через view.tree это реализуется элементарно:


                              $my_report_calendar $mol_calendar
                                  tools /
                                      < monthPrever
                                      < nearester $mol_clicker
                                          eventClick > eventNearest null
                                          childs / < nearesterLabel @ \Nearest
                                          title < nearesterHint @ \Nearest report date
                                      < monthNexter

                              Да, но их можно было бы преобразовать при очень большом желании. + nunjucks умеет в транспиляцию насколько я помню.

                              Откуда эта странная любовь к костылям к сторонним решениям, вместо написания своего без костылей?


                              Может внезапно не оказаться вложенного элемента который ожидался и кто-то потратит время на поиск глупейшей ошибки

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


                              И ни один из них не говорит о "неизбежности говнокода", как и о недостаточной гибкости реакта.

                              У меня получились несколько иные выводы


                              "ё" тоже можно одинаково везде использовать, но-таки не стоит.

                              В идентификаторах её нельзя использовать.


                              Лучше ввести несложные правила замены, тогда семантически везде будет одно и то же.

                              А зачем вводить дополнительные правила, если их можно не вводить? Только потому, что вы привыкли в-css-писать-имена-классов-через-дефисы, а.в.javascript.через.Точки?


                              В ts же вроде как аннотации есть, зачем тогда так извращаться?

                              Аннотации тут ни чем не помогут. Да и кроме TS есть и другие языки.


                              Ровно до тех пор, пока не появятся другие классы в неймспейсе, например, CustomerDao и CustomerView в mycompany.search.customer

                              Тогда незачем тавтология с "customer.Customer": "mycompany.search.customer.Dao", "mycompany.search.customer.Vew".


                              Сомнительный момент для, например, mycompany_search_customer_results_card_subdivision_account_identification_place_registration и человека, который разрабатывает форму лицевого счёта. И поверьте, в SID бывает ещё и не такое...

                              И что тут сомнительного? Ну, кроме того, что глубина вложенности скорее всего преувеличена. И что такое SID?


                              FQCN, до тех пор, пока не задано иное явно.

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


                              1. arvitaly
                                10.11.2016 10:44

                                > Представьте себе компонент «календарик». На тулбаре у него есть кнопки «предыдущий месяц», «следующий месяц» и «текущая дата». Вам нужен точно такой же, но с перламутровыми пуговицами кнопками «предыдущий месяц», «следующий месяц» и «ближайшая дата отчёта». Через view.tree это реализуется элементарно:

                                Это реализуется не элементарно, а за счет усложнения путем введения паттерна «наследование», который всегда хуже, чем композиция. И вот у вас 2 несвязанных компонента, может быть даже в разных проектах становятся связанными.
                                Однако mol или React тут не причем, в React наследование реализуется точно так же

                                class A extends React.Component(){
                                getPanel1(){
                                return Panel1
                                }
                                }
                                class B extends A{
                                getPanel1(){

                                }
                                }


                                1. vintage
                                  10.11.2016 10:55

                                  Это реализуется не элементарно, а за счет усложнения путем введения паттерна «наследование», который всегда хуже, чем композиция.

                                  Вам стоит быть очень осторожным, употребляя квантор всеобщности. В данной простой задаче композиция вам ни чем не поможет. Разве что вы скопипастите реализацию и "скомпонуете" компоненты по другому.


                                  И вот у вас 2 несвязанных компонента, может быть даже в разных проектах становятся связанными.

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


                                  Однако mol или React тут не причем, в React наследование реализуется точно так же

                                  Разница в том, что в реакте нужно специально выносить в отдельные функции, раздувая код и теряя все преимущества JSX, а в $mol оно получается само, потому что иначе нельзя, кода получается меньше, а иерархия не теряет своей наглядности.


                              1. saksmt
                                10.11.2016 11:06

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

                                Крайне странный пример, который не показывает в чём же у меня в этой ситуации возникнут проблемы. Особенно при условии, что футер и шапка поумолчанию null и не выводятся.


                                Я в предыдущем сообщении привёл привёл код, который генерируется. Зачем вы фантазируете?

                                Вы привели код, который генерирует реакт+jsx, а не jsx.


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

                                Вы его не аргументировали. "Мне не нравится" и "Слишком многословно" — это не аргументы в пользу "XML не наглядный".


                                Стандартными возможностями языка TypeScript нельзя иерархически объявлять свойства — только в плоском виде.

                                Вложенные вызовы, лямбды, ...


                                Через view.tree это реализуется элементарно

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


                                Откуда эта странная любовь к костылям к сторонним решениям, вместо написания своего без костылей?

                                Вы точно в продакшне работаете? Своё — это, за редким исключением, велосипед на костылях, который требует поддержки и покрытия.


                                Если перегружается компонент, который его содержит, то странно ожидать, что вложенные компоненты

                                Тем не менее это будет наверно самая популярная ошибка.


                                У меня получились несколько иные выводы

                                Которые опять-таки не говорят о ущербности реакта, а разве что о ущербности разработчиков конкретных библиотек + у вас там совершенно не честное сравнение вашего компонента, который почти ничего не умеет, и сферических космолётов в вакууме.


                                В идентификаторах её нельзя использовать.

                                Консоль огнелиса:


                                >> var ё="можно"
                                << undefined
                                >> ё
                                << "можно"

                                А зачем вводить дополнительные правила, если их можно не вводить? Только потому, что вы привыкли в-css-писать-имена-классов-через-дефисы, а.в.javascript.через.Точки?

                                Именно! Потому что эти символы в этих языках имеют смысл и не пересекаются с уже установленными соглашениями.


                                Аннотации тут ни чем не помогут. Да и кроме TS есть и другие языки.

                                @Component
                                class MyComponent {}

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


                                Тогда незачем тавтология

                                Не слишком удобно, когда каждый класс вьюхи называется View. С тем же успехом можно было бы назвать вообще все классы Class и просто класть в разные пакеты.


                                И что тут сомнительного? Ну, кроме того, что глубина вложенности скорее всего преувеличена. И что такое SID?

                                Сомнительно, что человек в здравом уме захочет иметь такие имена классов. А пример — не преувеличение. SID — что-то вроде унифицированной базовой модели любой биллинговой системы.


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

                                Вам примеры FQCN (Fully Qualified Class Name) нужны?


                              1. babylon
                                10.11.2016 15:08
                                -1

                                Дмитрий, бесполезно объяснять людям отличие плоского кода от иерархии, если они с этим не сталкивались в проектах. А это можно понять тогда когда количество однотипных методов переваливает хотя бы за полтыщи. Только зря тратите время. Лучше бы потратили время на кодоинтепретатор для Tree. На это мне действительно было бы интересно посмотреть и потестить. А пока Вас примитивно троллят, а Вы реагируете…


  1. Sirion
    01.11.2016 08:04

    А уже существует что-нибудь сложнее to-do list написанное с применением этого фреймворка? В последний раз, когда я пытался использовать FRP на продакшене, у меня получилась непонятная каша. Вероятно, это я не умею его готовить, но всё же хочется посмотреть рабочий пример, прежде чем делать второй подход к снаряду.


    1. nuit
      01.11.2016 08:15

      Вы не один :) У меня тоже с этим проблемы. Так же один из разработчиков популярной либы в этой теме реактивщины недавно поделился в личном общении о том что делать такую библиотеку конечно увлекательное занятие, но писать на таком реальные приложения у него так же мозг начинает кипеть.

      Хотя есть всякие последователи штальца, у которых если что-то не «реактивное», то они смотрят на тебя как на гавно, может просто нужно какое-то другое мышление, которого нам не хватает :)


    1. vintage
      01.11.2016 10:11

      В самом начале в статье разбирается приложение, которое имеет выделенную доменную модель с моками и несколько экранов. Или вам нужно что-то совсем зубодробительное?


      В $mol FRP и не используется. Тут OORP — с ним всё куда проще.


      1. Sirion
        01.11.2016 11:10

        Дело не в зубодробительности, а в практическом применении. Тут такая закономерность: если произвольно выбирать пример для демонстрации возможностей фреймворка, то будет выбран тот пример, с которым фреймворк хорошо справляется. А вот если приложение разрабатывается, грубо говоря, по заказу дяди и под его нужды, то дядя может быть не в курсе, что такие-то вещи фреймворк умеет не очень, он знает лишь, что ему эти вещи нужны, и желательно вчера. Поэтому для меня критерий — именно продакшн. Как-то так.


        1. vintage
          01.11.2016 11:35
          +1

          Это вполне реальное приложение. Правда ещё не доведённое до релиза (бэкенд ещё не готов, диайн не согласован).


      1. xGromMx
        01.11.2016 18:44

        Вы уверены что понимаете, что такое FRP? RxJS и другие это не FRP это просто реактивные композиции


        1. saksmt
          01.11.2016 23:22

          Rx — таки FRP, правда смапленное на понятие потоков. По определению FRP — асинхронность + event-ы + функциональные операции (map/filter/fold/flatMap (bind))


          1. xGromMx
            02.11.2016 00:18

            Да ну? А как же постоянные значения во времени (Behaviors) Rx есть просто кузина для FRP. И еще, FRP имеет сенс только в языках с сильной системой типов https://wiki.haskell.org/Functional_Reactive_Programming


            1. saksmt
              02.11.2016 01:37

              Ну с оговорками конечно, но в целом да. Behavior — принцип, но не определяющая. И я тут скорее о скале говорил нежели о жс.

              А за ссылочку спасибо — почитаем-с. Не сталкивался я ни с чем в хаскеле приближенным к реальности, хоть и делал набеги :)

              P.S. Зря вообще написал. Не думаю что имеет смысл продолжать эту ветку — не будем заранее отпугивать тех, кто совершенно не в теме.


        1. vintage
          02.11.2016 10:22

          Я уверен, что в этом терминологическом споре мы не придём к консенсусу. Однако, $mol_atom имеет модель реактивности, которая совершенно точно не относится к "функциональному программированию", ибо предполагает изменение состояния и побочные действия. Зато свойства объектов реализуются как идемпотентные функции, что позволяет писать простой и лаконичный код, не ломая себе мозг монадами.


    1. saksmt
      01.11.2016 22:26

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

      Из близкого по теме рекомендую посмотреть: Binding.scala для понимания тру-реактивности (требует наличия свежей jdk и scala + sbt), elm для понимания ФП и Rx%LANGNAME% для понимания реактивных потоков (советую язык со строгой типизацией)


      1. Sirion
        01.11.2016 22:51

        Работа с Bacon.js и семестр Prolog'а в универе не канают?

        З.Ы. Пролог приплёл для красного словца, знаю, что он декларативный, но не функциональный.


        1. saksmt
          01.11.2016 23:15

          Я скорее имел ввиду haskell, scala, akka/akka-streams (кстати есть порт на scala.js), spark и rx.

          Суть в понимании какие плюсы даёт ФП и ФРП: понимание что такое монада (в первом приближении) и зачем они нужны; понимание как разложить любой SQL запрос на map/flatMap (bind)/filter/fold; понимание какую выгоду можно поиметь от генераторов в js помимо бесконечных списков; понимание чем являются генераторы js и чем будут являться async/await; понимание Promise, не в том смысле как пользоваться, а на чём он основан.

          P.S. В целом конечно можно представлять ФРП и ФП как магию и при этом пытаться мыслить в категориях потоков данных, но ИМХО это проблематично.


  1. youlose
    03.11.2016 10:45

    Я тут ещё на одну проблему вашего тестирования хочу обратить внимание:
    вот вы генерите 2000 записей чтобы вывести и получается что ленивая подгрузка будет полезна.
    Но эта ситуация мало совместима с реальными задачами, потому что если эти 2 тыс. записей загружать аякс запросом и с какого-нибудь мобильного устройства, то сразу же выясниться что они так долго загружаются, что разницы какой фреймворк и за сколько эти данные отобразит уже не будет иметь значения (потому что будет отличаться в разы).


    1. vintage
      03.11.2016 11:40

      Мой опыт подсказывает обратное. Давайте проанализируем. Предположим, у нас очень мобильный интернет с задержкой пол секунды, скоростью загрузки в 100 записей в секунду. Скоростью рендеринга пренебрегаем.


      Обычный фреймворк:


      1. Делаем запрос за 2000 записей и ждём пол секунды.
      2. В течении 20 секунд загружаем данные.
      3. Наконец, показываем интерфейс.
      4. Скорее всего нужный пользователю элемент находится в первой десятке и он переходит к нему.

      Итого: заставили пользователя ждать 20 секунд, потратить трафика на 2000 записей, потратили батарейку на 20 секунд работы.


      Ленивый фреймворк:


      1. Сразу показываем интерфейс.
      2. Делаем запрос за 100 записями.
      3. Через полторы секунды уже показываем пользователю список.
      4. Скорее всего нужный пользователю элемент находится в первой десятке и он переходит к нему.

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


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


      Обычный фреймворк: спустя 21 секунду пользователь понимает, что нужного элемента в верху списка нет и применяет фильтры, на что у него уходит допустим 5 секунд + 1 секунда загрузки нового списка, данные элементов уже есть. Итого: 27.


      Ленивый фреймворк: спустя 2 секунды пользователь понимает, что нужного элемента вверху списка нет и применяет фильтры, на что у него уходит допустим 5 секунд + 1 секунда загрузки нового списка + 2 секунды загрузки данных первых 100 элементов. Итого: 10 секунд. Почти в 3 раза быстрее.


      1. youlose
        03.11.2016 12:07

        Делаем запрос за 2000 записей и ждём пол секунды.
        Делаем запрос за 100 записями

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

        На самом деле практическое решение и с вашим и с любым другим фреймворком для подобной задачи будет выглядеть примерно так — делается поле с автокомплитом и всё =) Никаких тысяч элементов на фронте. Всё грузиться, работает и рендериться быстро на любых технологиях и фреймворках, основная магия на бэкенде.


        1. vintage
          03.11.2016 12:33

          Спасибо, что держите нас в курсе, как оно, на самом деле в мире веб разработки. :-)


          Автокомплит всё так же показывает список. Но автокомплит годится только для сценария "знаю, что ищу". для сценария "что нового" или "на днях должны были прислать письмо" и тому подобных автокомплит не подходит — тут нужно сразу отобразить список. И никогда не знаешь захочет ли пользователь пролистнуть список на следующий экран, воспользоваться фильтром или поиском.


          1. youlose
            03.11.2016 13:00

            В сценариях «что нового», все данные-то тоже пачками по 2000 не грузятся клиенту (а при загрузке маленькими партиями разница в скорости между фреймворками ничтожна и для эффективного управления DOM элементами, обычно есть готовые легко подключаемые механизмы) и фильтром при ленивой подгрузке обычно ищут не на фронте, а на бэке, потому что легче так реализовать, чем решать проблему консистентности результата фильтрации (я буду благодарен, если вы сможете указать пример из работающего веб приложения (не обязательно на вашем фреймворке) где есть фильтрация по частично загруженным данным и она полезна).


            1. vintage
              03.11.2016 13:14

              Соль в том, что c использованием pull-фреймворка, кода получается куда меньше и он куда проще, чем, если ту же самую "загрузку и рендеринг маленькими порциями" реализовывать на push-архитектуре.


              wrike.com — частично серверная, частично клиентская сортировка/группировка/фильтрация. Когда вы меняете задачу — она тут же встаёт в нужное место списка задач, если хватает данных. Если не хватает — список перезагружается в фоне, чтобы узнать как её правильно расположить.