Pull to refresh

Comments 71

Теперь знаю, куда отправлять новичков при вопросе об организации JS кода)
Великолепная статья! Восхищаюсь вашему упорству!
Замечательная статья. Спасибо, такая подробная работа будет очень полезной.
Дошел до раздела «Выводы», думал, что уже конец, но не тут то было…
Спасибо за материал!
Пожалуйста! Я хотел побить статью на 2, но решил опубликовать все-в-одном, чтобы читатель не потерял «контекст» рассказа, изучая «кишки» эмуляторов модульности.
В этой статье это было бы оправдано. Хорошо, что я к примеру запомнил, где остановился…
Рад, что вам понравилось. Месяц писания статьи не прошел даром :)
Доааа. Читал-читал, удивлялся, кто так круто пишет. А тут — ты. Как всегда — респектище.
UFO landed and left these words here
Спасибо за статью. И спасибо за LMD, несмотря на некоторые недостатки, отличная вещь!
Кстати, а как на счёт того, чтобы писать на ES6, а потом сборщиком всё собирать?
WebStorm 6.0 с включенным ECMAScript Harmony ругается на синтаксис модуля. yield и прочие — ок.
Google traceur compiler очень плохой код делает на выходе. Не только для production не подходит, так из-за багов может легко «сломать» логику кода. Только для поиграться подходит. А ES6 Module Transpiler, насколько я понял, синтаксис ES6 модулей поддерживает не полностью.
Кстати TypeScript модули это практически ES6 Modules. Можно использовать «компилятор» TS для трансляции. Модули, к тому же транслируются в ES3. А вот у Traceur Compiler в ES5.
Для себя лично пока решил проблему с ES6 так: для let и const использую defs. Добавил в него поддержку rest, sparse и default params. Пока для меня достаточно es6 плюшек. Модули пишу на CommonJS.
А использовать TS — это хорошая идея, нужно будет попробовать объединить эти библиотеки.
Вот это статейка! Реальный крутяк.

Эх, я не один так невзлюбил AMD в Node.js, а в итоге и на клиенте забросил из за сложности и каши в конечном результате.
Можете рассказать, почему Вы и azproduction называете модули Node.js термином «AMD»? Насколько я понимаю, налицо шаг вперёд по отношению к AMD — это упрощение кода модулей в Node.js, и состоит оно в том, что Node.js самостоятельно дописывает аналог обёртки вокруг модуля:

define(function(require, module, exports){
   // . . . здесь код модуля . . .
});

так что можно не заморачиваться её копированием из модуля в модуль.

(Кстати, и реальная обёртка в Node несколько поярче вышеприведённого кода, кажется. Можно сделать модуль наследником некоторого класса, например.)
Не помню, что я называл Node.js модули AMD они, безусловно, CommonJS. Ну и, конечно, Node.js сам оборачивает CommonJS модуль контекстом c require, module, exports. И я, думаю, как и Вы, на стороне CommonJS модулей. Возможно, Вас смутил тот факт, что AMD модули можно использовать в Node.js с адаптором node-requirejs.
А мы и не называем их AMD. Возможно, я не совсем верно выразился, хотел сказать «AMD вместе с Node.js».

Напротив, Node.js использует CommonJS и родное подключение модулей в Node куда проще и юзабельнее, а AMD из require.js там выглядит чужеродно и громоздко. Не исключаю, что я не просек какую-то фишку, но с require.js я пришел к такой же каше, как и azproduction. А в модулях Node — все устраивает.
Упс, долго же не обновлял комменты :) Ну, в общем, суть понятна — мы понимаем, что Node.js не использует AMD.
Миша, ты как всегда архипедантичен и архискрупулёзен при походе к вопросу. Снимаю шляпу и отложу до выходных детальное изучение многобукаф. Надо будет осмыслить сказанное и проанализировать свой путь модульности.
Использую Browserify + Grunt на Node.js пока все устраивает. Как говорил на одной презентации substack, в browserify можно использовать библы из node.js, чем сильно облегчить себе жизнь в некоторых случаях. Возможно при большом объеме кода возникнут какие то траблы, но пока все гладко.
Да, конечно. По сути это менеджер пакетов и сборщик как npm+browserify или bower+bem-tools. Он так же использует CJS и может быть хорошим инструментом для работы с Web Components.
Желание получить «Асинхронный require модулей» я понимаю.

