Использование переменных окружения в Node.js
Материал, посвящённый переменным окружения в Node.js, перевод которого мы сегодня публикуем, написал Берк Холланд, веб-разработчик, в сферу интересов которого входят JavaScript, Node.js и VS Code. Кроме того, стоит отметить, что у него сложились непростые отношения с Java. Вот его история.
Переменные окружения — одна из фундаментальных конструкций среды Node.js, но почему-то я никогда не стремился научиться правильно ими пользоваться. Возможно, случилось так из-за их названия — «Environment Variables». Это название вызывало у меня нечто вроде посттравматического синдрома, неприятные воспоминания о том, как я пытался добавить путь к домашней директории Java в Windows. Я тогда толком не мог понять, надо ли добавлять этот путь в переменную PATH, в переменную JAVA_HOME, или и туда и туда. Было неясно и то, нужно ли, чтобы в конце этого пути стояла точка с запятой. Собственно говоря, тогда у меня возникал и вопрос о том, почему я использую Java. Как бы там ни было, я наконец нашёл в себе силы и приступил к знакомству с переменными окружения Node.
Если вы пишете для платформы Node.js, и, так же, как и я, неважно — по каким причинам, до сих пор не особенно хорошо знакомы с переменными окружения — предлагаю это исправить.
Переменные окружения в Node.js
В Node переменные окружения могут быть глобальными (как в Windows), но часто они используются в применении к конкретному процессу, в выполнении которого заинтересован разработчик. Например, если у вас имеется веб-приложение, это значит, что в нём могут применяться следующие переменные окружения:
- HTTP-порт, который будет прослушивать это приложение.
- Строка подключения к базе данных.
- Путь к JAVA_HOME, ох, подождите, это мимо. Прошу учесть, что выздоровление требует времени.
Если раньше вы программировали для .NET, возможно, вы знакомы с чем-то вроде файла web.config . Переменные окружения Node играют практически такую же роль, как настройки из web.config — они представляют собой механизм передачи в приложение информации, которую разработчик не хочет жёстко задавать в коде.
Кстати, на тему «hard code» — задания в коде неких значений вместо получений их из внешних источников, хочу поделиться моим собственным твитом.

Цитирую сам себя на пике помешательства
Как же использовать переменные окружения в Node.js-приложениях? Мне пришлось изрядно потрудиться для того, чтобы найти хороший материал по переменным окружения в Node, с непременным условием наличия в этом материале достаточного количества шуток о Java. Такого материала я не нашёл, поэтому решил написать его сам.
Вот несколько способов использования переменных окружения в приложениях для Node.js.
Указание переменных окружения в терминале
Вы можете указывать переменные окружения в терминале, в котором планируется запускать Node. Например, если у вас имеется приложение, использующее Express, и вы хотите передать ему сведения о порте, сделать это можно так:
PORT=65534 node bin/www
Кстати, интересная вещь. Оказывается, самое большое значение, которое может принимать номер порта, это 65535. Как я это узнал? Конечно, нашёл на StackOverflow. Как вообще кто-либо что-либо узнаёт? Но в Node самый большой номер порта — это 65534. Почему? Понятия не имею. Я не могу знать абсолютно всё.
Итак, для использования переменной окружения в коде нужно воспользоваться объектом process.env . Выглядит это так:
var port = process.env.PORT;
Однако использование описанного здесь подхода может плохо кончиться. Если у вас есть, например, строка подключения к базе данных, вы, вероятно, не будете особенно рады перспективам её ввода в терминал. На самом деле, ввод длинных значений в терминале похож на болезненную зависимость, а нам это совершенно ни к чему. Посмотрите сами:
PORT=65534 DB_CONN="mongodb://react-cosmos-db:swQOhAsVjfHx3Q9VXh29T9U8xQNVGQ78lEQaL6yMNq3rOSA1WhUXHTOcmDf38Q8rg14NHtQLcUuMA==@react-cosmos-db.documents.azure.com:19373/?ssl=true&replicaSet=globaldb" SECRET_KEY=b6264fca-8adf-457f-a94f-5a4b0d1ca2b9 node bin/www
Такой подход не масштабируется, а все хотят масштабирования. По мнению каждого архитектора, рядом с которым мне доводилось сидеть на разных мероприятиях, «масштабирование» — это даже важнее, чем сам факт работоспособности приложения.
Поэтому рассмотрим другой подход, который заключается в применении файлов .env .
Использование файлов .env
Файлы .env предназначены для хранения переменных окружения. Для использования этой технологии достаточно создать в проекте файл с именем .env и внести в него переменные окружения, начиная каждую с новой строки:
PORT=65534 DB_CONN="mongodb://react-cosmos-db:swQOhAsVjfHx3Q9VXh29T9U8xQNVGQ78lEQaL6yMNq3rOSA1WhUXHTOcmDf38Q8rg14NHtQLcUuMA==@react-cosmos-db.documents.azure.com:10255/?ssl=true&replicaSet=globaldb" SECRET_KEY="b6264fca-8adf-457f-a94f-5a4b0d1ca2b9"
Читать эти значения можно разными способами. Пожалуй, проще всего — с помощью пакета dotenv из npm :
npm install dotenv --save
После установки пакета его нужно подключить к проекту, а затем им можно пользоваться для работы с переменными окружения. Этот пакет найдёт файл .env и загрузит переменные, описанные в нём, в Node. Вот как это выглядит:
//Использование пакета dotenv для чтения переменных из файла .env в Node require('dotenv').config(); var MongoClient = require('mongodb').MongoClient; // Обращение к переменным из .env, которые теперь доступны в process.env MongoClient.connect(process.env.DB_CONN, function(err, db) < if(!err) < console.log("We are connected"); >>);
Кстати, хочу поделиться важным советом. Не заливайте файлы .env на GitHub. В них находятся сведения, не предназначенные для чужих глаз. Не повторяйте моих ошибок.
Итак, пока всё хорошо, но то, о чём мы тут говорили, не очень-то удобно. Подключать dotenv придётся в каждом файле, где надо использовать переменные окружения, и dotenv придётся использовать в продакшне, когда, в сущности, вам это не нужно. Я не отношусь к фанатам развёртывания бесполезного кода в продакшне, хотя, мне кажется, до сих пор я только этим и занимался.
Что же делать? К счастью, вы пользуетесь VS Code (я абсолютно в этом уверен), а это значит, что у вас есть ещё несколько вариантов.
Работа с файлами .env в VS Code
Для начала вы можете установить расширение DotENV для VS Code, которое даст приятную подсветку синтаксиса файлов .env .

