Самый sexy framework для веб-приложений +3



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




Итак, Sexy framework – это реактивный компилятор и анализатор Javascript кода для создания пользовательских интерфейсов. В отличии от других фреймворков, Sexy спроектирован, чтобы использовать весь нативный потенциал Javascript.


Это означает, что фреймворк не работает в runtime. Всю основную работу он делает в момент компиляции. Фреймворк анализирует ваши компоненты и переводит их в нативный Javascript с очень небольшим оверхедом: максимальный вес бандла всего 3.7kb gzip. (если будут использованы все типы рендеринга, анимации и т.д.)


Вообще, Sexy фреймворк был сделан для работы совместно с серверным рендерингом, где и показывает лучшие цифры по Google PageSpeed. Например, значение FID (Fisrt input delay) при гидратации 500 статичных компонентов занимает всего 50мс, когда у NuxtJs все 180мс, а у Svelte 500мс.


Это было бы не так важно, если бы доля мобильных устройств в интернете не была бы около 68% и дело не только в SEO.


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


Почему он такой Sexy?


1. Не работает в runtime


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


2. Не использует виртуальный DOM


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


3. Нет реактивных библиотек


Sexy полностью реактивен, но не использует ни одну реактивную библиотеку и, скорее всего, никогда не будет использовать. Почему? Потому что реактивность делается в момент компиляции. Sexy анализирует весь написанный Javascript код и проставляет реактивные зависимости вручную, за вас и за браузер.


4. Частичная гидратация


Sexy был спроектирован с подходом hydrate first. То есть изначально разрабатывался, чтобы гидратация была максимальна быстрая, дабы повысить FID и TTI. В среднем скорость начала работы на клиенте (при SSR) лучше, чем у других фреймворков минимум в 3 раза.


5. Действительно нативный


Sexy переводит весь код в нативные Javascript инструкции, поэтому sexy-компоненты весят мало.


6. Очень быстрый


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


Работа с Sexy framework


Существует два способа начать использовать Sexy фреймворк:


1. Простой – создать веб-приложение и начать писать код


npx create-sexy-app sexy-app

И запустить в режиме разработчика


npm run dev

2. Сложный – Подключить библиотеки вручную и настроить бандлер (webpack)


Подробнее в документации.


Sexy framework


Фреймворк находится в alpha версии.


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


Также в проект нуждаются контрибьюторы, core-команда еще не сформирована.