«Асинхронный provide модулей» — не совсем понимаю (те загрузился какой-то модули и он как-то внутри себя понимает, что он должет загрузить один из ресурсов a, b или с и только потом задекларировать себя) это же совсем не явный подход.

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

Асинхронный provide модулей очень нужен, когда модуль изолирован от данных (например, все данные получаются из REST API). Поэтому, пока модуль не получит данные и не инициализирует свое состояние — он бесполезен в использовании (если от него зависят другие модули, могут возникнуть коллизии из-за асинхронности загрузки данных в модуль).
В LMD как я понимаю сейчвс реализовать такой модуль возможности нет?
В LMD все модули (CommonJS) синхронны с возможностью пополнять «абстрактную ФС» модулей.

Я считаю, что нуджно разделять модули и данные. Если наличие модуля мы можем гарантировать, то вот гарантировать наличие данных мы не можем. Достаточно представить ситуацию, когда модуль полез за данными и произошел сетевой сбой и предоставить свой интерфейс он не может.
У модуля есть несколько путей решения этой проблемы:
1) повторять запрос — тем самым он заблокирует модуль-родитель и многократный повтор запроса не гарантирует поступление данных
2) выбросить исключение — тогда неизвестно кто его будет отлавливать или если кто-то как-то отловит(например Promise-way), то как прозрачно повторить попытку инициализации модуля?!
3) сообщить пользователю об ошибке — тогда модуль получает знание о приложении и подчинит себе логику — самый плохой случай
Ок, тогда задам другой вопрос. Как лучше спроектировать архитектуру проекта в таком случае:
Храним в API данные пользователя и некоторые настройки отображения сайта (например, в какой валюте отображать цены и что-нибудь подобное).
Есть много модулей (профиль пользователя, настройки, корзина, карточка товара и т.п.) которые непосредственно зависят от данных пользователя (например авторизован пользователь или нет, от его настроек и т.п.).
Получается, что каждый из этих модулей будет так или иначе запрашивать данные из API (следовательно для каждого из них нужно продумывать вариант исключений — что делать, если данные не загрузились), а так же добавлять логику работы с этими данными (например значение user.settings.currency="rub" означает что все цены нужно умножить на 30, ну и т.д.)

У меня в голове сложилась мысль, что лучше всего для таких данных API сделать модуль-интерфейс, который загрузит данные и предоставит к ним удобный интерфейс взаимодействия (причем как на чтение, так и на запись). Ну и самый очевидный вариант реализации — асинхронный provide. (или я что-то упускаю или смотрю не в ту сторону?)

Конечно, можно попробовать наполнить модуль минимальным набором данных «по умолчанию» (на случай проблем с доступом к API) и реализовать механизм оповещений об обновлении данных (его так или иначе реализовывать придется, если данные можно изменять), но все таки, на момент первого обращения к модулю хотелось бы получить его стабильное состояние (с загруженными или дефолтными [если загрузка невозможна], но окончательными данными) с которыми можно работать. (чтобы например избежать лишнего пересчета: сначала все делаем по данным дефолта, а как пришли данные из API — по полученным).

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

Лучше если эти данные будут доступны при старте приложения (будут отрендерены в html). За этими данными нам в любом случае придется сходить и лучше если мы обратимся к ним без лишнего запроса на сервер (приложение запустится быстрее и не будет проблем с сетевыми ошибками и лагами).

Если это невозможно, то стоит заблокировать работу приложения до того момента пока данные не придут:

var request = require('request');
request.get(config).then(run, epicFail);

// и с обработкой ошибки и перезапросом
var Attempt = require('attempt');

new Attempt(function promiseGenerator() {
    return request.get(config);
}, function timeoutGenerator(error, attemptNo) {
    // Если 404 или попыток больше 3 - не повторяем запрос
    if (error.status === 404 || attemptNo > 3) {
        return Infinity;
    }
    var repeatRequestIn = attemptNo * 2000;
    
    return repeatRequestIn;
})
.then(run, epicFail, notifyUser);