Вот как выглядит файл .env без подсветки синтаксиса и с подсветкой
Кроме того, отладчик VS Code, если вы им пользуетесь, предлагает некоторые более удобные возможности по загрузке значений из файлов .env .
Конфигурация запуска VS Code
Отладчик Node.js для VS Code (он устанавливается по умолчанию) поддерживает загрузку файлов .env посредством конфигураций запуска. Подробности о конфигурациях запуска можно почитать здесь.
Создание базовой конфигурации запуска для Node
После того, как вы создадите базовую конфигурацию запуска для Node (щёлкните по значку с шестерёнкой и выберите Node), можно выполнить одно из следующих действий, или сделать и то и другое.
Первый вариант заключается во включении переменных в конфигурационный файл.

Переменные в конфигурационном файле
Это — вполне приемлемый вариант, но меня немного беспокоит то, что каждое значение должно быть строкой. Всё-таки некоторые значения — это числа, а не строки, а в JavaScript есть лишь, скажем так, три основных типа данных, и мне не хотелось бы лишаться одного из них.
Передать переменные окружения в VS Code можно и более простым способом. Мы уже выяснили, что файлы .env — это наши друзья, поэтому вместо того, чтобы вписывать в конфигурационный файл значения переменных, просто укажем там путь к файлу .env .

Путь к файлу .env в конфигурационном файле
До тех пор, пока мы запускаем процесс Node.js из VS Code, файл с переменными окружения будет передаваться этому процессу. При этом нам не придётся втискивать числа в кавычки, делая из них строки, и не придётся разворачивать ненужный код в продакшне. Ну, по крайней мере, вам этого делать не придётся.
Запуск node-скриптов через NPM
Возможно, вы дошли до этого места и подумали о том, что никогда не запускаете Node-проекты командами вида node … , всегда пользуясь npm-скриптами вроде npm start . Конфигурациями запуска VS Code можно пользоваться и в этом случае. Однако, вместо применения стандартного запуска Node, можно настроить задачу Launch Via NPM.
Задача Launch Via NPM
После этого можно настроить значение параметра envFile , указав путь к файлу .env , и параметра runtimeArgs для запуска нужного скрипта. Обычно в качестве скрипта выступает start или debug .

Настройка запуска проекта с помощью npm
Обратите внимание на то, что в package.json нужно добавить, к npm-скрипту, флаг —inspect для того, чтобы VS Code мог подключить к процессу отладчик. В противном случае, хотя задача и запустится, отладчик не сможет сделать ничего полезного.
Флаг —inspect в package.json
Переменные окружения в продакшне
Итак, мы разобрались с тем, как пользоваться переменными окружения в ходе разработки. Вы, вероятнее всего, не будете применять файлы .env в продакшне, да и конфигурация запуска VS Code на сервере особой пользы не принесёт.
В продакшне определение переменных окружения будет зависеть от особенностей используемой платформы. Например, в случае с Azure есть три способа объявления таких переменных и управления ими.
Первый способ заключается в использовании Azure CLI.
az webapp config appsettings set -g MyResourceGroup -n MyApp --settings PORT=65534
Это работает, но выглядит не очень. Ещё один способ — использование веб-портала Azure. Я не часто пользуюсь веб-порталом, но когда это случается, я обращаюсь к нему именно для установки переменных окружения.
Здесь то, что мы называем «переменными окружения», называется «Application Settings».

Настройка переменных окружения в Azure
Ещё один вариант, учитывая то, что вы пользуетесь VS Code, заключается в установке расширения App Service и в настройке вышеописанных Application Settings прямо из редактора.
Настройка переменных окружения из VS Code
Мне нравится делать в VS Code абсолютно всё, и если бы там можно было бы писать электронные письма, я бы так и поступал. Кстати, похоже, моя мечта сбылась.
Итоги
Теперь вы знаете то же, что знаю я (не так много, позволю заметить), и я чувствую, что выполнил свою цель по шуткам на тему Java. Если вдруг их тут недостаточно — вот ещё одна, автор которой неизвестен: «Java — это очень мощный инструмент по превращению XML в стек-трейсы».
Надеемся, этот материал дал возможность восполнить пробелы в знаниях о переменных окружения в Node тем, кто раньше ими не пользовался, а тем, кто уже о них знал, позволил узнать что-нибудь новое о файлах .env, о применении переменных окружения на боевых серверах, и о том, как облегчить себе жизнь, занимаясь разработкой Node-проектов в VS Code. Если тема переменных окружения Node вам интересна — вот одна из наших предыдущих публикаций на эту тему, посвящённая особенностям работы с process.env.
Уважаемые читатели! Как вы решаете проблемы использования переменных окружения Node в ходе разработки и в продакшне?

