Давно применяю такой подход к разработке (очень успешно). Такое архитектурное решение я считаю наиболее эффективным из всех, что я применял. А роль Node-ы, как аггрегатора API, более чем опревдана и уместна. Про этот подход я отчасти рассказывал на html5camp.
При переходе 0.8->0.10 была улучшена производительность модулей, связанных с вводом-выводом. Как известно I/O самая медленная операция и поэтому буст в 1700% решает больше чем, чуть более быстрая молотилка цифр — те апгрейд V8. Так же был улучшен «алгоритм работы» nextTick() — что сильно бустануло все асинхронные операции и промисы.
Любое подключение внешней зависомости это своеобразный «контракт». И лучше, когда этот контракт явный.
use, import, require — это все способы декларации этого контракта. Кроме того, что этот statement/expression это директива интерпретатору/компилятору это еще и сигнал тому, кто читает этот код: этот модуль имеет внешние зависимости, можно использовать такие-то функции из такого-то списка, зависимости могут «намекнуть» на поведение модуля, исключают конфликты имен. Плюс мы имеем всякие плюшки вроде возможности статически анализировать модули для автокомплитов и прочих целей.
export, module.exports — это так же части этого контракта, но уже на поставку ресурсов.
import * from 'lib/dom-promise';
import * from 'lib/super-duper-abstract-module';
export class PromisedModule extends Module {
fetch () {
return new Promise(function () {
// do stuff
});
}
}
Если удалить импорты, то появляется куча вопросов: Module и Promise — ты кто такой? Какой у него интерфейс? Где посмотреть код? Класс абстрактный? На какую спеку опирается интерфейс Promise?
не помню случая, когда задавался вопросом, кого он использует
Если вы используете постоянно одинаковые модули, то вы наверняка знаете их интерфейс. Но как быть тому кто будет читать ваш код? Вы должны позаботиться о том, чтобы ваш код был максимально доступным, иначе никто не будет его использовать. «контракт» — это часть доступности.
как нужно проектировать библиотеки используя современный модульный подход в JS
Это очень станно, но ваше письмо от 31 мая получено, отмечано как прочитанное, но я почему-то не ответил на него. Хотя регулярно отвечаю на все письма. Отметил его как не прочитанное и обязательно отвечу.
Я работаю с несколькими проектами одновременно каждый over 9000 SLoC.
Почему я бы не стал использовать yamd:
1) [critical] Отвутствие явного импорта
— Если модуль большой, то невозможно найти те модули, которые использует данный модуль
— Любой валидатор по умолчанию будет ругаться на использование незадекларированной в этом файле глобальной переменной в нашем случае math
— При удалении/перемещении модуля сложно найти концы
— Я бы устал искать откуда какая часть пришла
console.info(math.add(7,2));
console.info(math.multiply(7,2)); // откуда пришел math?
2) Завязка на файловую систему
— Проблемы с глубокой вложенностью модулей. Что если у нас /a/b/c/d.js?
— Файловая систему диктует имена экспорта у модулей
3) Модуль можно убить через expose
— expose ожидает синхронную декларацию, но разработчик может задекларировать такой модуль асинхронно
setTimeout(expose, 0, 42);
4) [critical] Все модули выполняются при старте, а не по требованию
5) Проблема с циклической зависимостью — это чаще всего ошибка проектирования
Функции нужно объединить в один модуль. Если никак, то можно обойтись и без tableLookup: использовать другой формат экспорта — в случае с CommonJS:
exports.steps = steps;
6) [critical] Модули не изолированы
// Этот модуль сломет все
files = null;
7) Модуль не может вернуть не объект
8) Лишний код если собираются 2+ библиотеки модулей
Посмотрите еще раз на существующие модульные системы. Я уверен, что они могут решить все ваши проблемы.
как вы разрабатываете сложные JS библиотеки
В двух проектах используется огромная общая библиотека модулей. Все модули — CоmmonJS. Проекты собирабтся на LMD. Сторонние модули транслируются в CommonJS при сборке.
В обработчике beforeunload делаем синхронный XHR на сервер, который держит соединение, но никогда не отечает. Вкладка никогда не закроется сколько бы в нее ни тыкали. В Chrome эта проблема решена — при втором клике на [x] вкладка закроется.
На самом делеле с HTML 5 Notifications API не так все просто. Сейчас существует целых 3 реализации этого API со своими багами и интерфейсами:
— Есть старая версия (webkitNotifications) в Safari 5 в которой нет обратного вызова у функции requestPermission (это ахтунг), а вместо свойства permission есть метод checkPermission(), который возвращает число-статус, а не строку.
— Есть версия в Safari 6, в которой вместо свойства permission есть функция permissionLevel(), которая уже возвращает строку.
— Есть какая-то сборка Safari или Chromium в которой Notification есть, а .prototype у него нет и, соответственно, при попытке вызова new Notification вы огребаете :)
— Есть сборка Chromium в которой при попытке вызова new Notification вкладка крашится
И наконце есть свежая сборка в которой метод cancel() переименовали в close(), а iconUrl в icon…
И весь этот зоопарк гулят по просторам интернета, хотя и быстро обновляется. Ну и чтобы не огребать советую вам использовать библиотеку $.notification, которая все это чинит. Демка
LMD как и browserify основываются на CJS модулях и являются инструментами по сборке оных. browserify заточен под CJS и Node.js-way — отличная штука если нужно запустить node.js приложение в браузере.
Напомню, что в JS@DOM cвоя атмосфера: ресурсы далеко, ресурсы медленные, зачастую мы не можем грузить все ресурсы, HTTP не стабилен и каждый новый запрос может привести к ошибке. Есть еще сторонние не-CJS-модули (старые плагины к jQuery), что с ними делать? Что если приложение оптимизировано под 3 платформы(3 набора скриптов)?
LMD в отличии от browserify ближе к браузеру(браузерам и средам) и понимает его проблемы: трансформация не-CJS-модулей при сборке, текстовые файлы без пагинов, абстрактная модульная фс, группировка модулей по бандлам… и еще куча апгрейдов вроде кэша в LS и ленивой инициализации.
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(...);
}
});
http монжо так или иначе абстрагировать реально естественными способами вроде заводов или наследования. (Естественными=Привычными способами) Манкипатчинг exports модуля это какаое-то не привычное для разработчика поведение. Эту особенность тянут за собой уровни переопределения BEM?
Покажи, пожалуйста, пример, а то я не пойму в чем удобство.
Технически все ресурсы мы забираем по HTTP и все они текстовые — тут не поспоришь. Логически же мы их все-таки разделяем на скрипты, css, json и пр. по тем или иным признакам. Так же, я думаю, следует и различать модули и данные. И использовать для них разные «загрузчики».
use, import, require — это все способы декларации этого контракта. Кроме того, что этот statement/expression это директива интерпретатору/компилятору это еще и сигнал тому, кто читает этот код: этот модуль имеет внешние зависимости, можно использовать такие-то функции из такого-то списка, зависимости могут «намекнуть» на поведение модуля, исключают конфликты имен. Плюс мы имеем всякие плюшки вроде возможности статически анализировать модули для автокомплитов и прочих целей.
export, module.exports — это так же части этого контракта, но уже на поставку ресурсов.
Если удалить импорты, то появляется куча вопросов: Module и Promise — ты кто такой? Какой у него интерфейс? Где посмотреть код? Класс абстрактный? На какую спеку опирается интерфейс Promise?
Если вы используете постоянно одинаковые модули, то вы наверняка знаете их интерфейс. Но как быть тому кто будет читать ваш код? Вы должны позаботиться о том, чтобы ваш код был максимально доступным, иначе никто не будет его использовать. «контракт» — это часть доступности.
Эта статья должна ответить на ваши вопросы blog.izs.me/post/48281998870/unix-philosophy-and-node-js
Почему я бы не стал использовать yamd:
1) [critical] Отвутствие явного импорта
— Если модуль большой, то невозможно найти те модули, которые использует данный модуль
— Любой валидатор по умолчанию будет ругаться на использование незадекларированной в этом файле глобальной переменной в нашем случае
math
— При удалении/перемещении модуля сложно найти концы
— Я бы устал искать откуда какая часть пришла
2) Завязка на файловую систему
— Проблемы с глубокой вложенностью модулей. Что если у нас /a/b/c/d.js?
— Файловая систему диктует имена экспорта у модулей
3) Модуль можно убить через expose
— expose ожидает синхронную декларацию, но разработчик может задекларировать такой модуль асинхронно
4) [critical] Все модули выполняются при старте, а не по требованию
5) Проблема с циклической зависимостью — это чаще всего ошибка проектирования
Функции нужно объединить в один модуль. Если никак, то можно обойтись и без tableLookup: использовать другой формат экспорта — в случае с CommonJS:
6) [critical] Модули не изолированы
7) Модуль не может вернуть не объект
8) Лишний код если собираются 2+ библиотеки модулей
Посмотрите еще раз на существующие модульные системы. Я уверен, что они могут решить все ваши проблемы.
В двух проектах используется огромная общая библиотека модулей. Все модули — CоmmonJS. Проекты собирабтся на LMD. Сторонние модули транслируются в CommonJS при сборке.
PS Путь JavaScript модуля
В обработчике beforeunload делаем синхронный XHR на сервер, который держит соединение, но никогда не отечает. Вкладка никогда не закроется сколько бы в нее ни тыкали. В Chrome эта проблема решена — при втором клике на [x] вкладка закроется.
Код примера
— Есть старая версия (webkitNotifications) в Safari 5 в которой нет обратного вызова у функции
requestPermission
(это ахтунг), а вместо свойстваpermission
есть методcheckPermission()
, который возвращает число-статус, а не строку.— Есть версия в Safari 6, в которой вместо свойства
permission
есть функцияpermissionLevel()
, которая уже возвращает строку.— Есть какая-то сборка Safari или Chromium в которой
Notification
есть, а.prototype
у него нет и, соответственно, при попытке вызоваnew Notification
вы огребаете :)— Есть сборка Chromium в которой при попытке вызова
new Notification
вкладка крашитсяИ наконце есть свежая сборка в которой метод
cancel()
переименовали вclose()
, аiconUrl
вicon
…И весь этот зоопарк гулят по просторам интернета, хотя и быстро обновляется. Ну и чтобы не огребать советую вам использовать библиотеку $.notification, которая все это чинит. Демка
Напомню, что в JS@DOM cвоя атмосфера: ресурсы далеко, ресурсы медленные, зачастую мы не можем грузить все ресурсы, HTTP не стабилен и каждый новый запрос может привести к ошибке. Есть еще сторонние не-CJS-модули (старые плагины к jQuery), что с ними делать? Что если приложение оптимизировано под 3 платформы(3 набора скриптов)?
LMD в отличии от browserify ближе к браузеру(браузерам и средам) и понимает его проблемы: трансформация не-CJS-модулей при сборке, текстовые файлы без пагинов, абстрактная модульная фс, группировка модулей по бандлам… и еще куча апгрейдов вроде кэша в LS и ленивой инициализации.
http.vanilla
. Вижу неявное перекрытие абстрактного модуля и внедрение ненужной абстракции над модульной системой Node.js и не вижу преимуществ над:На клиенте все пути до модулей разруливаются конфигами, их наследованием и зависимостями модулей, в node все автоматом.
и подобные мысли чаще ведут к не поддерживаемому коду и мыслям: «А давай ка я просто проманкипатчу, зачем писать абстракцию?!»
Пример реализации интерфейса http под ноду и браузер, учитывая поддерживаемость и переносимость кода.
http
монжо так или иначе абстрагировать реально естественными способами вроде заводов или наследования. (Естественными=Привычными способами) Манкипатчинг exports модуля это какаое-то не привычное для разработчика поведение. Эту особенность тянут за собой уровни переопределения BEM?Покажи, пожалуйста, пример, а то я не пойму в чем удобство.