Pull to refresh

Comments 122

Пока на Angular 1.*, а там видно будет, насчет TypeScript — не сложится ли ситуация как с CoffeScript? И jQuery не фреймворк вовсе.
В крупных проектах есть острая потребность в статическом выведении типов. В мелких, она не такая острая, но тоже приносит не мало пользы.
Микрософт очень грамотно спозиционировала TypeScript — это современные спецификации ECMAScript + типы. И ничего лишнего. В отличие от CoffeScript или Dart, меняющих язык до неузнаваемости.

Он от тайпскрипта отличается очень незначительно. Разве что тайпскрипт — это ещё и транспайлер, а не только тайпчекер, что является преимуществом, так как:


  1. Набор поддерживаемых фичей должен быть одинаковым в транспиляторе и тайпчекере.
  2. Не надо по нескольку раз парсить JS, что ускоряет транспиляцию и уменьшает число точек потенциального сбоя.
  3. Есть возможность билдить инкрементально, что ещё сильнее ускоряет билд при разработке.
  4. Не нужно настраивать и дружить между собой несколько инструментов.
Flow создает так называемый Flowgraph, который представляет собой граф всего приложения — он запоминает, какие модули связаны друг с другом. TypeScript не предлагает ничего подобного, потому что это слишком большая задача, Flow заходит дальше. Flow определяет, отрезаны ли большие части приложения от остальной части кода, и предлагает удалить их! Джефф Моррисон много говорил об этом на ReactEurope.

@mxstbr

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

Flow определяет, отрезаны ли большие части приложения от остальной части кода, и предлагает удалить их!

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

Не сложится потому что в ECMAScript даже следующих версиях не предвидится типизации (до них все не дойдет что есть вещи поважнее чем их "сахар"), а TypeScript это и есть ECMAScript + опциональная типизация. Можно считать это расширением ECMAScript.
Если вы, как и я, устали от этих всё более усложняющихся инструментов… то присоединяйтесь к разработке простого и эффективного сферического фреймворка

Напрашивается картинка про 14 конкурирующих стандартов
Кстати, да, надо ещё реализовать адаптеры к остальным фреймворкам, чтобы можно было легко интегрировать в них сферические компоненты.
Да, создатели картинки про стандарты не учли адаптеры, надо создать еще одну картинку, на которой есть адаптеры :)
Если бы каждый год не изобретали новый стандарт передачи данных, мы бы до сих пор подключали периферию через COM порты и по пол часа выводили одну фоточку на печать.
У таких железных штук есть простые метрики, по которым их можно сравнивать. Другими словами, специалист всегда точно скажет когда нужно использовать SPI, а когда — PCI Express. Я так понимаю, что с UI фреймворками ситуация несколько иная, иначе этого поста вообще не было бы.
У фреймворков тоже есть простые метрики — бенчмарки. Но и у портов есть дискуссионные характеристики — распространённость, перспективность, надёжность.
Бенчмарки показывают только производительность. При этом, надо сильно постараться, что бы тормозил именно UI фреймворк, а не сосбтвенно бизнесс-логика. По вашему же бенчмарку видно, что большая часть фреймворков справилась с задачей мене чем за 7 секунд, учитывая, что было совершенно порядка 400 действий, можно посчитать что на одно действие тратилось не более 7/400 = 0.0175 секунды. Это чуть больше времени отображения одного кадра при 60 FPS (0.0166 c). По исследованиям Роберта Миллера, время реакции интерфейса меньше чем 0.1с считается мгновенным. Поэтому, даже если все эти фреймворки будут в 5 раз медленее — катастрофы не произойдет.

Получается, что бенчмарк — это хорошо, но он не показывает особого преимущества Angular Light над React.

В аппаратных интерфейсах (то что вы назвали "портами") вы кажется несколько некомпетенты. Каждый интерфейс выполняет свою задачу и к примеру нет дискуссий о надежности или перспективности. Тот же UART (который вы обозвали COM-портом), спокойно трудится в вашем смартфоне, соединяя чип Bluetooth с центральным процессором.
Что действительно стоит сравнивать — так это максимальную скорость, количество сигнальныйх линий, требования к линиям, ограничения на длину, устойчивость к помехам, требования к наличию PHY и т.д. Всё это можно выразить в числах и сравнить.

Новые интерфейсы изобретают просто потому что новые технологии позволяют сигналы быстрее и точнее.
Обычно действия несколько сложнее, чем отрендерить 4 дива. А если вам нужно выполнять действие во время скролла страницы (ленивая подгрузка данных, например), то все действия должны укладываться в весьма жёсткие рамки, иначе будут заметны рывки. Кроме того вы не учитываете, что кроме ноутбуков за несколько килобаксов тем же интерфейсом пользуются и со смартфонов баксов за 100. Так что любые разговоры про абсолютные значения в достаточно простом тесте не имеют никакого смысла. Смысл имеет только один факт: там, где один фреймворк уже тормозит, другой ещё не тормозит.

Ангуляр, например, без конца дёргает функцию фильтрации (которая — бизнес-логика, ага) только для того, чтобы узнать не изменился ли список выводимых строк, потому как он не знает от чего он зависит, а от чего — нет. Реакт будет на каждый чих пытаться перерендерить весь виртуальный дом, если мы не понапишем костылей в виде shouldComponentUpdate. В приветмирах всё это отрабатывает быстро, но когда это всё начинает тормозить — вам уже некуда деваться. И повляются статьи по оптимизациям в духе "не используете $timeout", "делайте одноразовые биндинги", "добавляйте debounce" и тп.