- JavaScript
- разработка
- Node.js
- переменные окружения
Обзор пакетов Node.js для разбора опций командной строки
Node.js, как и другие среды разработки, предоставляет базовые средства работы с опциями командной строки. В нашем случае это массив process.argv . Но обычно, кроме простейших случаев типа A + B, обрабатывать опции командной строки вручную очень неудобно. Для этого есть несколько популярных пакетов. Автор написал небольшую программу, которая построила сводную таблицу по этим пакетам, выбрал из них три самых популярных и рассмотрел их поближе.
(Материал статьи на 7 января 2020 года по прежнему актуален, сводная таблица обновлена и дополнена.)
Сводная таблица
(Из-за узкого формата страницы пришлось одну таблицу разбить на две: одна с информацией с NPM, другая с GitHub.)
| # | NPM Package | NPM Stars | Deprecated | Last Version | Last Update | Created | Dependencies |
|---|---|---|---|---|---|---|---|
| 1 | commander | 1011 | 4.1.0 | 2020-01-06 | 2011-08-15 | 0 | |
| 2 | yargs | 512 | 15.1.0 | 2020-01-02 | 2013-11-23 | 11 | |
| 3 | minimist | 432 | 1.2.0 | 2019-08-11 | 2013-06-25 | 0 | |
| 4 | optimist | 143 | V | 0.6.1 | 2018-03-21 | 2010-12-21 | 2 |
| 5 | meow | 79 | 6.0.0 | 2019-12-07 | 2013-01-24 | 11 | |
| 6 | cli | 69 | 1.0.1 | 2018-03-15 | 2011-01-01 | 2 | |
| 7 | command-line-args | 49 | 5.1.1 | 2019-03-31 | 2014-05-27 | 4 | |
| 8 | nopt | 47 | 4.0.1 | 2020-01-02 | 2011-03-30 | 2 | |
| 9 | nomnom | 32 | V | 1.8.1 | 2018-03-17 | 2011-04-08 | 2 |
| 10 | argparse | 21 | 1.0.10 | 2018-02-27 | 2012-05-17 | 1 | |
| 11 | stdio | 9 | 2.0.1 | 2019-12-19 | 2013-03-16 | 0 | |
| 12 | dashdash | 9 | 1.14.1 | 2017-12-28 | 2013-02-28 | 1 | |
| 13 | has-flag | 5 | 4.0.0 | 2019-04-06 | 2015-07-08 | 0 | |
| 14 | clp | 2 | 4.0.11 | 2019-01-03 | 2015-04-17 | 3 | |
| 15 | clap | 1 | 2.0.1 | 2019-12-17 | 2014-02-10 | 1 | |
| 16 | argentum | 0 | 0.6.0 | 2016-07-29 | 2015-11-26 | 0 | |
| 17 | getoptie | 0 | 1.0.2 | 2015-03-09 | 2015-03-09 | 0 |
| # | NPM Package | GitHub Repository | GitHub Stars | Last Commit |
|---|---|---|---|---|
| 1 | commander | tj/commander.js | 16885 | 2020-01-06 |
| 2 | yargs | yargs/yargs | 7154 | 2020-01-02 |
| 3 | minimist | substack/minimist | 3950 | 2015-08-29 |
| 4 | optimist | substack/node-optimist | 2589 | 2014-02-05 |
| 5 | meow | sindresorhus/meow | 2032 | 2019-12-07 |
| 6 | cli | node-js-libs/cli | 772 | 2016-10-23 |
| 7 | command-line-args | 75lb/command-line-args | 451 | 2019-09-22 |
| 8 | nopt | npm/nopt | 478 | 2019-01-26 |
| 9 | nomnom | harthur/nomnom | 471 | 2015-09-09 |
| 10 | argparse | nodeca/argparse | 359 | 2019-11-05 |
| 11 | stdio | sgmonda/stdio | 143 | 2019-12-19 |
| 12 | dashdash | trentm/node-dashdash | 124 | 2019-08-28 |
| 13 | has-flag | sindresorhus/has-flag | 53 | 2019-05-31 |
| 14 | clp | IonicaBizau/clp | 12 | 2019-01-03 |
| 15 | clap | lahmatiy/clap | 16 | 2020-01-06 |
| 16 | argentum | rumkin/argentum | 1 | 2016-07-29 |
| 17 | getoptie | avz/node-getoptie | 0 | 2015-03-09 |
Эта таблица была сгенерирована небольшой программой на JavaScript. Исходные тексты этого обзора, включая и эту программу, расположены в репозитории на GitHub. Так как через некоторое время эти данные скорее всего устареют, вы можете, загрузив себе эти исходники, перегенерировать эту таблицу, а также пополнить её новыми данными просто добавив соответствующие строки в файл со списком пакетов.
Пакеты в таблице упорядочены по рейтингу, который считается на основе количества звёзд на NPM и GitHub по формуле:
npmStars * k + githubStars
Коэффициент k понадобился, так как звёзды на NPM выглядят «весомее» звёзд на GitHub. Сам коэффициент считается очень просто: суммируем количество звёзд на NPM и на GitHub, затем делим число звёзд на GitHub на число звёзд на NPM, округляем получившееся число, это и есть наш коэффициент k :
k = floor( Sgithub / Snpm)
Из получившейся таблицы хорошо видно, что главный фаворит, это пакет commander. Далее идут с близким рейтингом пакеты minimist и yargs. Хороший рейтинг имеет также пакет optimist, но автором он объявлен устаревшим, а на его место он рекомендует им же написанный пакет minimist, а также советует посмотреть yargs и nomnom. В качестве преемника optimist также позиционируется пакет yargs. Авторы объявленного устаревшим nomnom рекомендуют commander.
Таким образом в первую очередь нужно рассмотреть пакеты commander, minimist и yargs. Вероятно есть смысл также обратить внимание на пакеты meow и nopt, но не в этот раз.
commander
Научиться использовать пакет commander несложно. Автор предоставил, хоть и не всегда ясную, но всё же неплохую документацию. Чтобы разобраться, как использовать этот пакет, нужно было как следует поэкспериментировать. Ниже я опишу основные моменты этого пакета.
Итак, после того как мы загрузили пакет:
const commander = require('commander')
Мы можем, вызывая последовательно или раздельно его функции, настроить его на обработку опций командной строки. При этом пакет обеспечивает:
- короткие опции, например, -s ;
- длинные опции, например, —source ;
- альтернативные названия опций, например, —source и -s ;
- дополнительные параметры;
- значения по-умолчанию для дополнительных параметров;
- обработчики для дополнительных параметров;
- субкоманды, например, install package ;
- автоматическое формирование подсказки;
- настройку подстказки.
Короткие опции объявляются так:
commander .option('-a', 'option a')
Первый аргумент функции option задаёт формат опции, а второй даёт ей словесное описание. Доступ к опции -a в коде программы осуществляется через соответствующее свойство commander :
if (commander.a)
Пример для длинной опции:
commander .option('--camel-case-option', 'camel case option')
При этом в коде доступ к опции будет происходить по имени camelCaseOption .
Возможно задание для опций параметров как обязательных, так необязательных:
commander .option('-s, --source ', 'source file') .option('-l, --list [items]', 'value list', toArray, [])
Во втором случае, параметр у опции list необязателен, для него назначены функция-обработчик и значение по-умолчанию.
Параметры опций могут обрабатываться также с помощью регулярных выражений, например:
commander .option('--size [size]', 'size', /^(large|medium|small)$/i)
Субкоманда подразумевает, что для неё пишется отдельный модуль. При этом, если основная программа называется program , а субкоманда command , то модуль субкоманды должен называться program-command . Опции, переданные после субкоманды передаются модулю команды.
commander .command('search [other. ]', 'search with query') .alias('s')
Для автоматической подсказки можно указать версию программы:
commander.version('0.2.0')
Подсказка может быть сопровождена дополнительными действия, например, дополнена нестандартными текстом. Для этого нужно обрабатывать событие —help .
commander.on('--help', () => < console.log(' Examples:') console.log('') console.log(' node commander.js') console.log(' node commander.js --help') console.log(' node commander.js -h') . console.log(' node commander.js --size large') console.log(' node commander.js search a b c') console.log(' node commander.js -abc') >)
Завершается настройка вызовом функции parse с параметром process.argv :
commander.parse(process.argv)
minimist
Автор пакета minimist предоставил весьма минималистичную документацию. Но всё равно попробуем разобраться.
После того как мы загрузили пакет, подключим и воспользуемся им:
const minimist = require('minimist') const args = minimist(process.argv.slice(2)) console.dir(args)
Этот незамысловатый код позволит нам начать работать с этим пакетом. Поэкспериментируем:
node minimist.js
Что мы здесь видим? Набор разобранных опций организуется в объект. Свойство с именем _ содержит список параметров, не связанных с опциями. Например:
node minimist.js a b c
node minimist.js --help
Как видим, minimist не предоставляет автоматического отображения подсказки, а просто определяет наличие данной опции.
node minimist.js -abc
Всё верно. Посмотрим ещё:
node minimist.js --camel-case-option
В отличие от minimist никаких преобразований.
Опция с параметром:
node minimist.js --source path
Со знаком равно тоже работает:
node minimist.js --source=path
Поддерживается специальный режим передачи опций с использванием — :
node minimist.js -h -- --size=large
Аргументы, следующие за — не обрабатываются и просто помещаются в свойство _ .
Вот в общем-то и всё, что есть в базе. Посмотрим, какие возможности настройки обработки опций предлагает нам minimist.
Для настройки обработки аргументов командной строки мы должны передать парсеру второй параметр с нашими настройками. Рассмотрим на примерах:
const minimist = require('minimist') const args = minimist(process.argv.slice(2), < string: ['size'], boolean: true, alias: , default: , unknown: (arg) => < console.error('Unknown option: ', arg) return false >>) console.dir(args)
node minimist-with-settings.js --help
node minimist-with-settings.js -h
Мы задали для опции —help синоним -h . Результат, как видим, идентичен.
Опция boolean , установленная в true , говорит о том, что все опции без параметров после знака равно будут иметь булево значение. Например:
node minimist-with-settings.js --no-help
Здесь мы увидели, как обрабатываются булевы опции: префикс no устанавливает значение опции равным false .
Но такой пример при этом больше не работает, нужен знак равно:
node minimist-with-settings.js --size large
Unknown option: large
Здесь же мы увидели обработку неизвестной опции и опции по-умолчанию.
Общий вывод: по сравнению с commander довольно минималистично, но вполне удобно.
yargs
В отличие от minimist и commander yargs предлагает весьма пространную документацию, доступную по ссылке.
Как обычно начнём с минимального примера:
const yargs = require('yargs') console.dir(yargs.argv)
node yargs.js
Здесь мы видим пустой список необработанных опций, а также имя файла нашей программы.
Рассмотрим пример посложней:
node yargs.js -abc --help --size=large 1 2 3
Здесь поинтереснее будет: во-первых, переданные опции восприняты верно; во-вторых, для их обработки мы не написали ни строчки кода.
Но уже здесь видно, что опция —help без предварительной настройки по предназначению не обрабатывается.
Рассмотрим теперь как использовать yargs в более сложных случаях на следующем примере:
const yargs = require('yargs') yargs .usage('Usage: $0 -abc [--list 1,2,3] --size large|meduim|small [--help]') .version('1.0.0') .demand(['size']) .choices('size', ['large', 'medium', 'small']) .default('list', [], 'List of values') .describe('list', 'value list') .array('list') .help('help') .alias('help', 'h') .example('$0 --size=medium') .epilog('(c) 2016 My Name') console.dir(yargs.argv)
node yargs.js -h
Usage: yargs.js -abc [--list 1,2,3] --size large|meduim|small [--help] Options: --version Show version number [boolean] --list value list [array] [default: List of values] --help, -h Show help [boolean] --size [required] [choices: "large", "medium", "small"] Examples: yargs.js --size=medium (c) 2016 My Name
В этом примере мы задали текст, который будет выводиться с опцией help . Опции help мы также указали синоним h . А ещё указали версию программы, которая будет выводиться с опцией version .
Опция size обязательная, более того, для неё задан список допустимых значений.
node yargs.js --size large
Если size передать значение, не соответствующее ни одному из списка, то получим сообщение об ошибке:
node yargs.js --size=middle
. Invalid values: Argument: size, Given: "middle", Choices: "large", "medium", "small"
Для опции list указано значение по умолчанию. Эта опция также трактуется как массив значений:
node yargs.js --list 1 2 3 --size=large
Резюме
Пакеты commander и minimist выделяются минимальным числом зависимостей, в то время как yargs поражает не только числом своих зависимостей, но и числом своих возможностей.
Какой пакет лучше, очевидно, сказать нельзя. По мнению автора, minimist вполне достаточен для простейших случаев, но в сложных ситуациях при его использовании придётся написать много кода обработки опций вручную. В этом случае лучше воспользоваться commander или yargs, на ваш вкус.
Все три рассматриваемые здесь пакета имеют определения типов на TypeScript, что позволяет иметь в Code работающий IntelliSense.
Архив
В первоначальной редакции от 13 сентября 2016 года сводная таблица в начале статьи была следующей:

Обновление
Добавлены в таблицу ещё три пакета, о которых сообщили в комментариях читатели:
- NPM: argparse | GitHub: nodeca/argparse
- NPM: argentum | GitHub: rumkin/argentum
- NPM: getoptie | GitHub: avz/node-getoptie
И два пакета, которые удалось обнаружить благодаря сервису npms.io:
- NPM: cli | GitHub: node-js-libs/cli
- NPM: dashdash | GitHub: trentm/node-dashdash
Соответственно, обновлённая таблица ниже:

Немного аналитики
Спустя три месяца с момента написания и публикации этой статьи на Habrahabr в
период с сентября по декабрь 2016 произошли некоторые интересные изменения в
сводной таблице.

- Рейтинг почти всех модулей вырос за исключением малопопулярных
stdio, getoptie, argentum. - Вперёд «рванул» yargs и вышел на второе место, сместив на третье minimist.
Это вполне объяснимо: minimist не развивается уже больше года, в то время как
для yargs регулярно выходят новые версии. - Пакет command-line-args обошёл stdio.
- Из 14 пакетов только два улучшили свои позиции. Но скорость роста рейтинга
разная, поэтому через несколько месяцев можно ожидать выявления новых фаворитов. - Не совершенствовались следующие модули: minimist, optimist (deprecated),
nomnom (deprecated), stdio, getoptie и argentum. Вероятно эти модули скоро можно
будет объявлять deprecated.
Самый главный вывод, который хотелось бы сделать, это то, что стоит задуматься, а
нужно ли в своих новых проектах использовать популярный minimist, ведь он
достаточно давно не разрабатывается?
Расклад голосования на 30 декабря 2016 таков. Проголосовало 72 читателя, воздержалось 65.
Из них отдали свои голоса следующим образом:
- yargs 31% (22)
- commander 29% (21)
- minimist 21% (15)
- process.argv 8% (6)
- другой пакет 7% (5)
- optimist 4% (3)
Налицо наибольшая популярность у yargs и commander, при этом minimist
также достаточно популярен.
Обновление от 8 февраля 2019
Сводная таблица обновлена, преобразована в формат Markdown и дополнена пакетами has-flag , clp и clap . Результаты голосования остались примерно такими же, какими были при последнем обновлении от 30 декабря 2016 года.
Обновление от 7 января 2020
Обновлена сводная таблица. Рейтинг пакетов существенно не изменился. Результаты голосования также показывают, что за год предпочтения ощутимо не поменялись.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Какой пакет для разбора опций командной строки в Node.js используете Вы? (Если несколько, то выберите наиболее предпочтительный вариант.)
Использование буферов в Node.js