timeoutGenerator в свою очередь может быть асинхронным.
Так, а если модуль ничего не делает, кроме как предоставляет удобный интерфейс доступа к данным. То есть например на основе параметра settings.currency предоставляет функцию getPrice (которая внутри себя содержит всю логику просчета цены исходя из настроек). Ну и помимо этого, дополняет данные функциями уведомления об изменении этих данных, а так же непосредственно предоставляет интерфейс изменения этих данных. То есть наш модуль это модель в MV* (например модуль возвращает объект на основе Backbone.Model, с данными загруженными из API). Соответственно отсутствие данных = отсутствие объекта-модели = невозможность работы приложения, которая обрабатывается в штатном режиме (то есть для всей системы наш модуль — это и есть данные, и только сам модуль определяет откуда реальные данные берутся, из файла, из АПИ, может быть несколько запросов к АПИ и т.п.)
Понимаю твою идею. В случае Model приложение не должно стартовать пока не произошел sync. Но я бы старался сделать конфиг пользователя доступным синхронно и это более чем реально сделать.

Ну и на крайний случай вот так:
// config.js
var request = require('request'),
    _ = require('underscore');

var promise = request.get('/cfg.json').then(function (data) {
    _.extends(exports, data);
});

exports.then = promise.then.bind(promise);

// index.js
require('config').then(function () {
    require('main');
}, require('epicFailHandler'));

// main.js
var config = require('config');

exports.getPrice = function () {
    console.log(config.currency);
};
У нас приложение целиком построено на данных из REST API (собственно апи предоставляет данные для сайта и для iOS приложения, и это апи с сайтом никак не связано). Поэтому сделать данные синхронными можно только добавив между пользователем и API промежуточное звено, которое получит данные из API и отдаст их клиенту вместе со страницей. Я думаю, что это звено лишнее.

Пример реализации мне нравится, стоит попробовать, спасибо!
Пока ходил, понял, что в LMD это можно сделать лучше без лишней обвязки с синхрнно-асинхронном конфигом:
// .lmd.js
{
    ...,
    "modules": {
        "config": "@http://site.com/user/config",
        ...
    },
    "async": true,
    "shortcuts": true,
    "promise": true
}

// index.js
// грузим асинхронный модуль, который резолвится по ссылке на http://site.com/user/config
// и после загрузки будет доступен синхронно под именем "config" и "http://site.com/user/config"
require.async('config').then(function (config) {
    require('main');
}, require('epicFailHandler'));


// main.js
var config = require('config');

exports.getPrice = function () {
    console.log(config.currency);
};
всё описанное также справедливо для синхронных provide — достаточно представить ситуацию, когда модуль начал делать синхронный provide (return) и произошло исключение и предоставить свой интерфейс он не может…
Если модуль выбросил исключение — это штатная ситуация. Если моуль пошел за данными и у него ничего не получилось и он выбросил исключение — это не штатная ситуация. Объясню на примере из жизни:

Есть завод(наше приложение) есть поставщик станков(модуль) и поставщик ресурсов(REST API). Если при поставке станков поезд сошел с рельс — это нормально — этот случай прописывался в договоре(документации к модулю) и завод застраховал этот риск (try catch) и сообщил клиентам, что предоставить какие-то ресурсы он не может.

Другое дело если станки поставляются вместе с их ресурсами: поставщик станков тем сымым подчиняет заказчика и задерживает поставку и станков и ресурсов(на время запроса). Если ресурсы не доходят, то поставщик станков говорит, что я не могу поставить станки потому, что ресурсы, которые обрабатывают эти станки не были поставлены. Это звучит по меньшей мере глупо. Завод не может сообщить своим клиентам о прогрессе(не знает о состоянии ресурсов) и более того не может повлиять на поставщика ресурсов (перезапросить).
предположи, что у тебя для синхронного провайда используется сторонний модуль в котором «вдруг» вылетает эксепшен — это ни чем по смыслу не отличается от любых проблем с асинхронным вызовом, все гарантии примерно одинаковые (а именно, никаких) и в обоих случаях по одинаковой стратегии нужно поступать
Стоит определить, что есть модуль, а что есть данные. Ошибки данных мы можем контролировать, а исключения модулей мы не можем контролировать (если модуль делает throw при инциализации то все — конец) даже если обработать исключение, то толку не будет и приложение от этого не заработает.
А вот если модуль ходит за данными и делает асинхронный provide(), то тогда ошибки данных становятся ошибками модуля и мы теряем над ними контроль.
технически разницы между данными и модулями нет — поэтому, на мой взгляд, система должна выдерживать все эти кейсы

ну и см. каменды Димы ниже
Технически все ресурсы мы забираем по HTTP и все они текстовые — тут не поспоришь. Логически же мы их все-таки разделяем на скрипты, css, json и пр. по тем или иным признакам. Так же, я думаю, следует и различать модули и данные. И использовать для них разные «загрузчики».
Пример как мы используем асинхронный provide модулей — мы используем api 2.0 Яндекс.карт для наших приложений. Он не умеет в принципе загружаться синхронно. Но при этом у нас есть куча классов-наследников от апишных классов. Как ты предлагаешь делать такие модули-наследники?
У нас это написано просто, есть модуль ymaps:

modules.define('ymaps', ['loader'], function(provide) {

loader.load('тут_урл_для_ymaps_api', function() {
    ymaps.ready(function() {
        provide(ymaps);
    });
})

});


Потом, где нам надо наследоваться от классов, предоставляемых api, мы просто это делаем, не думая, как оно там загрузилось:

modules.define('my-module', ['ymaps', 'inherit'], function(provide, ymaps, inherit) {

var MyClass = inherit(ymaps.GeoObject, { .... });

provide(MyClass);

});


Как ты предлагаешь разруливать такую ситуацию с синхронным провайдом модуля? Представленная выше ситуация далеко не единственная. И тут речь не идет ни о каких данных для модуля, речь идет о самом модуле.
Я перешел с асинронных модулей на синхронные, поняв тот факт, что в продакшене все модули так или иначе грузятся синхронно (упаковыны в 1 файл), а вся эта обвязка с define — это лишняя писанина.

Если говорить о собственном коде(не сторонних апи), то асинхронно догружаютя только крупные части приложения (бандлы): несколько модулей, упаковынных в 1 файл и такая дозагрузка в моем коде встречается крайне редко и только тогда я превращаю мой синхонный модуль в асинхронный, притом, что пришедший бандл может сам себя запустить и избавить меня от писанины:
// Активный по своей конфигурации
require.bundle('userSettings'); // сам себя запустит и будет использовать как модули из основой части так и свои

// Пассивный по своей конфигурации
require.bundle('bunchOfViews').then(function () {
    require('viewFromBunchOfViews').doStuff();
});

Я так же использую я.карты, но в максимально синхронном виде. Если говорить об собственном коде и стороннем апи карт, то в твоем случае все модули синхронны кроме ymaps и без ymaps ни один модуль не запустится. И тк загрузка апи асинхронна и без этого апи запуск приложения невозможен я выношу всю асинхронность в один модуль, позволяя остальным модулям быть синхронными:
// ready.js
var lang = require('lang'),
    config = require('config'),
    ymapsApi = config.ymapsApi.replace('%lang', lang);

var maps = $.getScript(ymapsApi).pipe(function () {
    var dfd = $.Deferred();
    window.ymaps.ready(dfd.resolve);
    return dfd.promise();
});

var ready = $.Deferred();
$(ready.resolve);

module.exports = $.when(maps, ready.promise());

// index.js
require('ready').pipe(function () {
    require('mapView')('#map');
});

// views/map.js
var ymaps = require('ymaps'); // из глобалов
// ...
Ты, все-таки, проигнорировал мой основной вопрос — как мне предоставить мои модули-классы, являющиеся наследниками классов стороннего кода, который не умеет быть синхронным, своему остальному коду?
Асинхронно загрузить сторонний код перед стартом приложения или его части, а потом унаследовать синхронно. В этом случае сторонний код будет тем самым редким случаем о котором я писал выше.
Ты предлагаешь разделить код по неким, достаточно искусственным, принципам. Опять вводя странные зависимости, обеспечиваемые неким кодом снаружи. То есть, опять вводя знания в свой код, что он неработоспособен, пока кто-то где-то как-то снаружи не обеспечит это. А мне кажется (вернее я даже уверен в этом), это одна из задач именно модульной системы. Тем более, верхний пример, вроде как прекрасно это доказывает.
В мире javascript вообще уже довольно странно делить код на синхронный/асинхронный. Например, те же промисы, позволяют тебе не думать об этом. Почему же для модульной системы эти различия должны играть такую роль?
То есть, опять вводя знания в свой код, что он неработоспособен, пока кто-то где-то как-то снаружи не обеспечит это.
Пример с внешним api это исключение, которое чинится 1 «асинхронным» модулем. Притом, что эту зависимость мы можем представить как синхронно (тег скрипт) так и асинхронно и вызов require('ymaps') в обоих случаях отработает одинакого. Зависимость модуля обеспечивается либо кодом, либо конфигом, либо явной зависимостью(тег скрипт).
Почему же для модульной системы эти различия должны играть такую роль?
В «асинхронности» для модулей нет смысла потому как 95% модулей могут быть получены без видимой блокировки основного потока (они загружаются при старте приложения). А для тех 5% модулей, которые мы должны догружать мы можем сделать обвязку, которая примит на себя всю асинхронноть(за счет теж же промисов) и обеспечит точечый контроль ошибок дозагрузки модуля. Я устал писать обертки над модулями(AMD-стиль), которые никогда не будут использоваться по назначению, поэтому я ввожу это различие. И хочу использовать весь потенциал динамических require без регулярок и прочей магии.
Притом, что эту зависимость мы можем представить как синхронно (тег скрипт) так и асинхронно и вызов require('ymaps') в обоих случаях отработает одинакого.

