Как мы готовим React, Require и Backbone

Как следует из официальной документации, React.js — V из MVC, и, как правило, вместе с ним применяются другие решения, в данном случае — Backbone.js и Require.js. А еще Jasmine, Karma и Grunt. Сегодня я поделюсь наброском проекта с применением этих инструментов.

Ссылка для нетерпеливых.

Хотелки


  • Прозрачная структура проекта;
  • Автоматизация всей рутинной работы;
  • Автоматизация тестирования;
  • Модульность;
  • Повторное использования кода;
  • Производительность.


Чего добились


Примерно так выглядит «дерево» проекта:

.
├── app
│   ├── app.js # Главный файл приложения
│   ├── bower_components # Зависимости, описанные в bower.json
│   │       └── ...
│   ├── index.html 
│   ├── scripts
│   │   ├── controllers # Backbone контроллеры 
│   │   │   └── src
│   │   │       ├── hello.jsx
│   │   │       ├── main.jsx
│   │   │       └── notfound.jsx
│   │   ├── router.js # Конфигурация роутинга
│   │   └── ui-components # React компоненты
│   │       └── src 
│   │           └── panel
│   │               ├── panel.jsx
│   │               └── panel.less
│   └── styles # Стили
│       └── src
│           └── main.less
├── bower.json # Описание зависимостей
├── Gruntfile.js 
├── install-deps.bat # Скрипты, 
├── install-deps.sh  # устанавливающие
├── install-env.bat   # зависимости
├── install-env.sh    # и окружение
├── package.json   # зависимости рабочего окружения node.js, на продакшене не нужны(Конечно, если серверная часть не на node.js)
├── server 
└── test # Тесты
    ├── test.config.js
    └── ui-components
        └── src
            └── panel.test.jsx

А так код


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

/*
 * app/app.js
 * Только этот файл подключается в index.html, все остальное делает require
 * Описывает пути к файлам проекта и запускает роутинг(Backbone).
 */
'use strict';
requirejs.config({
  baseUrl: './',
  paths: {
    app: './scripts',
    controllers: './scripts/controllers/dest', // dest - папки с результатами "компиляции" .jsx и .less
    ui: './scripts/ui-components/dest',  // В системе контроля версий не хранятся
    underscore: './bower_components/underscore/underscore',
    backbone: './bower_components/backbone/backbone',
    jquery: './bower_components/jquery/dist/jquery.min',
    react: './bower_components/react/react'
  }
});


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

/*
 * app/scripts/router.js
 */
'use strict';
define(function(require) {
  var Backbone = require('backbone');

  var AppRouter = Backbone.Router.extend({
    routes: {
      '': 'MainCtrl',
      'hello/:name(/)': 'HelloCtrl',
      '*actions': 'NotFoundCtrl'
    },

    MainCtrl: require('controllers/main'),
    HelloCtrl: require('controllers/hello'),
    NotFoundCtrl: require('controllers/notfound')
  });

  return new AppRouter();
});


В ui-components описываются обычные React-компоненты в синтаксисе .jsx и таблицы стилей для каждого отдельного компонента. Есть нечто общее с БЭМ. Каждый компонент лежит в отдельной папке и зависит только от самого React'а.

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

/*
 * app/scripts/controllers/src/hello.jsx
 */
'use strict';
define(['react', 'ui/panel/panel'], function(React, Panel){
  /* Аргумент из строки запроса */
  return function(name){
   /*
    * Реализуем логику приложения, например, отправляя запрос к серверу.
    * А потом рендерим компонент(ы).
    */
    React.render(
      <Panel title="Hello controller">
        <h1>Hello, {name}!</h1>
      </Panel>, document.body);
  };
});
.

Тесты


Тестировать UI сложно, поэтому Facebook любезно предоставил TestUtils специально для тестирования React компонентов, тесты для которых могут выглядеть как-то так:

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

/*
 * app/scripts/ui-components/src/panel.jsx
 */
