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

от автора

Я — 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 ругается, что текущая версия уже зарегистрирована.

ссылка на оригинал статьи http://habrahabr.ru/post/275011/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *