Правила выбора JS-фреймворка

    TL;DR

    • В статье не рассматриваются JS-фреймворки из списка TOP-3
    • При разработке на JS-фреймворке не из списка TOP-3 приходится решать на порядок больше технических вопросов, чем это ожидается в начале разработки
    • История основана на реальных событиях

    История началось с одного мини-проекта, который изначально разрабатывался на основе библиотек backbone.js и marionette.js. Это, конечно, великие библиотеки, с которых начиналась история разработки одностраничных приложений. Но уже в то время они представляли, скорее, историческую, чем практическую ценность. Упомяну лишь тот факт, что для отображения простой таблицы нужно было создать: 1) модуль с описанием модели, 2) модуль с описанием коллекции, 3) модуль с определением вью модели, 4) модуль с определением вью коллекции, 4) шаблон строки таблицы, 5) шаблон таблицы, 6) модуль контроллера. Имея в небольшом приложении около 10 сущностей — у Вас уже на самом начальном этапе было более полусотни мелких модулей. И это только начало. Но сейчас не об этом.

    В какой-то момент, после полугода работы приложения, оно все еще не появилось в поисковой выдаче. Добавление в проект prerender.io (который тогда использовал движок phantom.js) помогло, но не так существенно, как ожидалось. И над приложением, и надо мной начали сгущаться тучи, после чего я понял что нужно сделать что-то очень быстро и эффективно, желательно сегодня. Цель я поставил такую: перейти на серверный рендеринг. C backbone.js и marionettejs это сделать практически невозможно. Во всяком случае, проект rendr на backbone.js, который разрабатывался под руководством Spike Brehm (автора идеи изоморфных/универсальных приложений), собрав 58 контрибьюторов и 4184 лайков на github.com, был остановлен в 2015 году, и явно не предназначался для однодневного блица. Я начал искать альтернативу. TOP-3 JS-фреймворка я исключил из рассмотрения сразу, так как не имел запаса времени на их освоение. После недолгих поисков я нашел бурно в то время развивающийся JS-фреймворк riot.js github.com/riot/riot, который на сегодняшний день имеет 13704 лайков на github.com, и, как я надеялся, вполне мог бы со временем выйти на первые позиции (чего, однако, не произошло).

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

    На этом история успеха заканчивается и начинается история поражений. Следующий проект был существенно сложнее. Правда был положительный момент в том что 99% экранов приложения находились в личном кабинете пользователя, поэтому не было необходимости в серверном рендеринге. Окрыленный первым успешным опытом применения riot.js, я начал продвигать идею закрепить успех и применить на фронтенде riot.js. Тогда мне казалось, что, наконец, найдено решение которое совмещало простоту и функциональность, как это и обещала документация riot.js. Как же я ошибался!

    С первой проблемой я столкнулся, когда нужно было обеспечить верстальщика HTML-документов всеми необходимыми инструментами для разработки. В частности, нужны были плагины для редактора кода, движок, в котором можно было бы размещать компоненты и сразу наблюдать полученный результат, в том числе, с горячей перегрузкой компонентов (hot-reload). Все это нужно было в готовом к промышленной эксплуатации виде отдать в ближайшее время, и всего этого не было. В результате, верстка приложения началась на одном из традиционных шаблонизаторов, что в результате привело к неблагодарному этапу перевода HTML-документов в компоненты riot.js.

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

    Следующей подкатила проблема с роутингом. Роутинг в riot.js идет почти из коробки github.com/riot/route — во всяком случая от того же самого разработчика. Это позволяло надеяться на беспроблемную его работу. Но в какой-то момент я обратил внимание, что некоторые страницы непредсказуемо перегружаются. То есть один раз переход на новый роут может произойти в режиме одностраничного приложения, а в другой раз тот же самый переход перегружал полностью HTML-документ, как при работе с классическим веб-приложением. При этом, естественно, терялось внутреннее состояние, если оно еще не было сохранено на сервере. (В настоящее разработка этой библиотеки остановлена и с версией riot.js 4.0 оне не используется).

    Единственный компонент системы который работал, как ожидалось, был минималистичный flux-подобный менеджер состояний github.com/jimsparkman/RiotControl. Правда для работы с этим компонентом приходилось назначать и отменять слушателей изменения состояние гораздо чаще, чем этого хотелось бы.

    Первоначальный замысел этой статьи был такой: показать на примере собственного опыта работы с фреймворком riot.js задачи, которые придется решать разарботчику, решившему (решившимуся) разрабатывать приложение на JS-фреймворке не из списка TOP-3. Однако, в процессе подготовки я решил освежить в памяти некоторые страницы из документации riot.js, и так узнал, что вышла новая версия riot.js 4.0, которая полностью (с чистого листа) была переработана, о чем можно прочитать в статье разработчика riot.js на medium.com: medium.com/@gianluca.guarini/every-revolution-begins-with-a-riot-js-first-6c6a4b090ee. Из этой статьи я узнал что все основные проблемы, которые меня волновали, и о которых я собирался рассказать в этой статье, были устранены. В частности, в riot.js версии 4.0:

    • полностью переписан компилятор (вернее впервые написан т.к. раньше движок работал на регулярных выражениях) — это повлияло, в частности на информативность сообщений об ошибках
    • в дополнение в серверному рендерингу была добавлена гидрация (hydrate) на клиенте — это позволило, наконец-то, начать писать универсальные приложения без двойного рендеринга (первый раз на сервере и тут же на клиенте из-за отсутствия в старых версиях функции hydrate())
    • добавлен плагин для горячей перегрузки компонентов github.com/riot/hot-reload
    • и много других полезных изменений

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

    К сожалению, проделанная разработчиками riot.js работа еще не была должным образом оценена сообществом. Например, библиотека серверного рендеринга github.com/riot/ssr за полгода прошедшие с начала ее разработки собрала трех котрибьюторов и три лайка на github.com (не все лайки сделаны контрибьюторами, хотя один таки контриьбютором).

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

    Итак, начинаем. Для примера было сделана реализация приложения github.com/gothinkster/realworld. Это проект уже не раз обсуждался на Хабре. Для тех кто еще не знаком с ним, коротко опишу его идею. Разработчики этого проекта при помощи разных языков программирования и фреймворков (или без них) решают одну и ту же задачу: разработка движка блогов, по функциональности похожего на упрощенную версию medium.com. Это компромисс между между сложностью реальных приложений, которые нам приходится ежедневно разрабатывать, и todo.app, которое не всегда позволяет реально оценить работу с библиотекой или фреймворком. Это проект пользуется уважением среди разработчиков. В подтверждение сказанного могу сказать, что есть даже одна реализация от Rich Harris (мажорного разработчика sveltejs) github.com/sveltejs/realworld.

    Среда разработки


    Вы конечно готовы ринуться в бой, но подумайте об окружающих Вас разработчиках. В данном случае сконцентрируйтесь на вопросе, в какой среде разработки работают Ваши коллеги. Если для фреймворка, с которым Вы собираетесь работать, нет плагинов для основных сред разработки и редакторов программного кода — то Вас вряд ли поддержат. Я, например, для разработки использую редактор Atom. Для него есть плагин riot-tag github.com/riot/syntax-highlight/tree/legacy, который не обновлялся последние три года. И в этом же репозитарии есть плагин для sublime github.com/riot/syntax-highlight — он актуальный и поддерживает актуальную версию riot.js 4.0.

    Впрочем, компонент riot.js это валидный фрагмент HTML-документа, в котором JS-код содержится в теле элемента script. Так что все просто работает, если Вы добавите для расширения *.riot тип документа html. Разумеется, это вынужденное решение, так как иначе здесь продолжать дальше было бы просто невозможно.

    Подсветку синтаксиса в текстовом редакторе мы получили, и теперь нам нужен более продвинутый функционал, то что мы привыкли получать от eslint. В нашем случае JS-код компонентов содержится в теле элемента script, я надеялся найти и нашел плагин для извлечение JS-кода из HTML-документа — github.com/BenoitZugmeyer/eslint-plugin-html. После этого моя конфигурация eslint стала выглядеть так:

    {
      "parser": "babel-eslint",
        "plugins": [
          "html"
        ],
      "settings": {
        "html/html-extensions": [".html", ".riot"]
      },
      "env": {
        "browser": true,
        "node": true,
        "es6": true
      },
      "extends": "standard",
      "globals": {
        "Atomics": "readonly",
        "SharedArrayBuffer": "readonly"
      },
      "parserOptions": {
        "ecmaVersion": 2018,
        "sourceType": "module"
      },
      "rules": {
      }
    }
    

    Наличие плагинов для подсветки синтаксиса и eslint — наверное, не самое первое, о чем начинает думать разработчик выбирая JS-фреймворк. Между тем, без этих инструментов Вы можете столкнуться с противодействием коллег и с их массовым бегством по «уважительным» причинам с проекта. Хотя единственной и действительно уважительной причиной является, то что им некомфортно работать, не имея полного арсенала разработчика. В случае с riot.js проблема была решена способом Колумба. В том смысле, что на самом деле плагинов для riot.js нет, но благодаря особенностям синтаксиса шаблонов riot.js, который выглядит как фрагмент обычного HTML-документа, мы покрываем 99% нужной функциональности, задействуя инструменты для работы с HTML-документом.

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

    Сборка проекта


    К большинству фич, которые нужны при сборке проекта мы уже успели привыкнуть, и даже перестаем задумываться о том, что может быть иначе. Но иначе быть может. И, если Вы выбрали новый JS-фреймворк, то желательно предварительно убедиться, что все работет как Вы и ожидаете. Например, как я уже упоминал, наибольшая проблемы при разработке на старых версиях riot.js была связана с отсутствием в сообщениях об ошибках компиляции и времени выполнения информации о компоненте, в котором эта ошибка произошла. Также важной является скорость компиляции. Как правило, для ускорения скорости компиляции, в правильно построенных фреймворках перекомпилируется только изменившаяся часть, в результате время реакции на изменения текста компонентов минимальны. Ну и совсем хорошо, если поддерживается горячая перезагрузка компонентов без полной перезагрузки страницы в веб-браузере.

    Поэтому постараюсь перечислить чек-лист, на что нужно особо обратить внимание при анализе средств сборки проекта:

    1. Наличие режима разработки и рабочего приложения
    В режиме разработчика:
    2. Информативные сообщения об ошибках компиляции проекта (имя исходного файла, номер строки в исходном файле, описание ошибки)
    3. Информативные сообщения об ошибках времени выполнения (имя исходного файла, номер строки в исходном файле, описание ошибки)
    4. Быстрая пересборка измененных модулей
    5. Горячая перегрузка компонентов в браузере
    В рабочем режиме:
    6. Наличие версионности в имени файлов (например 4a8ee185040ac59496a2.main.js)
    7. Компоновка мелких модулей в один или несколько модулей (чанков)
    8. Разбиение кода на чанки с использованием динамического импорта

    В riot.js версии 4.0 полявился модуль github.com/riot/webpack-loader, котрый полностью соответсвует приведенному чеклисту. Я не буду перечислять все особенности конфигурации сборки. Единственное на что обращу внимание, что в рассматриваемом проекте я применяю модули для express.js: webpack-dev-middleware и webpack-hot-middleware, которые позволяют сразу, с момента верстки, работать на полнофункциональном сервере. Это, в частности, позволяет разрабатывать универсальные/изоморфные веб-приложения. Обращу Ваше внимание, что модуль горячей перегрузки компонентов действует только для веб-браузера. В то же время, компонент, отрендеренный на стороне сервера, остается неизменным. Поэтому необходимо прослушивать его изменения, и в нужный момент удалить весь кэшированный сервером код и загрузить код измененный модулей. Как это сделать долго описывать, поэтому только приведу ссылку на реализацию: github.com/apapacy/realworld-riotjs-effector-universal-hot/blob/master/src/dev_server.js

    Роутинг


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

    Проведем небольшой экскурс в историю. На заре интернета URL/URI соответствовали имени файла, который хостится на сервере. Перевернем сразу несколько страниц истории и мы узнаем о появлении Model 2 (MVC) Architecture. В этой архитектуре появляется фронт-контроллер, который выполняет функцию роутинга. Я задался вопросом, кто первый решил из фронт-контроллера выделить функцию роутинга в отдельный блок, который дальше отправляет запрос на один из множетсва контроллеров и пока не нашел ответа. Такое впечатление что это начали делать все и сразу.

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

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

    … позволяет легко создавать SPA-приложения. Включает следующие возможности

    • Вложенные маршруты/представления
    • Модульная конфигурация маршрутизатора
    • Доступ к параметрам маршрута, query, wildcards
    • Анимация переходов представлений на основе Vue.js
    • Удобный контроль навигации
    • Автоматическое проставление активного CSS класса для ссылок
    • Режимы работы HTML5 history или хэш, с автопереключением в IE9
    • Настраиваемое поведение прокрутки страницы

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

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

    И такую библиотеку я нашел, это github.com/kriasoft/universal-router. Если в двух словах описать идею этой библиотеки, то она конфигурирует роуты, которые на входе принимают строку URL, а на выходе вызывают асинхронную функцию, которой передают разобранный URL в виде фактического параметра. Честно говоря, хотелось спросить: и это все? И как дальше с этим всем нужно работать? И тут я нашел статью на medium.com medium.com/@ippei.tanaka/universal-router-history-react-97ec79464573, в которой был предложен достаточно хороший вариант, за исключением пожалуй переписывания метода push() history, который бал совершенно не нужен и который я из своего кода удалил. В результате работа роутера на стороне клиента определяется примерно так:

    const routes = new UniversalRouter([
      { path: '/sign-in', action: () => ({ page: 'login', data: { action: 'sign-in' } }) },
      { path: '/sign-up', action: () => ({ page: 'login', data: { action: 'sign-up' } }) },
      { path: '/', action: (req) => ({ page: 'home', data: { req, action: 'home' } }) },
      { path: '/page/:page', action: (req) => ({ page: 'home', data: { req, action: 'home' } }) },
      { path: '/feed', action: (req) => ({ page: 'home', data: { req, action: 'feed' } }) },
      { path: '/feed/page/:page', action: (req) => ({ page: 'home', data: { req, action: 'feed' } }) },
      ...
      { path: '(.*)', action: () => ({ page: 'notFound', data: { action: 'not-found' } }) }
    ])
    
    const root = getRootComponent()
    
    const history = createBrowserHistory()
    
    const render = async (location) => {
       const route = await router.resolve(location)
       const component = await import(`./riot/pages/${route.page}.riot`)
       riot.register(route.page, component.default || component)
       root.update(route, root)
    }
    
    history.listen(render)
    

    Теперь при любом вызове history.push() будет инициироваться роутинг. Для навигации внутри приложения также нужно создать компонент, оборачивающий стандартный HTML-элемент a (anchor), не забывая отменить его поведение по умолчанию:

    <navigation-link href={ props.href } onclick={ action }>
      <slot/>
      <script>
        import history from '../history'
        export default {
          action (e) {
            e.preventDefault()
            history.push(this.props.href)
            if (this.props.onclick) {
              this.props.onclick.call(this, e)
            }
            e.stopPropagation()
          }
        }
      </script>
    </navigation-link>
    

    Управление состоянием приложения


    Изначально, я включил в проект библиотеку mobx. Все работало как и ожидалось. За исключением того, что не вполне соответствовала задаче — исследование, которую я поставил в начале статьи. Поэтому я переключился на github.com/zerobias/effector. Это очень мощный проект. Он дает 100% от функциональности redux (только без больших накладных расходов) и 100% функциональности mobx (хотя в этом случае кодировать нужно будет немного больше, впрочем все же меньше, если сравнивать с mobx без декораторов)

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

    import { createStore, createEvent } from 'effector'
    import { request } from '../agent'
    import { parseError } from '../utils'
    
    export default class ProfileStore {
      get store () {
        return this.profileStore.getState()
      }
    
      constructor () {
        this.success = createEvent()
        this.error = createEvent()
        this.updateError = createEvent()
        this.init = createEvent()
        this.profileStore = createStore(null)
          .on(this.init, (state, store) => ({ ...store }))
          .on(this.success, (state, data) => ({ data }))
          .on(this.error, (state, error) => ({ error }))
          .on(this.updateError, (state, error) => ({ ...state, error }))
      }
    
      getProfile ({ req, author }) {
        return request(req, {
          method: 'get',
          url: `/profiles/${decodeURIComponent(author)}`
        }).then(
          response => this.success(response.data.profile),
          error => this.error(parseError(error))
        )
      }
    
      follow ({ author, method }) {
        return request(undefined, {
          method,
          url: `/profiles/${author}/follow`
        }).then(
          response => this.success(response.data.profile),
          error => this.error(parseError(error))
        )
      }
    }
    

    В этой библиотеке используются полноценные редьюсеры (они так в документации effector.js и называются), которых многим не хватает в mobx, но с разительно меньшими усилиями по кодированию, по сравнению с redux. Но главное даже не это. Получив 100% от функциональности redux и mobx, я использовал только десятую часть того функционала, который заложен в effector.js. Из чего можно сделать вывод, что его применение в сложных проектах может существенно обогатить средства разработчиков.

    Тестирование


    TODO

    Выводы


    Итак, работа завершена. Результат представлен в репозитарии github.com/apapacy/realworld-riotjs-effector-universal-hot и в этой статье на Хабре.
    Демо сайт на now realworld-riot-effector-universal-hot-pnujtmugam.now.sh

    И в завершении поделюсь своими впечатлениями отразработки. Разрабатывать на riot.js версии 4.0 достаточно удобно. Многие конструкции записываются проще чем в том же React. На разработку ушло ровно две недели без фанатизма в послерабочее время и в выходные дни. Но… Одно маленькое но… Чудо опять не произошло. Серверный рендеринг в React работает в 20-30 раз быстрее. Корпорации опять побеждают. Впрочем опробованы в работе две интересные библиотеки роутинга и менеджера состояний.

    apapacy@gmail.com
    17 июня 2019г.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 32

      +1
      Вот никогда не поверю, что освоить с нуля Ангулар было сложнее, чем ковыряться с backbone или riot. Да и вообще, имхо, хороший разработчик — ленивый разработчик. Проще выучить новый фреймворк и быстро разработать, чем пытаться реализовать медленно на старом.
        –1
        Основной идеей riot.js была дать которое могло быт изучено в течение нескольких минут. Что и послужило его выбором для одного из проектов. Что касается любого из фреймворков ТОП-3 не думаю, что их можно было бы изучить хотя бы поверхностно за аналогичный период. тем более если говорить о периоде 5-летней давности когда было меньше и статей и средств разработки.
          +1
          Очевидно, что чем проще фреймворк (изучить за 5 минут), тем больше работы придется делать руками, чудес не бывает. Вон в vanila JS, буквально десяток-полтора методов выучить и можно лабать.
          Порог вхождения на то и выше, что тратя время сначала, потом ты существенно экономишь время. Тут очень тонкий баланс. С одной стороны, на маленьких проектах не хочется тратить много времени на ядро проекта и всякие CI, c другой стороны, это всегда потом экономит кучу времени, когда билдишь и деплоишь по одной кнопке.
            0
            Порог вхождения на то и выше, что тратя время сначала, потом ты существенно экономишь время.

            В «порог вхождения» в этом случае нужно включать не только время на чтение документации и мануалов, но и постоянные расходы на проект (бойлерплейт и прочую муть). В силу этого делать на ангуляре что-то маленькое (да и даже среднее, если честно) — не слишком-то имеет смысл, эти все расходы просто не успеют отбиться, и «сначала» время будет потрачено, а вот «потом» так и не наступит.
        +1
        При разработке на JS-фреймвёрке не из списка TOP-3 приходится решать на порядок больше технических вопросов, чем это ожидается в начале разаботки

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

          +1
          Статья не о конкретном фреймворке, а о том на что нужно обратить внимание при выборе нового, малоизвестного фреймворка. Про ТОП-3 во-первых все и всем известно и о проблемах тоже по многочисленным ISSUE. Во-вторых, с учетом их многолетней разработки тысячами разработчиков часть из которых работает на фултайме и за хорошую зарплату — там действительно все очень добротно — от документации, многочисленных статей в интернете до плагинов для редакторов и т.п. По новым фреймворкам, прежде чем начинать с ними работать нужно понять насколько решены сопутствующие вопросы.

          Если Вы имеете в виду какой-то конкретный фреймворк, который не входит в ТОП-3, и в котором решены все или многие из поднятых вопросов — я готов проделать ту же самую работу с этим фреймворком (имеется в виду разработку приложения realworld и написания по итогам сообщения на Хабре) — только назовите его.
            +1

            Согласен, надо на многое обращать внимание, однако и у ТОП-3 полно косяков, которые тоже надо иметь ввиду. Например, скорость загрузки и работы приложения, удобство отладки, архитектурная сложность, статическая типизация. Насчёт 1000 разработчиков — это вы на пару порядков преувеличили. Взять тот же Ангуляр — там всего пара десятков активных разработчиков. Отчасти это связано с архитектурной сложностью — не каждый сможет и захочет в это погружаться. А вот статей, конечно полно, в этом и суть хайпа. Ведь ТОП-3 они именно за счёт хайпа, а не за счёт каких-то своих исходных качеств.


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


            1. Наличие режима разработки и рабочего приложения

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


            1. Информативные сообщения об ошибках компиляции проекта (имя исходного файла, номер строки в исходном файле, описание ошибки)

            Всё процессится тайпскриптом, так что с этим всё хорошо.


            1. Информативные сообщения об ошибках времени выполнения (имя исходного файла, номер строки в исходном файле, описание ошибки)

            Плюс путь к объекту в рантайме от корня приложения.


            1. Быстрая пересборка измененных модулей

            Пересобирать приходится всё, чтобы тайпскрипт всё проверил. Но делается это относительно быстро — неколько секунд.


            1. Горячая перегрузка компонентов в браузере

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


            1. Наличие версионности в имени файлов (например 4a8ee185040ac59496a2.main.js)

            Лучше так: main.js?hash=4a8ee185040ac59496a2 или так: main.js?time=2019-06-17T00:00:00Z.


            1. Компоновка мелких модулей в один или несколько модулей (чанков)
            2. Разбиение кода на чанки с использованием динамического импорта

            А ещё лучше, когда кода получается так мало, что его не надо резать на чанки.

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

              Если фреймворк о котором Вы говорите это $mol — то я готов поработать с ним. Однако есть один нюанс. Мне интересно работать с технологиями которые поддерживают универсальные/изоморфные приложения то есть в общем случае хотя бы ssr. Если в этом фреймворке ssr идет из коробки или же я смогу найти к нему какую-нибудь библиотеку которая обеспечит ssr — то мне это будет интересно.
                +1

                Да, он полностью изоморфный. Нужен только лёгкий адаптер для экспресса c примерно следующим содержимым:


                // Configure ambient context
                const $ = $mol_ambient({
                    $mol_stateg_arg : class extends $mol_state_arg {
                        static href : ()=> href
                    }
                })
                
                // Application instance with context
                const app = $mol_app_todomvc.make({ $ })
                
                // Render app to html
                const html = app.dom_tree().outerHTML
          +1
          Извините за занудство, но «фреймвёрки» и «фреймворки» в одном тексте сильно бьют по глазам.
            0
            Спасибо, исправил. Мне вобще-то ближе транскрипция «фреймвёрки» и начинал я статью в OpenOffice. Но потом перенес документы в googledoc, а там в тезаурусе буква ё вообще отсутсвует (кстати просто по советским ГОСТам получается). И исправил кругом на «фреймворки» Но как выяснилось любой рефакторинг с переименованиями не бывает беспроблемным
            0
            Позвольте… А где же обещанные «Правила выбора JS-фреймворка»?
              0
              Ну как же об этом же статья вся. Если еще раз перечислить
              1. Среда разработки
              2. Среда сборки
              3. Наличие роутинга
              4. Менеджер состояний.

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

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

              Идея статьи в том что кроме самого фреймворка еще нужно обращать внимание на процесс разработки.
                0
                Да нет. Про что статья — я понял, прочитав её.
                Но она не содержит того, о чём указано в заголовке.

                Если бы у вас был перечень важных вопросов и рекомендации по выбору в зависимости от ответов — то да, это были бы правила.

                У вас же получился только краткий и неполный перечень того, о чём нужно бы подумать тому, кто в первый раз попробует применить js-фреймворк…
                  0
                  Да. У некоторых фреймворков справиться с проблемами не получится например нельзя исправить если нет сообщений об ошибках. Также если это проект не на долгие годы то не получится написать свои плагины для редакторов или линтер. Если найти весь джентльменский набор не получилось фреймворк не выбираем. Если получилось можно подумать о том чтобы выбрать.
                    0
                    :) Три раза прочём ваш комментарий, но так и не понял, что вы хотели сказать…
              0
              Спасибо за статью, интересный опыт!

              Справедливости ради, эффектор покрывает не 100% функционала мобыкса. У мобыкса все зависимости by design ленивые — это может быть лучше по производительности и априори предотвражает утечки памяти.

              Хотя учитывая остальные особенности мобыкса, я бы тоже выбрал эффектор.
                –1

                Какие особеноости?

                  0
                  Лично для меня эти: github.com/artalar/flaxom#Why-not-MobX
                    0

                    Я так и не понял что не так с мобыксом. Ну кроме размера.

                      0
                      Как там реализуются Map и Set, их «слабые» версии?
                      Из-за мутабельности могут быть похожие баги.
                      По поводу рантайм семантики (и связанных с этим проблем у мобыкса) вы уже видели мой доклад, на сколько я помню, но вот еще раз.
                        0
                        Как там реализуются Map и Set, их «слабые» версии?

                        Через прокси. Как например $mol_atom2_dict.


                        Из-за мутабельности могут быть похожие баги.

                        А при чём тут мутабельность и кривой дизайн эффектора? MobX вполне адекватно реагирует на исключения. Разве что поддержки SuspenseAPI не хватает.


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

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

                0
                > Через прокси
                Нет, как обычному пользователю взять и начать использовать обычный Map / Set с мобыксом? В базовой поставке его нет, нужно писать свои враперы и мокать ВСЕ апи? Выглядит очень не тривиально, особенно учитывая что для некоторых методов нужно будет батчинг делать. Ну и это только пример, но он касается еще перечня менее популярных структур данных. В эффекторе / редаксе все просто — работай с данными как угодно, главное верни нову ссылку, все.
                > MobX вполне адекватно реагирует на исключения
                Разве исключения в derived откатят зависимые изменения? А, часто, именно этот flow подразумевается (пример по ссылке с эффектор не связан в принципе, это общая проблема реактивных данных)

                По поводу семантики… Это вещи уже, я так наблюдаю (и за собой), более вкусовые, но, кажется, в мобыксе с прокси объекты могут уходить и в сторонние либы и что там будет происходить совершенно неизвестно? И автор мобыкса мне лично говорил что это крутая фича, а не то что нужно ее избегать и каждый раз сериализовать в plain data отдаваемые либам данные.

                Поймите, я не хейчу мобыкс, даже часто зажищаю его (с чего начался тред), но нужно объективно оговаривать все аспекты…
                  0
                  У mobx самый главный недостаток в том что автор добавил в стейт-менеджер кучу опциональных вещей а народ записывает опциональные вещи в недостатки. Если вам не нравятся декораторы, прокси, геттеры-сеттеры, или кастомные структуры которыми мобикс оборачивает массив, мапы и сеты — то никто не заставляет вас их использовать — можно использовать только апи атомов в мобиксе и получить стейт-менеджер с таким же по удобству апи как и у того же effector-а
                    0
                    Оно не тришейкается. Я просил вынести ядро отдельно — но он считает это не рентабельным, типа мало кому оно надо.

                    (забавно, вся эта ветка уже происходила в телеграме пол года назад)
                      0

                      А вы так и не попробовали $mol_mem? :-)

                        0
                        Я плохо понимаю как его использовать вне экосистемы всего $мол. А можно какой-то маааленький пример, вроде каунтера на ваниле + мол_мем?
                  0
                  как обычному пользователю взять и начать использовать обычный Map / Set с мобыксом?

                  Думаю так же как и с эффектором — никак. Является ли это проблемой?


                  В эффекторе / редаксе все просто — работай с данными как угодно, главное верни нову ссылку, все.

                  Можно подумать в мобыксе так нельзя.


                  Разве исключения в derived откатят зависимые изменения? А, часто, именно этот flow подразумевается

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


                  прокси объекты могут уходить и в сторонние либы и что там будет происходить совершенно неизвестно

                  Как и любые другие функции. Значит ли это, что от функций стоит отказаться?

                  0
                  > с эффектором — никак
                  Вполне как — засунули в стор, в редусере иммутабельно меняете, проблем нет, все очевидно
                  > пользователю всегда нужно именно это
                  пользователю библиотеки(!) нужен выбор как хендлить исключения: с иммутабельными данными можно поставить трай-кетч в редусере и в кетче выбрать дальнейшее поведение: упасть и «откатить» все наработки других редусеров / вернуть значение по умолчанию / вернуть ошибку… А с мутациями «откат» сделать будет наааамного сложнее
                  > Как и любые другие функции. Значит ли это, что от функций стоит отказаться?
                  Я думаю у нас конструктивный разговор, а вы передергиваете. Я стараюсь приводить достаточно понятные и практические пример, если у меня не получается — текстом дальше сложно общаться.
                    0

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


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


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

                    0

                    Проект, который разрабатывался в ходе написания этой статьи, наконец, был одобрен контрибьюторами https://github.com/gothinkster/realworld и добавлен ссылкой в основной проект (как Riot.js + Universal + Effector):


                    image

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

                    Самое читаемое