Frontender Magazine

В поисках идеального JavaScript-фреймворка

В наши дни в области фронтенд-разработки есть множество фреймворков и библиотек. Какие-то из них хорошие, какие-то нет. Часто нам нравится только определённый принцип или определённый синтаксис. Правда в том, что универсального инструмента нет. Эта статья про будущий фреймворк — фреймворк, которого ещё не существует. Я резюмировал достоинства и недостатки некоторых популярных JavaScript- фреймворков и отважился помечтать об идеальном решении.

Абстракции опасны

Всем нам нравятся простые инструменты. Сложность убивает. Она делает наши жизни сложнее, а кривую обучения — более отвесной. Программистам необходимо знать, как вещи работают. Иначе они чувствуют себя неуверенно. Если мы работаем со сложной системой, появляется большой разрыв между «я этим пользуюсь» и «я знаю, как это работает». К примеру, такой код скрывает сложность:

var page = Framework.createPage({
    'type': 'home',
    'visible': true
});

Предположим, что это реальный фреймворк. Под капотом createPage создает новый класс отображения, который загружает шаблон из home.html. В зависимости от значения параметра visible мы вставляем (или нет) созданный элемент DOM в дерево. А теперь представьте себя на месте разработчика. Мы прочитали в документации, что этот метод создаёт новую страницу с заданным шаблоном. Нам неизвестны конкретные детали, потому что это абстракция.

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

А что если мы перепишем пример выше вот так:

var page = Framework.createPage();
page
    .loadTemplate('home.html')
    .appendToDOM();

Теперь разработчик знает, что происходит. Создание шаблона и вставка в дерево теперь производятся разными методами API. Так что программист может что-то сделать между этим вызовами и контролирует ситуацию.

Возьмём к примеру Ember.js. Это отличный фреймворк. С его помощью мы можем построить одностраничное приложение всего несколькими строчками кода. Но всё имеет свою цену. Он объявляет за кулисами несколько классов. К примеру:

App.Router.map(function() {
    this.resource('posts', function() {
        this.route('new');
    });
});

Фреймворк создаёт три маршрута, и за каждым закреплён контроллер. Можете использовать эти классы, можете не использовать, но они всё равно есть. Они нужны фреймворку для работы.

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

У Backbone.js, например, всего несколько заранее определённых объектов. В них содержится базовая функциональность, но настоящая реализация остаётся за программистом. Класс DocumentView расширяет Backbone.View. Это всё. Всего один уровень между нашим кодом и кодом ядра фреймворка.

var DocumentView = Backbone.View.extend({
    'tagName': 'li',
    'events': {
        'mouseover .title .date': 'showTooltip',
        'click .open': 'render'
    },
    'render': function() { ... },
    'showTooltip': function() { ... }
});

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

Недостающий конструктор

Некоторые из фреймворков принимают наши определения классов, но не создают конструкторов. Фреймворк сам решает, где и когда создать экземпляр. Я был бы рад увидеть больше фреймворков, позволяющих нам делать именно так. Вот, к примеру Knockout:

function ViewModel(first, last) {
    this.firstName = ko.observable(first);
    this.lastName = ko.observable(last);
}
ko.applyBindings(new ViewModel("Planet", "Earth"))

Мы объявляем модель и сами же её инициализируем. А вот в AngularJS чуть по-другому:

function TodoCtrl($scope) {
    $scope.todos = [
        { 'text': 'learn angular', 'done': true },
        { 'text': 'build an angular app', 'done': false }
    ];
}

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

Работа с DOM

Что бы мы ни делали, нам нужно взаимодействовать с DOM. То, как мы это делаем, очень важно, обычно каждое изменение узлов дерева на странице влечёт за собой пересчёт размеров или перерисовку, а это могут быть весьма дорогостоящие операции. Давайте в качестве примера разберём такой класс:

var Framework = {
    'el': null,
    'setElement': function(el) {
        this.el = el;
        return this;
    },
    'update': function(list) {
        var str = '<ul>';
        for (var i = 0; i < list.length; i++) {
            var li = document.createElement('li');
            li.textContent = list[i];
            str += li.outerHTML;
        }
        str += '</ul>';
        this.el.innerHTML = str;
        return this;
    }
}

Этот крошечный фреймворк генерирует ненумерованный список с нужными данными. Мы передаём элемент DOM, в котором следует поместить список, и вызываем функцию update, которая отображает данные на экране.

Framework
    .setElement(document.querySelector('.content'))
    .update(['JavaScript', 'is', 'awesome']);

Вот, что у нас из этого вышло:

Скриншот

Чтобы продемонстрировать слабую сторону такого подхода мы добавим на страницу ссылку и назначим на ней обработчик события click. Функция снова вызовет метод update, но с другими элементами списка:

document.querySelector('a').addEventListener('click', function() {
    Framework.update(['Web', 'is', 'awesome']);
});

Мы передаём почти те же самые данные, поменялся только первый элемент массива. Но из-за того, что мы используем innerHTML, перерисовка происходит после каждого щелчка. Браузер не знает, что нам надо поменять только первую строку. Он перерисовывает весь список. Давайте запустим DevTools браузера Opera и запустим профилирование. Посмотрите на этом анимированном GIF'е, что происходит:

Анимация

Заметьте, после каждого щелчка весь контент перерисовывается. Это проблема, особенно, если такая техника применяется во многих местах на странице.

Гораздо лучше запоминать созданные элементы <li> и менять только их содержимое. Таким образом, мы меняем не весь список целиком, а только его дочерние узлы. Первое изменение мы можем сделать в setElement:

setElement: function(el) {
    this.list = document.createElement('ul');
    el.appendChild(this.list);
    return this;
}

Теперь нам больше не обязательно хранить ссылку на элемент-контейнер. Достаточно создать элемент <ul> и один раз его добавить в дерево.

Логика, улучшающая производительность, находится внутри метода update:

'update': function(list) {
    for (var i = 0; i < list.length; i++) {
        if (!this.rows[i]) {
            var row = document.createElement('LI');
            row.textContent = list[i];
            this.rows[i] = row;
            this.list.appendChild(row);
        } else if (this.rows[i].textContent !== list[i]) {
            this.rows[i].textContent = list[i];
        }
    }
    if (list.length < this.rows.length) {
        for (var i = list.length; i < this.rows.length; i++) {
            if (this.rows[i] !== false) {
                this.list.removeChild(this.rows[i]);
                this.rows[i] = false;
            }
        }
    }
    return this;
}

Первый цикл for проходит по всем переданным строкам и создаёт при необходимости элементы <li>. Ссылки на эти элементы хранятся в массиве this.rows. А если там по определённому индексу уже находится элемент, фреймворк лишь обновляет по возможности его свойство textContent. Второй цикл удаляет элементы, если размер массива больше, чем количество переданных строк.

Вот результат:

Анимация

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

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

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

Обработка событий DOM

Приложения на JavaScript обычно взаимодействуют с пользователем через события DOM. Элементы на странице посылают события, а наш код их обрабатывает. Вот отрывок кода на Backbone.js, который выполняет действие, если пользователь взаимодействует со страницей:

var Navigation = Backbone.View.extend({
    'events': {
        'click .header.menu': 'toggleMenu'
    },
    'toggleMenu': function() {
        // ...
    }
});

Итак, должен быть элемент, соответствующий селектору .header.menu, и когда пользователь на нём кликнет, мы должны показать или скрыть меню. Проблема такого подхода в том, что мы привязываем объект JavaScript к конкретному элементу DOM. Если мы захотим подредактировать разметку и заменить .menu на .main-menu, нам придётся поправить и JavaScript. Я считаю, что контроллеры должны быть независимыми, и не следует их жёстко сцеплять с DOM.

Определяя функции, мы делегируем задачи класса JavaScript. Если эти задачи — обработчики событий DOM, есть смысл создавать их из HTML.

Мне нравится, как AngularJS обрабатывает события.

<a href="#" ng-click="go()">click me</a>

go — это функция, зарегистрированная в нашем контроллере. Если следовать такому принципу, нам не нужно задумываться о селекторах DOM. Мы просто применяем поведение непосредственно к узлам HTML. Такой подход хорош тем, что он спасает от скучной возни с DOM.

В целом, я был бы рад, если бы такая логика была внутри HTML. Интересно, что мы потратили кучу времени на то, чтобы убедить разработчиков разделять содержимое (HTML) и поведение (JavaScript), мы отучили их встраивать стили и скрипты прямо в HTML. Но теперь я вижу, что это может сберечь наше время и сделать наши компоненты более гибкими. Разумеется, я не имею в виду что-то такое:

<div onclick="javascript:App.doSomething(this);">banner text</div>

Я говорю о наглядных атрибутах, которые управляют поведением элемента. Например:

<div data-component="slideshow" data-items="5" data-select="dispatch:selected">
    ...
</div>

Это не должно выглядеть, как программирование на JavaScript в HTML, скорее это должно быть похоже на установку конфигурации.

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

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

RequireJS — один из популярных инструментов разрешения зависимостей. Идея состоит в том, что код оборачивается в замыкание, в которое передаются необходимые модули:

require(['ajax', 'router'], function(ajax, router) {
    // ...
});

В этом примере функции требуется два модуля: ajax и router. Магический метод require читает переданный массив и вызывает нашу функцию с нужными аргументами. Определение router выглядит примерно так:

// router.js
define(['jquery'], function($) {
    return {
        'apiMethod': function() {
            // ...
        }
    }
});

Заметьте, тут ещё одна зависимость — jQuery. Ещё важная деталь: мы должны вернуть публичное API нашего модуля. Иначе код, запросивший наш модуль, не смог бы получить доступ к самому функционалу.

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

myModule.factory('greeter', function($window) {
    return {
        'greet': function(text) {
            alert(text);
        }
    };
});
function MyController($scope, greeter) {
    $scope.sayHello = function() {
        greeter.greet('Hello World');
    };
}

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

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

var router:<inject:Router>;

Если зависимость будет находиться рядом с определением переменной, то мы можем быть уверены, что внедрение этой зависимости производится, только если она нужна. RequireJS и AngularJS, к примеру, работают на функциональном уровне. То есть, может случиться так, что вы используете модуль только в определённых случаях, но его инициализация и внедрение будут происходить всегда. К тому же, мы можем определять зависимости только в строго определённом месте. Мы к этому привязаны.

Шаблоны

Мы часто пользуемся шаблонами. И мы делаем это из-за необходимости разделять данные и разметку HTML. Как же современные фреймворки работают с шаблонами? Вот самые распространённые подходы:

Шаблон определён в <script>

<script type="text/x-handlebars">
    Hello, <strong> </strong>!
</script>

Такой подход часто используется, потому что шаблоны находятся в HTML. Это выглядит естественно и не лишено смысла, раз уж в HTML есть теги. Браузер не отрисовывает содержимое элементов <script>, и покорёжить внешний вид страницы это не может.

Шаблон загружается AJAX'ом

Backbone.View.extend({
    'template': 'my-view-template',
    'render': function() {
        $.get('/templates/' + this.template + '.html', function(template) {
            var html = $(template).tmpl();
        });
    }
});

Мы положили свой код во внешние файлы HTML и избежали использования дополнительных тегов <script>. Но теперь нам нужно больше запросов HTTP, а это не всегда уместно (по крайней мере, пока поддержка HTTP2 не станет шире).

Шаблон — часть разметки страницы

Фреймворк считывает шаблон из дерева DOM. Он полагается на заранее сгенерированный HTML. Не нужно производить дополнительных запросов HTTP, создавать файлы или добавлять элементы <script>.

Шаблон — часть JavaScript

var HelloMessage = React.createClass({
    render: function() {
        // Обратите внимание: следующая строка кода не является корректным JavaScript.
        return <div>Hello {this.props.name}</div>;
    }
});

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

Шаблон — не HTML

Некоторые фреймворки вообще не используют HTML напрямую. Вместо этого шаблоны хранятся в виде JSON или YAML.

Напоследок о шаблонах

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

Вообще, поведение AngularJS ближе всего к желаемому. Он считывает шаблон из содержимого предоставленной страницы, и в нём реализовано волшебное двустороннее связывание. Впрочем, оно ещё не идеально. Иногда наблюдается мерцание. Это происходит, когда браузер отрисовывает HTML, но загрузочные механизмы AngularJS ещё не запустились. К тому же, в AngularJS применяется грязная проверка того, поменялось ли что-нибудь. Такой подход порой очень затратен. Надеюсь, скоро во всех браузерах будет поддерживаться Object.observe, и связывание будет лучше.

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

Модульность

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

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

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

Открытое API

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

var Framework = function() {
    var router = new Router();
    var factory = new ControllerFactory();
    return {
        'addRoute': function(path) {
            var rData = router.resolve(path);
            var controller = factory.get(rData.controllerType);
            router.register(path, controller.handler);
            return controller;
        }
    }
};
var AboutCtrl = Framework.addRoute('/about');

У такого фреймворка есть встроенный маршрутизатор. Мы определяем путь, и контроллер инициализируется автоматически. Когда пользователь посещает определённый URL, маршрутизатор вызывает у конструктора метод handler. Это здорово, но что если нам нужно выполнять небольшую функцию JavaScript при совпадении URL? По какой-то причине, мы не хотим создавать дополнительный контроллер. С текущим API такое не получится.

Мы могли бы сделать по-другому, например, вот так:

var Framework = function() {
    var router = new Router();
    var factory = new ControllerFactory();
    return {
        'createController': function(path) {
            var rData = router.resolve(path);
            return factory.get(rData.controllerType);
        }
        'addRoute': function(path, handler) {
            router.register(path, handler);
        }
    }
}
var AboutCtrl = Framework.createController({ 'type': 'about' });
Framework.addRoute('/about', AboutCtrl.handler);

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

Тестируемость

