Pull to refresh

Comments 34

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

Основная беда нативных модулей — невозможность объединить несколько модулей в один файл. И ладно бы была поддержка jar-ов, так нет же, нативные модули невозможно использовать на клиенте без компиляции в какой-нибудь UMD.


И нет, http/2 не решает проблему "пока модуль не загружен, браузер не знает какие зависимости надо загрузить ещё".

UFO just landed and posted this here

Тут проблема в синтаксисе, загрузчик никаким образом её не решит.

UFO just landed and posted this here
нативные модули невозможно использовать на клиенте без компиляции в какой-нибудь

… строку в кастомном формате..

Можно уточнить, а почему нельзя?

Сейчас у меня написан таск для Gulp который собирает модуль в один файл. При этому модуль содержит кучу AMD файлов, которые находятся внутри модуля.

Возможно я не правильно вас понял. Тогда можете поподробнее пояснить.

Заранее спасибо!

Речь про нативные модули (последние в статье). С амд -то всё ок.

Я все еще не понимаю почему нельзя.
Есть webpack.
В предыдущей версии дополненный babel-ем прекрасно собирает нативные модули в один файл.
В webpack@2 вроде и без babel умеет.

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

Ну вы загляните внутрь этого файла. Нативные модули преобразуются в ненативные (в тот же амд). То есть для самого распространённого юзкейса нативные модули не годятся и приходится заниматься сложной транспиляцией. И это не временная мера — с этой зависимостью от пропиетарных транспайлеров мы останемся на долго.

Я немного не понял, что Вы имеете в виду под проприетарными транспайлерами?

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

Зачем нужно использовать результат работы бандлера в каких-либо модулях?
Почему нельзя напрямую использовать исходные коды, и бандлы использовать только как скрипты из нулевых?

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

Webpack не умеет собирать ES6 модули в один файл.


То, что вы называете "прекрасно собирает нативные модули в один файл", это преобразование ES6 модулей в CommonJS модули и собирание в один файл уже их.

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


Теперь, что касается YModules. Концепция логичная, но я бы заменил provide промисом. Гораздо удобнее и лаконичнее, по-моему:


modules.define('theTrue', () => {
    return {
         theTrue: true,
    };
});

modules.define('theTrue.async', () => {
    return Promise.resolve({
         theTrue: true,
    });
});
Согласен, это довольно распространенная практика — возвращать из асинхронных модулей промисы. Но если у вас есть обычный модуль (без асинхронной работы) и вы используете его в большом количестве мест, при этом у вас также появляются другие пользователя этого модуля. То, когда происходит изменение интерфейса (в данном примере обычный модуль начинает возвращать промис), вам надо будет исправить все места, где используется этот модуль и каким-то образом оповестить других пользователей этого модуля об изменившемся интерфейсе, что в комплексе получается не очень удобно.

В том-то и дело, что пользователям менять ничего не надо.


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

Почему не надо?

В первом случае модуль возвращает объект:
return {
     theTrue: true,
};


Во втором — зарезолвленный промис:
return Promise.resolve({
     theTrue: true,
});


В последнем случае пользователю библиотеки надо будет добавить
.then(yourFunction)

Изначально в этом треде предлагается избавиться от provide вообще. Модуль определяется возвращаемым значением. Если возвращается промис, то значит модуль асинхронный и библиотека должна подождать его резолва.


Сравните сами. Что смотрится опрятнее?
Provide


modules.define('asyncModule', provide => {
   fetch('/api/endpoint').then(response => provide(response))
});

Promise


modules.define('asyncModule', () => {
   return fetch('/api/endpoint');
});
Имхо это тоже самое, только в профиль. Если возвращать промис, то в вашем случае пользователь библиотеки должен соответствующим образом его обработать.

modules.require(['asyncModule'], function(asyncModule) {
    asyncModule.then(response => doSomething(response));
});


При этом пользователь становится привязан к промису, т.е. если интерфейс библиотеки в будущем вновь станет возвращать объект, это вызовет ошибку.

Ну нет же.
Можно сделать YModules чуть умнее и научить резолвить промис, полученный от функции модуля.
И пользователь получит результат резолва и никак не заметит, что модуль стал асинхронным


modules.require(['asyncModule'], function(asyncResponse) {
  console.log(asyncResponse);
});
Ок, интересная мысль

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


singular.inject(['mongo', 'redis'])
.then(([mongo, redis]) => {
    // Отлично! Работаем дальше.
}, (err) => {
    // Обрабатываем ошибки.
});

Как видите здесь используются стандартные возможности языка и появляется возможность применить await, например так:


let [mongo, redis] = await singular.inject(['mongo', 'redis']);
Посоветуйте какой-нибудь популярный Externally Defined Dependencies загрузчик. Не могу ни одного найти
При написании статьи, я долго пытался найти загрузчик, использующий этот подход, но увы — не нашел.
Сейчас механизм DI используется во фреймворках Angular 2...

Но и в первом Aнгуляре DI присутствует.
Да, конечно, у меня пример написан с использованием именно первой версии, не стал просто повторяться.
UFO just landed and posted this here

Спасибо за статью, очень интересно было вспомнить разом, как мы пришли к тому что имеем.


Никогда не любил Module Pattern и AMD, они всегда казались костылями, хотя использовать все-равно приходилось, чтоб избежать еще более костыльных решений. Из смешанных в свое время очень впечатлил ExtJS 4. Также, сразу прижился CommonJS, а когда впервые услышал про ES Modules — радости не было придела. Очень жду, когда его можно будет использовать без babel, хотя-бы в node.

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

Недостатком модулей ES6 является поднятие (hoisting) import-деклараций и, соответственно, невозможность ни сделать подключение условным, ни сделать юзеру красиво обработать ошибку загрузки, особенно если речь идет о неконтролируемом CDN и прочем third party.
Sign up to leave a comment.