node modules что это
npm для простых смертных
Эта статья предназначена для тех, кто не очень дружит с Node.js, но хочет использовать приложения вроде Grunt, Gulp и тому подобные. Процесс работы с этими приложениями подразумевает редактирование файла package.json и использование команд npm, так что понимание принципов работы npm поможет вам справиться с трудностями.
Node.js за 5 минут
Понимание того, что такое Node.js поможет вам лучше разобраться с npm. В двух словах — Node.js это интерпретатор языка JavaScript. Сам по себе Node.js является приложением, которое получает на входе и выполняет его.
Давайте создадим простую программу. Создайте файл helloworld.js и поместите в него следующий код:
Программа просто выведет строку «Hello World» в терминал.
Пакеты в Node.js
Файл package.json
Файл package.json содержит в себе информацию о вашем приложении: название, версия, зависимости и тому подобное. Любая директория, в которой есть этот файл, интерпретируется как Node., даже если вы не собираетесь публиковать его.
Способ использования файла package.json зависит от того, собираетесь ли вы скачивать пакет или публиковать его.
Скачивание пакетов
Если вы хотите скачать пакет вручную, вам необязательно использовать для этого package.json. Вы можете выполнить в терминале команду npm с названием нужного пакета в качестве аргумента команды, и пакет будет автоматически скачан в текущую директорию. Например:
Также для скачивания пакетов вы можете использовать package.json. Создайте в директории вашего проекта файл package.json, и добавьте в него следующий код (мы не указываем название нашего пакета и версию, мы не собираемся его публиковать; мы указываем название и версию пакетов для загрузки):
Публикация пакета
Чтобы опубликовать пакет, вам потребуется собрать все исходные коды и файл package.json в одной директории. В package.json должны быть указаны название, версия и зависимости пакета. Например:
Использование пакета в качестве исполняемого файла
Теперь вы знаете, что такое пакет и как он может зависеть от других пакетов. Также вы узнали, как npm работает с пакетами. Давайте перейдём от теории к практике и установим Grunt.
Установка Grunt с помощью npm
Подробно о модулях в Node.js
Подключение модулей в Node делается примерно так :
Рассмотрим каждый из этих этапов подробнее.
Путь к файлу может быть указан как относительный, однако перед загрузкой файла в память, Node опеределяет его абсолютный путь.
Когда мы запрашиваем модуль без указания пути к нему :
Создадим папку node_modules в текущей директории и создадим в ней файл find-me.js такого создержания :
Тогда require(‘find-me’); найдёт этот модуль.
Модуль в виде папки
Относительные и абсолютные пути
Отношения parent-child между файлами
Создадим файл lib/util.js :
Теперь запускаем файл index.js :
exports, module.exports, и синхронная загрузка модулей
Теперь запустим index.js и увидим эти атрибуты :
Немного сократил выдачу чтобы лучше было видно. Объект exports теперь имеет те атрибуты которые мы задавали в каждом из модулей. Можете использовать сколько угодно атрибутов, и можно заменить весь объект на чтонибудь другое. К примеру можно заменить его на функцию :
Объект module.exports который есть в каждом модуле, это то что возвращает функция require из этого модуля.
Поменяем файл index.js :
Свойства из lib/util экспортируются в констранту UTIL :
В отложенном запуске console.log видим что lib/util.js и index.js полностью загрузились.
Объект exports становится завершённым когда Node завершает загрузку модуля и делает пометку loaded: true. Весь процесс подключения и загрузки модуля происходит синхронно. Это означает что нам нельзя изменять exports асинхронно. К примеру нельзя делать так :
Круговая, кольцевая, циклическая зависимость модулей.
Запускаем module1.js и видим следующее :
JSON и C/C++ аддоны
Подключение JSON файлов полезно в случае конфигурационных файлов. К примеру :
Подключается он так :
Как писать аддоны на C++ можно почитать тут.
Код каждого модуля оборачивается в функцию
Вспомним как работают глобальные переменные в браузере. Когда мы объявляем :
В Node это происходит по другому. Когда мы объявляем переменную в одном модуле, в других модулях она не видна.
Код модуля становится телом этой функции. Поэтому все переменные верхнего уровня в модуле имеют область видимости только в нём и не видны из других модулей.
У функции 5 аргументов : exports, require, module, __filename, и __dirname. Они только кажутся глобальными, но на самом деле у каждого модуля они свои. И получают свое значение когда Node запускает эту функцию враппер.
Это выглядит примерно так :
Объект require
Её можно переопределить к примеру так :
require.resolve
require.main
require.cache
Допустим у нас есть модуль такой :
Если мы подключаем этот модуль несколько раз, то дата будет одной и тойже, т.к. экземпляр модуля берётся из кэша. По сути это как singleton.
А теперь почистим кэш и посмотрим что получится :
Node.js v17.3.0 documentation
Modules: CommonJS modules #
In the Node.js module system, each file is treated as a separate module. For example, consider a file named foo.js :
Here are the contents of circle.js :
The module.exports property can be assigned a new value (such as a function or object).
Below, bar.js makes use of the square module, which exports a Square class:
The square module is defined in square.js :
The module system is implemented in the require(‘module’) module.
Accessing the main module #
Package manager tips #
Below we give a suggested directory structure that could work:
Let’s say that we wanted to have the folder at /usr/lib/node/ / hold the contents of a specific version of a package.
Because Node.js looks up the realpath of any modules it loads (that is, it resolves symlinks) and then looks for their dependencies in node_modules folders, this situation can be resolved with the following architecture:
Thus, even if a cycle is encountered, or if there are dependency conflicts, every module will be able to get a version of its dependency that it can use.
All together. #
To get the exact filename that will be loaded when require() is called, use the require.resolve() function.
Putting together all of the above, here is the high-level algorithm in pseudocode of what require() does:
Caching #
Modules are cached after the first time they are loaded. This means (among other things) that every call to require(‘foo’) will get exactly the same object returned, if it would resolve to the same file.
Provided require.cache is not modified, multiple calls to require(‘foo’) will not cause the module code to be executed multiple times. This is an important feature. With it, «partially done» objects can be returned, thus allowing transitive dependencies to be loaded even when they would cause cycles.
To have a module execute code multiple times, export a function, and call that function.
Module caching caveats #
Modules are cached based on their resolved filename. Since modules may resolve to a different filename based on the location of the calling module (loading from node_modules folders), it is not a guarantee that require(‘foo’) will always return the exact same object, if it would resolve to different files.
Core modules #
Node.js has several modules compiled into the binary. These modules are described in greater detail elsewhere in this documentation.
The core modules are defined within the Node.js source and are located in the lib/ folder.
Core modules can also be identified using the node: prefix, in which case it bypasses the require cache. For instance, require(‘node:http’) will always return the built in HTTP module, even if there is require.cache entry by that name.
Cycles #
When there are circular require() calls, a module might not have finished executing when it is returned.
Consider this situation:
By the time main.js has loaded both modules, they’re both finished. The output of this program would thus be:
Careful planning is required to allow cyclic module dependencies to work correctly within an application.
File modules #
Folders as modules #
It is convenient to organize programs and libraries into self-contained directories, and then provide a single entry point to those directories. There are three ways in which a folder may be passed to require() as an argument.
The first is to create a package.json file in the root of the folder, which specifies a main module. An example package.json file might look like this:
This is the extent of the awareness of package.json files within Node.js.
If there is no package.json file present in the directory, or if the «main» entry is missing or cannot be resolved, then Node.js will attempt to load an index.js or index.node file out of that directory. For example, if there was no package.json file in the previous example, then require(‘./some-library’) would attempt to load:
If these attempts fail, then Node.js will report the entire module as missing with the default error:
Loading from node_modules folders #
If it is not found there, then it moves to the parent directory, and so on, until the root of the file system is reached.
This allows programs to localize their dependencies, so that they do not clash.
It is possible to require specific files or sub modules distributed with a module by including a path suffix after the module name. For instance require(‘example-module/path/to/file’) would resolve path/to/file relative to where example-module is located. The suffixed path follows the same module resolution semantics.
Loading from the global folders #
If the NODE_PATH environment variable is set to a colon-delimited list of absolute paths, then Node.js will search those paths for modules if they are not found elsewhere.
On Windows, NODE_PATH is delimited by semicolons ( ; ) instead of colons.
NODE_PATH was originally created to support loading modules from varying paths before the current module resolution algorithm was defined.
NODE_PATH is still supported, but is less necessary now that the Node.js ecosystem has settled on a convention for locating dependent modules. Sometimes deployments that rely on NODE_PATH show surprising behavior when people are unaware that NODE_PATH must be set. Sometimes a module’s dependencies change, causing a different version (or even a different module) to be loaded as the NODE_PATH is searched.
Additionally, Node.js will search in the following list of GLOBAL_FOLDERS:
These are mostly for historic reasons.
It is strongly encouraged to place dependencies in the local node_modules folder. These will be loaded faster, and more reliably.
The module wrapper #
Before a module’s code is executed, Node.js will wrap it with a function wrapper that looks like the following:
By doing this, Node.js achieves a few things:
The module scope #
__dirname #
Example: running node example.js from /Users/mjr
__filename #
The file name of the current module. This is the current module file’s absolute path with symlinks resolved.
For a main program this is not necessarily the same as the file name used in the command line.
See __dirname for the directory name of the current module.
Running node example.js from /Users/mjr
exports #
module #
require(id) #
require.cache #
Modules are cached in this object when they are required. By deleting a key value from this object, the next require will reload the module. This does not apply to native addons, for which reloading will result in an error.
require.extensions #
Instruct require on how to handle certain file extensions.
Deprecated. In the past, this list has been used to load non-JavaScript modules into Node.js by compiling them on-demand. However, in practice, there are much better ways to do this, such as loading modules via some other Node.js program, or compiling them to JavaScript ahead of time.
require.main #
The Module object representing the entry script loaded when the Node.js process launched. See «Accessing the main module».
require.resolve(request[, options]) #
The paths option is now supported.
Use the internal require() machinery to look up the location of a module, but rather than loading the module, just return the resolved filename.
If the module can not be found, a MODULE_NOT_FOUND error is thrown.
require.resolve.paths(request) #
The module object #
In each module, the module free variable is a reference to the object representing the current module. For convenience, module.exports is also accessible via the exports module-global. module is not actually a global but rather local to each module.
module.children #
The module objects required for the first time by this one.
module.exports #
For example, suppose we were making a module called a.js :
Then in another file we could do:
Assignment to module.exports must be done immediately. It cannot be done in any callbacks. This does not work:
exports shortcut #
The exports variable is available within a module’s file-level scope, and is assigned the value of module.exports before the module is evaluated.
When the module.exports property is being completely replaced by a new object, it is common to also reassign exports :
module.filename #
The fully resolved filename of the module.
module.id #
The identifier for the module. Typically this is the fully resolved filename.
module.isPreloading #
module.loaded #
Whether or not the module is done loading, or is in the process of loading.
Основы работы с модулями в Node.js
Любой проект посложнее «Hello World» состоит из некоторого количества файлов, по которым разносят код. Это дает возможность структурировать проект, вынести независимые части, которые можно будет использовать в других проектах и вообще сделать код нагляднее.
Вместе с Node.js поставляется несколько встроенных модулей, для подключения которых нужно просто указать название модуля.
Чтобы подключить модуль который находится в node_modules достаточно указать его название.
Поэтому нет необходимости указывать файл с расширением, так как require(‘./lib/users.js’) и require(‘./lib/users’) подключит один и тот же модуль.
Представим, что у нас есть модули и один и них logger представлен в виде папки с файлами:
Подключение модуля logger заключается в том, что мы просто передадим путь к этой папки:
Дальше Node.js сам попытается определить какой из файлов папки представляет собой точку входа для модуля. Для начала будет проверено или существует в папке файл package.json в котором будет указано в поле main имя файла, который нужно подключить.
С подключением модулей закончили, теперь рассмотрим несколько интересных моментов, связанных с ними. Независимо от того как вы подключаете модуль, он кэшируется сразу после подключения. Это означает, что сколько бы раз не подключался модуль, его код исполнится только один раз.
Это поведение можно изменить, если после каждого вызова модуля удалять его из кэша.
module — это параметр, который вы передавали функции require для подключения модуля.
Так что если вам нужно, чтобы ваш модуль каждый раз когда его подключают что-то выполнял — для этого нужно или чистить кэш, или возвращать функцию, которую нужно будет вызвать, что выполнить работу.
Эту функцию можно использовать, если у вас так случилось, что установлены модули разных версий в нескольких местах и нужно удостоверится, что подключается нужная версия. Нужно зайти в папку где лежит файл, в котором происходит подключение, запустить Node.js в режиме работы из командной строки и вызвать функцию с нужным модулем. Конечно, такая ситуация означает, что нужно пересмотреть структуру проекта и лучше вынести все подключаемые модули в корневую папку проекта.
Эту функцию также можно использовать если вы хотите подключить один из файлов модуля, который установлен через NPM.
Так как вы не знаете где находится папка node_modules с нужным модулем, можно воспользоваться тем, что Node.js сам пройдется по всем возможным местам расположения модуля.
В объекта module есть свойство exports и ему нужно присваивать все что вы хотите вернуть из модуля. Именно module.exports вернется как результат подключения модуля.
После подключения модуля с данным кодом, в ответе будет объект с данным методом.
Использование модулей Node.js с приложениями Azure
Этот документ содержит указания по использованию модулей Node.js с приложениями, размещенными в Azure. В нем описывается, как обеспечить использование конкретной версии модуля приложением и использовать собственные модули в Azure.
Возможно, вы уже знакомы с использованием модулей Node.js, файлов package.json и npm-shrinkwrap.json. Ниже приводится краткая сводка по тем вопросам, которые рассматриваются в этой статье.
Служба приложений Azure может считывать файлы package.json и npm-shrinkwrap.json и устанавливать модули на основании записей в этих файлах.
Облачные службы Azure ожидают, что все модули должны быть установлены в среде разработки, а каталог node_modules должен быть включен в пакет развертывания. Можно включить поддержку установки модулей с помощью файлов package.json или npm-shrinkwrap.json в облачных службах, однако для такой конфигурации требуется настройка сценариев по умолчанию, используемых проектами облачных служб. Пример того, как настроить эту среду, см. в записи блога Azure Startup task to run npm install to avoid deploying node modules (Задача запуска Azure для выполнения установки npm во избежание развертывания модулей узла).
В данной статье не рассматриваются виртуальные машины Azure, так как процедура развертывания на виртуальной машине зависит от операционной системы, размещенной в этой виртуальной машине.
Модули Node.js
Модули — это загружаемые пакеты JavaScript, предоставляющие вашему приложению определенные функциональные возможности. Модули обычно устанавливаются с помощью программы командной строки npm, однако некоторые из них (например, HTTP-модуль) предоставляются в составе основного пакета Node.js.
Установленные модули сохраняются в каталоге node_modules, находящемся в корне структуры каталогов приложения. У каждого модуля в каталоге node_modules есть собственный каталог, который содержит все модули, от которых он зависит. Такие каталоги есть у каждого модуля в цепочке зависимостей. Эта среда позволяет каждому установленному модулю иметь собственные требования к версиям модулей, от которых он зависит, однако это может привести к слишком большому размеру структуры каталогов.
Когда каталог node_modules развертывается в составе приложения, размер развертывания становится больше, чем при использовании файла package.json или npm-shrinkwrap.json, однако это гарантирует, что в рабочей среде используются те же версии модулей, что и в среде разработки.
Собственные модули
Хотя большинство модулей представляют собой просто текстовые файлы JavaScript, некоторые модули являются двоичными образами, предназначенными для конкретной платформы. Такие модули обычно компилируются во время установки, обычно для этого используется Python и node-gyp. Так как облачные службы Azure полагаются на развертывание папки node_modules в составе приложения, любой собственный модуль, входящий в состав установленных модулей, должен работать в облачной службе при условии, что он был установлен и скомпилирован в системе разработки Windows.
Служба приложений Azure не поддерживает все собственные модули и может сообщать об ошибках при компиляции модулей со специфическими предварительными требованиями. Хотя у некоторых популярных модулей, например MongoDB, имеются дополнительные собственные зависимости, и они прекрасно работают без них, было найдено два обходных решения, подходящих почти для всех собственных модулей, доступных в настоящее время:
Выполните npm install на компьютере под управлением Windows, на котором установлены все необходимые компоненты собственного модуля. Затем разверните созданную папку node_modules как часть приложения в службе приложений Azure.
Использование файла package.json
С помощью файла package.json можно указать зависимости верхнего уровня, необходимые вашему приложению, чтобы платформа размещения могла установить зависимости, а не требовать от вас включить папку node_modules в состав развертывания. После развертывания приложения используется команда npm install, чтобы проанализировать файл package.json и установить все указанные зависимости.
Во время разработки можно использовать параметры —save, —save-dev или —save-optional при установке модулей, чтобы автоматически добавить запись для модуля в файл package.json. Дополнительные сведения см. в разделе с описанием npm-install.
Одна потенциальная проблема с файлом package.json заключается в том, что он указывает версию только для зависимостей верхнего уровня. Каждый установленный модуль может указать или не указывать версию модулей, от которых он зависит, поэтому в результате вы можете получить иную цепочку зависимостей, чем в среде разработки.
Если при развертывании приложения в службе приложений Azure ваш файл package.json ссылается на собственный модуль, то может отобразиться ошибка, аналогичная той, что возникает при публикации приложения с помощью Git:
npm ERR! module-name@0.6.0 install: ‘node-gyp configure build’
npm ERR! ‘cmd «/c» «node-gyp configure build»‘ failed with 1
Использование файла NPM-shrinkwrap. JSON
Файл npm-shrinkwrap.json представляет собой попытку устранения ограничений управления версиями модуля для файла package.json. Хотя файл package.json содержит только версии модулей верхнего уровня, файл npm-shrinkwrap.json содержит требования к версиям для всей цепочки зависимостей модулей.
Когда приложение будет готово к перемещению в рабочую среду, можно заблокировать требования к версии и создать файл npm-shrinkwrap.json с помощью команды npm shrinkwrap. Эта команда будет использовать версии, установленные в папке node_modules, а их запись будет производиться в файл npm-shrinkwrap.json. После развертывания приложения в среде внешнего размещения используется команда npm install, чтобы проанализировать файл npm-shrinkwrap.json и установить все указанные зависимости. Дополнительные сведения см. в статье о npm-shrinkwrap.
Если при развертывании приложения в службе приложений Azure ваш файл npm-shrinkwrap.json ссылается на собственный модуль, то может отобразиться ошибка, аналогичная той, что возникает при публикации приложения с помощью Git:
npm ERR! module-name@0.6.0 install: ‘node-gyp configure build’
npm ERR! ‘cmd «/c» «node-gyp configure build»‘ failed with 1