Я говорил вот об этих портах.
При этом каждый вложенный элемент получил уникальное имя

… где же я это видел? А, конечно. asp.net Web Forms. Гарантированно уникальный идентификатор для каждого контрола.
Да так-то такой подход много где используется. Я использую фреймворк Yii, там есть нечто похожее. Генерация простая, вида "'w' .+ $counter++" ('w' от слова widget). А в web forms это как делается?
Да почти так же, магические константы и счетчики на каждом уровне. Другое дело, что это одна из тех вещей, за которые вебформзы ненавидели.
Во-первых, получившиеся идентификаторы были гигантские и неудобоваримые.
А во-вторых, получались они на сервере и слабопредсказуемо, поэтому работать с ними из клиентского кода было адски неприятно.
В предлагаемом мной решении, идентификаторы обоснованно длинные. Помимо идентификационной функции они несут также и отладочную. Ну и в целом человекопонятны. Например, по идентификатору $mol_app_todo.app().taskRow(4).dropper() сразу понятно, что это кнопка удаления задачи в четвёртой по счёту строке в приложении mol_app_todo.
В вебформзах тоже было понятно, что где. К сожалению, недостатков это не перевешивало.
Из упомянутых вами недостатков, я вижу тут только один — длинна. Зато достоинств — масса.
Все зависит от того, кто работает с объектами по этим идентификаторам. Если только сами компоненты, то пофиг. А вот если идентификаторы используются снаружи — вот тогда начинается цирк.
Какой, например, цирк?
А как мне, находясь снаружи компонента — получить идентификатор какого-нибудь поля внутри компонента?
Например, если я хочу сделать с ним что-нибудь инструментами, отличными от вашего фреймворка?
Какими, например?

Ну, допустим, так:

var cellId = row.cell('title').objectPath
var cellNode = row.cell('title').node().get()
Какими, например?

Ванильным JS, например.

var cellId = row.cell('title').objectPath

Неплохо, но печально. Печально по двум причинам: во-первых, мне надо как-то догадаться, что objectPath — это идентификатор, назначенный HTML-элементу, и, во-вторых, на строковом идентификаторе все прелести статической проверки умерли.
Все равно же генерить надо — так почему бы не генерить нормальные свойства?
Так что вы хотите сделать на ванильном JS? В норме, фреймворк должен абстрагировать от реальных узлов, потому как этих реальных узлов может и не быть в случае нативных компонент.

Не так уж это и сложно, догадаться:

'title' — это ключ, для создания множества типовых объектов. Ничто не мешает сделать по отдельной фабрике для каждой ячейки:

var cellId = row.cellTitle().objectPath
var cellNode = row.cellTitle().node().get()

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

Вооо, вот мы и вернулись к стартовому "Все зависит от того, кто работает с объектами по этим идентификаторам. Если только сами компоненты, то пофиг."
Я не понимаю, что вы пытаетесь доказать. Впрочем, как обычно.
Ну вот есть JSF. Он для компонентов генерирует свои id. Причём, значение id при работе с компонентами в рамках самого фреймворка и id, которые получаются после рендера — разные.

Пока всё укладывается в рамки JSF, всё хорошо. Можно просто получить id компонента.
Но если вдруг так случалось, что нужно было получить правильный id уже отрендеренного компонента, начинались костыли.

Как видно, эти проблемы не только в вебформах, а в подходе в целом.
Странно что в 2016 году вы приводите синтаксис Polymer 0.5 при том, что версия 1.0 доступна уже давно, а вчера вышла 1.3.
В 1.x всё ещё нет наследования, только композиция. А [select] это не хак, это фактический стандарт веб-компонентов (в v1 версии спецификации веб-компонентов заменено на именованные слоты, что немного отличается, но в целом более практично).
Не подскажете, как эти примеры будут выглядеть на последней версии полимера?
А в какой версии оно есть?
Это хак в том смысле, что берётся одно дерево и оно видоизменяется. При этом происходит жёсткая завязка на конкретную структуру этого дерева.
На самом деле ваш пример не сильно отличается, там такие вещи как data-binding и подобные сделали более простыми и явными:

<dom-module id="my-panel">
    <template>
        <div class="header">
            <content select="[my-panel-head]" />
        </div>
        <div class="bodier">
            <content />
        </div>
    </template>
    <script>Polymer({is: 'my-panel'})</script>
</dom-module>

Нет больше полностью декларативного объявления компонентов, как минимум то, что я написал выше нужно сделать, потом оно найдет модуль с id соответствующим is и таким образом инициализирует компонент.
Также в 1.x получше дела с производительностью, много интересных фич в плане стилей — CSS variables & CSS mixins везде.

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