Нет нужды убеждать вас в необходимости писать тесты для кода. Дело даже не только в том, что надо писать тесты, а в том, что надо писать код, который возможно покрыть тестами. Иногда это невероятно сложно и занимает много времени. Я убеждён, что если мы на что-то не напишем тесты, даже на что-то очень маленькое, то именно в этом месте в приложении начнут плодиться баги. Это в особенности касается JavaScript на клиентской стороне. Несколько браузеров, несколько операционных систем, новые спецификации, новые фичи и их полифилы — да куча причин начать практиковать разработку через тестирование.

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

Я с радостью бы увидел больше стандартизованных утилит и методов для тестирования. Мне хотелось бы использовать одну утилиту для тестирования всех фреймворков. Было бы ещё хорошо, если бы тестирование было как-то включено в процесс разработки. Следует обратить больше внимания на сервисы вроде Travis CI. Они работают как индикатор не только для того программиста, который вносит изменения, но также и для других контрибьюторов.

Я всё ещё работаю с PHP. Мне приходилось иметь дело с фреймворками вроде WordPress. И множество людей спрашивало меня, как я тестирую свои приложения: какой фреймворк я использую, как я запускаю тесты, есть ли у меня вообще компоненты. Правда в том, что я ничего не тестирую. И всё потому у меня нет компонентов. То же самое относится и некоторым фреймворкам на JavaScript. Некоторые их части тяжело тестировать, потому что они не дробятся на компоненты. Разработчикам следует подумать и в этом направлении. Да, они предоставляют нам умный, изящный и рабочий код. Но код должен быть ещё и тестируемым.

Документация

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

Я бы разделил хорошую документацию на три части:

Заключение

Будущее, конечно, тяжело предугадать. Но зато мы можем о нём помечтать! Важно говорить о том, что мы ожидаем и что мы хотим от фреймворков на JavaScript! Если у вас есть замечания, предложения или вы хотите поделится своими мыслями, пишите в твиттер с хэштегом #jsframeworks.

Если вы заметили ошибку, вы всегда можете отредактировать статью, создать issue или просто написать об этом Антону Немцеву в skype ravencry.

Krasimir Tsonev
Автор:
Krasimir Tsonev
GitHub:
krasimir
Twitter:
@KrasimirTsonev
Сaйт:
http://krasimirtsonev.com/
Антон Хлыновский
Переводчик:
Антон Хлыновский
GitHub:
subzey
Twitter:
@subzey

Комментарии (8 комментариев, если быть точным)

Автар пользователя
asci

У себя в домашнем проекте использую связку requirejs+marionette.js+backbone.js+rivets.js и чувствую себя хорошо. Марионет и бекбон дают прозрачные абстракции, а риветс — прозрачный биндинг. Ну рекваир — привык я к нему уже, потом в любом случае сборка rjs в один файл зависимостей первого уровня идет.

Автар пользователя
mistakster

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

Проблема такого подхода в том, что мы привязываем объект JavaScript к конкретному элементу DOM. Если мы захотим подредактировать разметку и заменить .menu на .main-menu, нам придётся поправить и JavaScript.

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

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

Какая-то глупость, на мой взгляд. Абстракции нужны и полезны. В его примере с абстрактным View автор забыл, что этот класс обеспечивает полный жизненный цикл объектов представления. Surprise, surprise… View — это не только нарисовать шаблон. В Backbone как раз реализация рисование отдана на откуп разработчику.

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

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

А если изменение значения в одном поле влияет на кучу других полей, а те, в свою очередь, влияют на другие поля, и т.д.? Лавину изменений сложно контролировать. Умные люди придумываю альтернативные архитектуры работы с данными (Flux, например).

И вообще, его мысли противоречат друг другу.

Мне нравится, когда фичи можно включать и выключать.

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

Автар пользователя
A

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

js var router:<inject:Router>;

А чем CommonJS не угодил?

js var router = require('./router');

Автар пользователя
subzey

Кстати, импорты в ES6 тоже весьма и весьма неплохи:

javascript import ('./router') as router;

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

До самого ES6 нам, конечно, ещё тащиться и тащиться, но этот сахарок уже можно использовать.

Автар пользователя
rudinandrey

в своих проектах использую riot.js (для данных и роутинга) + ractive.js (для View модели). Все понятно, прозрачно, открыто. Единственно пугает размер ractive, но во второй модели riot.js запилили View модель похожую на React.js сейчас вот знакомлюсь и возможно откажусь от Ractive.js. При том что riot весит всего 10кб.

Автар пользователя
dolphin4ik

В работе и своих проектах использую https://matreshka.io Более прозрачной и понятной логики не находил

Автар пользователя
rudinandrey

dolphin4ik, спасибо! интересно очень, посмотрю.

Автар пользователя
iamstarkov

@dolphin4ik @rudinandrey как нет смысла писать свой велосипед, тем меньше смысла использовать чужой