define(['react'], function(React){
  'use strict';
  var Panel = React.createClass({
    render: function(){
      return (
      <div className="panel panel-default">
        <div className="panel-heading">
          <h1>{this.props.title}</h1>
        </div>
        <div className="panel-body">
            {this.props.children}
       </div>
     </div>);
    }
  });

  return Panel;
	
});


А это — тесты для panel, написанные с применением Jasmine, можно использовать любой фреймворк который вам нравится, например, разработчики React используют Jest. Тесты запускаются при помощи Karma, к сожалению пока и не смог завести PhantomJS для этих тестов, так что приходится мириться с постоянно всплывающим хромом.

/*
 * test/ui-components/src/panel.test.jsx
 */
'use strict';
define(['react', 'ui/panel/panel'], function(React, Panel) {
  describe('Panel behaviour tests', function() {
    var TestUtils = React.addons.TestUtils;
    var panel;
    var p;
    /*
     * Аналог this.setUp() из xxxUnit
     */
    beforeEach(function(){
      panel = TestUtils.renderIntoDocument((
        <Panel title="Test">
          <p>Paragraph content</p>
        </Panel>));
    });

    /* Проверяем что компонент вообще рендерится */
    it('Should render itself into DOM', function(){
      expect(TestUtils.isCompositeComponent(panel)).toBe(true);
    });

    /* И что заголовок, переданный атрибутом отображается */
    it('Should render title from props', function(){
      var h1 = TestUtils.findRenderedDOMComponentWithTag(panel, 'h1');
      expect(h1.getDOMNode().innerHTML).toBe('Test');
    });
    
    /* А также потомки никуда не исчезли */
    it('Should render children from props', function(){
      var paragraph = TestUtils.findRenderedDOMComponentWithTag(panel, 'p');
      /* 
       * Specific react feature, it does not render text node directly, 
       * but renders <span ... >Paragraph content</span>
       */
      expect(paragraph.getDOMNode().innerHTML).toContain('Paragraph content');
    });

  });
});


Кстати, index.html выглядит довольно коротко и аккуратно:

<html>

<head>
    <title>React+Backbone</title>
    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">
    <link rel="stylesheet" href="styles/dest/styles.css">
</head>

<body>
    <div id="main"></div>
    <script type="text/javascript" src="bower_components/requirejs/require.js"></script>
    <script type="text/javascript" src="app.js"></script>
    <script src="//localhost:35729/livereload.js"></script>
</body>

</html>


Автоматизация


С ней прекрасно справляется grunt который «компилирует» less и jsx, прогоняет тесты, обновляет страничку в браузере при сохранении файлов и делает еще много прикольных вещей.

Повторное использование и модульность


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

И зачем все это нужно?


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

И что дальше?


