Основы работы с модулями в 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 имя файла, который нужно подключить.

    {
        main: "./console.js"
    }

В нашем случае будет загружен файл './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 () {
      return new User();
    }

После подключения модуля с данным кодом, в ответе будет объект с данным методом.

Можно также воспользоваться более кратким вариантом, переменной exports, которая по-сути просто ссылка на module.exports.

    exports.createUser = function () {
      return new User();
    }

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

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

С помощью module также можно узнать или данный модуль запущен из командной строки. Для этого нужно проверить свойство module.parent — оно должно быть undefined.

Я думаю этой информации будет достаточно для работы с модулями в Node.js. Также я рекомендую ознакомится с NPM, так как с ним вам придется сталкиваться в каждом проекте на Node.js.
  • +20
  • 76k
  • 7
Поделиться публикацией

Похожие публикации

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 7

    +12
    Вы уж извините, но совсем для малышей.
      0
      ни чего удивительно, в духа Хабра-туториала
        +2
        Тогда не помешало бы рассказать о том, что можно использовать не весь модуль, а лишь некоторые его функции, например:

        Существует моудль math.js:
        exports.add = function() {
            var sum = 0, i = 0, args = arguments, l = args.length;
            while (i < l) {
                sum += args[i++];
            }
            return sum;
        };
        

        И какой-нибудь код, который использует функцию add из этого модуля
        var add = require('math').add;
        exports.increment = function(val) {
            return add(val, 1);
        };
        

        В данном примере мы видим, что переменной add идет присвоение функции add из модуля math
          +1
          Какой в этом смысл? При вызове require модуль в любом случае обрабатывается целиком и кешируется для всех последующих вызовов.
            –1
            Позвольте ответить вопросом на вопрос: а какой смысл работать с объектом, в котором находится N функций, если вам нужна только одна?
      0
      Так что из любого модуля можно узнать или данный модуль был запущен напрямую или он был подключен как зависимость другого модуля.

      Приведите пожалуйста пример кода.
        0
        if (require.main === module) {
            // Модуль вызван напрямую
        } else {
            // require.main не ссылается на данный модуль, значит 
            // данный модуль был подключен с помощью require() в require.main
            // или другом модуле, которых был подключен в require.main
        }
        

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

      Самое читаемое