Как стать автором
Обновить

Node.JS Избавься от require() навсегда

Время на прочтение4 мин
Количество просмотров9.1K
Анализируя исходные коды прошлых проектов, рейтинг популярности прямых вызовов функций показал, что прямой вызов require() встречается в коде node-модулей почти так же часто, как Array#forEach(). Самое обидное, что чаще всего мы подключаем модули "util", "fs" и "path", чуть реже "url". Наличие других подключенных модулей зависит уже от задачи модуля. Причем, говоря о модуле "util", загружается в память node-процесса даже если вы ни разу его не подключали.

В прошлой статье Node.JS Загрузка модулей по требованию я поведал о возможности автоматической загрузкой модуля при первом обращении к его именованной ссылке. Если честно, на момент написания той статьи, я не был уверен в том, что такой подход не станет причиной странного поведения node-процесса. Но, уже сегодня с гордостью могу ручаться, что demandLoad() работает уже пол года в продакшене. Как мы его только не гоняли… Это и нагрузочное тестирование конкретного процесса, и работа demandLoad() в worker-процессах кластеров, и работа процесса под небольшой нагрузкой в течении долгого времени. Результаты сравнивались с использованием demandLoad() и с использованием require(). Никаких существенных отклонений в сравнении не было замечено.

Сегодня речь пойдет уже не о стабильности demandLoad(). Если кому интересно, задавайте вопросы в комментариях, сделаю скриншоты, могу рассказать о методах и инструментах тестирования, других возможностях использования подхода. Сегодня, как следует из заголовка статьи, мы будем избавляться от успевших уже надоесть require() в шапках каждого node-модуля.

Заранее отмечу, ни в коем случае не агитирую использовать предложенный метод в продакшене. Практика изложена для ознакомления и не претендует на статус «true-практики». Громкий заголовок только для привлечения внимания.

Предположим, мы работаем над проектом с названием "mytestsite.com", и находится он у нас здесь:

~/projects/mytestsite.com

Создадим исполняемый файл нашего проекта, например, по адресу:

~/projects/mytestsite.com/lib/bin/server.js

Внутри попробуем вызвать подключенные модули без предварительных require():

console.log(util.inspect(url.parse('https://habrahabr.ru/')));

Теперь создадим файл "require-all.js" где-нибудь, вне всех проектов.
Например, здесь:

~/projects/general/require-all.js

Согласно документации, все предопределенные переменные и константы каждого node-модуля являются свойствами global. Соответственно, мы можем определять и свои глобальные объекты. Так мы должны поступить со всеми используемыми нами модулями.

Наполним require-all.js списком всех используемых модулей во всех проектах:

// нет смысла оставлять неподгруженным модуль "util",
// т.к. его все равно до загрузки подгружает модуль "console".
// А console.log(), если ему передать объект единственным параметром,
// в свою очередь вызывает util.inspect()
global.util = require('util');

// так выглядит подключение других стандартных модулей, например:
demandLoad(global, 'fs', 'fs');
demandLoad(global, 'path', 'path');
demandLoad(global, 'url', 'url');

// абсолютно так же выглядит подключение npm-модулей, например:
demandLoad(global, 'express', 'express');

// а, вот, например, так можно подключить локальный модуль:
demandLoad(global, 'routes', './../mytestsite.com/lib/routes');

// определение demandLoad
function demandLoad(obj, name, modPath){
// тело вырезано для простоты схемы
// необходимо взять из статьи по ссылке выше.
}

Можно представить список модулей в виде массива или карты (Map), и, например, пройтись по нему/ней циклом, чтобы не повторять строчку кода с вызовом demandLoad(). Можно, например, прочитать список используемых npm-модулей из package.json. Если, например, количество используемых модулей очень высокое, и не хочется засорять глобальный скоуп, можно определить, например, пустой объект m (let m = {}), определить m в global (global['m'] = m), и уже к m применять demandLoad(). Как говорится, кому как удобнее.

Теперь, осталось лишь запустить это хозяйство. Добавим ключ --require к запуску node (версии >= 4.x):

node    --require ~/projects/general/require-all.js \
        ~/projects/mytestsite.com/lib/bin/server.js

Ошибок нет. Скрипт отработал как надо:

Url {
  protocol: 'https:',
  slashes: true,
  auth: null,
  host: 'habrahabr.ru',
  port: null,
  hostname: 'habrahabr.ru',
  hash: null,
  search: null,
  query: null,
  pathname: '/',
  path: '/',
  href: 'https://habrahabr.ru/' }

Если у вас много проектов, для удобства разворачивания проектов, можно создать по своему require-all.js внутри каждого проекта по отдельности.

node    --require ~/projects/mytestsite.com/lib/require-all.js \
        ~/projects/mytestsite.com/lib/bin/server.js

Расширяя последний случай, отмечу, можно даже использовать несколько таких require-all.js одновременно:

node    --require ~/projects/general/require-all.js \
        --require ~/projects/mytestsite.com/lib/require-all.js \
        ~/projects/mytestsite.com/lib/bin/server.js

Как отмечено в комментарии ниже, связка --require+global также может быть использована для расширения/перегрузки стандартных возможностей node.

Напоследок, повторюсь из прошлой статьи: Если demandLoad() определена не в нашем файле(1) (откуда вызываем demandLoad()), а в каком-нибудь файле(2), причем файл(1) и файл(2) находятся в разных директориях, последним параметром необходимо передавать полный путь до модуля, например:

demandLoad(global, 'routes', path.join(__dirname, './../mytestsite.com/lib/routes'));

Иначе, тот require(), что вызывается из demandLoad() будет искать модуль относительно папки, где расположили тот самый файл(2) с описанием demandLoad(), вместо того, чтобы искать модуль относительно файла(1), откуда мы вызываем demandLoad().

Спасибо за внимание. Всем удачного рефакторинга!
Теги:
Хабы:
Всего голосов 6: ↑3 и ↓30
Комментарии16

Публикации

Истории

Работа

Ближайшие события