Всё и сразу пока не работает. В ближайших планах реализовать сборку проекта на продакшен, с минификацией всего, что можно минифицировать. В чуть более далеких — написание yoman генератора для скаффолдинга котроллеров и компонентов.
Метки:
Поделиться публикацией
Похожие публикации
Комментарии 34
  • +5
    Предлагаю еще посмотреть в сторону webpack, сам раньше всегда использовал requirejs, но оказалось что писать загрузку модулей с commonjs удобнее и красивее
    • 0
      мне CommonJS тоже нравится больше, однако мне почему-то жутко не нравится куча var-ов на каждую переменню. Одна var на все эти переменные тоже не сильно нравится… :) Вот import из ES6 было бы круче всего, как по мне.
      • +2
        можно взять 6to5, 6to5.org/docs/usage/modules/ там все это есть :)

        а с кучей var тоже просто.

        var React = require('react'),
        Component = require('./component')

        :)
        • –1
          вот к 6to5 тоже присматриваюсь, а вот с одним var все-равно выглядит не красиво в моем понимании :) уж сильно отдает паскалем как-то… уж эстет я, извините :)
        • +4
          Смотрите в сторону DI (dependency injection) менеджера, чтобы избавиться от var'ов, получить более элегантый и удобный для тестирования код.

          Вот простой пример:
          /** @jsx React.DOM */
          
          let appRenderInit = (app, window, $, React, Hello, World, undefined) => {
            'use strict';
          
            return (element) => {
              var Index = React.createClass({
                render() {
                  /* ... */
                }
              });
          
              React.render(<Index />, element);
            };
          };
          
          export default appRenderInit;
          


          Code source.
          • 0
            Офигенно, спасибо!
            • 0
              Вот только на момент когда это дело минифицируется мы все равно приходим к декларациям как в AMD ^_^''
              Примеры в документации angular: docs.angularjs.org/tutorial/step_05
        • 0
          Спасибо, обязательно посмотрю.
        • +3
          Заголовок был многообещающим, а по факту не понятно как вы собираетесь передавать данные из моделей, будете ли использовать Бекбон вью и т.д.
          • 0
            нуб-вопрос про весь этот реакт/ангуляр/эмбер:
            получается что вся страница собирается уже на клиенте, как обстоят дела с индексацией роботами и как увидят сайт пользователи, например ИЕ8 (да, приходится поддерживать)
            • 0
              Если вы специально не позаботились, то роботы ничего не увидят.

              Поддержки IE8 в Ангулар нет, в Реакте через полифилы и придется шаблоны прекомпилять. Про Эмбер не знаю. Если кратко, то очень плохая. Но на мой взгляд это закономерно.
              • 0
                Информация касательно индексации таких приложений яндексом.
                • 0
                  По ссылке:
                  Каждая индексируемая AJAX-страница должна иметь HTML-версию

                  Другими словами: яндекс не умеет индексировать ajax (да и просто сгенерированные на клиенте) страницы.
                  • 0
                    Получается что так. Логику подстановки данных в html шаблон придется реализовывать в двух местах, на клиенте для людей и сервере для Яндекса, Google и т.д.
                    Кстати, если ваша серверная часть написана на node.js, можно реализовать пререндеринг страниц на стороне сервера.
              • 0
                Можно рендерить на бекенде страницы, а потом поверх запускать фронтэнд приложение. По крайней мере так на backbone делаем
                • 0
                  prerender.io в помощь
                • +1
                  >>Всё и сразу пока не работает.
                  Убило наповал. Зачем статья?

                  Зачем тащить было в проект Backbone и requirejs? Обоснование отсутствует. Вы не понимаете Flux, поэтому притащили в проект Backbone? Или в нем есть необходимость, потому что Flux не устраивает этим и этим?

                  React + Flux + 6to5 + webpack избавляет от прошлого века requirejs.

                  Фига себе прозрачность в контроллерах Backbone находятся компоненты React.
                  • 0
                    А насколько у webpack хорошо с компиляцией в рантайме? Я вот поставил requirejs с jsx плагином и могу все на живую компилировать, никаких watcher'ов и т.п., webpack же, насколько я понимаю, заставит меня через свой сервер ходить для этого.
                    • 0
                      Если вы не хотите использовать watcher-ы, то это ваши проблемы. Можете не использовать.

                      Можно компилировать наживую, через watcher-ы, через watcher wabpack или запускать webpack для сборки. Как настроите так и будет.

                      Я совсем не использую requirejs т.к. это устаревшая технология, уже можно использовать es6 и es6 модули. Если проект новый и нет предрассудков и каких-то страхов, то зачем использовать устаревшие инструменты, когда есть современные?
                      • +1
                        Я не совсем согласен насчет «устаревшей» технологии, не так все плохо… Плюс он спокойно расширяется через модули и прикрутить туда компиляцию es6 никакого труда не составляет. А бонусы от компиляции в рантайме при этом остаются.

                        Я переформулирую вопрос — какие webpack предоставляет возможности для быстрой компиляции в режиме «изменил-сохранил-посмотрел в браузере», насколько быстро он успевает все подхватить и перестроить для средних размеров проекта? Для меня важно, чтобы инструмент успевал отработать за то время, пока я переключаюсь из редактора в браузер и пока обновляется страница.
                        • 0
                          У меня проект среднего размера, React + Flux + Webpack, при изменении файла автоматически пересобирается проект. Первая сборка забирает где-то 4.5 секунды, а все последующие 3.7. После сборки срабатывает autoreload. В принципе поменять что-то в коде и переключится в браузер — достаточно приемлимая скорость. Хотя я бы и сам не отказался бы от того, чтобы её ускорить.
                          • 0
                            У меня есть ноут, на котором сборка чего угодно, на чем угодно занимает 10 минут.
                            • 0
                              Вот я по этой самой причине (сборка дольше 3 секунд) и пришел к выводу, что я хочу все компилировать прямо в браузере, потому как пару раз ловил неприятные баги от того, что после переключения в браузер что-то успело собраться, а что-то нет.
                              • +1
                                >> сборка дольше 3 секунд
                                Вот по этой причине я и пришел к выводу, что прекомпиляция по частям и нормальные компьютеры спасут мир. Болтовня про скорость — чушь! Я таких разговоров ещё 10 лет назад наслушался. Прошло 10 лет, частота процессоров выросла на тысячи, появились SSD, а разговоры не изменились. Прямо маркетинговые лозунги — мой инструмент умеет быстро*.

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

                                *, зато не имеет ещё 100500 нужных возможностей.
                                • 0
                                  Вы забываете, что размер проекта тоже растет. На проекте более тысячи файлов. И SSD есть почти у всех девелоперов, коих по последним подсчетам более ста человек. Вы так замечательно все додумываете, колкие фразочки пишете, а по существу ничего так толком и не сказали.
                                  • 0
                                    Что я должен сказать по существу о вашем абстрактном проекте, вашей абстрактной сборке на вашем абстрактном компьютере?
                                    • 0
                                      Например, вы могли бы рассказать о статистике на Вашем не-абстрактном проекте, Вашей не-абстрактной сборке на Вашем не-абстрактном компьютере. Например, как это сделал standy.
                                • 0
                                  Не знаю откуда такие цифры, если использовать webpack.watch, то все ускоряется на порядок.

                                  У меня проект, где react собирается из исходников (это 500кб кода), несколько входных точек и общие модули выделяются в отдельный файл, дев-сборка (без минификации) происходит за 3с. Сборка с минификацией и с дедупликацией за 12с. После этого watch отрабатывает за 200мс любое сохранение.
                                  • 0
                                    Проект весит 20 мегабайт… тут знаете ли приходится выдумывать всякое, и как разделить его на динамически подгружаемые модули и как собрать максимально быстро.
                      • 0
                        >>насколько быстро он успевает все подхватить и перестроить для средних размеров проекта? Для меня важно, чтобы инструмент успевал отработать за то время, пока я переключаюсь из редактора в браузер и пока обновляется страница.
                        У меня успевает, нареканий не было, проект большой и растет, но не вижу смысла обсуждать скорость webpack.

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

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

                        Вы же понимаете, что пока не попробуете сами ничего не узнаете наверняка?!
                        • 0
                          Вы же понимаете, что пока не попробуете сами ничего не узнаете наверняка?!


                          Логично. Но пока я буду переводить основной проект с тысячами файлов на webpack пройдут недели, а самое плохое — испытание на малом кол-ве файлов показательным не особо будет, так что прогнозировать, как оно заработает на большом проекте довольно трудно. Поэтому нужен чужой опыт.
                          • 0
                            >>пока я буду переводить основной проект с тысячами файлов на webpack пройдут недели
                            Не знаю почему, но вам видней. Расскажите в чем проблема?
                            webpack.github.io/docs/comparison.html
                            • 0
                              На проекте используются requirejs плагины, а также не весь сайт собирается разом, он разбит на составные части, у которых собственный билд-процесс. Чтобы все нормально перевести на вебпак и протестировать придется потратить некоторое время, поэтому на данный момент я провожу анализ, стоит ли игра свеч.

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