ymaps нельзя загрузить синхронно через тэг script. Через тэг ты получишь только заглушку, у которой нужно дождаться еще ready.

В «асинхронности» для модулей нет смысла потому как 95% модулей могут быть получены без видимой блокировки основного потока (они загружаются при старте приложения)

О какой блокировке основного потока идет речь при «асинхронности» модулей?
Через тэг ты получишь только заглушку, у которой нужно дождаться еще ready.
Действуем по аналогии с DOMContentLoaded — см мой пример в ready.js

О какой блокировке основного потока идет речь при «асинхронности» модулей?
Это если бы я синхронно грузил модули с сервера
Действуем по аналогии с DOMContentLoaded — см мой пример в ready.js
Все-таки, это больше похоже на «Делаем костыли по аналогии с DOMContentLoaded» ;)

В наших проектах мы примерно так и делали до поры до времени, но, в какой-то момент устали костылять подобные вещи (ymaps это далеко не единственный пример). В какой-то момент переписали все на наши асинхронные модули и избавились от этих странных прослоек, и больше даже никогда не задумываемся о них. Для нас игра стоила свеч.
Хорошее обсуждение получилось. Мне в свою очередь важен формат CommonJS. Этот «хак» c DOMContentLoaded как правило нужен перед стартом(ну в самом деле не вешаеть же его в зависимости каждому модулю). Ну и 1 «хакнутый» модуль это не «хак» а особеннсоть ;-) И, кстати, многие модульные системы «чинят» DOMContentLoaded.
Зачем каждому-то? У нас он только в бутстрапе приложения:
modules.require(['jquery', 'app'], function($, app) {

$(function() {
    app.start();
});

});
Ага, аналогично и у меня с картами и DOMContentLoaded
Теперь по второму пункту, возможности додекларации/передекларации модуля, примеры:
  • Код, который работает и в браузере, и на сервере (node.js). Есть некая общая часть, предоставляющая единый интерфейс в модуле для всего остального кода. И есть некоторые специфичные части, зависимые от платформы, на которой код исполняется. Например, модуль, который ходит по http: есть файл http.vanilla.js, в котором декларируется общая часть модуля http, есть файл http.browser.js, в котором додекларируется модуль http реализацией через XmlHttpRequest, и, есть файл http.node.js, где додекларируется http реализаций походом через нодовский http.request.
    Для всего остального кода это просто модуль http, он не знает его подробностей. Эту проблему можно было бы решить и по-другому, например, через третьи модули, но зачем? Так гораздо естественней.
  • Использование в BEM-предметной области, где есть уровни переопределения и финальный рантайм собирается из их суммы.
  • Мы используем эту возможность в тестах: тесты у нас собираются динамически, то есть мы просто говорим — хочу запустить все тесты из такой-то папки. Каждый тест представляет из себя додекларацию модуля test. В итоге, в раннере тестов мне достаточно сделать modules.require(['test'], function(test) { test.run() }).

Ну, то есть, это тупо удобно.
Так гораздо естественней
http монжо так или иначе абстрагировать реально естественными способами вроде заводов или наследования. (Естественными=Привычными способами) Манкипатчинг exports модуля это какаое-то не привычное для разработчика поведение. Эту особенность тянут за собой уровни переопределения BEM?

Покажи, пожалуйста, пример, а то я не пойму в чем удобство.
Я написал, что эту проблему можно решить с помощью третьих модулей, но часто это бывает просто избыточно.
Пример чего именно ты хочешь чтобы я привел? Вроде я привел их в верхнем комментарии.
Есть такая штука как поддерживаемость кода и
часто это бывает просто избыточно
и подобные мысли чаще ведут к не поддерживаемому коду и мыслям: «А давай ка я просто проманкипатчу, зачем писать абстракцию?!»
Пример чего именно ты хочешь чтобы я привел?
Пример реализации интерфейса http под ноду и браузер, учитывая поддерживаемость и переносимость кода.
А есть еще такая штука — «опьянение шаблонами» и «оверинжиниринг».

Простой пример, чтобы суть донести:

http.vanilla.js:
modules.define('http', ['inherit'], function(provide, inherit) {

provide(inherit({
    ...
    request : function(params) {
        if(this.hasCache(params)) {
            return this.getCache(params);
        }
        
        var result = this.doRequest(params);
        this.setCache(params, result);
        return result;
    },

    doRequest : function(params) {}
    ...
});

});


http.browser.js:
modules.define('http', ['inherit'], function(provide, inherit, base) {

provide(inherit(base, {
    ...
    doRequest : function(params) {
        var xhr = this._createXhr();
        return xhr.send(...);
    }
    ...
});

});


http.node.js:
modules.define('http', ['inherit'], function(provide, inherit, base) {

var http = require('http');

provide(inherit(base, {
    ...
    doRequest : function(params) {        
        return http.request(...);
    }
    ...
});

});


Остальной код (что в браузере, что в ноде), просто использует этот модуль http.
base это, как я понял, непрописанный http.vanilla. Вижу неявное перекрытие абстрактного модуля и внедрение ненужной абстракции над модульной системой Node.js и не вижу преимуществ над:
// node_modules/request.js - node
var http = require('http'),
    abstractHttp = require('../lib/abstract-http');

// extends - как вариант наследования
module.exports = abstractHttp.extends({
    doRequest : function(params) {        
        return http.request(...);
    }
});

// js/request.js - browser
var $ = require('$'),
    abstractHttp = require('abstract-http');

module.exports = abstractHttp.extends({
    doRequest : function(params) {        
        return $.ajax(...);
    }
});

// index.js - common
var request = require('request');

request.get('http://ya.ru/').then(function () {

});

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

Я думаю, тут стоит остановиться ) У любых задач есть несколько способов решения, каждый выбирает какой ему ближе.
Согласен, спасибо, что разъяснил твой модульный подход!
Тебе спасибо за статью и комментарии, теперь есть куда ссылку давать, чтобы каждый раз не объяснять все заново :)
$ cat js/*.js build.js

Вероятно, вы имели ввиду:
$ cat js/*.js > build.js
> Например, в этом коде LMD поможет не забывть 3 плагина
забыть, опечатка
> И если так получиться, что мне придется вернуться к AMD
всё таки «получится»
Ещё что-то было, но потерял, когда перечитывал.

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

Модули ES6 мне не по нраву, как и многое другое, что предлагается в новом стандарте. Мне очень по нраву CJS-модули.
LMD как и browserify основываются на CJS модулях и являются инструментами по сборке оных. browserify заточен под CJS и Node.js-way — отличная штука если нужно запустить node.js приложение в браузере.

Напомню, что в JS@DOM cвоя атмосфера: ресурсы далеко, ресурсы медленные, зачастую мы не можем грузить все ресурсы, HTTP не стабилен и каждый новый запрос может привести к ошибке. Есть еще сторонние не-CJS-модули (старые плагины к jQuery), что с ними делать? Что если приложение оптимизировано под 3 платформы(3 набора скриптов)?

LMD в отличии от browserify ближе к браузеру(браузерам и средам) и понимает его проблемы: трансформация не-CJS-модулей при сборке, текстовые файлы без пагинов, абстрактная модульная фс, группировка модулей по бандлам… и еще куча апгрейдов вроде кэша в LS и ленивой инициализации.
Было бы любопытно узнать, к чему пришел автор столь монументального труда два года спустя. ES6/ES2015 с последующей babel-изацией? Webpack? Browserify?
Автор сравнительно недавно был ведущим коллективного твиттер-аккаунта @jsunderhood. Он касался этой темы.
Only those users with full accounts are able to leave comments. Log in, please.