Как создать собственные модули в Node.js и опубликовать в NPM

Как создать собственные модули в Node.js и опубликовать в NPM
4.8 (96%) 5 votes

До того, как мы начнём разработать собственные модули в Node.js, давайте познакомимся с NPM. Узнаем что такое NPM, для чего он может понадобиться нам, как с ним можно работать, ознакомимся с основными командами и сделаем несколько важных настроек.
Содержимое:

Знакомство с NPM

Значительная часть обширной функциональности, связанной с Node, реализована в модулях независимых разработчиков. Это модули маршрутизации, модули для работы с реляционными и документными СУБД, модули шаблонов, модули тестирования и даже модули платежных сервисов.

Чтобы использовать модуль, вы можете загрузить его исходный код, а затем установить его вручную в своей среде. Многие модули содержат базовые инструкции по установке, или, как минимум, требования к установке можно вычислить просмотром файлов и каталогов, включенных в модуль. Однако существует куда более простой способ установки модулей Node — менеджер npm.

Node.js поставляется с установленной копией npm, но эта версия npm не всегда является самой новой. Если вы захотите использовать другую версию npm, введите следующую команду для обновления имеющейся версии (используйте sudo , если этого требует ваша система):

npm install npm -g

Чтобы получить подробную сводку команд npm, воспользуйтесь следующей командой:

npm help npm

Модули могут устанавливаться глобально или локально. Локальная установка лучше всего подходит для работы над изолированным проектом, когда всем остальным пользователям той же системы доступ к модулю не нужен. При локальной установке (а этот вариант используется по умолчанию) модуль устанавливается в текущей позиции каталога node_modules.

npm install имя_модуля

Например, для установки Express используется следующая команда:

npm install express

Программа npm не только устанавливает модуль Express, но и находит модули, от которых он зависит, и устанавливает их. Чем сложнее модуль, тем больше зависимостей устанавливается.

После того как модуль будет установлен, он будет находиться в каталоге node_modules вашего локального каталога. Все зависимости также будут установлены в каталог node_modules этого модуля.

Чтобы установить пакет глобально, используйте параметр -g или —global :

npm install express -g

Если вы работаете в системе Linux, не забудьте использовать sudo для глобальной установки модуля.

Если пакет существует в нескольких версиях, есть возможность установить конкретную версию:

npm install module_name@0.3

Если модуль больше не используется, его можно удалить:

npm uninstall имя_модуля

Следующая команда приказывает npm проверить наличие новых модулей и обновить найденные:

npm update

Также можно обновить один модуль:

npm update имя_модуля

А если вы хотите узнать, есть ли среди пакетов устаревшие, воспользуйтесь командой:

npm outdated

Для вывода списка всех установленных пакетов и зависимостей передайте параметр list , ls , la или ll :

npm ls

С параметрами la и ll команды выводят расширенные описания. Например, в число зависимостей Request входит tunnel-agent@0.4.1 (версия 0.4.1 пакета tunnel-agent). Что это вообще такое — tunnel-agent? Ввод запроса npm la в командной строке выводит список всех зависимостей, включая tunnel-agent, с дополнительной информацией:

tunnel-agent@0.4.1
HTTP proxy tunneling agent. Formerly part of mikeal/request,
now a standalone module
git+https://github.com/mikeal/tunnel-agent.git
https://github.com/mikeal/tunnel-agent#readme

Флаг -d устанавливает все зависимости напрямую. Например, в каталоге модуля введите команду:

npm install -d

Чтобы узнать, какие модули установлены глобально, используйте следующую команду:

npm ls -g

Для получения дополнительной информации об установке npm используйте команду config. Следующая команда выводит список параметров конфигурации npm:

npm config list

Более подробное описание всех параметров конфигурации выводится командой:

npm config ls -l

Изменение или удаление параметров конфигурации из командной строки осуществляется либо командой:

npm config delete ключ
npm config set ключ значение

Также можно провести поиск модуля по условиям, которые, как вы думаете, могут вернуть лучший результат:

npm search html5 parser

При первом проведении поиска npm строит индекс, на что может потребоваться несколько минут. Впрочем, после завершения индексирования вы получаете список возможных модулей, соответствующих заданному условию (или условиям).

Сайт npm предоставляет реестр модулей с возможностью просмотра и регулярно обновляемый список модулей, чаще всего используемых другими модулями или приложениями Node. В следующем разделе я приведу подборку таких модулей.

И последнее замечание по поводу npm перед тем, как двигаться дальше. В самом начале ваших экспериментов с npm в конце вывода могут появляться предупреждения. В первой строке сообщается о том, что файл package.json не найден, а в остальных содержатся всевозможные предупреждения, связанные с отсутствующим файлом package.json .

Документация npm рекомендует создать файл package.json для сопровождения локальных зависимостей. В принципе, это не обязательно, но предупреждения слегка раздражают.

Чтобы создать файл package.json по умолчанию в каталоге проекта, выполните следующую команду:

npm init --yes