Наследования пока нет вообще (если не считать прототипы в локальных ветках отдельных разработчиков), обещают давно, не уверен на сколько оно сейчас готово. Слоты есть в спецификации, Polymer их не внедряет пока не появятся в стабильных версиях популярных браузеров.
Спасибо, поправил статью.
Критика в адрес Polymer выглядит неубедительно, очень советую автору копнуть его глубже.
С полимером всё оказалось куда хуже, чем я думал изначально. Или вы считаете отсутствие наследования, избыточность кода и теряющиеся в нём имена параметров не убедительными аргументами?
Наследование есть, с именами параметров все ок, со стилями там тоже все ок (можно использовать инкапсуляцию а можно общие стили) избыточность присуща любому фреймворку, ну и вообще по всем пунктам все неверно. А главное, что по сути вообще не важно что там внутри Полимера, потому как это только надстройка над веб-компонентами и вам ничего не мешает реализовать любые "финты" и хитромудрые архитектуры имея очень гибкий инструмент для декомпозиции. Прямо сейчас я делаю сложнейшее (в плане интерфейса) приложение на Polymer, и даже не знаю что бы я без него делал. До этого я делал приложения на React и Angular, поэтому мне есть с чем сравнивать.
Покажите же как реализуется наследование, не будьте голословными. Ну и было бы интересно узнать что это за сложнейшее приложение такое.
Или вы считаете отсутствие наследования

А вам точно нужно наследование в UI-фреймворке?
Мне нужна кастомизация готовых компонент. Какие-то блоки удалить, какие-то добавить, какие-то перенести в другое место, какие-то по другому раскрасить. Знаете способы реализации этого без наследования?
Конечно, знаю. Точки расширения, шаблоны, стратегии — первое, что в голову пришло. Наследование для кастомизации, на самом деле, далеко не идеально подходит — слишком неочевиден баланс между is-a и has-a.
Вы не в теме проблематики. Типичная ситуация — нужно прикрутить календарик. Берём готовое оупенсорсное решение и используем. Всё хорошо, но вот потребовалось нам, например, скрыть селектор года (ибо год в нашем контексте всегда один и тот же), а вместо него поставить кнопку "на следующей неделе", которая автоматически устанавливает дату на ближайший рабочий день после текущей недели. Варианты действий:

  1. Форкнуть и героическими усилиями за неделю добавить недостающие точки расширения, шаблоны, стратегии, параметры или что там ещё может прийти в голову. Потом ещё долго огребать от несовместимостей с основной веткой.
  2. Оставить фичереквест мейнтейнерам и надеяться, что до дедлайна они соизволят необходимый функционал реализовать.
  3. Сказать заказчику, что это не возможно с готовыми решениями и требуется пара недель на разработку своего велосипеда.
  4. Написать мартышкин патч, который после рендеринга пробегается по DOM-у и приводит его к нужному виду. Обычно так и делают.
  5. Отнаследоваться и перегрузить необходимые аспекты поведения. Такое мало где поддерживается.
Вы не в теме проблематики.

Вам, конечно, виднее.

Отнаследоваться и перегрузить необходимые аспекты поведения.

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

Другое дело, что у вас, конечно, есть точки расширения, но уверены ли вы, что любое вписанное в них поведение не сломает компонент? Например, была у вас табличка, вы хотите переопределить общий генератор таблички, но оставить генераторы строчек — как (конечно же, не глядя в исходный код) это сделать?
Затем, чтобы создать свой компонент на 90% похожий на оригинал. Да, на разных страницах нужны разные календарики.

Перегрузить генератор таблички (rows) и не трогать генератор строчек (row). Все компоненты создаются через фабрики во владеющих их компонентах.
Затем, чтобы создать свой компонент на 90% похожий на оригинал.

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

Перегрузить генератор таблички (rows) и не трогать генератор строчек (row).

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

Если очень грубо, то:

type Table =
  //...
  member this.Render context =
    html {
      yield! RenderHeader
      yield! Rows |> Seq.concat (fun r -> r.Render(context))
      yield! RenderFooter
    }

Нас интересует фрагмент Rows |> Seq.concat (fun r -> r.Render(context)). Если мне нужна фильтрация, то я пишу this.Rows |> Seq.filter ... |> Seq.concat (fun r -> r.Render(context))), а если переопределение отображения — то:

  for (i, r) in Rows do
    yield "<tr>"
    yield! ["<td>"; i; "</td>"]
    yield! r.Cells |> Seq.concat (fun c -> c.Render(context))
    yield "</tr>"

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

Не понял, где у вас там что ломается. Но код, конечно, жуткий.
То что lair описал — это не наследование, а декоратор:

     public class BaseClass
     {
           public void DoSmthng(obj param)
           {
                 .....
           }
     }

     public class Wrapper
     {
           private BaseClass _innerObj = new BaseClass();           

           public void DoSmthng(obj param)
           {
                 ...
                 _innerObj.DoSmthng(param);
           }
     }     
Это реализация наследования через декоратор + не упомянутые точки расширения. Что выглядит как наследование и ведёт себя как наследование — это и называют наследованием. Давайте оставим терминологические споры и вернёмся в практическое русло. Я продемонстрировал простое решение, позволяющее легко и просто кастомизировать готовый компонент, не привлекая 100500 паттернов и не превращая код в груду копипасты вида public void DoSmthng(obj param){ _innerObj.DoSmthng(param); } ради непонятно чего. Есть идеи как сделать проще и гибче? Буду рад их услышать.
то выглядит как наследование и ведёт себя как наследование

Оно не ведет себя как наследование: вы не можете (в строго типизированных языках) использовать Wrapper вместо BaseClass.

