- PVSM.RU - https://www.pvsm.ru -

Классический подход к управлению зависимостями в сравнении с RequireJS

Hello World,

Helios Kernel [1] — это библиотека для управления зависимостями между javascript-модуями, реализующая «классический» подход, часто встречаемый в других языках и средах — с помощью функции include().

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

Helios Kernel придерживается принципа KISS [2], поэтому здесь отсутствуют некоторые возможности, которые сегодня принято ожидать от библиотеки по управлению зависимостями. При использовании Helios Kernel не нужно описывать конфиг с правилами поиска путей для разных модулей, или экспортировать библиотечные функци через специальный объект. Но эта библиотека была написана как раз потому, что хотелось просто подключать нужные модули и писать код, не натыкаясь на крутые возможности при указании каждой новой зависимости.

Helios Kernel поддерживает динамическую загрузку (и выгрузку) зависимостей в рантайме, а сама библиотека и формат модулей являются совместимыми между nodejs и броузерной средой — то есть модули можно использовать без изменений или трансляции.

В этой статье классический подход реализованный в Helios Kernel сравнивается с управлением зависимостями с помощью RequireJS [3] и показывается, каким образом подход Helios Kernel позволяет избежать некоторых сложностей.

Введение

Helios Kernel обладает такими особенностями:

  • Зависимости определяются с помощью функции include() в шапке модуля. В качестве аргумента используется точный относительный путь к файлу
  • После перечисления зависимостей определяется функция init() содержащая код модуля. Этот код будет выполнен после загрузки всех зависимостей

Поэтому описание зависимости между двумя модулями выглядит следующим образом.

Библиотечный модуль myLibrary.js, объявляющий некоторую функциональность:

init = function() {
    // объявление (глобального) библиотечного объекта
    myLibrary = {};

    // библиотечная функция
    myLibrary.writeHello = function() {
        console.log("Hello World!");
    }
}

И модуль подключающий и использующий эту библиотеку:

include("path/to/myLibrary.js");

init = function() {
    // здесь библиотека подключена и можно её использовать
    myLibrary.writeHello();
}

Здесь для «экспорта» библиотечного объекта используется определение глобальной переменной myLibrary в первом модуле. Сейчас такой способ принято считать «неправильным», потому что к глобальным объектам можно получить доступ откуда угодно. Но я его здесь использую, поскольку он является самым наглядным в качестве примера. Helios Kernel не накладывает никаких ограничений на способ передачи объектов между модулями: внутри функции init может быть любой код. В том числе там можно определить и функцию-фабрику, которая будет возвращать только «экспортируемый объект».

Но если нужно только определять локальные переменные модуля, для этого можно использовать область видимости функции init().

Далее я буду приводить примеры, в которых использование Helios Kernel позволяет сделать код проще по сравнению с RequireJS

Описания модуля

Формат описания модуля с зависимостями выглядит так:

RequireJS Helios Kernel
define(
    // зависимости
    ['fooLibrary', 'barLibrary'],
    function( foo, bar ){
        // использование
        foo.doSomething();
    }
);

// зависимости
include("path/to/fooLibrary.js");
include("path/to/barLibrary.js");

init = function() {
    // использование
    foo.doSomething();
}

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

Это особенно усложняет читаемость модулей RequireJS, когда зависимостей становится больше:

RequireJS Helios Kernel
define(
    [ 'dep1', 'dep2', 'dep3', 'dep4', 'dep5', 'dep6', 'dep7' ],
    function (  dep1,   dep2,   dep3,   dep4,   dep5,   dep6,   dep7) {
        // ...
    }
);

include("deps/dep1.js");
include("deps/dep2.js");
include("deps/dep3.js");
include("deps/dep4.js");
include("deps/dep5.js");
include("deps/dep6.js");
include("deps/dep7.js");
include("deps/dep8.js");

init = function() {
    // ...
}

Для того чтобы упростить такой код, в RequireJS был даже придуман альтернативный способ описания зависимостей, simplified CommmonJS [4]. Используя этот приём, можно описывать зависимости следующим образом:

define(
    [ 'dep1', 'dep2', 'dep3', 'dep4', 'dep5', 'dep6', 'dep7' ],
    function (require) {
        var dep1 = require('dep1'),
            dep2 = require('dep2'),
            dep3 = require('dep3'),
            dep4 = require('dep4'),
            dep5 = require('dep5'),
            dep6 = require('dep6'),
            dep7 = require('dep7');
            // ...
    }

});

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

Helios Kernel позволяет всего этого избежать. Дополнительная зависимость требует только одного упоминания с помощью include().

Создание собирающих модулей

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

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

Однако в случае с RequireJS нужно будет ещё экспортировать требуемые зависимости и «пробросить» их через экспортируемый объект общего модуля.

В случае с Helios Kernel достаточно просто перечислить зависимости:

RequireJS Helios Kernel
define(
    'depCommon',
    [ 'dep1', 'dep2', 'dep3' ],
    function( dep1, dep2, dep3 ) {
        return {
            dep1 : dep1,
            dep2 : dep2,
            dep3 : dep3
        }
    }
);

include('deps/dep1.js');
include('deps/dep2.js');
include('deps/dep3.js');

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

А вот в случае с общим модулем с CommonJS придётся ещё и переписать все случаи его экспорта. Если раньше к отдельным экспортированным частям нужно было обращаться как dep1, dep2,..., то теперь это нужно заменить на depCommon.dep1, depCommon.dep2,…

Простота использования обычных javascript-библиотек

Речь идёт о библиотеках, которые предполагается подключать на html-страницу с помощью тега script.

Формат модуля Helios Kernel гораздо ближе к обычным библиотекам, потому что внутри функции init() может содержаться любой код. Поэтому для того, чтобы превратить обычную библиотеку в модуль, достаточно обернуть её в определение функции init(), и после этого её можно будет подключить с помощью include().

В случае с RequireJS конвертировать библиотеку в модуль гораздо сложнее, потому что модуль должен экспортировать определяемые объекты, и пришлось бы делать рефакторинг. Вместо этого предлагается описать модуль в конфиге с помощью аттрибута shim [5]. Однако даже такой подход может привести к проблемам [6].

Простота портирования между вебом и nodejs

Для того чтобы использовать модули RequireJS на сервере в среде nodejs, можно воспользоваться библиотекой r.js [7], которая позволяет такие модули подключать в nodejs.

В обратную сторону: проект, написанный в формате модулей CommonJS, используемом в node, можно отконвертировать в веб-библиотеку с помощью browserify [8]. Но это получится не модуль, а большой бандл, подключаемый тегом script (или конфигом shim в RequireJS).

Helios Kernel упрощает эту задачу за счёт того, что модули работают без изменений и конвертации на обоих платформах.

Буду рад советам и комментариям.

Сайт: http://asvd.github.io/helios-kernel/ [1]
Код: https://github.com/asvd/helios-kernel/releases/download/v0.9.4/helios-kernel-0.9.4.tar.gz [9]
Гитхаб: https://github.com/asvd/helios-kernel [10]

Автор: xpostman

Источник [11]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/46739

Ссылки в тексте:

[1] Helios Kernel: http://asvd.github.io/helios-kernel/

[2] KISS: http://en.wikipedia.org/wiki/KISS_principle

[3] RequireJS: http://requirejs.org/

[4] simplified CommmonJS: http://requirejs.org/docs/api.html#cjsmodule

[5] shim: http://requirejs.org/docs/api.html#config-shim

[6] может привести к проблемам: http://esa-matti.suuronen.org/blog/2013/03/22/journey-from-requirejs-to-browserify/#disappearing-globals

[7] r.js: http://requirejs.org/docs/node.html

[8] browserify: http://browserify.org/

[9] https://github.com/asvd/helios-kernel/releases/download/v0.9.4/helios-kernel-0.9.4.tar.gz: https://github.com/asvd/helios-kernel/releases/download/v0.9.4/helios-kernel-0.9.4.tar.gz

[10] https://github.com/asvd/helios-kernel: https://github.com/asvd/helios-kernel

[11] Источник: http://habrahabr.ru/post/199162/