Команда создает в каталоге файл package.json по умолчанию; при этом вам будет предложено ответить на ряд вопросов по проекту: ваше имя, название проекта и т. д., причем у каждого вопроса имеется значение по умолчанию. В дальнейшем при установке модуля вы уже не будете получать часть раздражающих сообщений. Чтобы избавиться от остальных, необходимо обновить данные JSON в этом файле и включить в них описание и информацию о репозитории. Кроме того, если вы хотите, чтобы файл обновлялся при установке обновленного модуля, используйте следующий синтаксис:

npm install express --save-dev

Имя и версия модуля сохраняются в поле devDependencies файла package.json. Также можно сохранить модуль в зависимостях модулей, но об этом подробнее я расскажу в следующей части этой статьи.

Создание и публикация собственных модулей Node

Как и в случае с кодом JavaScript на стороне клиента, многократно используемый код JavaScript стоит выделить в собственные библиотеки. Единственное отличие заключается в том, что вам придется выполнить пару лишних действий для преобразования библиотеки JavaScript в модуль, рассчитанный на взаимодействие с Node.



Создание модуля

Допустим, у вас имеется библиотечная функция JavaScript concatArray, которая получает строку и массив строк, выполняет конкатенацию первой строки с каждой строкой в массиве и возвращает новый массив:

function concatArray(str, array) {
  return array.map(function(element) {
    return str + ' ' + element;
  });
}

Вы хотите использовать эту функцию наряду с другими в своих приложениях Node. Чтобы преобразовать библиотеку JavaScript для использования в Node, необходимо экспортировать все функции с помощью объекта exports , как показано в следующем примере:

exports.concatArray = function(str, array) {
  return array.map(function(element) {
    return str + ' ' + element;
  });
};

Чтобы использовать concatArray в приложении Node, импортируйте библиотеку. После этого вы сможете использовать экспортированную функцию в своем приложении:

const newArray = require('./arrayfunctions.js');
let data = newArray.concatArray('hello', ['test1','test2']);
console.log(data);

Вы также можете создать модуль, состоящий из конструктора или функции, и экспортировать его с использованием module.exports.

Например, модуль Mime , от которого могут зависеть многие другие модули, создает функцию Mime():

function Mime() { ... }

добавляет функциональность с использованием свойства prototype :

Mime.prototype.define = function(map) {...}

создает экземпляр по умолчанию:

const mime = new Mime();

присваивает функцию Mime одноименному свойству:

mime.Mime = Mime;

и после этого экспортирует экземпляр:

module.exports = mime;

После этого вы сможете использовать различные функции Mime в своем приложении:

const mime = require('mime');
console.log(mime.lookup('phoenix5a.png');

Упаковка целого каталога

Вы можете разбить свой модуль на несколько файлов JavaScript, находящихся в каталоге. Node может загружать содержимое каталогов при условии, что его содержимое организовано одним из двух способов.

Первый способ основан на создании файла package.json с информацией о каталоге. Структура содержит разнообразную информацию, но к упаковке модуля относятся два свойства — name и main:

{
  "name": "mylibrary",
  "main": "./mymodule/mylibrary.js"
}

Первое свойство, name , содержит имя модуля, а второе свойство, main, обозначает точку входа модуля.

Во втором способе загрузки содержимого в каталог добавляется файл index.js или index.node , служащий главной точкой входа модуля.

Зачем использовать каталог вместо одного модуля? Чаще всего это делается из-за того, что вы используете существующие библиотеки JavaScript и предоставляете файл — <<обертку>>, который <<упаковывает>> экспортируемые функции командой exports . А может быть, ваша библиотека настолько велика, что вы хотите разбить ее для удобства внесения изменений.

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

Подготовка модуля к публикации

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

Ранее я упоминала о файле package.json. Документацию по JSON для npm можно найти по этому адресу https://docs.npmjs.com/files/package.json. Она базируется на рекомендациях системы модулей CommonJS.

В пакет package.json рекомендуется включать следующие поля:

  • name — имя пакета (обязательно).
  • description — описание пакета.
  • version — текущая версия, соответствующая требованиям семантической версии (обязательно).
  • keywords — массив условий поиска.
  • maintainers — массив ответственных за сопровождение пакета (с именами, адресами электронной почты и сайтами).
  • contributors — массив соавторов пакета (с именами, адресами электронной почты и сайтами).
  • bugs — URL-адрес для отправки ошибок.
  • licenses — массив лицензий.
  • repository — репозиторий пакетов.
  • dependencies — необходимые пакеты и их номера версий.

Обязательны только поля name и version , хотя включить рекомендуется все поля. К счастью, npm упрощает создание этого файла. Если ввести в командной строке следующую команду:

npm init

программа переберет все обязательные/рекомендованные поля, предложив вам ввести значение каждого. Когда все будет сделано, она сгенерирует файл package.json.

Сначала мы создадим в node_modules подкаталог с именем inputcheck и перенесем в него существующий код InputChecker. Файлу необходимо присвоить имя index.js . Затем в код вносятся изменения и из него извлекается часть, реализующая новый объект. Мы сохраним ее для будущего тестового файла.

const util = require('util');
const eventEmitter = require('events').EventEmitter;
const fs = require('fs');
exports.InputChecker = InputChecker;

function InputChecker(name, file) {
    this.name = name;
    this.writeStream = fs.createWriteStream(`./${file}.txt`,
        {'flags' : 'a',
            'encoding' : 'utf8',
            'mode' : 0666
        }
    );
};

util.inherits(InputChecker,eventEmitter);
InputChecker.prototype.check = (input) => {
    const command = input.toString().trim().substr(0,3);
    if (command == 'wr:') {
        this.emit('write', input.substr(3, input.length));
    } else if (command == 'en:') {
        this.emit('end');
    } else {
        this.emit('echo', input);
    }
};

Экспортировать функцию объекта напрямую не удастся, потому что util.inherits ожидает, что в файле с именем InputChecker существует объект. Прототип объекта InputChecker изменяется позднее в файле, также можно было изменить ссылки в коде и использовать exports.InputChecker , но это неуклюжее решение. С таким же успехом объект можно присвоить в отдельной команде.

Чтобы создать файл package.json, я выполнил команду npm init и ответил на все вопросы.

Сгенерированный файл package.json для модуля inputChecker:

{
  "name": "inputcheck",
  "version": "1.0.0",
  "description": "Looks for and implements commands from input",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "command",
    "check"
  ],
  "author": "HackerX.ru",
  "license": "ISC"
}

