Руководство по basis.js. Часть 1: Начало работы, представления, модули, инструменты


    basis.js – JavaScript-фреймворк для разработки одностраничных веб-приложений, ориентированный на скорость и эффективность. Возможно он пока не такой популярный. Но благодаря моим выступлениям на различных конференциях и meetup'ах, некоторые уже слышали о нем и заинтересовались. Однако, чтобы начать использовать фреймворк или разбираться в нем, большинству не хватает руководства.

    И вот, собрав волю в кулак (ну какой программист не любит писать документацию?), я сел писать руководство. Просто, доступно, последовательно.

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

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



    Подготовка


    Для разработки нам потребуются:

    • консоль (командная строка)
    • локальный веб-сервер
    • браузер (желательно Google Chrome)
    • ваш любимый редактор

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

    Dev-сервер


    Проекты на basis.js не требуют сборки в процессе разработки. Но для их работы требуется веб-сервер. Может подойти любой веб-сервер, но лучше использовать dev-сервер, входящий в состав basisjs-tools, так как он дает больше возможностей при разработке.

    basisjs-tools – набор консольных инструментов, написанный на javascript и работающий под управлением node.js. Этот набор включает в себя сборщик, dev-сервер и кодогенератор. Устанавливается как обычный npm модуль:

    > npm install -g basisjs-tools
    

    Если установить инструменты глобально (флаг -g), то в консоли станет доступна команда basis.

    Давайте запустим dev-сервер, для этого выполним в консоли простую команду:

    > basis server
    

    После этого запустится сервер на порту 8000 (это можно изменить, используя флаг --port или -p). Теперь можно открыть в браузере http://localhost:8000 и убедиться, что сервер работает. Тем не менее он отдает ошибку, так как папка нашего проекта еще пуста. Давайте же исправим это.

    Индексный файл и подключение basis.js


    Для начала нужно добавить папку с исходниками basis.js в проект. Для этого можно либо клонировать проект из репозитария, либо использовать bower.

    > bower install basis
    

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

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <title>My first app on basis.js</title>
    </head>
    <body>
      <script src="bower_components/basisjs/src/basis.js" basis-config=""></script>
    </body>
    </html>
    

    Пока ничего необычного. Единственное, что может вызвать вопросы – атрибут basis-config у тега <script>.

    Этот атрибут дает возможность ядру basis.js найти тег <script>, которым он был подключен. Это необходимо для того, чтобы определить путь к исходникам basis.js и разрешать пути к его модулям.

    Первое представление


    Сейчас наша страница как белый лист бумаги – абсолютно пуста. Давайте наполним ее смыслом и выведем традиционное «hello world».

    Сделаем это, создав представление с таким отображением. Вот, что должно получиться:

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <title>My first app on basis.js</title>
    </head>
    <body>
      <script src="bower_components/basisjs/src/basis.js" basis-config=""></script>
      <script>
        basis.require('basis.ui');
    
        var view = new basis.ui.Node({
          container: document.body,
          template: '<h1>Hello world!</h1>'
        });
      </script>
    </body>
    </html>
    

    Обновив страницу, увидим задуманное – «Hello world!». Рассмотрим, что здесь происходит.

    Во-первых, мы сказали, что нам нужен модуль basis.ui, используя функцию basis.require. Эта функция работает практически так же, как функция require в node.js и умеет подключать модули по их имени или по имени файла. В данном случае basis.ui это имя модуля. Как мы увидим дальше, эта функция может «подключить» любой файл по его имени.

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

    Во-вторых, мы создали само представление, экземпляр класса basis.ui.Node. Пусть вас не смущает название Node, вместо традиционного View. Дело в том, что в basis.js все компоненты и представления вкладываются в друг друга. Так, некоторый блок может выглядеть как единое целое, но на самом деле может состоять из множества вложенных представлений (узлов).

    В целом, весь интерфейс организуется в виде одного большого дерева. Его листьями являются узлы (node), которые представляют собой представления и имеют перекрестные ссылки. Можно трансформировать это дерево, добавляя, удаляя и перемещая узлы. Основное API для этого имеет много общего с браузерным DOM. Но мы к этому еще вернемся.

    А пока посмотрим как мы создали представление. Для этого мы передали в конструктор объект с «настройками» – конфиг. Задав свойство container мы указали куда нужно поместить DOM фрагмент представления, когда он будет создан. Это должен быть DOM элемент. А в свойстве template указали описание шаблона. Это описание указано в самом конфиге для примера. Такая возможность, указывать описание шаблона строкой в конфиге, удобна для прототипирования и примеров. Но для публикуемых (production) приложений она не используется и позже мы это переделаем.

    Модули


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

    Давайте вынесем код представления в отдельный модуль. Для этого создадим файл hello.js и перенесем в него то, что было указано в <script>.

    Этого оказывается достаточно, и пока больше ничего с кодом делать не нужно. Осталось только подключить модуль в index.html:

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <title>My first app on basis.js</title>
    </head>
    <body>
      <script src="bower_components/basisjs/src/basis.js" basis-config=""></script>
      <script>
        basis.require('./hello.js');
      </script>
    </body>
    </html>
    

    Здесь снова использована функция basis.require, но на этот раз ей передан путь к файлу. Важно, чтобы путь к файлу начинался с "./", "../" или "/". Так basis.require однозначно поймет, что значение является путем к файлу, а не именем модуля. Такое же соглашение действует и в node.js.

    Продолжим разбираться с модульностью. Например, разметка в коде нам ни к чему. А раз так, вынесем описание шаблона в отдельный файл – hello.tmpl. Тогда код представления примет такой вид:

    basis.require('basis.ui');
    
    var view = new basis.ui.Node({
      container: document.body,
      template: basis.resource('./hello.tmpl')
    });
    

    Все осталось как прежде, но строка с описанием шаблона заменена на вызов функции basis.resource. Эта функция создает «интерфейс» к файлу. Такой подход дает возможность определять какие файлы нужны, не скачивая их до тех пор, пока нет в этом необходимости.

    Интерфейс, создаваемый basis.resource, представляет собой функцию с дополнительными методами. Вызов такой функции, или ее метода fetch, приводит к загрузке файла. Загружается файл лишь раз, а результат кешируется. Больше деталей можно найти в статье Ресурсы (модульность).

    Еще один момент: на самом деле, вызов basis.require('./file.name') эквивалентен basis.resource('./file.name').fetch().

    В данном случае, можно было бы использовать и basis.require. Но шаблоны часто описываются в классах, а для этих случаев не нужно загружать файл до тех пор, пока не будет создан первый экземпляр класса. Мы увидим это в других примерах. Поэтому для единообразия: при назначении шаблона, лучше всегда использовать basis.resource.

    Преимущества модулей


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

    Например, имя файла можно получить из переменной __filename, а папку размещения модуля из переменной __dirname.

    Но важнее, что становятся доступны локальные функции require и resource. Они работают так же как basis.require и basis.resource, за исключением того, как разрешаются относительные пути к файлам. Если для функциям basis.require и basis.resource передается относительный путь, то он разрешает относительно html файла (в нашем случае это index.html). В тоже время, require и resource разрешают такие пути относительно модуля (то есть его __dirname).

    В модулях удобнее использовать именно локальные функции require и resource. Таким образом, код hello.js немного упрощается:

    require('basis.ui');
    
    var view = new basis.ui.Node({
      container: document.body,
      template: resource('./hello.tmpl')
    });
    

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

    То же относится и к css, файлам локализации и другим типам файлов. Единственные изменения, требующие перезагрузки страницы, это изменение html файла и изменение javascript модулей, которые уже инициализированы.

    Механизм обновления файлов обеспечивает dev-сервер из basisjs-tools. Это одна из главных причин почему стоит использовать именно его, а не обычный веб-сервер.

    Давайте попробуем как это работает. Создадим файл hello.css, такого вида:

    h1
    {
      color: red;
    }
    

    После этого немного изменим шаблон (hello.tmpl):

    <b:style src="./hello.css"/>
    <h1>Hello world!</h1>
    

    Как только изменения в шаблоне будут сохранены, текст станет красным. При этом совсем не нужно обновлять страницу.

    В шаблон мы добавили специальный тег <b:style>. Этот тег говорит, что когда используется данный шаблон, то на страницу нужно подключить заданный файл стилей. Относительные пути разрешаются относительно файла шаблона. Один шаблон может подключать произвольное количество файлов стилей. Нам не нужно беспокоиться о добавлении и удалении стилей, об этом заботится фреймворк.

    Итак, мы создали простое статическое представление. Но веб-приложения, это в первую очередь динамика. Так что давайте попробуем использовать в шаблоне значения из представления и как то взаимодействовать с ним. Для первого используются биндинги (bindings), а для второго – действия (actions).

    Биндинги и действия


    Биндиги позволяют переносить значения из представления в его DOM фрагмент. В отличие от большинства шаблонизаторов, шаблоны basis.js не имеют прямого доступа к свойствам представления. И могут использовать только те значения, что само представление предоставляет шаблону.

    Для задания значений доступных шаблону используется свойство binding в описании экземпляра или класса, унаследованного от basis.ui.Node. Значения задаются в виде объекта, где ключ – это имя, которое будет доступно в шаблоне, а значение – функция, вычисляющая значение для шаблона. Таким функциям единственным параметром передается владелец шаблона, то есть само представление. Вот так можно предоставить шаблону значение name:

    require('basis.ui');
    
    var view = new basis.ui.Node({
      container: document.body,
      name: 'world',
      template: resource('./hello.tmpl'),
      binding: {
        name: function(node){
          return node.name;
        }
      }
    });
    

    Стоит добавить, что свойство binding является авто-расширяемым свойством. Когда задается новое значение для свойства, при создании экземпляра или класса, то новое значение расширяет предыдущее, добавляя и переопределяя прежние значения. По умолчанию у basis.ui.Node уже есть несколько полезных значений, которые можно использовать наряду с определенным нами name.

    Изменим шаблон (hello.tmpl), чтобы использовать name.

    <b:style src="./hello.css"/>
    <h1>Hello, {name}!</h1>
    

    В шаблонах используются специальные вставки – маркеры. Они используются для получения ссылок на определенные части шаблона и расстановки значений. Такие вставки указываются в фигурных скобках. В данном случае мы добавили {name}, для вставки значения как обычный текст.

    Описание шаблона выглядит похожим на формат описания в других шаблонизаторах. Но в отличие от них, шаблонизатор basis.js работает с DOM узлами. Для данного описания будет создан элемент <h1>, в котором будет содержаться три текстовых узла "Hello,", "{name}" и "!". Первый и последний будут статичными и их текст не будет меняться. А вот среднему будет проставляться значение из представления (будет меняться его свойство nodeValue).

    Но хватит слов, давайте обновим страницу и посмотрим на результат!

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

    <b:style src="./hello.css"/>
    <div>
      <h1>Hello, {name}!</h1>
      <input value="{name}" event-keyup="setName"/>
    </div>
    

    В шаблоне добавился элемент <input>. Для его атрибута value использован тот же биндинг, что и в заголовке – {name}. Но это работает только для записи в DOM.

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

    В нашем примере мы добавили атрибут event-keyup, который обязует представление выполнить действие setName, когда срабатывает событие keyup. Если у представления не будет определено какое-то действие, то в консоли мы увидим предупреждающее сообщение об этом и больше ничего не произойдет.

    А теперь добавим описание действия. Для этого используется свойство action. Работает оно аналогично binding, но только описывает действия. Функции в action получают параметром объект события. Это не оригинальное событие, а его копия с дополнительными методами и свойствами (оригинальное событие хранится в его свойстве event_).

    Вот как теперь будет выглядеть представление (hello.js):

    require('basis.ui');
    
    var view = new basis.ui.Node({
      container: document.body,
      name: 'world',
      template: resource('./hello.tmpl'),
      binding: {
        name: function(node){
          return node.name;
        }
      },
      action: {
        setName: function(event){
          this.name = event.sender.value;
          this.updateBind('name');
        }
      }
    });
    

    Здесь мы читаем значение из event.sender, а это элемент, у которого произошло событие – <input>. Для того чтобы представление заново вычислило значение и передало его шаблону, мы вызвали метод updateBind.

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

    Представления, как и модели, умеют хранить данные в виде ключ-значение. Данные хранятся в свойстве data и меняются методом update. Когда меняются значения в data, срабатывает событие update. Воспользуемся этим механизмом для хранения имени:

    require('basis.ui');
    
    var view = new basis.ui.Node({
      container: document.body,
      data: {
        name: 'world'
      },
      template: resource('./hello.tmpl'),
      binding: {
        name: {
          events: 'update',
          getter: function(node){
            return node.data.name;
          }
        }
      },
      action: {
        setName: function(event){
          this.update({
            name: event.sender.value
          });
        }
      }
    });
    

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

    require('basis.ui');
    
    var view = new basis.ui.Node({
      container: document.body,
      data: {
        name: 'world'
      },
      template: resource('./hello.tmpl'),
      binding: {
        name: 'data:name'
      },
      action: {
        setName: function(event){
          this.update({
            name: event.sender.value
          });
        }
      }
    });
    

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

    Основное, что нужно запомнить. Представление вычисляет и передает значения шаблону, для этого используется binding. А шаблон перехватывает и передает представлению события, вызывая действия из action. Фактически, binding и action основные точки соприкосновения представления и шаблона. При этом, представление практически ничего не знает об устройстве шаблона, а шаблон – об устройстве представления. Вся логика (javascript) находится на стороне представления, а работа с отображением (DOM) – на стороне шаблона. Так, в подавляющем большинстве случаев, достигается полное разделение логики и представления.

    Разделение логики и представления


    Список


    Итак, теперь мы знаем как создать простое представление. Давайте создадим еще одно, немного посложнее – список. Для этого создадим новый файл list.js с таким содержанием:

    require('basis.ui');
    
    var list = new basis.ui.Node({
      container: document.body,
      template: resource('./list.tmpl')
    });
    
    var Item = basis.ui.Node.subclass({
      template: resource('./item.tmpl'),
      binding: {
        name: function(node){
          return node.name;
        }
      }
    });
    
    list.appendChild(new Item({ name: 'foo' }));
    list.appendChild(new Item({ name: 'bar' }));
    list.appendChild(new Item({ name: 'baz' }));
    

    Код этого модуля похож на hello.js, но добавились новые конструкции.

    Прежде чем их разобрать, отметим, что в basis.js используется компонентный подход. Так, если мы делаем, например, список, то это будет не одно представление, а несколько. Одно представление это сам список. И каждый элемент списка – это тоже представление. Так мы отдельно описываем поведение списка, и поведение элементов списка. Чуть более подробно про этот подход, например, рассказано в докладе «Компонентный подход: скучно, неинтересно, бесперспективно»: слайды и видео.

    Как упоминалось ранее, представления могут вкладываться друг в друга. В данном случае элементы списка вкладываются в список. При этом вложенные представления являются дочерними (хранятся в свойстве childNodes), а для них, представление, в которое они вложены, является родительским (ссылка хранится в свойстве parentNode).

    Описание самого списка ничем не отличается от того, что мы делали ранее. Далее по коду был создан новый класс, унаследованный от basis.ui.Node. В этом классе указан файл шаблона и простой биндинг. После этого было создано три экземпляра этого класса и добавлены списку.

    Как было сказано выше, для организации дерева представлений используются принципы DOM. Для вставки используются методы appendChild и insertBefore, для удаления removeChild, а для замены replaceChild. Так же есть нестандартные методы: setChildNodes позволяет задать список дочерних представлений, а clear – удаляет все дочерние представления махом.

    Поэтому уже сейчас можно сделать код немного проще:

    require('basis.ui');
    
    var list = new basis.ui.Node({
      container: document.body,
      template: resource('./list.tmpl')
    });
    
    var Item = basis.ui.Node.subclass({
      template: resource('./item.tmpl'),
      binding: {
        name: function(node){
          return node.name;
        }
      }
    });
    
    list.setChildNodes([
      new Item({ name: 'foo' }),
      new Item({ name: 'bar' }),
      new Item({ name: 'baz' })
    ]);
    

    Список дочерних узлов можно задать и в момент создания представления. Попробуем:

    require('basis.ui');
    
    var Item = basis.ui.Node.subclass({
      template: resource('./item.tmpl'),
      binding: {
        name: function(node){
          return node.name;
        }
      }
    });
    
    var list = new basis.ui.Node({
      container: document.body,
      template: resource('./list.tmpl'),
      childNodes: [
        new Item({ name: 'foo' }),
        new Item({ name: 'bar' }),
        new Item({ name: 'baz' })
      ]
    });
    

    Самостоятельно создавать однотипные дочерние узлы не так интересно. Хотелось бы просто указывать конфиг и чтобы список сам их создавал, если это необходимо. И такая возможность есть. Это регулируется двумя свойствами childClass и childFactory. Первое задает класс экземпляра, который может быть добавлен как дочерний узел. А второе свойство определяет функцию, которой передается, добавляемое как дочерний узел, значение, которое не является экземпляром childClass. Задача такой функции создать подходящий экземпляр. По умолчанию, эта функция создает экземпляр childClass, используя переданное значение как конфиг:

    basis.ui.Node.prototype.childFactory = function(value){
      return new this.childClass(value);
    };
    

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

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

    require('basis.ui');
    
    var Item = basis.ui.Node.subclass({
      template: resource('./item.tmpl'),
      binding: {
        name: function(node){
          return node.name;
        }
      }
    });
    
    var list = new basis.ui.Node({
      container: document.body,
      template: resource('./list.tmpl'),
      childClass: Item,
      childNodes: [
        { name: 'foo' },
        { name: 'bar' },
        { name: 'baz' }
      ]
    });
    

    Продолжим улучшать код. Ведь его можно сделать еще проще.

    Класс Item нигде больше не используется, потому нет смысла сохранять его в переменную. Этот класс можно сразу задать в конфиге. Но это не все что мы можем сделать. Когда создается новый класс или экземпляр и некоторое его свойство является классом, а мы хотим создать новый класс на его основе, то не обязательно создавать класс явно, можно просто задать объект с расширениями нового класса. Звучит сложно, но, на самом деле, это все про то, что нам не обязательно указывать basis.ui.Node.subclass, можно просто передать объект. И мы получаем:

    require('basis.ui');
    
    var list = new basis.ui.Node({
      container: document.body,
      template: resource('./list.tmpl'),
      childClass: {
        template: resource('./item.tmpl'),
        binding: {
          name: function(node){
            return node.name;
          }
        }
      },
      childNodes: [
        { name: 'foo' },
        { name: 'bar' },
        { name: 'baz' }
      ]
    });
    

    Вот, так гораздо лучше. Осталось лишь описать шаблоны.

    Сначала создаем шаблон списка list.tmpl:

    <div id="mylist">
      <h2>Мой первый список</h2>
      <ul{childNodesElement}/>
    </div>
    

    Совершенно обычная разметка, за исключением того, что после имени тега ul идет незнакомая конструкция {childNodesElement}. Знакомтесь, это тоже маркер. Так мы говорим, что мы хотим ссылаться на этот элемент по имени childNodesElement. На самом деле, лично нам эта ссылка пока не нужна. Но она нужна представлению списка, что понять куда вставлять DOM фрагменты дочерних узлов. Если ее не указать, то дочерние узлы будут вставляться в корневой элемент (в нашем случае это <div id="mylist">).

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

    Теперь нужно создать шаблон для элемента списка (item.tmpl):

    <li>
      {name}
    </li>
    

    И последнее, нужно подключить модуль в нашу страницу:

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <title>My first app on basis.js</title>
    </head>
    <body>
      <script src="bower_components/basisjs/src/basis.js" basis-config=""></script>
      <script>
        basis.require('./hello.js');
        basis.require('./list.js');
      </script>
    </body>
    </html>
    

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

    Композиция


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

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

    Для начала изменим сами модули. Во-первых, нужно убрать использование свойства container, так как их расположение будет определять родительское представление. А во-вторых, нужно чтобы модуль возвращал само представление, чтобы его можно было использовать. Для этого используется exports или module.exports (все как в node.js).

    Теперь hello.js примет такой вид:

    require('basis.ui');
    
    module.exports = new basis.ui.Node({
      data: {
        name: 'world'
      },
      template: resource('./hello.tmpl'),
      binding: {
        name: 'data:name'
      },
      action: {
        setName: function(event){
          this.update({
            name: event.sender.value
          });
        }
      }
    });
    

    А модуль списка (list.js) такой:

    require('basis.ui');
    
    module.exports = new basis.ui.Node({
      template: resource('./list.tmpl'),
      childClass: {
        template: resource('./item.tmpl'),
        binding: {
          name: function(node){
            return node.name;
          }
        }
      },
      childNodes: [
        { name: 'foo' },
        { name: 'bar' },
        { name: 'baz' }
      ]
    });
    

    Как видно, поменялось не много.

    У любого приложения обычно есть единственная точка входа. Это такой модуль, который создает корневое представление и делает ключевые настройки. Создадим такой файл app.js:

    require('basis.ui');
    
    new basis.ui.Node({
      container: document.body,
      childNodes: [
        require('./hello.js'),
        require('./list.js')
      ]
    });
    

    Здесь все уже должно быть знакомо. Можно заметить, что для представления мы не задали шаблон. В этом случае, по умолчанию, будет использоваться пустой <div>. Пока нас устроит.

    Осталось поменять сам index.html:

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <title>My first app on basis.js</title>
    </head>
    <body>
      <script src="bower_components/basisjs/src/basis.js" basis-config=""></script>
      <script>
        basis.require('./app.js');
      </script>
    </body>
    </html>
    

    Два вызова basis.require заменились на один. Но не писать и его, а использовать опцию autoload в basis-config:

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <title>My first app on basis.js</title>
    </head>
    <body>
      <script src="bower_components/basisjs/src/basis.js" basis-config="autoload: 'app'"></script>
    </body>
    </html>
    

    Согласитесь, стало гораздо лучше.

    И все же осталась небольшая проблема. Да, порядок дочерних представлений задается в корневом представлении. Но они добавляются последовательно, один за другим. А достаточно часто нам необходимо размещать дочерние представления в конкретные точки разметки, более сложной чем просто пустой <div>. Для этого необходимо использовать – сателлиты.

    Сателлиты


    Сателлиты – это именованные дочерние представления. Этот механизм используется для представлений, которые играют определенную роль и не повторяются.

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

    Вот так будет выглядеть app.js, с использованием сателлитов:

    require('basis.ui');
    
    new basis.ui.Node({
      container: document.body,
      template: resource('./app.tmpl'),
      binding: {
        hello: 'satellite:hello',
        list: 'satellite:list'
      },
      satellite: {
        hello: require('./hello.js'),
        list: require('./list.js')
      }
    });
    

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

    require('basis.ui');
    
    new basis.ui.Node({
      container: document.body,
      template: resource('./app.tmpl'),
      binding: {
        hello: require('./hello.js'),
        list: require('./list.js')
      }
    });
    

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

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

    <div>
      <div id="sidebar">
        <!--{list}-->
      </div>
      <div id="content">
        <!--{hello}-->
      </div>
    </div>
    

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

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

    Реструктуризация файлов проекта


    Результатом наших экспериментов стали три основных представления, три модуля и 9 файлов:

    Структура файлов


    В настоящих приложениях десятки и сотни модулей. А среднее приложение на basis.js это, обычно, 800-1200 файлов. Хранить все файлы в одной папке неудобно и неразумно. Попробуем реструктурировать расположение файлов.

    Создадим папку hello и перенесем туда файлы относящиеся к этому модулю (т.е. hello.js, hello.tmpl и hello.css). А так же папку list, в которую перенесем list.js, list.tmpl и item.tmpl. Все что нам осталось – это поменять пути подключения модулей в app.js:

    require('basis.ui');
    
    new basis.ui.Node({
      container: document.body,
      template: resource('./app.tmpl'),
      binding: {
        hello: require('./hello/hello.js'),  // здесь
        list: require('./list/list.js')      // и здесь
      }
    });
    

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

    Структура файлов


    Выглядит не плохо, но файлы и папки самого приложения смешиваются с файлами и папками другого назначения. Поэтому будет лучше, если мы расположим все исходные файлы приложения в одной отдельной папке. Создадим папку src и поместим туда все файлы и папки за исключением bower_components и index.html. После этого нужно подправить один путь в index.html:

    <!-- было -->
    <script src="bower_components/basisjs/src/basis.js" basis-config="autoload: 'app'"></script>
    
    <!-- стало -->
    <script src="bower_components/basisjs/src/basis.js" basis-config="autoload: 'src/app'"></script>
    

    Структура файлов должна получится такой:

    Структура файлов


    Если пойти по пути универсализации, то можно организовать файлы, например, так:

    Структура файлов


    Так дочерние модули располагаются в папке module. Основной javascript файл модуля называется index.js. Шаблоны и все что к ним относится (стили, изображения и т.д.) располагаются в папке template. Это наиболее частая структура организации проектов на данный момент.

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

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

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

    Итоговый результат можно посмотреть здесь.

    Инструменты


    С ростом приложения растет количество файлов и его сложность. Для того чтобы было проще разрабатывать нужны инструменты. У basis.js есть два вспомогательных инструмента: devpanel и плагин для Google Chrome.

    devpanel – это небольшая панель с кнопками, которую можно перетаскивать. Выглядит она так:

    devpanel


    Для ее подключения нужно добавить такой вызов, лучше всего в основной модуль (app.js):

    /** @cut */ require('basis.devpanel');
    

    После перезагрузки страницы, панель должна появиться. Здесь использован специальный комментарий /** @cut */, он позволяет вырезать строки при сборке. Нам ведь не нужно показывать эту панель пользователям, правда?

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

    Плагин устанавливается из Google Web Store вот по этой ссылке. Для его работы необходима devpanel, так как она предоставляет API для работы с basis.js.

    Плагин предоставляет:

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

    Вот так выглядит наше приложение глазами сборщика:

    Граф приложения


    Сборка


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

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

    Сборщик старается самостоятельно разобрать и понять структуру проекта. Ему практически не нужно что-то специально объяснять. Ведь как вы узнаете, где и что подключается и используется? Вы открываете исходный код, читаете и понимаете. Вот так же работает и сборщик basisjs-tools.

    Сначала он получает на вход html файл (в нашем случае index.html). Он анализирует его, находит теги <script>, <link rel="stylesheet">, <style> и другие. Понимает какие файлы подключаются. Потом приступает к анализу этих файлов, находит в них конструкции подключающие другие файлы (например, в javascript вызовы basis.require, basis.resource и другие, а в css@import, url(..) и т.д.). Так, рекурсивно обрабатывая все файлы, сборщик строит граф приложения. После этого он анализирует связи, перестраивает и оптимизирует файлы. А результат своей работы складывает в отдельную папку, в виде гораздо меньшего количества файлов.

    Давайте соберем проект. Все что для этого нужно — выполнить простую команду:

    > basis build
    

    Вот и все. Результатом сборки будут три файла index.html, script.js и style.css, расположенные в папке build. Эти три файла и есть наше приложение в собранном виде. Все что нужно сделать после сборки – это скопировать содержимое папки build на сервер. Все необходимые файлы для работы приложения будут в ней.

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

    > basis build --help
    

    Например, самые частые оптимизации, такие как удаление отладочного кода и сжатие javascript и css можно выполнить указав флаг --pack (или его короткую версию -p):

    > basis build --pack
    

    Вот что мы увидим в консоли выполнив эту команду:

    Результат выполнения basis build --pack

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

    Заключение


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

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



    Если вы нашли ошибку, неточность или знаете как улучшить статью – напишите мне на хабрапочту. Эта статья так же доступна на GitHub, вы можете сделать PR и я бережно перенесу правки сюда.

    Чтобы вам не пришлось мучать поисковики, привожу список полезных ссылок по теме:

    Приветствуются любые вопросы, критика, поддержка и помощь, в чем бы она не выражалась :)
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 58

      0
      Спасибо за наводку (:
        +1
        Я извиняюсь, но объясните, пожалуйста, за что минусы (включая карму)? На хабре уже вешают за благодарность? о_0
          –2
          Полагаю, что это из-за того, что ваш комментарий не несёт полезной читателям нагрузки (благодарность можно автору и в личку написать). Но это только моё предположение (минусы не мои).
            0
            Ну вот, теперь и мне минусов насыпали :) А я меж тем всего-навсего пытался объяснить возможную мотивацию тех, кто, вероятно, минусы ставил (сам я ничего страшного не вижу в том, что порой благодарят автора в комментарии).
          +1
          Занимательное чтиво, а будут ли сравнения с другими js фреймворками, тесты?
            +4
            Я как раз читаю слайды по basisjs, так что поделюсь ссылкой www.slideshare.net/basisjs/ — там есть сравнения по производительности. Интересный фреймворк.
              0
              Там по большей части сравнение с jQuery, судя по заключению:
              Не использовать DOM для хранения данных
              т.к. упомянутые там Ember и Backbone вроде как не используют DOM для хранения данных.
              Хочется тесты в сравнении с другими фреймворками, особенно с Angular.js, которые можно «пощупать», наподобие jsfiddle.net/lega911/ZUne7/
                0
                Там по большей части сравнение с jQuery

                Не совсем. Если вы о докладе про данные, то там объясняется почему чаще всего медленно работает на больших количествах данных. Хранение данных в DOM наиболее частая ошибка, которая приводит к печальным результатам.

                т.к. упомянутые там Ember и Backbone вроде как не используют DOM для хранения данных.

                Они сами нет, но их пользователи – да. Когда стравниваются backbone и ember речь идет чисто про данные, и DOM уже не затрагивается, сравниваются разные реализации.

                Хочется тесты в сравнении с другими фреймворками, особенно с Angular.js, которые можно «пощупать», наподобие

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

                Ваш тест плохо организован. Не совсем понятно что вы тестируете. На результаты влияют посторонние факторы и особенность организации теста.
                Так же не стоит использовать new Date, так как он не дает точность (например в Chrome дискретность 16ms). Нужно использовать performance.now() если доступен, и если нет – тогда new Date. Хотя там такие цифры что это не так страшно :)
                Я добавил basis в тест: jsfiddle.net/GdE3r/1/
                Цифры говорят сами за себя ;) И это при том, что другие создают только разметку, а basis – полноценные интерфейсные узлы, а у них богатый функционал.
                  0
                  Это не мой тест, он от сюда: Toster — Angular vs Knockout на больших списках.
                  Ваш вариант у меня не работает (Chrome 33), мне кажется basis пытается брать зависимости не от туда:
                  GET http://fiddle.jshell.net/GdE3r/1/show/light/basis/ui.js 404 (Not Found)
                  
                    0
                    Сорри, fiddle как то странно сохраняет.
                    Вот работающий вариант: jsfiddle.net/GdE3r/2/
                    +1
                    Разбил тот тест по файлам на Plunker.
                      +1
                      Ну вот, вам и пример. Используя angular вы смогли сделать все решения гораздо медленнее, чем они есть на самом деле.

                      Вот цифры по вашей ссылке


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


                      Выходит в вашем варианте не правильно измерялось и тестировалось, так как это добавило значительные накладные расходы (шутка ли jQuery на заполнении был в два раза медленнее, Knockout — в 3, basis.js — в 6). Малое влияние изменений на Angular Light (inline) видимо из-за того, что использовались его таймеры.
                        0
                        Выходит в вашем варианте не правильно измерялось и тестировалось
                        Нет, просто там дополнительно учитывается время рендеринга. Посмотрите в вашем варианте на Knockout.js 595ms и Angular.js 1075ms, у KO время лучше хотя визуально видно что он отрисовывается в 2 раза медленнее — т.е. цифры врут.

                        Используя angular вы смогли сделать все решения гораздо медленнее, чем они есть на самом деле.
                        Так оно, но это плата за «удобства», а в реальных приложениях «узкие» места можно переписать на vanilla js, например для теста выше можно было сделать директиву ng-fast-list которая не уступала бы по скорости.
                          +1
                          Нет, просто там дополнительно учитывается время рендеринга. Посмотрите в вашем варианте на Knockout.js 595ms и Angular.js 1075ms, у KO время лучше хотя визуально видно что он отрисовывается в 2 раза медленнее — т.е. цифры врут.

                          У того и другого свои заморочки с отложенными вычислениями.
                          Учитывать время рендеринга (именно отрисовки) нет смысла. Во-первых, это уже не часть измеряемого кода. Во-вторых вы не можете гарантированно сказать, когда он отработает.
                          То есть если мы изменяем скорость какого-то решения в целом, то имеет смысл включать все расходы (но через js всего не измерить). А если мы делаем тест, сравнивающий разные решения, то нет смысла включать оверхед, общий для всех.
                          В целом, я с вами согласен, что выпал рендеринг. Он занимает примерно одинаковое время. Если посмотреть на цифры, то для jQuery, Angular и basis.js цифры поменялись примерно одинаково, то есть на ~250ms на заполнении и ~150ms на обновлении. То что так сильно упало время для Knockout, скорей говорит о том, что часть работы он делает отложено и это не попадает в результат. Для Angular Light время заполнения уменьшилось больше чем на 250ms, возможно тоже что-то не учлось (или погрешность), зато обновление похоже на правду. Почему время Angular Light (inline) осталось неизменным, для меня загадка. Я думаю, что в обоих случаях выпадает часть работы. Так как в вашем варианте он близок к basis.js, но визуально работает в несколько раз медленнее.

                          Так оно, но это плата за «удобства», а в реальных приложениях «узкие» места можно переписать на vanilla js, например для теста выше можно было сделать директиву ng-fast-list которая не уступала бы по скорости.

                          Если честно, не увидел особых удобств. Код на angular запутан и его не назовешь «изящным» (но, возможно, это дело вкуса, конечно). И это на простой задаче, а если мы начнем добавляться функционал, то вы быстро обрастете кодом, и решение станет еще более запутанным.
                          Можно сделать дерективу? Сделайте, было бы интересно увидеть результаты. Но тогда нужно учитывать, что, например, для того же basis.js так же можно не создавать UI ноды, а использовать чисто шаблонизатор. Будет в 2-3 раза быстрее, но код будет «не очень» и не будет многих «удобств» (basis.ui как раз прячет за своим интерфейсом всю «неприятную» работу). Добавил реализацию для сравнения (файл bs_template.js).
                            +2
                            Я думаю, что в обоих случаях выпадает часть работы. Так как в вашем варианте он близок к basis.js, но визуально работает в несколько раз медленнее.
                            В Angular Light ничего не «выпадает», все действия отрабатывают последовательно (в одной «итерации»).

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

                            Можно сделать дерективу? Сделайте, было бы интересно увидеть результаты.
                            Вот, конечно это крайний вариант, без шаблонизатора, но показывает до куда можно опуститься, по сути директива просто дает точку позиционирования = элемент + данные, далее вы делаете что угодно.
                            Сейчас у меня такой результат:
                            JS: 19 — 10
                            al-fast-list: 21 — 10
                            Basis.js template: 55 — 18
                              0
                              В Angular Light ничего не «выпадает», все действия отрабатывают последовательно (в одной «итерации»).

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

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

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

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

                              Очень здорово, что добавили вариант с plain js. Да, так можно увидеть базовую линию, то есть ниже которой не опуститься. Заполнение правда можно сделать еще быстрее если сгенерить строку со всем html, и запихнуть через innerHTML. Правда в этом случае у нас не будет ссылок на элементы и обновление будет в несколько раз медленнее (если использовать ту же технику).
                              Любой шаблонизатор создает overhead, плата за универсальность, все дела. Но, по-моему, не плохой результат. Что скажете?
                              А реализация директивы это больше хак, чем реальное применение. То же можно проделать практически для любого решения. Так что можно сказать, что это «неспортивное поведение» ;)
                              Основная проблема с plain js (и соотвественно директивой), что если нам нужно поменять разметку, добавить больше значений, обработчики событий и т.д., динамически менять все это, и эти изменения делаются в множестве мест… я думаю, вы сами прекрасно знаете, что будет :)

                              P.S. Сначала подумал, странно что цифры выросли. И лишь потом заметил, что речь уже про 10k элементов, а не 4k :) Knockout все так же неправильно считается, у меня висит секунд 15 наверное, а показывает 1.5 — странно все это.
                                0
                                Тогда как вы объясните одинаковые результаты и что визуально оно отрабатывает медленнее?
                                У меня оно соотносится с цифрами — визуально чуть быстрее чем Angular.js

                                Возможно, у нас разные представления о больших приложениях.
                                Возможно, но Angular так же и официально, «покрывает» нишу больших приложений.

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

                                Но, по-моему, не плохой результат. Что скажете?
                                Да, результат очень хороший.

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

                                Так что можно сказать, что это «неспортивное поведение» ;)
                                Я просто показал как можно победить узкие места, да и вообще по большому счету большинство синтетических тестов показывают «сферического коня».
                                  +1
                                  У меня оно соотносится с цифрами — визуально чуть быстрее чем Angular.js

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

                                  Возможно, но Angular так же и официально, «покрывает» нишу больших приложений.

                                  Ок. Тогда, хотелось бы от вас услышать характеристики большого приложения.

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

                                  В целом, да. Но это так определяется порог, когда вам нужно прибегать к «хакам».
                                  У большинства «мейнстримовых» решений проблема именно в этом. Когда начинаем работать с большими количеством данными, начинаются проблемы, нужно думать про всякие «хаки». Мне кажется, что фреймворк должен подводить к этому на каких то безумных количествах, например, сотнях тысяч или миллионах моделей. Но не раньше (на тысячах).
                                    0
                                    В общем, в тесте была проблема, так что неправильно измерялось время для многих решений, из-за того, что они делают вычисления отложено (angular, knockout etc). Фактически не учитывался вызов callback.
                                    Я поправил, теперь больше похоже на правду: plnkr.co/edit/RzZP7146NgWHlVchXZF7?p=preview
                                    Чтобы не считать расходы на рендеринг я спрятал блок, куда рендерятся списки. Добавил галочку, чтобы можно было учитывать это время. Можно заметить, что даже спрятать/показать список из 10k элементов получается не быстро. Это время у всех будет практически одинаковым.
                                    Смущает, что используется setTimeout(callback, 0) — это может накидывать дополнительное время.
                                    Так же tenshi добавил свое решение и немного изменил решения на jQuery и Knockout, они стали побыстрее.
                                    В итоге у меня получились такие цифры:



                                    Конечно, нужно измерять каждое решение в отдельности. Некоторые, особо прожорливые до памяти, съедают ее столько, что это сильно сказывается на времени выполнения других.
                  0
                  Этого точно не будет в руководстве, но возможно будет отдельная статья.
                  Как правильно заметил vk2, некоторые сравнения есть практически в любой презентации – www.slideshare.net/basisjs/
                    +1
                    К теме сравнения, вот расширенный тест Jeremy Ashkenas с анимированными кружочками:

                    +2
                    Меня интересует несколько моментов:
                    1) Я так понимаю, что default оформление ui было взято у sencha? Как они к этому относятся?
                    2) Самописная билд система? Почему не используете уже существующие?
                    3) Какие решения вы используете для наследования и что вы думаете о примесях
                    4) Скоро современные браузеры начнут поддерживать ES6, не сигнал ли это к приведению кода к ES6 нотации, особенно для новых фреймворков? Я имею ввиду, что можно уже сейчас реализовывать большую часть спецификации, а поддержку старых браузеров обеспечивать полифилами. Это дало бы вам некоторую отличительную особенность, по сравнению с основными frontend фреймворками.

                    Вообще, почти любой труд заслуживает благодарности, и я уверен, что ваш фреймворк найдет свою нишу, но пока я не понимаю, чем он принципиально отличается от остальных. Присоединяюсь к xeLL, хотелось бы посмотреть на сравнение бенчмарков с др. фреймворками.
                      0
                      Справедливо добавить, что после просмотра презентации, ссылку на которую кинул vk2, вопрос про сравнение с фреймворками отпадает. Но все же, я думаю было бы лучше, если б вы добавили эту информацию в статью.
                        0
                        Чуток истории. Если я хорошо помню, то Basis Рома начал делать еще в 2006 году, если не раньше. А вот оформление UI он делал при мне году так в 2008 — и за базу он брал оформление OS Windows Vista. Сидел вырезал бекграунды со скриншотов и т.п. :)
                          +2
                          1. sencha никак к этому не относится, так оформление взято не у них. Возможно оформление вам показалось знакомым из-за использование сине-голубых оттенков. Но это больше заимствование от windows разных версий + собственные додумки. В целом, дефолтные стили используются редко, там где не важен дизайн. А в большинстве случаев переопределяются шаблоны или пишутся собственные компоненты.
                          2. Причина номер один: когда начинали делать сборщик, не было ни grunt, ни gulp, ни broccoli, ни чего то подобного. Во-вторых, мне, например, не известны системы сборки которые умеют строить граф файлов и производить оптимизации на основе их типов и взаимосвязей. В нашем случае, сборка нечто большее, чем просто «возьми файлы из этой папки, объедини и прогони через упаковщик». К тому же используется CommonJS, а он так просто не собирается. Думаю, об этом еще будет отдельная статья.
                          3. Про классы и наследование можно прочитать тут. Примеси практически не используются и, в целом, не очень хорошая идея.
                          4. В принципе ничего не мешает использовать ES6 уже сегодня с любыми фреймворками и библиотеками, надо лишь обеспечить трансляцию ES6 -> ES5. Это не задача фреймворка. Но есть мысли, например, по поводу использования синтаксиса для работы с модулями (import).

                          Спасибо. Если посмотреть на все фреймворки, то они ничем друг от друга не отличаются, все они фреймворки и написаны на javascript :)
                            +1
                            А борщик уже был? Или вы о нем не слышали?
                              0
                              Конечно, я слышал о нем и даже лично знаком с текущим мейнтейнером :)
                              Борщик гораздо проще и не делает анализа кода или организации проекта.
                                –1
                                Разработчикам всегда интересно повышать эффективность кода, не изменяя своих привычек :-)
                                Отсюда и вопросы про борщик\грант и прочее.

                                Скорее народ интересует вот что: планируете ли вы интеграцию своих решений во что-нибудь мейнстримовое? Чтобы полностью не менять привычную среду разработки. Та же сборка проекта борщиком с флагом basis, например, который будет проводить необходимый анализ кода организации проекта.
                                  +2
                                  Насчет борщика таких мыслей не было. А насчет grunt/gulp были… но есть нюансы ;)
                                  Мы стараемся не делать решений на перспективу, у всего должен быть свой пользователь, чтобы сказать как правильно и насколько решение хорошее. Если будет необходимость в интеграции, то неприменно что нибудь придумаем ;) Тем более, что basisjs-tools можно использовать как модуль в своих скриптах (на это был расчет при проектировании).
                              0
                              К сожалению, ситуация вокруг сборщика немного сложнее: у вас, насколько я понимаю, нет возможности инкриментальных сборок, нет кеширования, watcher'ов. А это значит, что либо мне придется работать с двумя сборщиками, например, Gulp и ваш, либо писать свои велосипеды для оборачивания shell команд под CoffeeScript или SASS, что явно не упрощает процесс разработки. Это уже не говоря, про, скажем, разделение окружений на dev и production для сборок. А по поводу commonJS, уже есть решения для разнообразных билд систем, в чём такая сложность его сборки? Что касается «киллер-фичи» с графами файлов — вам ничего не мешает вынести ее как плагин к одной из готовых систем, это намного проще, чем писать очередной сборщик. По поводу ES6 — это не задача фреймворка, но когда стандарт будет увтержден, лично я отдам предпочтение фреймворкам, которые будут иметь нативную поддержку таких вещей, как модули, например, чем буду использовать amd/commonJS. Разумеется, это всё лишь моё мнение и я не претендую, что оно единственное правильное.

                              P.S. Если посмотреть на все фреймворки, то они отличаются друг от друга подходом к созданию приложений. Например, я думаю, многие не согласятся с вашим мнением, что Backbone не отличается от Angular.
                                0
                                Инкрементальной сборки нет, вы правы. По той причине, что сборка нужна только для публикации приложения, но не для разработки. Кеширование есть на уровне dev-сервера, он запоминает (текстовые) файлы, которые запрашивает клиент и при перезагрузке отдает множество файлов как один, тем самым уменьшая количество запросов и время загрузки. Так даже приложение на 1500 файлов обновляется быстро (что-то вроде 1сек). Хотя, как написано в статье, обновление нужно только если вы обновляете html или javascript.
                                Что касается компилируемого контента, таких как coffeescript, sass и т.п., то тут все зависит от самого решения. Например, у coffeescript есть компилятор написанный на js, это значит что мы можем транслировать его в js хоть на сервере, хоть на клиенте. И если вы пройдете по этой ссылке, то увидите как это сделать, как раз на примере coffeescript. Что касается sass, то тут немного сложнее, так как компилятор на ruby. Можно использовать какой нибудь вотчер, который будет транслировать файлы в css при их изменении, а дальше все будет работать как раньше. Другой вариант, чтобы это делал dev-сервер и сборщик. Этого пока нет, так как не существовало спроса на эту функциональность. Но это совсем не проблема имплементировать, там все относительно не сложно.
                                Насчет графа файлов, вы недооцениваете задачу и пока не видите преимуществ. Основной смысл, что мы не просто обрабатываем какие то файлы, но строим их взаимосвязи и понимаем как они друг друга используют. Вот, например, вся верстка у нас в шаблонах, а стили в css. Мы можем найти соответствия и нестыковки. Например, что такой то класс никогда не используется в разметке, а вот для этого класса в шаблоне нет никаких стилей. Поэтому мы можем на этапе разработки рассказать о ряде проблем, а на этапе сборке применить агрессивную оптимизацию, которая заменит все классы и id на одно-двух буквенные. Это как, например.
                                То есть суть в том, что обрабатывается не просто какой то определенный файл, или тип файлов. А обрабатываются все файлы сразу, с учетом их связей, перестраиваются структуры. Например, те же шаблоны попадают в сборку в виде AST, которое уже не требует парсинга. И т.д.

                                P.S. Это была шутка ;) И, кстати, ни Backbone, ни Angular не являются фрейморками, если что…
                                  +1
                                  сборка нужна только для публикации приложения, но не для разработки

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

                                  Например, у coffeescript есть компилятор написанный на js, это значит что мы можем транслировать его в js хоть на сервере, хоть на клиенте.

                                  Только что вам даст трансляция coffee->js без возможности сохранить результат? Или вы будете eval'ить код, который будет выходить из такого транслятора? И будете делать это для всех coffee файлов? Я могу сказать, что вы здесь ничего не выигрываете.

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

                                  Действительно, можно, однако инкрементальные сборки придумали как раз, чтобы уйти от того, что у вас в одной вкладки консоли открыт sass watch, в другой coffee watch, а в третей бог весть что. И более того, это по-прежнему не покрывает кейсов тестирования после изменения coffee файлов.

                                  не существовало спроса на эту функциональность

                                  Вы считаете, что таск-раннеры а-ля Grunt, или системы сборки типо Gulp, Broccoli, Brunch не пользуются спросом?

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

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

                                  Не могу представить себе иного сценария, как файл index.html как корень графа, из которого идут две ветви: css и js, в которых в css отражаются зависимости директивы @import, а в js — amd/commonJS модулей. Как это помогает разработке?

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

                                  Chrome Dev Tools -> вкладка audit -> start

                                  ни Backbone, ни Angular не являются фрейморками, если что…

                                  Не могли бы вы в таком случае пояснить мне, чем они являются, если не MV* фреймворками?
                                  Лично я основвываюсь классического определения «фреймворк», данного википедией, и в моём понимании, angular и backbone под него подходят
                                    0
                                    Что насчет тестирования? Я придерживаюсь разработки через тестирование, и т.к. я тестирую не отдельные функции, а пользовательские сценарии, я бы хотел иметь собранный проект, т.к. чаще всего пользовательские действия затрагивают сразу несколько модулей, как быть тогда?


                                    Отсутсвие сборки никак не мешает тестированию. Или я чего-то не понимаю?

                                    Только что вам даст трансляция coffee->js без возможности сохранить результат? Или вы будете eval'ить код, который будет выходить из такого транслятора? И будете делать это для всех coffee файлов? Я могу сказать, что вы здесь ничего не выигрываете.


                                    А зачем его сохранять? Да, код будет eval'ится (через new Function). Так же eval'ится и код всех модулей, на этом построена работа commonjs. Node.js делает точно так же. И, поверьте, в этом нет проблемы.
                                    Когда вы делаете сборку, то в процессе код оборачивается в функции и в собранном варианте уже не будет eval'ится.
                                    Здесь дело не в мифическом выигрыше, а в удобстве. Например, вам не нужно хранить временные файлы (результат компиляции coffeescript), если код не скомпилится вы увидите это в консоли браузера, в панели sources вы видите все файлы ровно так как они располагаются в вашей файловой системе etc.

                                    Действительно, можно, однако инкрементальные сборки придумали как раз, чтобы уйти от того, что у вас в одной вкладки консоли открыт sass watch, в другой coffee watch, а в третей бог весть что. И более того, это по-прежнему не покрывает кейсов тестирования после изменения coffee файлов.


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

                                    Вы считаете, что таск-раннеры а-ля Grunt, или системы сборки типо Gulp, Broccoli, Brunch не пользуются спросом?


                                    Вы меня не правильно поняли, речь шла о поддержке sass в basisjs-tools, а не о таск-раннерах.

                                    Не могу представить себе иного сценария, как файл index.html как корень графа, из которого идут две ветви: css и js, в которых в css отражаются зависимости директивы import, а в js — amd/commonJS модулей. Как это помогает разработке?


                                    Html подключает javascript, css, изображения и другие файлы. Javascript подключает другие javascript модули и другой контент, такой как шаблоны, словари, json etc, для некоторых вещей свои синтаксические конструкции. Шаблоны — css, изображения, словари, другие шаблоны. И далее…

                                    Chrome Dev Tools -> вкладка audit -> start


                                    У вас всегда вся возможная разметка на странице? Речь не про статичные сайты, а про приложения, где динамически генерируются, вставляются, удаляются представления в зависимости от ситуации, а еще добавляются и удаляются классы, меняется разметка. Вариантов состояния разметки настолько много, что вы просто не сможете сделать так, чтобы audit правильно показал что используется, а что нет. Если речь, конечно, не про «Hello world» ;)
                                      0
                                      Отсутсвие сборки никак не мешает тестированию. Или я чего-то не понимаю?

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

                                      А зачем его сохранять? Да, код будет eval'ится (через new Function). Так же eval'ится и код всех модулей, на этом построена работа commonjs. Node.js делает точно так же. И, поверьте, в этом нет проблемы.

                                      А что насчет минификации выходных файлов? Суть в том, чтобы после трансляции coffee->js склеить и минифицировать все JS файлы. Как вы это будете делать, если собираетесь компилировать Coffee на клиенте? Это проигрыш в скорости, как с точки зрения eval'a, так и с точки зрения трафика.

                                      Html подключает javascript, css, изображения и другие файлы. Javascript подключает другие javascript модули и другой контент, такой как шаблоны, словари, json etc, для некоторых вещей свои синтаксические конструкции. Шаблоны — css, изображения, словари, другие шаблоны. И далее…

                                      Ну, во-первых это очень похоже на web-компоненты(см. polymer, mozilla x-tags), особенно идея с подключением стилей для конкретных объектов. По-факту, ваша система позволяет производить инкапсуляцию css, js и html в рамках компонента, верно? В таком случае это 1 в 1 повторяет идеологию web-компонентов.

                                      У вас всегда вся возможная разметка на странице?

                                      Нет, но у меня всегда подключается gzip-пакет, в котором лежат все JS и CSS. А т.к. мои «фреймворки» позволяют мне выбрать шаблонизатор, с которым я буду работать, то компилируется этот код в JS, и позднее склеивается и минифицируется вместе с остальными JS файлами, тем самым получая максимально маленький размер трафика, который мне надо отдать пользователю. Это уже не говоря о том, что я могу провести вашу проверку на использование стилей в несколько раз быстрее, пробежав всего по 2 файлам: 1 css(результирующий) и 1 js(шаблоны), в то время как при вашем подходе мне придется открывать по 1 все стили и все шаблоны.
                                        0
                                        Unit-тестированию не мешает, а функциональному тестированию мешает. Вы не можете провести функциональное тестирование до момента создания консистентного состояния системы, которое достигается успешной сборкой проекта.

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

                                        А что насчет минификации выходных файлов? Суть в том, чтобы после трансляции coffee->js склеить и минифицировать все JS файлы. Как вы это будете делать, если собираетесь компилировать Coffee на клиенте? Это проигрыш в скорости, как с точки зрения eval'a, так и с точки зрения трафика.

                                        По той ссылке, что я вам привел есть и про сборку. При сборке coffee будет транслироваться в js, этот код будет обернут, проанализирован, сконкатенирован и минимизирован на ряду с остальным javascript. Трансляция coffee->js на клиенте происходит только в dev режиме.

                                        Ну, во-первых это очень похоже на web-компоненты(см. polymer, mozilla x-tags), особенно идея с подключением стилей для конкретных объектов. По-факту, ваша система позволяет производить инкапсуляцию css, js и html в рамках компонента, верно? В таком случае это 1 в 1 повторяет идеологию web-компонентов.

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

                                        Нет, но у меня всегда подключается gzip-пакет, в котором лежат все JS и CSS. А т.к. мои «фреймворки» позволяют мне выбрать шаблонизатор, с которым я буду работать, то компилируется этот код в JS, и позднее склеивается и минифицируется вместе с остальными JS файлами, тем самым получая максимально маленький размер трафика, который мне надо отдать пользователю. Это уже не говоря о том, что я могу провести вашу проверку на использование стилей в несколько раз быстрее, пробежав всего по 2 файлам: 1 css(результирующий) и 1 js(шаблоны), в то время как при вашем подходе мне придется открывать по 1 все стили и все шаблоны.

                                        Как я уже писал, сборщик basisjs-tools тоже оптимизирует структуру шаблонов при сборке.
                                        Пробежаться у вас вряд ли получится, потому что шаблонизаторы, те что вы имеете ввиду, прозводят функции, которые конкатенируют строки. Вы не можете предсказать результат такой конкатенации, так как там подставляются значения (переменные). Поэтому это одна из главных проблем — узнать на каком элементе, какие классы бывают. А еще, в таком подходе, те же классы часто меняются из представления. Например, $(this.el).addClass('foo-' + bar + '-' + baz) — вы можете сказать какой класс тут будет в результате? Вам так же не поможет поиск, если вы видите класс в селекторе, вы не сможете его найти в шаблоне, если он динамический, и так просто ответить на вопрос используется ли он.
                                        В «нашем» подходе не нужно открывать файлы по одному. Вы просто описываете шаблоны и стили. В basisjs-tools есть команда extract, которая позволяет извлекать факты о вашем приложении. По сути она выполняется перед сборкой, так как сборщик использует эту иформацию, чтобы уже произвести результат. Так вот, вызвав такую команду можно получить список проблем, вам напишут в каком файле и какая проблема. Так как вы разрабатываете постепенно, то так же постепенно и исправляете проблемы и миллиона варнингов вы не увидите. Можно так же использовать плагин для Google Chrome (про который говорилось в статье), тогда не нужно выполнять эту команду, а можно посмотреть все варнинги в Developer Tools -> basis.js -> Warnings. На скриншоте, как раз видно сейчас в проекте 2 варнинга, они как раз про то, что использованы id для которых нет стилей.
                                          0
                                          Да, верно подмечено, это похоже на web-components. Но там есть ряд отличий и шаблоны basis.js дают гораздо больше, чем просто инкапсуляция.

                                          Хотелось бы услышать об этом во второй части статьи

                                          Например, $(this.el).addClass('foo-' + bar + '-' + baz) — вы можете сказать какой класс тут будет в результате?

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

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

                            Спасибо за статью, жду продолжения с описанием привязки контролов к состоянию модели.
                              0
                              Интересно было бы прочесть об организации контроллеров и роутингов.
                              А так, напомнило ExtJS способами описания конфигов представлений.
                                0
                                Как таковых контроллеров нет. Но если рассуждать согласно, например, вот этой штуке, то контроллер это basis.ui.Node, а представление это шаблон. Хотя многие говорят что у нас тут MVVM. Но, честно говоря, мы не особо заморачиваемся насчет классификации ;)
                                Про роутер будет следующих частях, скорей всего в третьей.
                                –1
                                Очень похож на суррогат ExtJS. Похоже, что ребята решили сделать что-то свое в этом направлении, только недопили до конца. Идея интересная, посмотрим как будет развиваться.
                                  +1
                                  Простите, но в каком месте это суррогат ExtJS?
                                  Боюсь, что вы рано делаете выводы, это всего лишь первая часть руководства и в нем рассказана лишь малая часть из того, что есть во фреймворке ;)
                                    0
                                    Когда basis только начали создавать ExtJs даже еще не родился.
                                      +1
                                      По-моему, все просто имеют ввиду, что фреймворк «опоздал» на несколько лет. В далеком 2006 это был бы огромный прорыв вперед, но в 2014 это просто еще один фреймворк на JS. Если вы со мной не согласны — объясните, в чём он «уникален», т.к. из статьи этого не понятно.
                                        +2
                                        Уникальность фреймворка, он очень высоко производительный. Он граматно работает с DOM, возможно лучшая реализация, какую-либо я втречал. C ExtJS имеет общее только тем, что в своей основе использует компонентный подход(что-то подобное на дерективы из «ангуляра» или web components). У ExtJS, есть большой недостаток он плохо кастомизируется. Хотя существует много и других недостатков, но это бы назвал основным.
                                        Basisjs Уникален своими инструментами, написанными на привычном для фронтеенд разработчика стеке. В инструментах есть почти все, что вам может понадобиться. Есть киллер фича лайв редактирование.
                                        Фреймворк, очень хорошо подходит для больших проектов. Он заставляет писать вас быстрый код. Модульная система позволит вам переиспользовать ваш код без боли, даже в другом проекте.
                                        Есть и недостатки, но я не буду о них упоминать.)
                                        Я советую, Вам познакомиться ближе с фреймворком, попробуйте его. Из описания действительно не видно его крутости.
                                        Я думаю, Роман раскроет в следующих статьях все прелести basisjs.
                                        Вообще молодцы ребята, что продолжают развивать проект, а не зарыли его внутри компании, и пытаются поделиться большим опытом разработки действительно сложных фронтенд систем.
                                          +2
                                          Уникальность фреймворка, он очень высоко производительный

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

                                          Есть киллер фича лайв редактирование.

                                          livereload.com/ — а такое решение не устраивает? Или я что-то не так понял?

                                          Фреймворк, очень хорошо подходит для больших проектов

                                          Как и любой другой фреймворк, собственно.

                                          Уникален своими инструментами, написанными на привычном для фронтеенд разработчика стеке

                                          Можно поинтересоваться, какие инструменты в этом фреймворке уникальны?

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

                                          Это не достоинство фреймворка, это достоинство модульной системы.

                                          Есть и недостатки, но я не буду о них упоминать.)

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

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

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

                                            basisjs.com/basisjs/test/speed/tree.html Это конечно синтетические примеры, но хоть что-то.

                                            livereload.com/ — а такое решение не устраивает? Или я что-то не так понял?

                                            Нет чуть другое. Просто попбробуйте. Это нужно просто увидеть. У basisjs, очень хорошее разделении на данных и представления.
                                            Как и любой другой фреймворк, собственно.

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

                                            Уникальны не инструменты, а то что у вас из коробки есть экоситема для разработки на basisjs. Нет необходимости искать, нужный вам инструмент.
                                            Это не достоинство фреймворка, это достоинство модульной системы.

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

                                            О недостатках. Мало реальных примеров и гайдов. Но как видим ситуация улучшается.
                                            Порог вхождения, я бы не назвал низким. Подходы в разработке, отличаются от имеющихся сейчас популярных фреймворков(это мое мнение). Маленькое комьюнити или вообще его отсутсвие. Я бы не рекомендовал, начинать проект на basisjs, если вы не очень опытный разработчик.
                                            Я кстати, никаким боком не отношусь к созданию или разработке фрэймворка. Говорю со стороны пользователя.
                                              –1
                                              Спасибо за ссылку на бэнчмарки, поглядел — действительно все очень быстро. По поводу лайв редактирования по-прежнему не понимаю: может, вы имеете ввиду data-binding? Я просто уже теряюсь в догадках :) По поводу «бороться» с фреймворком — это скорее просто говорит об отсутствии правильно спроектированной архитектуры.

                                              Что касается описываемых вами недостатков — это даже не недостатки, а, скорее, небольшие трудности, которые переживает только что зародившийся проект. Я обязательно взгляну на этот проект более подробно, как представится случай. Возможно, я и правда слишком критичен к нему, раз столько человек говорит, что это такая замечательная система.
                                                +1
                                                Хотел вам ответить, но mavrin уже не плохо справился с этим :)
                                                Немного добавлю.

                                                Таких тестов, чтоб «бери и пробуй» пока мало. Вот один из них, что я делал для последнего доклада: тест моделей lahmatiy.github.io/lib-compare/ (репозитарий), сравниваются два разных модуля basis, Backbone и Ember.

                                                livereload.com/ — а такое решение не устраивает? Или я что-то не так понял?


                                                Livereload и подобные, перегружают страницу, когда меняется какой то файл. С basis.js вам нужно это делать только для javascript файлов. Если вы меняете шаблоны, стили или словари локализации этого делать не надо. Базис перестраивает часть страницы, применяя изменения. То есть в этом случае нет перезагрузки страницы. Это не критично для сайтов, но очень критично для приложений, так как нам нужно сохранять состояние. Я планирую записать скринкаст, думаю из него все станет ясно. А пока можете либо проследовать руководству, и попробовать у себя. Либо поиграться в интерактивном туре, например, на этом слайде: basisjs.com/basisjs/tour/#ui_loose_coupling. Там сверху можно переключать закладки (файлы), код можно менять. Попробуйте выбрать пункт списка внизу, а потом менять файлы *.tmpl или *.css.

                                                Фреймворк, очень хорошо подходит для больших проектов
                                                Как и любой другой фреймворк, собственно.


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

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


                                                Очень хорошо сказано :) Правда фреймворку уже 7-8 лет… но он только начинает выходить, так сказать, в массы.
                                                  0
                                                  Я был бы только рад, если бы этот фреймворк продолжил развиваться. Но вы действительно поздновато вытащили его на свет. Реально, хотелось бы увидеть более мощную экосистему у этого продукта. С полными примерами по всем возможностям фреймворка, с руководством, рассмотрением всех тулзов (сборщик, генератор документации, создание тестов функциональных и модульных) и многое, многое другое, что есть в Sencha (простите, что опять вспоминаю *=)).
                                                  Может я слепой, но из всех описанных возможностей здесь и в документации проекта, я ничего нового не увидел. Все это уже есть в ExtJS. Тесты на скорость, могут вам показать те же результаты, если вы вникнете в суть ExtJS.

                                                  Я ни хочу сказать ничего плохого о фреймфорке, просто высказываю свое субъективное мнение. Я вижу в basis.js прямого конкурента ExtJS по своим возможностям, и это очень хорошо! Никакие ангулары, метеоры, бэкбоны, прототипы и иже с ними здесь рядом не стоят.
                                                  В этом фреймворке есть все, что необходимо для разработки single-page. И я бы рекомендовал его всем, кто не хочет раскашеливаться на коммерческую лицензию ExtJS, ну или кто просто хочет использовать что-то помимо него.

                                                  Кстати, каким вы видите будущее basis.js, в свете ES6 и развивающегося Dart?
                                                    0
                                                    На свет он вышел практически сразу после рождения и сайт был другой, и доки небольшие были и примеры. Но вот его широкий «пиар» начался действительно гораздо позже, когда уже он сформировался в текущий вид и успел засветиться в серьезных проектах.
                                                      +1
                                                      Спасибо. Все нормально, я вас понимаю ;)

                                                      Хоть на первый взгляд многие видят сходство с extjs, на самом деле у них ничего общего.
                                                      Мощь extjs строится на готовых компонентах, и его можно назвать конструктором. Тюнить имеющие или создавать свои – занятие непростое. А без готовых компонент он практически бесполезен. К тому же он очень большой. В нем много готового, это и хорошо и плохо. Хорошо, потому что можно собрать быстро некоторое решение. Но плохо, что сложно сделать что-то свое уникальное или стилизовать.
                                                      basis.js: Компоненты которые в нем есть больше для демонстрации и для прототипирования. В каждом проекте по большей части делаются свои компоненты, это не сложно. Либо полностью заменяются шаблоны и верстка (так как представление слабосвязано с шаблоном, то ему можно подсунуть практически любой шаблон). Ну и, конечно, часто используются собственные наработки, качующие из проекта в проект. То есть в этом отношении basis является основой для создания компонент и интерфейса.
                                                      Еще, к пример, live update это не только про быстрый и удобный процесс верстки без перезагрузки страницы. Это так же адаптация представления под данные (мало данных одна верстка, много данных – другая) и такая клевая штука как темы. Еще хорошо развита работа с данными, об этом будет в следующей части. Думаю после нее отпадут вопросы о схожести с чем либо… по крайней мере с extjs точно ;)
                                                      Кстати extjs достаточно не быстрый, судя по моим экспериментам. Но мы можем сравнить, мне было бы весьма интересно.
                                                      Еще в extjs нет разделения логики и представления, нет дружелюбного сборщика (хотя может что-то поменялось?) и много чего еще. С другой стороны в basis.js тоже нет многого из того, что есть в extjs. Но у того фреймворка целая компания, куча людей, которая занимается исключительно его разработкой и развитием. Нам пока приходится только мечтать об этом :) Работа над фреймворком не является основной деятельностью, и сам он является продуктом работы над разными проектами и задачами. Так сказать собрание лучших решений, что получилось найти и различные best practices, накопленные в течении нескольких лет.
                                                      Кстати, а про какой генератор документации речь?

                                                      Насчет будущего, вижу его светлым и жизнерадостным, я оптимист ;)
                                                      basis.js старается не использовать странные стороны js и быть как можно ближе к его идеалогии. Например, года три назад мы отказали от this.inherit() (например, в Ember такой метод называется this._super()) – вызов переопределенного метода, так как поддержание это механизма дорого для производительности. Отказавшись такого хелпера, мы получили буст производительности в 2 раза (сам был в шоке). Сейчас мы пишем так
                                                      var MyClass = basis.ui.Node.subclass({
                                                        method: function(a, b){
                                                          basis.ui.Node.prototype.method.call(this, a, b);
                                                          ...
                                                        }
                                                      });
                                                      

                                                      Длинно, но зато быстро. Да и сейчас редко, когда нужно переопределять методы (для этого тоже было сделано не мало).
                                                      ES6 вместе с тем нацелено решать проблемы языка. В частности эту проблему. С ES6 мы сможем писать так:
                                                      var MyClass = basis.ui.Node.subclass({
                                                        method: function(a, b){
                                                          super(a, b);
                                                          ...
                                                        }
                                                      });
                                                      

                                                      А те фреймворки, что используются свой this.inherit/super/etc никакого преимущества не получат.
                                                      Еще мы смотрим на модульность в ES6. Выглядит так, что мы сможем адаптировать текущую реализацию чтобы
                                                      // помимо этого 
                                                      var Node = require('basis.ui.Node');
                                                      // работало и это
                                                      import Node from 'basis.ui';
                                                      

                                                      Что касается Dart, то тут затрудняюсь ответить. Я не уверен, что получится подружить реализацию классов basis.js с идеалогией Dart, а на ней многое строится. Но я пока не копал эту тему и возможно там не все так страшно.
                                                        0
                                                        Я думаю, нет смысла ориентироваться на Dart. После оф. отказа представителей Mozilla, Microsoft и Apple от нативной поддержки, он просто встает в ряд со всей массой языков, компилируемых в JS, а это уже не интересно
                                                          0
                                                          Это же гугл, так просто не задвинут, тем более Dart это совсем не аналог CoffeeScript, как многие думают…
                                                            +1
                                                            Я и не думаю, что Dart — аналог CoffeeScript.
                                                            Гугл не один раз закрывал свои проекты, и лично я думаю, что Dart ждет такая же участь.
                                        –1
                                        Очень попахивает экстом и его ужасными фичами вида «перегрузка свойств в конструкторе» и «автосоздание вьюшек по конфигам». Тем не менее есть и много свежих идей.

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

                                        Также я не разделяю эту любовь к requireJS. Какая разница будет у вас глобальная переменная или же зарегистрированный модуль, который доступен по глобальному имени? Те же глобальные переменные, но зачем-то спрятанные в недра глобальной функиции require. Относительные пути — это тоже зло, потому что при переносе модуля нужно фигурно исправлять относительные ссылки в него и из него. Лучше ссылаться ко всем модулям по глобальному имени и при переносе просто пробегаться реплейсом по всем файлам. Просто и надёжно. И если не плодить иерархию сверх меры, то вполне юзабельно.

                                        Почитай про мою реализацию: hyoo.ru/?article=%D0%90%D1%82%D0%BE%D0%BC%D1%8B+%D0%BD%D0%B0+JS;author=Jin
                                          0
                                          Очень попахивает экстом и его ужасными фичами вида «перегрузка свойств в конструкторе» и «автосоздание вьюшек по конфигам».

                                          Почему то все сравнивают с extjs, это прям тенденция :)
                                          Перегрузки свойств в конструкторе нет, по крайней мере в том виде что в extjs. Есть нормализация, но это другое.
                                          Предполагаю, что под «созданием вьюшек по конфигам» имеется ввиду фича extjs с xtype. Нет тут несколько другое. В следующей части будет рассказано про работу с данными, тогда станет понятно для чего на самом деле нужны childClass и childFactory. Данная статья вводная, объясняющая основные моменты и общий воркфлоу. Она делалась максимально простой, это обучающий материал, потому и примеры простые, и чем то напоминают другие фреймворки. Так что не пропустите следующую часть, она расставит точки на i.

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

                                          Вы не верно поняли. А значит, в тексте плохое разъяснение. Не могли бы вы указать, что навело вас на такое мнение?
                                          На самом деле, ничего не удаляется и перевставляется (если это конечно не перестроение DOM фрагмента по новому шаблону). Как вы правильно заметили это очень дорого. И по списку тоже не «бегаем». Узлы (basis.ui.Node) хорошо знают свою структуру и не делают лишних телодвижений без необходимости. Перемещения делаются перестановкой элементов, через insertBefore, только в том случае, если это действительно нужно.

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

                                          Модульная система не связана никак с requirejs. Requirejs – это AMD, а в basis.js используется CommonJS. Реализация очень близкая к реализации node.js.
                                          Имена никуда не зашиты. Неймспейсы (названия) проецируются на файловую систему. Для корневого неймспейса (например, basis – это корневой неймспейс) ассоцируется некоторый путь. К примеру, в статье basis.js подключается как bower_component/basis/src/basis.js, значит для basis базовый путь будет bower_component/basis/src/. Дальше использовался модуль basis.ui, его имя преобразуется в basis/ui.js. То есть точки заменяются на / и в конце подставляется расширение. К имени добавляется базовый путь и получаем путь к файлу bower_component/basis/src/basis/ui.js. То есть
                                          basis.require('basis.ui');
                                          // эквивалентно
                                          basis.require('./bower_component/basis/src/basis/ui.js');
                                          

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

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

                                          Суть в том, что мы должны стараться ссылать только вглубь. И даже больше, только на файлы модуля и на корневые файл вложенных модулей. В этом случае, обычно, все сводится к одной единственной замене. Это было показано в разделе про реструктуризацию файлов. Но, конечно, всегда бывают исключения.
                                          Пока получаем только плюсы от относительных путей. То, про что вы говорите, скорей всего, про ситуацию, когда много перекрестных относительных ссылок (путей). Да, это проблема и этого нельзя допускать. Если возникается такая ситуация, то скорей всего у вас не правильно организованы модули и нужно подумать как это исправить.

                                          Почитай про мою реализацию

                                          Если правильно понял, то это про вычисления и данные. Про это будет в следующей части руководства. Предполагаю для вас она будет куда более интересной.
                                          0
                                          Один вопрос о модульности. Можно ли догружать скрипты и CSS по требованию? Скажем, пусть на странице есть кнопка «Показать диалог», и весь код этого диалога вы подкачиваете только после того, как пользователь по ней кликнет. Будет ли это работать и в релизной версии приложения? Сможет ли сборщик определить, что скрипты диалога являются отдельным блоком, который надо отдельно скомпоновать и сжать? В прошлом именно из-за отсутствия этой возможности я отказался использовать сборщик ExtJS (Sencha CLI), поскольку даже в сжатом виде весь код моего приложения весил несколько сотен килобайт (в команде было 20 разработчиков).
                                            0
                                            ExtJS сам по себе огромный, там очень много кода и сборка получается большой.
                                            Все модули basis.js, компоненты и шаблоны в сумме дают 389Kb (111Kb gzip). Но все модули никогда не используются, обычно доля кода фреймворка ~250Kb, тогда как размер самого приложения (достаточно большого) 800-900Kb (~250Kb в gzip). Фреймворк позволяет писать достаточно компактный код.
                                            Сборщик basis.js собирает все в один пакет. Так проще и дешевле — скрипт загружается один раз и кешируется, при этом делается один запрос на js и один для css. Код тем не менее иниацилизируется лениво, по мере активации той или иной функциональности — потому старт достаточно быстрый.

                                          Only users with full accounts can post comments. Log in, please.