Собственно, вот и различие is-a и has-a.
Вы запутались в паттренах :-) Это Адаптер я не могу использовать, так как меняется интерфейс, а Декоратор реализует тот же самый интерфейс. В том его и суть.
В примере выше нет "того же самого интерфейса", так что лично я ни в чем не запутался.
Именно так и работает наследование.

Нет, так работает композиция. Вы правда не знаете разницы, или просто шутите так?

Не понял, где у вас там что ломается.

Ну давайте еще раз, на пальцах.

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

GenerateTable
  GenerateHeader
  for each row
    GenerateRow
  GenerateFooter

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

Так вот, в типичном "компоненте" GenerateTable и GenerateRow связаны между собой настолько сильно, что вмешательство в один требует постоянной сверки с другим (это, кстати, не проблема наследования, это проблема кастомизации вообще). Как вы решаете эту проблему?
Допустим у нас такое описание:

$my_stats : $mol_tabler childs
    < header : $mol_view childs < foot : null
    < rows : null
    < footer : $mol_view childs < foot : null

Тут у нас объявлены следующие свойства: childs, header, head, footer, foot, rows. Можете перегружать любое из них.

rows реализуется как-то так:

rows() { this.prop( () => {
    var next = []
    for( var i = 0 ; i < 100 ; ++i ) next.push( this.row( i ).get() )
    return rows
} ) }

row — просто фабрика, например:

row( id : number ) { return (new $mol_view).setup( _ => {
    _.child = () => this.prop( id )
} ) }

Её тоже можно переопределить.
… и когда я переопределяю rows, я должен повторить большую часть этого замечательного кода ради измения, скажем в var i = 0 ; i < 50 ; ++i?
Если число строк захардкожено прямо в коде, то, очевидно, да. А вы ожидали чуда?
Ну да, вы же обещали чудо — "решение, позволяющее легко и просто кастомизировать готовый компонент". А я вам и показываю, что если компонент спроектирован без точки расширения в нужном вам месте, у вас не выйдет легко и просто.

Что же касается необходимости наследования: если header, rows и footer будут не перегружаемыми методами, а свойствами, содержащими функции, то каждый из нх все равно останется точкой расширения:

new table
{
  header = () => {/* custom header generation */},
  rows = () => {/* custom header generation */},
  footer = () => {/* custom header generation */}
}