Буфер — пространство в памяти (как правило, оперативной), в котором хранятся бинарные данные. В Node.js мы можем получить доступ к этим пространствам памяти со встроенным классом Buffer . Буферы хранят последовательность целых чисел, аналогично массиву в JavaScript. В отличие от массивов, вы не можете изменять размер буфера после его создания.
Возможно, вы уже опосредованно использовали буферы, если вы уже написали код Node.js. Например, когда вы читаете данные из файла с fs.readFile() , данные, возвращаемые в обратный вызов или Promise, являются буферным объектом. Кроме того, когда запросы HTTP поступают в Node.js, возвращаются потоки данных, которые временно хранятся во внутреннем буфере, когда пользователь не может обработать весь поток сразу.
Буферы управления полезны при взаимодействии с бинарными данными — как правило, на низком уровне сетей. Также они дают возможность осуществлять точную манипуляцию данными в Node.js.
В этом обучающем вы будете использовать Node.js REPL для работы с различными примерами буферов — создание буферов, чтение из буферов, запись данных в буферы и копирование из буферов, а также использование буферов для конвертации между бинарными и кодированными данными. В конце этого обучающего модуля вы научитесь использовать класс Buffer для работы с бинарными данными.
Предварительные требования
- Вам потребуется установить Node.js на вашем рабочем компьютере. В этом обучающем руководстве мы используем версию 10.19.0. Чтобы установить его в macOS или Ubuntu 18.04, следуйте указаниям руководства Установка Node.js и создание локальной среды разработки в macOS или раздела Установка с помощью PPA руководства Установка Node.js в Ubuntu 18.04.
- В этом обучающем руководстве вы будете работать с буферами в Node.js REPL (чтение-оценка-печать-цикл). Если хотите освежить знания по эффективному использованию Node.js REPL, почитайте наше руководство Как использовать Node.js REPL.
- Для этой статьи мы предполагаем, что пользователю будет удобнее использовать базовый JavaScript и его типы данных. Вы можете получить эти базовые знания в нашей серии Программирование на JavaScript.
Шаг 1 — Создание буфера
На первом шаге вы увидите два основных способа создания буферного объекта в Node.js.
Для выбора нужного метода необходимо ответить на вопрос: Вы хотите создать новый буфер или извлечь буфер из существующих данных? Если вы хотите хранить в памяти данные, которые еще не получили, то вам нужно создать новый буфер. Для этого в Node.js мы используем функцию alloc() класса Buffer .
Давайте откроем Node.js REPL и посмотрим. В терминале введите команду node :
Появится командная строка, начинающаяся с > .
Функция alloc() принимает размер буфера в качестве первого и единственного аргумента. Размер представляет собой целое число, указывающее, сколько байтов памяти будет использовать буферный объект. Например, если бы мы захотели создать буфер размером 1КБ (килобайт), что эквивалентно 1024 байтам, то мы бы ввели в консоль следующее:
Для создания нового буфера мы использовали глобально доступный класс Buffer , который имеет метод alloc() . Предоставив 1024 в качестве аргумента для alloc() , мы создали буфер размером 1 КБ.
По умолчанию при инициализации буфера с помощью alloc() буфер заполнен бинарными нулями в качестве заполнителя для дальнейших данных. Но при желании мы можем изменить значение по умолчанию. Если бы мы захотели создать новый буфер с единицами вместо нулей , то мы бы настроили второй параметр функции alloc() — fill .
Создайте в терминале новый буфер (при помощи командной строки диалога REPL), заполненный единицами :
Мы только что создали новый буферный объект, указывающий на пространство в памяти, в котором хранится 1 КБ единиц. Хотя мы ввели целое число, все данные, хранимые в буфере, являются бинарными.
Бинарные данные могут быть представлены самыми различными форматами. Например, рассмотрим бинарную последовательность, представляющую байт данных: 01110110 . Если бы эта бинарная последовательность представляла строку на английском языке с использованием стандарта кодирования ASCII, то это была бы буква v . Но если бы наш компьютер обрабатывал изображение, то эта бинарная последовательность могла бы содержать информацию о цвете пикселя.
Компьютер понимает, что их необходимо обрабатывать по-разному, т.к. байты закодированы по-разному. Кодирование байта — это форма байта. Буфер в Node.js использует систему кодирования UTF-8 по умолчанию, если она инициализируется со строковыми данными. Байт в UTF-8 представляет собой число, букву (на английском и других языках) или символ. UTF-8 — надмножество ASCII (Американский стандартный код информационного обмена). ASCII может кодировать байты с прописными и строчными английскими буквами, цифрами 0-9, и некоторыми другими символами — например, восклицательным знаком (!) или амперсандом (&).
Если бы мы писали программу, способную работать только с символами ASCII, то мы бы смогли изменить кодировку, используемую нашим буфером, при помощи третьего аргумента функции alloc() — encoding .
Создадим новый буфер длиной пять байтов для хранения только символов ASCII:
Буфер инициализируется с пятью байтами символа а при помощи представления ASCII.
Примечание. По умолчанию Node.js поддерживает следующие кодировки символов:
- ASCII, представленная как ascii
- UTF-8, представленная как utf-8 или utf8
- UTF-16, представленная как utf-16le или utf16le
- UCS-2, представленная как ucs-2 или ucs2
- Base64, представленная как base64
- Hexadecimal, представленная как hex
- ISO/IEC 8859-1, представленная как latin1 или binary
Все эти значения можно использовать в функциях класса Buffer, принимающих параметр кодирования . Таким образом, все эти значения действительны для метода alloc() .
Пока что мы создавали новые буферы с помощью функции alloc() . Но иногда нам может потребоваться создать буфер из уже существующих данных, например строки или массива.
Для создания буфера из уже существовающих данных мы используем метод from() . Мы можем использовать эту функцию для создания буферов из следующего:
- Массив целых чисел: целые значения могут составлять от 0 до 255 .
- ArrayBuffer : это объект JavaScript, хранящий фиксированную длину байтов.
- Строка.
- Еще один буфер.
- Другие объекты JavaScript, имеющие свойство Symbol.toPrimitive . Это свойство указывает для JavaScript, как конвертировать объект в примитивный тип данных: булев , пустой , неопределенный , число , строка или символ . Дополнительную информацию о Symbols можно найти в документации JavaScript сообщества Mozilla.
Посмотрим, как можно создать буфер из строки. В диалоге Node.js введите следующее:
Теперь у нас есть буферный объект, созданный из строки My name is Paul . Создадим новый буфер из другого буфера, который мы создали ранее:
Мы создали новый буфер asciiCopy , содержащий те же данные, что и asciiBuf .
Теперь, когда мы получили опыт создания буферов, мы можем переходить к примерам чтения их данных.
Шаг 2 — Чтение из буфера
Есть много способов доступа к данным в буфере. Мы можем получить доступ к отдельному байту в буфере, а также мы можем извлечь все содержимое.
Для доступа к одному байту буфера мы передаем индекс или местоположение требуемого байта. Буферы хранят данные последовательно, в виде массивов. Они также индексируют свои данные в виде массивов, начиная с 0 . Мы можем использовать обозначение массива в объекте буфера для получения отдельного байта.
Посмотрим, как это выглядит, создав буфер из строки в REPL:
Теперь прочитаем первый байт этого буфера:
При нажатии ENTER REPL отобразит следующее:
Output72
Целое число 72 соответствует представлению UTF-8 для буквы H .
Примечание: значения для байтов могут быть числами от 0 до 255 . Байт — последовательность из 8 бит. Бит — бинарная единица данных, поэтому он может иметь только два значения: 0 или 1 . Если у нас есть последовательность из 8 битов и двух возможных значений для бита, то у нас есть максимум 2⁸ возможных значений байта. Таким образом, в байте может быть максимум 256 чисел. Поскольку мы начинаем считать с нуля, это означает, что наше самое высокое число — 255.
Давайте сделаем то же самое для второго байта. Введите в REPL следующее:
REPL возвращает 105 , что представляет собой строчную букву i .
Наконец, получим третий символ:
Вы увидите число 33 , отображаемое в REPL, что соответствует символу ! .
Давайте попробуем извлечь байт из недействительного индекса:
REPL выдаст следующее:
Outputundefined
Это похоже на то, как если бы мы попытались получить доступ к элементу в массиве с неверным индексом.
Теперь, когда мы увидели, как читать отдельные байты буфера, рассмотрим наши варианты одновременного получения всех данных, хранимых в буфере. Объект буфера имеет методы toString() и toJSON() , возвращающие все содержимое буфера в двух разных форматах.
Как и предполагает его название, метод toString() конвертирует байты буфера в строку и возвращает их пользователю. Если мы используем этот метод в hiBuf , то получаем строку Hi! . Давайте попробуем.
В диалоге введите следующее:
REPL выдаст следующее:
Output'Hi!'
Этот буфер был создан из строки. Посмотрим, что произойдет, если мы используем toString() в буфере, который не был составлен из строковых данных.
Создадим новый пустой буфер размером 10 байтов.
Теперь используем метод toString() :
Мы увидим следующий результат:
'\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'
Строка \u0000 представляет собой символ Unicode для NULL . Он соответствует числу 0 . Когда данные буфера не кодируются как строка, метод toString() возвращает кодирование UTF-8 байтов.
toString() имеет опциональный параметр — encoding (кодировка) Мы можем использовать этот параметр для изменения кодирования возвращаемых данных буфера.
Например, если вы хотите шестнадцатеричную кодировку hiBuf , то необходимо ввести в диалоге следюущее:
Это выражение даст следующее:
Output'486921'
486921 — это шестнадцатеричное представление байтов, представляющих строку Hi! . В Node.js, когда пользователи хотят конвертировать кодирование данных из одной формы в другую, то обычно они ставят строку в буфер и вызывают toString() с необходимым кодированием.
Метод toJSON() ведет себя по-разному. Независимо от того, был ли создан буфер из строки или нет, он всегда возвращает данные как целочисленное представление байта.
Давайте снова используем буферы hiBuf и tenZeroes , чтобы потренироваться с JSON() . В диалоге введите следующее:
REPL выдаст следующее:
Output
Объект JSON имеет свойство type , которое всегда будет иметь класс Buffer . Это сделано для того, чтобы программы могли отличать эти объекты JSON от других объектов JSON.
Свойство data (данные) содержит массив целочисленного представления байтов. Возможно, вы заметили, что 72 , 105 и 33 соответствуют значениям, полученным нами при индивидуальном выводе байтов.
Давайте попробуем метод toJSON() с tenZeroes :
В REPL вы увидите следующее:
Output
Тип — такой же, как и ранее. Но теперь данные являются массивом с десятью нулями.
Теперь, когда мы рассмотрели основные способы чтения из буфера, посмотрим, как можно изменять содержимое буфера.
Шаг 3 — Изменение буфера
Есть много способов изменения существующего буферного объекта. Как и в случае с чтением, мы можем изменять байты в буфере по отдельности с использованием синтаксиса массива. Также мы можем записывать новое содержимое в буфер, заменяя существующие данные.
Давайте начнем с того, как можно изменять отдельные байты буфера. Вспомним нашу переменную буфера hiBuf , содержащую строку Hi! . Давайте изменим каждый байт, чтобы он вместо этого содержал Hey .
В REPL сначала попробуем задать второй элемент hiBuf как e :
Теперь рассмотрим этот буфер в качестве строки для подтверждения того, что он хранит нужные данные. Далее вызовите метод toString() :
Его значение будет таким:
Output'H\u0000!'
Мы получили такое странное значение, поскольку буфер может принимать только целые числа. Мы не можем присвоить его букве e ; вместо этого, нам нужно присвоить ему число, чей бинарный эквивалент представляет собой e :
Теперь, когда мы вызываем метод toString() :
Мы получаем следующее в REPL:
Output'He!'
Для изменения последнего символа в буфере нам нужно задать третий элемент в виде целого числа, соответствующего байту y :
Давайте подтвердим, используя метод toString() еще раз:
Ваш REPL отобразит следующее:
Output'Hey'
Если мы попытаемся написать байт, который находится вне диапазона буфера, то он будет проигнорирован, а содержимое буфера не изменится. Например, попробуем задать несуществующий четвертый элемент буфера, чтобы :
Мы можем подтвердить, что буфер не изменился с помощью метода toString() :
В результате по-прежнему получаем:
Output'Hey'
Если захотим изменить содержимое всего буфера, то можем использовать метод write() . Метод write() принимает строку, которая заменит содержимое буфера.
Давайте используем метод write() для изменения содержимого hiBuf обратно в Hi! . В оболочке Node.js введите в диалоге следующую команду:
Метод write() выдал результат 3 в REPL. Это связано с тем, что он записал три байта данных. Каждая буква имеет размер в один байт, поскольку в этом буфере используется кодировка UTF-8, где каждому символу соответствует один байт. Если бы в буфере использовалась кодировка UTF-16, где предусмотрено минимум два байта на символ, то функция write() выдала бы результат 6 .
Теперь проверьте содержимое буфера, используя toString() :
REPL выдаст следующее:
Output'Hi!'
Это быстрее, чем изменять каждый элемент по отдельным байтам.
Если вы попробуете записать больше байтов, чем вмещается в буфер, то буферный объект будет принимать только то, на что хватает байтов. Для иллюстрации создадим буфер, в котором хранится три байта:
Теперь попробуем написать в него слово Cats :
Когда оценивается вызов write() , REPL возвращает 3 — это указывает на то, что в буфер было записано только три байта. Теперь убедитесь, что буфер содержит первые три байта:
REPL выдает следующее:
Output'Cat'
Функция write() добавляет байты в последовательном порядке, поэтому только первые три байта были помещены в буфер.
Для сравнения создадим буфер , в котором хранится четыре байта:
Запишем в него то же самое содержимое:
Затем добавим какое-нибудь новое содержимое, которое занимает меньше места, чем первоначальное содержимое:
Поскольку буферы записывают данные последовательно, начиная с 0 , если мы распечатаем содержимое буфера:
Мы увидим приветствие:
Output'Hits'
Первые два символа перезаписаны, но остальная часть буфера не затронута.
Иногда данные, которые мы хотим иметь в нашем ранее созданном буфере, находятся не в строке, а в другом буферном объекте. В таких случаях мы можем использовать функцию copy() для изменения того, что хранится в нашем буфере.
Давайте создадим два новых буфера:
Буферы wordsBuf и catchphraseBuf содержат строковые данные. Мы хотим изменить catchphraseBuf так, чтобы в нем хранились слова Nananana Turtle! вместо слов Not sure Turtle! . Мы используем copy() для получения Nananana из wordsBuf в catchphraseBuf .
Для копирования данных из одного буфера в другой мы используем метод copy() в том буфере, который является источником информации. Поэтому, поскольку wordsBuf имеет строковые данные, которые мы хотим скопировать, нам нужно копировать следующим образом:
Параметр target в данном случае — catchphraseBuf .
Когда мы вводим это в REPL, получаем результат 15 , указывающий на то, что было записано 15 байтов. Строка Nananana использует только 8 байтов данных, поэтому мы сразу же узнаем, что наша копия не была отправлена так, как было необходимо. Используйте метод toString() для просмотра содержимого catchphraseBuf :
REPL выдает следующее:
Output'Banana Nananana!'
По умолчанию copy() приняла все содержимое wordsBuf и поместила его в catchphraseBuf . Нам нужно быть более избирательными для нашей задачи и копировать только Nananana . Давайте перепишем первоначальное содержимое catchphraseBuf перед тем, как продолжить:
Функция copy() имеет несколько дополнительных параметров, позволяющих нам настраивать данные, копируемые в другой буфер. Вот список всех параметров этой функции:
- target — единственный необходимый параметр функции copy() . Как мы увидели ранее, это тот буфер, в который мы хотим копировать данные.
- targetStart — индекс байтов в целевом буфере, в который мы собираемся копировать данные. По умолчанию значение равно 0 , это означает, что копируются данные, начинающиеся с начала буфера.
- sourceStart — индекс байтов в исходном буфере, откуда мы собираемся копировать данные.
- sourceEnd — индекс байтов в исходном буфере, где мы должны прекратить копировать данные. По умолчанию значение равно длине буфера.
Поэтому для копирования Nananana из wordsBuf в catchphraseBuf наша цель должна быть catchphraseBuf , как и ранее. targetStart будет равен 0 , поскольку мы хотим, чтобы слово Nananana стояло в начале catchphraseBuf . sourceStart должен быть равен 7 , т.к. это индекс того, где слово Nananana начинается в wordsBuf . sourceEnd будет по-прежнему равен длине буферов.
В диалоге REPL скопируйте содержимое wordsBuf следующим образом:
REPL подтверждает, что было записано 8 байтов. Обратите внимание, что wordsBuf.length используется в качестве значения для параметра sourceEnd . Как и массивы, свойство length дает нам размер буфера.
Теперь посмотрим содержимое catchphraseBuf :
REPL выдает следующее:
Output'Nananana Turtle!'
Успешно! Мы смогли изменить данные catchphraseBuf , скопировав содержимое wordsBuf .
Если хотите, можете выйти из Node.js REPL. Отметьте, что при этом все созданные переменные станут недоступными:
Заключение
В этом руководстве вы узнали, что буферы представляют собой пространства фиксированной длины в памяти, хранящие бинарные данные. Сначала вы создали буферы, задав их размер в памяти и инициализировав их с существующими данными. Затем вы прочли данные из буфера посредством просмотра их отдельных байтов и при помощи методов toString() и toJSON() . В конце вы изменили данные, сохраненные в буфере, изменив их отдельные байты, а также используя методы write() и copy() .
Буферы дают вам хорошее представление о том, как Node.js манипулирует бинарными данными. Теперь, когда вы можете взаимодействовать с буферами, вы можете наблюдать различные способы кодирования символов, влияющие на то, как хранятся данные. Например, вы можете создать буферы из строковых данных, которые не кодируются в UTF-8 или ASCII, и посмотреть их разницу в размере. Также вы можете взять буфер с UTF-8 и использовать toString() для его конвертации в другие системы кодирования.
Для получения информации о буферах в Node.js вы можете прочитать документацию Node.js в объекте Buffer . Если хотите продолжить изучение Node.js, то можете вернуться к серии Программирование на Node.js или ознакомиться с проектами и конфигурациями на нашей странице разделов Node.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
Основы работы с модулями в Node.js
Любой проект посложнее «Hello World» состоит из некоторого количества файлов, по которым разносят код. Это дает возможность структурировать проект, вынести независимые части, которые можно будет использовать в других проектах и вообще сделать код нагляднее.
Так вот, в Node.js каждый такой файл и представляет собой модуль, который можно подключить.
Подключение происходит с помощью вызова функции require , которой нужно передать путь к файлу.
var authModule = require('./auth');
Данный код подключает модуль авторизации и делает его доступным через переменную authModule .
В зависимости от того, какой параметр передан в функцию require , будет отличатся алгоритм подключения модуля. Так что давайте посмотрим на принципы подключения модулей в Node.js. Отмечу, что вся эта информация доступна в документации.
Вместе с Node.js поставляется несколько встроенных модулей, для подключения которых нужно просто указать название модуля.
var http = require('http'); var cluster = reqiure('cluster');
Нужно отметить, что встроенные модули имеют приоритет над всеми остальными, если в функцию require передано их название. Так к примеру, require(‘http’) всегда вернет встроенный модуль, даже если будет сторонний модуль с таким названием или файл с таким именем. Список всех встроенных модулей и документацию по ним можно найти на сайте. Исходники этих модулей можно посмотреть в репозитории проекта.
Если передано название модуля и он не является встроенным, тогда идет подключение модуля из папки node_modules . В данной папке находятся все модули, которые добавлены с помощью NPM. NPM — это менеджер пакетов для Node.js, который упрощает поиск и подключение сторонних модулей. На момент написания этой статьи в нем находилось уже 89 503 модулей.
Чтобы подключить модуль который находится в node_modules достаточно указать его название.
var express = require('express'); var async = require('async');
Давайте рассмотрим ситуацию, когда такой код был вызван из папки ‘/var/www/demo’ , тогда Node.js попытается найти указанные модули в следующих папках:
/var/www/demo/node_modules /var/www/node_modules /var/node_modules /node_modules
Node.js рекурсивно, каждый раз переходя в родительскую папку, будет искать папку node_modules с нужным модулем. NPM также даем возможность установить модуль глобально ( npm install -g MODULE ), тогда он будет доступен из любого места.
Рассмотрим еще один вариант подключения, если переданный параметр в функцию require начинается с / , ../ , или ./ , тогда файл для подключения будет происходить по абсолютному пути или относительно текущей папки.
var logger = require('../lib/logger'); var profiler = require('/var/lib/profiler');
Сначала будет проверено или существует файл с именем точно соответствующим указанному, если такой файл не будет найден, тогда Node.js попытается подключить файл добавляя к имени разные расширения: .js , .json , а также .node .
Поэтому нет необходимости указывать файл с расширением, так как require(‘./lib/users.js’) и require(‘./lib/users’) подключит один и тот же модуль.
Модули могут быть настолько большими, что может возникнет необходимость вынести некоторые части в отдельные файлы, чтобы лучше организовать код модуля. А так как эти файлы используются только этим модулем, не нужно чтобы они находились среди остальных модулей. Для решения этой проблемы в Node.js есть возможность организовать модуль в виде папки в которой будут находится все файлы модуля. Чтобы подключить такой модуль нужно просто передать путь данной папки в функцию require .
Представим, что у нас есть модули и один и них logger представлен в виде папки с файлами:
┌─auth.js ├─user.js └─logger ├─ index.js ├─ console.js ├─ package.json ├─ config.js └─ db.js
Подключение модуля logger заключается в том, что мы просто передадим путь к этой папки:
var require('./logger');
Дальше Node.js сам попытается определить какой из файлов папки представляет собой точку входа для модуля. Для начала будет проверено или существует в папке файл package.json в котором будет указано в поле main имя файла, который нужно подключить.
В нашем случае будет загружен файл ‘./logger/console.js’ . Если файла package.json нету, тогда Node.js попытается подключить файлы ‘./logger/index.js’ или ‘./logger/index.node’ .
С подключением модулей закончили, теперь рассмотрим несколько интересных моментов, связанных с ними. Независимо от того как вы подключаете модуль, он кэшируется сразу после подключения. Это означает, что сколько бы раз не подключался модуль, его код исполнится только один раз.
Это поведение можно изменить, если после каждого вызова модуля удалять его из кэша.
delete require.cache[ module ];
module — это параметр, который вы передавали функции require для подключения модуля.
Так что если вам нужно, чтобы ваш модуль каждый раз когда его подключают что-то выполнял — для этого нужно или чистить кэш, или возвращать функцию, которую нужно будет вызвать, что выполнить работу.
Чтобы получить полный путь, по которому был найден модуль, можно воспользоваться функцией require.resolve .
var modulePath = require.resolve('express');
Эту функцию можно использовать, если у вас так случилось, что установлены модули разных версий в нескольких местах и нужно удостоверится, что подключается нужная версия. Нужно зайти в папку где лежит файл, в котором происходит подключение, запустить Node.js в режиме работы из командной строки и вызвать функцию с нужным модулем. Конечно, такая ситуация означает, что нужно пересмотреть структуру проекта и лучше вынести все подключаемые модули в корневую папку проекта.
Эту функцию также можно использовать если вы хотите подключить один из файлов модуля, который установлен через NPM.
var package = require.resolve('express/package.json');
Так как вы не знаете где находится папка node_modules с нужным модулем, можно воспользоваться тем, что Node.js сам пройдется по всем возможным местам расположения модуля.
Функция require кроме всего этого имеет еще одно полезное свойство main . Оно хранит в себе модуль который был запущен из командной строки. Так что из любого модуля можно узнать или данный модуль был запущен напрямую или он был подключен как зависимость другого модуля.
Кроме уже рассмотренной функции require , в каждом модуле доступен объект module . Основная задача этого объекта дать возможность модулю вернуть результат своего исполнения. Это может быть и объект, и функция, и строка — любой тип данных.
В объекта module есть свойство exports и ему нужно присваивать все что вы хотите вернуть из модуля. Именно module.exports вернется как результат подключения модуля.
module.exports.createUser = function ()
После подключения модуля с данным кодом, в ответе будет объект с данным методом.
Можно также воспользоваться более кратким вариантом, переменной exports , которая по-сути просто ссылка на module.exports .
exports.createUser = function ()
Так как это ссылка, между этими двумя вариантами существует одна важная разница. Результат работы модуля можно вернуть только через объект module.exports , поэтому, если переменной exports присвоить другое значение, она уже не будет ссылаться на module.exports , а значит модуль ничего и не вернет.
А вот сам module.exports можно изменить. Вместо объекта, например, можно вернуть функцию и тогда результатом работы модуля будет не объект с методом в виде некоторой функции, а сама функция.
С помощью module также можно узнать или данный модуль запущен из командной строки. Для этого нужно проверить свойство module.parent — оно должно быть undefined .
Я думаю этой информации будет достаточно для работы с модулями в Node.js. Также я рекомендую ознакомится с NPM, так как с ним вам придется сталкиваться в каждом проекте на Node.js.