Комментарии (61):

  1. pfffffffffffff
    /#21813998

    Мм ручное управление реактивностью

    • kirBurkhanov
      /#21814010

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

      Но фреймворк сам проставляет реактивные зависимости вручную в момент компиляции компонента.

      • Akuma
        /#21814110

        Ага. Ровно до первого случая когда «щас мы тут изъеб**мся» перестанет работать, потому как кроме разработчика никто не поймет что он там задумал :)

        UPD: Прям с ходу:http://sexy-js.ninja/docs/single-file-components/reactivity
        > Так делать не стоит, значение изменится, но обновления DOM не запустятся

        • kirBurkhanov
          /#21814190

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


          Насчёт того, как делать не стоит, можете уточнить, что имеете в виду?


          Пока единственная проблема (но она решаема) это полная реактивность объектов

          • Akuma
            /#21814382

            Вы уж определитесь:
            > Нет никакой ручной реактивности
            > разработчик явно помечает реактивные данные

            Насчёт того, как делать не стоит, можете уточнить, что имеете в виду?

            Это цитата из документации.

      • MaZaAa
        /#21814192 / -1

        Лол, посмотрите как работает MobX.

        • kirBurkhanov
          /#21814246

          Что конкретно вы имеете в виду?

          • MaZaAa
            /#21814288 / -2

            class State {
                @observable counter = 1;
            
                increment = () => {
                    this.counter++;
                };
            }
            const state = new State();
            
            export const App = observer(() => {
                return (
                    <div>
                        <h1>Counter: {state.counter}</h1>
                        <button onClick={state.increment}>incr</button>
                    </div>
                );
            });
            


            Вот это настоящая реактивность. Уже дофига лет в JS существуют getters/setters, а с появлением ES6 появилась Proxy, что делает реактивность ещё круче.

            codesandbox.io/s/frosty-hawking-jz7gg?file=/src/App.tsx

            • kirBurkhanov
              /#21814374

              Во первых, на прямую во фреймворках вы со всем этим все равно не работаете.


              Во вторых, вы говорите про то, как реактивность работает под капотом.


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

              • MaZaAa
                /#21814524

                Если взять именно возможности именно JS, то у вас не реактивность, у вас publisher / subscriber причем в явном виде руками, в MobX же это сделано внутри getters / setters в 4ой версии, и в Proxy в 5ой версии.

                То есть this.counter++ работает как ожидается, а у вас надо counter(counter() + 1);
                Или же this.item.name = 'asd' в MobX работает как ожидается, а у вас надо дополнительно вызывать руками функцию для вызова реакций

                • kirBurkhanov
                  /#21814680

                  Верно. Как указали ниже, обёртку можно и другую использовать, это не проблема.


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

                  • Riim
                    /#21814730

                    Так что на счёт самого алгоритма? Есть ли computed? Есть ли транзакции? Если этого нет, то MaZaAa прав, пока всё плохо.

                    • kirBurkhanov
                      /#21814768

                      Computed есть, транзакций пока нет, но тоже добавить реально.


                      Единственное, что делает Фреймворк, он проставляет зависимости для computed и subscriber без вызова функции на этапе компиляции. Чтобы не вызывать эту функцию в рантайме

                      • Riim
                        /#21814898

                        транзакций пока нет, но тоже добавить реально

                        вот с этим как раз больше всего проблем возникнет. Наиболее интересная на эту тему статья на на хабре: Изучаем и реализуем алгоритм работы правильного observer паттерна для react компонентов. Она, конечно, сильно облегчит вам работу, но может не стоит изобретать колесо? Посмотрите на cellx и на mobx. Оба решения отлично подходят для встраивания в фреймворки.

                        • kirBurkhanov
                          /#21814920

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

                          • Riim
                            /#21814938

                            Зависимости вычисляются при каждом вызове вычисляемой ячейки. А как вам нужно?

                            • kirBurkhanov
                              /#21814948 / +1

                              Вычислять зависимости на этапе компиляции, чтобы при запуске на клиенте не выполнилась лишняя работа

                              • Riim
                                /#21815012

                                Плохая это идея, посмотрите на следующий код:


                                let a = cellx(true);
                                let b = cellx(1);
                                let c = cellx(() => {
                                    console.log('Compute "c"');
                                    return a() || b();
                                });
                                
                                c.subscribe(() => {});
                                
                                b(2);
                                // => Compute "c"
                                
                                a(false);
                                // => Compute "c"
                                
                                b(3);
                                // нет вычисления "c"

                                При вычислении зависимостей на этапе компиляции они будут прибиты к вычисляемым ячейкам и в последней строке примера вместо 'нет вычисления "c"' будет 'Compute "c"', то есть лишнее вычисление. В конечном счёте на таких лишних вычислениях вы будете терять куда больше производительности. Плюс головная боль при отладке.

                              • nin-jin
                                /#21815028

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

                                • kirBurkhanov
                                  /#21815206

                                  Riim nin-jin

                                  Точно. Что-то я не подумал об этом. Можно попробовать разделить subscriber на 2 этапа:
                                  — «Биндинг» зависимостей
                                  — Работа с DOM

                                  Возможно это решение можно будет прикрутить к Svelte и не нужно будет изобретать велосипед. Попробую, спасиб!

                                  • nin-jin
                                    /#21815578

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

                                    • kirBurkhanov
                                      /#21815776

                                      Тестанул. Действительно, я переусердствовал с оптимизацией.

                                      Реактивные либы, если биндить зависимости, но пропускать работу рендера, работают отлично и никакого overhead не добавляют. Походу где-то я накосячил с предыдущим бенчем.

                                      Попробую узнать, как можно внедрить в svelte. Будет круто, если не нужно будет дальше изобретать велик.

                                    • Riim
                                      /#21815842

                                      Не факт, что это сильно ускорит

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

            • Riim
              /#21814518

              Вот это настоящая реактивность.

              в mobx просто другие обёртки над алгоритмом реактивности. Если сам алгоритм у kirBurkhanov написан хорошо, то не понимаю, что мешает считать такую реактивность настоящей. Дописать удобные обёртки (в тч. Proxy) делов на пару выходных.

  2. Imbecile
    /#21814022

    Код на гитхаб закрыт. :(

  3. Griboks
    /#21814138

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

    • kirBurkhanov
      /#21814182

      Конечно, проблема в том, что я один, не успеваю все сделать. Для начала хотелось бы понять, нужно ли данное решение кому нибудь кроме меня, прежде чем делать production-ready решение.


      Данная статья, alpha версия и дока как раз, чтобы проверить, нужно ли «ещё одно» решение для рынка.

  4. arvitaly
    /#21814210

    Совершаете ту же ошибку, что и Svelte на старте, без поддержки TypeScript — все это даже смотреть бессмысленно.

    • kirBurkhanov
      /#21814270

      Синтаксис добавить не проблема. Главное Это понять нужно ли новое решение, утвердить весь синтаксис и довести Фреймворк до production

      • Akuma
        /#21814390

        Синтаксис не нужно добавлять. Нужно сразу писать на TS.
        «Потом» поддерживать @types — так себе занятие будет.

      • arvitaly
        /#21814400

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

        • Imbecile
          /#21814664

          А что именно в Ангуляре не так с поддержкой ТС?

          • radtie
            /#21815632

            Ну я так понимаю проблема в шаблонах, там и с типизацией беда, а поддержки синтаксиса вообще нет.

            • Imbecile
              /#21815730

              Вы давно не открывали Angular и VSCode.


              • radtie
                /#21816788

                Видимо не совсем ясно выразился.

                Я имел ввиду, что в шаблонах нет поддержки синтаксиса TS, есть элвис-оператор и все, нельзя использовать привидение типов и т.п.:

                (item as SomeClass).prop1


                И в input-binding можно подсунуть совсем не тот тип, что описан в компоненте

                • Imbecile
                  /#21816830

                  Согласен. Тут пока ещё всё не очень. :(
                  Иногда этого не хватает.

    • kirBurkhanov
      /#21814418 / +1

      Я был бы очень рад, если бы это кто то сделал за меня и приложения стали работать быстрее, правда.


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

  5. Alroniks
    /#21814488

    Да это ж Svelte какой-то! На минималках…

    • kirBurkhanov
      /#21814640

      Почти так оно и есть, только Гидратация работает быстрее раз в 10.

      • JustDont
        /#21816766

        Svelte в своё время тоже работала «быстрее». А потом её взялись доводить до ума и до удобства использования, и вот уже оба-на, «в десять раз быстрее» кто-то другой. Правда, если этого другого довести до ума и удобства использования — фиг знает, что там получится.

        • kirBurkhanov
          /#21816998

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

          • MaZaAa
            /#21817020

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

            • kirBurkhanov
              /#21817094

              А можете показать пример проекта, где производительность реакта Вас устраивает?

              • MaZaAa
                /#21817352 / -1

                Любой проект написанной мной/командой возглавленной мной работает замечательно и быстро, заметте именно в связке с MobX, даже вместо локального состояния компонентов MobX. Голый реакт или реакт + redux(и ему подобное) это дно, я знаю.

                • kirBurkhanov
                  /#21817368

                  Верю, но хочется увидеть живой пример, цифры pagespeed

  6. a1ex322
    /#21814868

    Подскажите, есть ли роутинг? Я не фронтендер, но вроде для svelte он делается на стороне сервера (sapper), а в модных фремворках-клиента.

    • kirBurkhanov
      /#21814924

      Есть, роутинг пока очень простой. Но он везде делается и там и там, где есть ssr

  7. nin-jin
    /#21814888

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


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

    • kirBurkhanov
      /#21815052

      1. Согласен
      2. Двусторонний биндинг можно кастомизировать с помощью своей директивы
      3. Так и есть, как часто вообще используется «ручной» перенос?
      4. На данный момент так и есть, нужен свой MobX, Vuex и т.д.

      Проблем много, но они решаемы. Вопрос только нужно ли новое решение… И насколько остро стоит вопрос производительности при запуске фреймворка. А-ля замена «jQuery» для сайтов с изолированными компонентами и нормальным тестированием.

      • nin-jin
        /#21815634

        2) Писать по директиве для каждого параметра каждого компонента, где нужна двусторонняя связь — довольно утомительно.
        3) В рамках списка переносы тоже ведь не в ручную делаются. Достаточно уметь идентифицировать компонент не в рамках одного списка, а глобально.


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

        • kirBurkhanov
          /#21815866

          2. Согласен
          3. У меня в списке элементы заново не рендерятся. Так-то можно через привязку к ключу реализовать.

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


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

          • nin-jin
            /#21816472

            Потому что какой сайт ни откроешь — ждёшь по 10 секунд. Типичная картина выглядит как-то так:



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

  8. lega
    /#21817326

    О! Я тоже на днях новый фрейморк запилил, (уже ни дня без новых фреймворков :)
    Сделал аналогичную кнопку с главной страницы, получилось 1кб виджет против 5кб на sexy-js

    У вас есть пример TodoMVC для сравнения?, у меня оно весит всего 2.7kb

    • kirBurkhanov
      /#21817842

      5кб это из за роутинга и глобальных компонентов, которые добавлены в SSR

  9. kirBurkhanov
    /#21817386

    Сделаю, но скорее всего больше, цель была не размер сократить, а скорость сделать быструю.


    Подход с innerHTML и template тоже использую.

  10. VolCh
    /#21818024 / +1

    Название фреймворк затруднит, по-моему, внедрение в серьёзных компаниях.

  11. /#21818186

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

    По коду на гитхабе видно, что вы много оттуда позаимствовали. Вот мне и любопытно, какие были фундаментальные причины кроме NIH.

    • kirBurkhanov
      /#21818190

      Оттуда было позаимствована их вариация map + идея с пустыми textNode.

      Гидратация в sinuous очень медленная, хуже чем во Vue.