Ну да, а еще бывает вот так:```cs
Html.Table()
.Header(() => {//})
.Footer(() => {//})
.RowTemplate(item => {//})

И тоже никакого наследования.

Но самое интересное все равно начинается в тот момент, когда вы поменяли поведение контрола (того же календаря), а теперь хотите, чтобы оно поменялось по всей системе (например, чтобы рабочие дни у вас помечались с понедельника по субботу), включая те места, где этот контрол создаете не вы, а другие контролы (например, в заголовке колонки грида есть кнопка "Фильтр", по которой для колонок с типом "дата" выпадает календарь). И вот если вы <i>это</i> хотите сделать легко и просто, то наследование вам не поможет (точнее, его недостаточно).
Речь шла не о всех возможных видах кастомизаций, а о кастомизации порядка и состава вложенных компонент.

В JS/TS методы — это поля содержащие функции. То, что вы реализовали — это тоже форма наследования, мартышкин патч. Она менее эффективна, чем через классы.

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

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

А там будут те же проблемы. Не вынесли конкретную "компоненту" в свойство — здравствую, копипаста.

То, что вы реализовали — это тоже форма наследования, мартышкин патч.

Нет, это не наследование — объект остается того же типа. Это как раз чистая кастомизация.

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

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

Я вам скажу страшную вещь, но грид не должен ничего знать про типы колонок — всё это инъектируется извне.

Грид как минимум знает про типы колонок, которые он умеет отображать. Метаданные конкретного набора данных он действительно получает снаружи.
Не вынесли конкретную «компоненту» в свойство —
— свалится при компиляции :-) Во view.tree создавать подкомпоненты можно исключительно через именованные фабрики.

Нет, это не наследование — объект остается того же типа. Это как раз чистая кастомизация.
В JS/TS все объекты «одного типа». Различия лишь в составе и значениях полей.

В каждом компоненте будете ее перегружать (и молиться, чтобы каждый компонент ее выставлял)? Или будете надеяться, что все компоненты смотрят на общую фабрику (и используют один интерфейс)?
Можно во все компонентны инъектировать и общую фабрику. Если уж надо совсем-совсем гарантированно везде, то проще, просто заменить исходный класс своим.

Грид как минимум знает про типы колонок, которые он умеет отображать.
Мой грид не занимается отображением колонок. Что я делаю не так? :-)
Во view.tree создавать подкомпоненты можно исключительно через именованные фабрики.

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

В JS/TS все объекты «одного типа».

Даже в JS это не так, а уж в TS — тем более. Ну и да, о какой тогда строгой типизации вы пишете в своем посте?

Можно во все компонентны инъектировать и общую фабрику.

Можно. Для этого вам придется налагать на все компоненты очень жесткие требования (которые при этом сложно статически валидировать).

Мой грид не занимается отображением колонок. Что я делаю не так

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

Даже в JS это не так, а уж в TS — тем более. Ну и да, о какой тогда строгой типизации вы пишете в своем посте?
Вы плохо знаете JS :-) В TS — структурная типизация.

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

Ну вот видите.

В TS — структурная типизация.

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

Или наоборот, не превращаю простую абстракцию табличного вывода данных в кухонный комбайн

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

1) Урезанный функционал, например нет переключения активных/удаленных задач
2) Меньше DOM/HTML, упрощенный HTML дает до +30% скорости (400 DOM элементов вместо 800 на список).
3) Упрощенные стили (CSS), это дает до +15% скорости рендеринга
4) Хитрый способ сохранения в localStorage + JSON.stringify, дает до +10% скорости, мы тут не скорость конвертирования JSON меряем, сохранение должно быть везде одинаково, либо убрано вообще.

Так что если вы приведете свое приложение в соответствие с остальными, то результат может заметно просесть.

Кстати ваше приложение как то не так работает в FireFox (как минимум у меня), не видно тасков в todo листе в момент тестирования. Судя по результатам вы в нем и тестировали.

А в Chrome у меня такие результаты после 3-х запусков (с последние версией Angular Light):

Оло-ло, а я поверил автору на слово и смотрел только исходники (и судя по всему, которые не понял), а наделе там просто какое-то своё «todomvc» (http://nin-jin.github.io/todomvc/examples/mol/)

vintage, весь смысл проекта TodoMVC показать идентичный результат, через призму разных подходов. Вы не просто «упростили», как написал lega, вы даже не используете шаблоны и CSS предоставляемый TodoMVC. Ай-ай.
  1. Не дошли руки добавить, да, однако фильтрация через изменение адреса поддерживается. Это не сильно влияет на результат.
  2. Если тот же функционал можно реализовать меньшим числом элементов — это разве не плюс? Да и HTML не столь уж и проще. В решении на $mol используется 4 элемента на строку. В решении на AL — 5. Возможно вас смутило, что в решении на $mol рендерятся лишь те строки, что попадают в видимую область. Ну так это тоже плюс, а не минус.
  3. К сожалению, изначально стили там написаны через одно место, так что требуется их адаптация. Кроме того, там есть ещё и 2 версии дизайна. 15% к скорости отрисовки — возможно, но сомнительно. К общему времени выполнения — совсем крохи.
  4. Мы меряем общую скорость работы идиоматичного кода. На одних фреймворках удобнее хранить все данные одним огромным JSON-ом, на других — отдельными парами ключ-значение. В этом нет ничего хитрого или нечестного.
  5. Это прототип, а не готовое решение, в ИЕ и ФФ я ещё даже не запускал. Бенчмарки я гонял в Хроме. Там погрешность порядка 100мс, так что выигрывает то одно решение, то другое. Если последняя версия AL стабильно выигрывает, то жду пул-реквест. :-)
Неважно как оно сверстано, я например не верстаю, а работаю с готовым html/css, который уже вылизан под все браузеры и прошел все цепочки принимающий, так что переверстывать уже некогда, да и бессмысленно, мой инструмент должен легко справиться с чем угодно. Вы же пошли по пути перепила под ваш инструмент, что наводит на определенные мысли.

Да, TodoMVC не образец красивой верстки, но тогда нужно было бы сделать два вариант, первый как есть, а второй свой, но который выглядит точно также как и первый, только «правильно». А дальше хоть статью пиши, как можно упростить и выиграть в скорости просто оптимизировав верстку.
Я пошёл по пути более грамотного построения процесса: сначала пишется описание компоненты, потом верстальщик дизайнит её, глядя на неё во всех возможных состояниях, а тем временем программист добавляет логику. А не так, что верстальщик каждый раз присылает новый хтмл, а программист его без конца "натягивает".

Визуализацию я подтяну, не волнуйтесь, внешне будет не отличимо.
Я пошёл по пути более грамотного построения процесса:

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

А не так, что верстальщик каждый раз присылает новый хтмл

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

Визуализацию я подтяну, не волнуйтесь, внешне будет не отличимо.

Вот с этого и нужно было начинать, только помимо визуализации, нужно и функционал привести в идентичное состояние.
Спасибо, что открыли мне глаза на то, как устроена жизнь :-) Тем не менее своё решения я позиционирую как то, на котором сложно сделать плохо (тормозно, нерасширяемо и тп), но легко сделать хорошо (эффективное обновление состояний — по умолчанию, точки расширения компонент — придётся, иначе не заработает, бем-аннотации элементов — автоматически).
Я правильно понял, что если сейчас захочу попробовать $mol, мне придется с нуля переделать свои уже компоненты, потому что они «неправильные» и я просто не смогу загнать свою уже существующую вёрстку в него?
А вы думали можно ничего не переписывая сменить парадигму программирования?
Да, именно так я и думаю.

При переходе на Angular, React, Meteor, Aurelia и так далее, я смогу использовать уже готовую верстку. Даже в Elm, можно загнать верстку или просто воспользоваться каким-нибудь elm-html.
Тут вы тоже можете забить на любезно предоставленные вам фреймворком поддерживающие наследование бэм-атрибуты и вручную хардкодить определённые классы при каждом наследовании/использовании. Только зачем? Понимаете ли, тут подход к разработке с другой стороны. Не оживление статического HTML, а собирание интерфейса из конструктора. И демонстрационные приложения показывают, как легко и просто собрать интерфейс приложения из готовых компонент.
Если тот же функционал можно реализовать меньшим числом элементов — это разве не плюс?
Это плюс, но для большинства фреймворков можно оптимизировать HTML, что даст лучшие показатели. В итоге получается соревнование кто лучше HTML подпилит, а не скорость фреймворка (хотя они и составят основную часть). Чтобы было честно нужно для всех оптимизировать HTML, а это накладно. Поэтому, я считаю, как минимум нужно выкладывать «каноническую» версию, ну и для интереса можно оптимизированную прикладывать.

15% к скорости отрисовки — возможно, но сомнительно. К общему времени выполнения — совсем крохи.
Общее время сокращается, например крайний случай — если отключить стили совсем то общее время выполнения снижается на ~30% (для AL).

жду пул-реквест. :-)
Да, я ещё оптимизированную версию запилю, интересно :)
И всё же DOM в принципе не может быть идентичным. У каждого фреймворка свои особенности по работе с ним и их надо учитывать. Коробочные фреймворки типа ExtJS или OpenUI5 не ориентированы на прямую работу с хтмл. Они предоставляют компоненты (кнопки, чекбоксы, инпуты), которые и надо использовать. Не использовать эти компоненты — это всё равно, что не использовать фреймворк. А тестировать надо всё же идиоматичный код, а не заточенный под тесты.

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

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

Сферический идеальный фреймворк
Я правильно понял что все манипуляции вокруг tree формата (в основном) для того что-бы можно было наследовать шаблоны (и CSS)?, а JS/TS и так наследовать можно.
Т.е. вы на каждый элемент создаете глобальную переменную?, это только в debug режиме?
Любой объект доступен по абсолютному пути, который и используется в качестве идентификатора. Это фича такая. Там ещё ссылки на объекты контролируются — когда на него не остаётся ссылок — он уничтожается. Вообще говоря, можно было бы сильно ускорить работу, если бы браузер предоставлял апи по отключению GC.

Я правильно понял что все манипуляции вокруг tree формата (в основном) для того что-бы можно было наследовать шаблоны (и CSS)?, а JS/TS и так наследовать можно.
Наследование, биндинги, минимизация визуального шума, возможность объявлять и использовать компоненты изучив лишь один простой язык (он проще чем html, но даёт больше возможностей, и гораздо проще чем js).
биндинги

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

Clear completed {{getCount()}}

Вам нужно "переместить" этот кусок в js и "назначить" дополнительное имя для связывания, таким образом шаблон получается разорван на 2 части, часть в tree другая часть в ts, хотя это по видимому ViewModel, но проще это этого не стало, да и верстальщику от этого поплохеет — что-бы поправить текст на месте, нужно лести в дебри TS.
Кстати это не гибко т.к. (по видимому) нельзя даже параметр в эту ф-ию передать (например: {{getCount('active')}}, {{getCount('removed')}} )

минимизация визуального шума

С учетом того что к view еще нужно портянку в TS писать, шума тут явно больше (чем в том же ангуляре).

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

Думаю проще было-бы сделать какой-нибудь трансформер html, например это

$my_panelExt : $my_panel childs
    < header
    < bodier
    < footer : $mol_block childs < foot : null

Можно сделать как-то так:

myPanelExt = htmlTransform(myPanel, {
  header: true,
  bodier: true, 
  footer: foot()
})

О! Я вспомнил, раньше модно было использовать XSLT, кажется вы изобретаете то же самое, только синтаксис другой.
Вам нужно «переместить» этот кусок в js и «назначить» дополнительное имя для связывания, таким образом шаблон получается разорван на 2 части, часть в tree другая часть в ts, хотя это по видимому ViewModel, но проще это этого не стало, да и верстальщику от этого поплохеет — что-бы поправить текст на месте, нужно лести в дебри TS.
Текстами и не верстальщик должен заниматься. В общем случае тексты нужно будет переключать в зависимости от языка, не трогая шаблоны. Я ещё не продумывал этот аспект, но он точно не должен быть в шаблонах.

Кстати это не гибко т.к. (по видимому) нельзя даже параметр в эту ф-ию передать (например: {{getCount('active')}}, {{getCount('removed')}} )
Идея в том, что шаблон не знает ничего про програмный код. Всё, что делает шаблон — это предоставляет слоты. А уже программист засовывает в эти слоты всё, что нужно.

С учетом того что к view еще нужно портянку в TS писать, шума тут явно больше (чем в том же ангуляре).
Конкретно во view.tree его меньше, во view.ts ещё меньше сделать не получилось без ущерба для статической типизации.

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

Думаю проще было-бы сделать какой-нибудь трансформер html
И статическая типизация идёт лесом. И верстальщик будет счастлив на JS шаблоны делать.

О! Я вспомнил, раньше модно было использовать XSLT, кажется вы изобретаете то же самое, только синтаксис другой.
ответить
Polymer куда ближе к XSLT, он делает выборки из деревьев по селекторам.
vintage Выложил оптимизированную версию (хотя она не шибко быстрее), github не позволяет сделать реквест именно на ваш репозиторий (опция в репе? либо из-за того что я clone сделал с вашего, а push в свой), поэтому коммит тут.
На данный момент результат такой (если сравнить с предыдущим скрином, видно что цифры немного ходят туда сюда).

Думаю дело в том, что вы не сделали форк через github. Чуть позже перенесу патч и добавлю редизайн своей версии. Кстати, вы перевели на новый дизайн или всё ещё используется старый?
Перезалил комит, сделал пул-реквест.
Смёржил, убрав читы со схлопыванием сохранения. Оптимизированную версию вынес отдельно, но её пришлось пока дисквалифицировать, так как она не комплитит задачи. Ну а своё решение привёл к новому дизайну.
Чуть чуть не дождались вы, вот исправленная версия: https://github.com/nin-jin/todomvc/pull/2
А что значит "не комплитит задачи"? Вручную все работает (да и на тесте тоже).
А что значит «не комплитит задачи»
Не правильно прочитал, хз, но сейчас все нормально работает.
Обновил статью новыми результатами бенчмарков. Теперь самый быстрый — AL.
Кому интересно, я тут на коленке запилил осображение графа зависимостей для компонент. Например, для ToDoMVC он сейчас выглядит так.
Ещё один программист собрался написать ещё один новый фреймворк

У меня вот на работе коллега написал ещё один JS-грид, он конечно по сути ничем не отличается от десятков других, но зато свой родной.

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

Те же атомы — я написал свою реализацию в 160 строк и доволен как слон. Что касается фреймворков… нужна простота. минимальное количество кода, чтобы проще было скопипастить к себе исходники, чем читать документацию. Максимальная приближенность к голому хтмл и яваскрипту.
Не поделитесь своей реализацией в 160 строк? Я бы с радостью выкинул свои 410 :-)

Мой тезис в том, что html + javascript плохо подходят для написания ui-компонент, в отличие от view.tree + typescript. html позволяет лишь передать элементу в качестве параметров лишь набор текстовых пар "ключ-зачение" и не более одного анонимного html-поддерева. А требуется-то и несколько именованных поддеревьев передавать, от чего возникают костыли со вложенными элементами, выносом параметров в отдельные функции и выборками по селектору. js код сложно исследовать — только в отладчике можно узнать наверняка какие поля есть у того или иного объекта.
Тайпскрипт — да. Я обожаю его за то, что тайпскрипт — это яваскрипт с метаданными. Прозрачная кодогенерация и полная совместимость с яваскриптом не оставляет ни одной причины НЕ использовать его.

А вот html… пытаться от него изолироваться, имхо, бесполезное дело. Но я до сих пор не уверен, как делать правильно. Возможно, использование компонента не обязано быть единственным тегом? Как в старые добрые времена jquery — мы пишем разметку, а потом она "оживает". типа: <div class=tabs activeTab={activeTab}><div class=tab></div><div class=tab></div></div>. Не знаю.
Есть несколько мест, где тоже нужны компоненты, но они никак не связаны c DOM — рендеринг в canvas и нативные компоненты. Кроме того, есть такие штуки как всплывающие окна, которые рендериться должны не внутрь компонента, а вообще где-то в стороне, но при этом принадлежать этой компоненте (она контролирует время жизни всплывающего окна). Вы вот привели типичный костыль, когда часть параметров передаются в атрибутах, а часть во вложенных элементах. Предлагаемое мной решение избавляет от этой дилемы — все параметры передаются единообразно:

$my_app : $my_tab_list
    activeTab < activeTab < first
    child < tabs
         < first : $my_tab
         < second : $my_tab
Вынудили вы меня заняться оптимизацией :-)

100 задач:

image
400 задач:

image
Глупостью вы занимаетесь, тратите время, оптимизируете что-то, в то время когда я просто взял готовый инструмент, который легко можно заменить на другой. Я мог, например использовать Inferno, который быстрей citojs. Но главное, ещё все эти инструменты имеют тесты, поддержку и много где используются, а citojs так прекрасно в IE8 работает.

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

По поводу «табличного процессора». Под такую задачу, с вероятностью 100% это будет canvas, а ячейки пересчитывать в едином цикле. Хотя возможно я бы попробовал какие-то элементы RP, взял бы а какую-нибудь уже готовую библиотеку, вроде reactor.js, хотя думаю единый цикл будет эффективнее.

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

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

гибкая реализация с атомами, с меньшей алгоритмической сложностью.

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

Опять же, если RP это прямо-таки «серебренная пуля», почему нет крупных игроков? Да, есть Метеор, но там всё совсем не так гладко, а Blaze выпиливают в угоду React. В итоге имеем только Rx, да Beacon.

P.S. Но, я всё равно желаю вам удачи, это хорошая тема для исследования, но я бы всё попытался отточить сами атомы и скомбинировать их уже с каким-то готовым решением, а не пилить абсолютно всё своё, это путь в никуда.
$mol ещё на стадии прототипа. А по вашей логике авторы inferno и citojs тоже глупостями занимаются, ибо вместо того, чтобы просто взять готовый и протестированный react, пилят что-то своё, оптимизируют. Что ж вы, кстати, не воспользовались inferno?

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

Зачем тут canvas? И каким таким образом canvas спасёт вас от необходимости перевычислять значения всех ячеек на каждый чих? Может и от stack overflow он как-то поможет при длинных зависимостях между ячейками? Ну а касательно reactor.js — вы фактически признали ущербность подхода "перевычисляем всё на каждый чих", ибо reactor.js — это крайне урезанная версия атомов.

Вы видите основания для тормозов и утечек по мере роста приложения в случае $mol_view? Я бы с радостью про них послушал. Аргумент "сперва добейся" пропустим.

Опять же, можете обосновать откуда может взяться необоснованный рост обсерверов? Может я не вижу каких-то очевидных вещей? Может не всегда A зависит от B и C при A=B+C?

А может беда не в RP, а в том, что метеор имеет кривую архитектуру?

Комбинировать атомы легко с чем угодно. Для интероперабельности атомы поддерживают интерфейсы обсерверов (on) и промисов (then,catch). На прошлом проекте я, например, комбинировал их с angular. Правда сам ангуляр был как пятая нога — зачем делать грязную проверку всего скоупа, если и так известно какие свойства поменялись?
А по вашей логике авторы inferno и citojs тоже глупостями занимаются, ибо вместо того, чтобы просто взять готовый и протестированный react

Нет, citojs не замена, а дополнение, его можно использовать в качестве альтернативы React.createElement.

Что ж вы, кстати, не воспользовались inferno?

Нет поддержки IE8.

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

Как это признал, я же написал, что буду использовать cavnas и единый цикл, как раз перерисовка всего и вся на каждый чих. А cavnas, потому что DOM под такую задачу не годиться, слишком медленный и не удобный. Сейчас даже проверил, Spreadsheets гугловый наконец-то переделали на canvas, вроде меньше тормозить стал. Осталось Docs ещё переделать, уверен что они либо это уже делают, либо сделают скоро.

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

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

Аргумент "сперва добейся" пропустим.

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

Опять же, можете обосновать откуда может взяться необоснованный рост обсерверов?

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

А может беда не в RP, а в том, что метеор имеет кривую архитектуру?

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

Как вы ловко перескакиваете с обсуждения архитектуры на конкретные решения и обратно, а также сравниваете полноценный фреймворк с библиотекой для рисования. Судя по тестам, что я привёл выше, недоделанный и толком даже не оптимизировавшийся $mol_view уделал citojs на 400 задачах. Это называется "kvo далеко позади"? Есть такой показатель как "алгоритмическая сложность". И у vdom сложность обновления зашкаливает O(DataSize*AppComplexity*DOMSize). В случае атомов она куда меньше O(SubAppComplexity).

Реализация на citojs: 6.2кб без учёта транспиляции в js и собственно citojs.
Реализация на $mol_view: 5кб без учёта транспиляции и не относящихся к $mol_app_todo модулей.

Общий объём скомпилированного кода со всеми библиотеками оценить сложно, ибо вы и использовали babel, который секунду только компилирует приложение при загрузке. В любом случае, ваша реализация представляет из себя один единственный шаблон на всё приложение и гигантский свич на все действия пользователя, что, мягко выражаясь, не особо поддерживаемо, ибо порождает горы копипасты. Для сравнения: шаблон у вас весит 3кб, а у меня 1кб.

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

Если тянуть с собой мегабайт зависимостей — такого слона продать не менее сложно.
Эх, короче ладно, я вас услышал, точнее понял, вы на своей волне и полностью погружены в свою тему, но вот я, да и судя по всему остальные, так и не могу проникнуться вашими идеями. На этом всё, сливаюсь, могу только удачи вам пожелать.
Спасибо за статью.
У меня вопрос, как замеры делаете? А то мне кажется мое f12 в Chrome кажется совсем колхозным решением.

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

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

Помните раньше было выражение — сферический конь в вакууме, вспомнилось :)

Про todomvc — какой оценивать реальную производительность фреймворка по этому показателю?

Думаете кто то реально будет использовать фреймворк на 1мб для реализации подобных задач?

Почти любой разраб реализует эту задачу на vanila.js более качественно чем с фреймворком.

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

Если отрисовка и фпс — самый критичный фактор — вряд ли кто то вообще будет использовать фреймворк, т.к. нативная реализация будет заведомо более оптимизируемой и контролируемой.
Про todomvc — какой оценивать реальную производительность фреймворка по этому показателю?

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


Думаете кто то реально будет использовать фреймворк на 1мб для реализации подобных задач?

Это уже происходит, ибо с фреймворком разработка идёт быстрее.


Почти любой разраб реализует эту задачу на vanila.js более качественно чем с фреймворком.

Почему же тогда VanillaJS реализация существенно проигрывает половине фреймворков? Ассемблер Ванильный яваскрипт позволяет талантливому разработчику написать быстрое приложение. Си++ Фреймворк позволяет посредственному разработчику написать приложение не хуже за вдвое меньшее время.


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

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


Если отрисовка и фпс — самый критичный фактор — вряд ли кто то вообще будет использовать фреймворк, т.к. нативная реализация будет заведомо более оптимизируемой и контролируемой.

Почему же игры (где за низкий фпс вас закидают геймпадами) поголовно делаются на фреймворках (так называемых "движках")?

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


вот именно, вам говорят о том же, к чему ваши ссылки на тудумвц бенчмарки?
Почему же тогда VanillaJS реализация существенно проигрывает половине фреймворков?


вы про реализацию чего говорите?
И на главный вопрос не ответили,

Прочитал статью и первый вопрос который возник — идеальный фреймворк для чего?
Sign up to leave a comment.

Articles