Команда npm init не запрашивает информацию о зависимостях, поэтому их придется добавить прямо в файл. Впрочем, есть другое, более правильное решение — использовать команду npm install —save при установке модулей, чтобы зависимости были добавлены автоматически. Однако модуль InputChecker не зависит от внешних модулей, поэтому эти поля можно оставить пустыми.

На этой стадии мы можем протестировать новый модуль и убедиться в том, что он действительно работает как модуль. В листинге 3.4 приведен фрагмент предыдущей версии приложения InputChecker, которая тестирует новый объект, теперь выделенный в отдельное тестовое приложение.

Тестовое приложение InputChecker:

const inputChecker = require('inputcheck').InputChecker;
const ic = new inputChecker('Shelley','output');

ic.on('write', (data) =>
  this.writeStream.write(data, 'utf8');
);

ic.addListener('echo', ( data) =>
    console.log(`${this.name} wrote ${data}`);
);

ic.on('end', () => process.exit());

process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', (input) => ic.check(input));

Теперь тестовое приложение можно переместить в новый подкаталог test в каталоге модуля, чтобы оно было упаковано в модуле как пример. По общепринятым правилам следует предоставить каталог test с одним или несколькими тестовыми приложениями, а также каталог doc с документацией. Для такого маленького модуля файла README должно быть достаточно.

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

"scripts": {
  "test": "node test/test.js"
},

Это довольно примитивный тест, который не использует мощные тестовые возможности Node, но и он показывает, как может работать тестирование. Чтобы запустить тестовое приложение, введите в командной строке следующую команду:

npm test

Остается создать сжатый tar-архив модуля. Впрочем, если вы публикуете модуль так, как описано в разделе «Публикация модуля», этот шаг можно пропустить.

Публикация модуля

Создатели npm также предоставили нам руководство с подробным описанием того, что необходимо сделать для публикации модуля Node: Developer Guide (https://docs.npmjs.com/misc/developers).

В документации приведены дополнительные требования к файлу package.json. В дополнение к уже созданным полям также необходимо добавить поле directories с хешем папок, включая уже упоминавшиеся test и doc:

"directories": {
  "doc" : ".",
  "test" : "test",
  "example" : "examples"
}

Перед публикацией в руководстве рекомендуется протестировать модуль на корректность установки. Чтобы выполнить тестирование, введите следующую команду в корневом каталоге модуля:

npm install . -g

К этому моменту мы протестировали модуль inputChecker, изменили файл package.json, добавили в него каталоги и убедились в том, что пакет успешно устанавливается.

Затем необходимо добавить себя как пользователей npm, если это не было сделано ранее. Введите следующую команду:

npm adduser

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

Осталось сделать последний шаг:

npm publish

Можно указать путь к tar-архиву или каталогу. Как предупреждает Guide, доступ предоставляется ко всему содержимому каталога, если только вы не используете в файле package.json директиву .npmignore со списком файлов для игнорирования материала. Тем не менее перед публикацией модуля лучше просто удалить все, без чего можно обойтись.

После публикации — и после того, как исходный код также будет загружен в GitHub (если вы используете этот репозиторий), — модуль официально готов для использования другими разработчиками. Рекламируйте свой модуль в Твиттере, Google+, Facebook, на своем сайте и вообще повсюду, где люди смогут о нем узнать. Такая реклама не хвастовство, а распространение информации.


Об авторе

Занимаюсь программированием уже более 7 лет. Часто использую JavaScript (Node.js) и Python.

Комментарии