Загрузчик модулей для node js с поддержкой локальных модулей и загрузки модулей по требованию

в 9:06, , рубрики: javascript, node.js, npm

Я — frontend разработчик и в последнее время мне все чаще приходится пользоваться нодой, будь то использование webpack-а для сборки проекта, либо настройка различных gulp тасков. Хоть у меня и нету большого опыта в использование ноды, со временем у меня накопилось три вещи, которые мне хотелось бы улучшить при работе с модулями:

  • Избавиться от кучи require-ов в начале каждого файла
  • Подгружать модули только тогда, когда они нужны(особенно это актуально для gulp тасков)
  • Иметь возможность работать с локальными модулями проекта, как с внешними модулями, то есть вместо, например,
    вызова var core = require('../../deep/deep/deep/core/core'), вызывать этот же модуль вот так var core = require('core')

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

Например, для подгрузки модулей по требованию(он же lazy load или load on demand), есть модуль gulp-load-plugins. Он решает 1-ую и 2-ую проблему, но не решает 3-юю и имеет еще один недостаток — чтобы подключить модули, нужно в каждом файле, где эти модули нужны, производить инициализацию gulp-load-plugins модуля. Можно, конечно, делать инициализацию в отдельном файле и экспортировать из файла значение, но в таком случае придется подключать этот файл с использованием относительных или абсолютного путей.

Для решения 3-ей проблемы около года назад в npm добавили поддержку так званых локальных модулей. Суть сводится к тому, что
в package.json в dependencies нужно указать в качестве ключей имена модулей, а в значениях — относительные пути к папкам локальных модулей с префиксом «file:», например, вот часть package.json файла:

  "dependencies": {
    "lodash": "^2.0.0",
    "core": "file:deep/deep/deep/core",
    "my-other-module": "file:/my-other-module"
  }

При этом папки локальных модулей должны быть оформлены, как обычные модули, то есть должны содержать свой package.json и readme.md файлы. После запуска npm i ваши локальные модули будут установлены в папку node_modules, как обычные модули. По мне так это крайне неудобно класть каждый файл проекта в отдельную папку, да еще и заводить на него package.json и readme.md файлы.

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

В итоге я решил написать свое решение, возможно, свой велосипед, о котором и хочу поведать вам. На сколько он хорош или плох судить вам. Итак, позвольте представить вам sp-load. Сразу оговорюсь, префикс sp- не несет в себе никакого сакрального смысла, это всего лишь первые буквы моей фамилии и имени и добавлен не с целью прославить меня, а по причине того, что имена «load», «loader» и прочие были уже заняты.

Итак, вы сделали npm i sp-load -S в своем проекте. Предположим, что вы имеете следующее содержимое package.json файла:

{
    "name": "your-project",
    "version": "1.0.0",
    "main": "index.js",
    "dependencies": {
        "lodash": "^3.10.1",
        "sp-load": "^1.0.0"
    },
    "devDependencies": {
        "gulp": "^3.9.0",
        "webpack": "^1.12.9"
    },
    "_localDependencies": {
        "core": "./core/core",
        "some-module": "./deep/deep/deep/deep/deep/deep/deep/some-module"
    }
}

И имеете следующую структуру файлов:

your-project/
    node_modules
        sp-load/
            ...
        gulp/
            ...
        lodash/
            ...
        webpack/
            ...
    package.json
    core/
        core.js
    deep/
        deep/
            deep/
                deep/
                    deep/
                        deep/
                            deep/
                                some-module.js
    gulpfile.js
    index.js

Что вам нужно сделать, чтобы использовать sp-load в простейшем виде? Всего одну вещь, сделать var $ = require('sp-load'); внутри какого-либо файла, например, вот содержимое gulpfile.js:

'use strict';

var $ = require('sp-load'),
    webpackConfig = {};

$.gulp.task("webpack", function (callback) {
  $.webpack(webpackConfig, function (err, stats) {
    callback();
  });
});

Содержимое some-module.js:

'use strict';

function someModuleFuction() {
    console.log('I'm some module function call!');
}

module.exports = someModuleFuction;

Содержимое core.js:

'use strict';

function coreModuleFuction() {
    console.log('I'm core module function call!');
}

module.exports = coreModuleFuction;

Содержимое index.js:

'use strict';

var $ = require('sp-load');

$.someModule();

$.core();

Как вы видите, всё что нужно сделать — подключить sp-load модуль. Он возвращает объект, содержащий список модулей, которые будут подгружены по требованию, то есть модуль будет загружен node-ой при первом обращение по имени модуля, например, $.core().

Также вы, наверное, заметили нестандартное свойство "_localDependencies" в package.json. В этом свойстве вы можете определить список локальных модулей вашего проекта. Ключи объекта — названиям модулей, значения — относительный путь к файлу модуля(путь относительный package.json файла).

Если же вы хотите обращаться к модулям не как к свойствам объекта, а как к переменным, то можете сделать это следующим образом(в примере используется es6 деструктуризация. как включить возможности es6 в nodejs вы можете прочесть в документацие nodejs):

'use strict';

var {someModule, core} = require('sp-load');

someModule();

core();

Или с использованием es5:

'use strict';

var $ = require('sp-load'),
    someModule = $.someModule,
    core = $.core;

someModule();

core();

В обоих этих примерах, модули someModule и core буду подгружены при присвоение, если же вы хотите, чтобы они были подгружены в момент первого их использования(то есть on demand), то обращайтесь к модулям, как к свойствам объекта $.

Это было простейшее использование sp-load, без каких-либо конфигураций, за исключением использования свойства "_localDependencies" в package.json. Теперь же хочу показать какие настройки поддерживает sp-load. Для того, чтобы конфигурировать sp-load, необходимо добавить свойство "_sp-load" в package.json. Ниже приведен пример package.json файла, в котором указаны все возможные настройки с комментариями о назначение каждой из них:

{
    "name": "your-project",
    "version": "1.0.0",
    "main": "index.js",
    "dependencies": {
        "lodash": "^3.10.1",
        "sp-load": "^1.0.0"
    },
    "devDependencies": {
        "gulp": "^3.9.0",
        "webpack": "^1.12.9"
    },
    "_localDependencies": {
        "core": "./core/core",
        "some-module": "./deep/deep/deep/deep/deep/deep/deep/some-module"
    },
    "_sp-load": {
        /*
			если значение true, имена модулей будут в виде camel case.
			например, вместо $['some-module'] будет $.someModule.
			дефолтное значение - true.
        */
        "camelizing": false,
        /*
			эта настройка отвечает за переименование имен модулей. например, вместо $.lodash модуль будет доступен
			как $._
        */
        "renaming": {
            "lodash": "_",
            "gulp": "supergulp"
        },
        /*
			если вы хотите заменить часть названия модулей, используйте эту настройку. ключи - регулярные выражения,
			значения - строки, на которые будет произведена замена. наиболее частый случай использования - gulp 
			плагины, большинство из которых начинаются с префикса gulp-, например, gulp-concat, а вы хотите обращаться
			к нему как $.concat вместо $.gulpConcat.
        */
        "replacing": {
            "/^gulp-/": ""
        }
    }
}

Если же вы не хотите засорять package.json файл, то поместите настройки sp-load и список локальных модулей в файл _sp-load.json, который должен находиться в той же папке, где и package.json, то есть:

yourProject/
    package.json
    _sp-load.json

Вот пример содержимого _sp-load.json файла:

{
    "_localDependencies": {
        "core": "./core/core",
        "some-module": "./deep/deep/deep/deep/deep/deep/deep/some-module"
    },
    "_sp-load": {
        "camelizing": false,
        "renaming": {
            "lodash": "_",
            "gulp": "supergulp"
        },
        "replacing": {
            "/^gulp-/": ""
        }
    }
}

И последнее, о чем еще не упомянул. Когда вы делаете $ = require('sp-load');, объект $ содержит свойство "_spModulesList" в своем прототипе. Это свойство содержит объект, где ключи — имена модулей, а значения — абсолютный путь к файлу модуля. Вот пример содержимого этого объекта:

{
    "lodash": "lodash",
    "sp-load": "sp-load",
    "gulp": "gulp",
    "webpack": "webpack",
    "core": "D://your-project//core//core.js",
    "some-module": "D://your-project//deep//deep//deep//deep//deep//deep//deep//some-module.js"
}

Для чего это может пригодиться? Например, при использование System.js загрузчика.

Пожалуй, это всё. Перед тем, как опубликовать модуль на npmjs.com, протестировал его, но в реальном проекте ещё его не использовал, поэтому, если будут какие-либо ошибки — буду рад, если сообщите о них.

Ссылка на сам модуль: sp-load.

P.S.: Может, кто-нибудь подскажет, как удалить опубликованный модуль из npmjs.com? Нигде не нашел, как это сделать, а npm unpublish удаляет модуль, но при последующем npm publish приходится увеличивать версию модуля т.к. npm ругается, что текущая версия уже зарегистрирована.

Автор: pavel06081991

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js