что такое node modules
Подробно о модулях в 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 с приложениями 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
Модули¶
Node.js использует модульную систему. То есть вся встроенная функциональность разбита на отдельные пакеты или модули. Модуль представляет блок кода, который может использоваться повторно в других модулях.
При необходимости мы можем подключать нужные нам модули. Какие встроенные модули есть в node.js и какую функциональность они предоставляют, можно узнать из документации.
После получения модуля мы сможем использовать весь определенный в нем функционал, который опять же можно посмотреть в документации.
В файле app.js подключим наш модуль:
В отличие от встроенных модулей для подключения своих модулей надо передать в функцию require относительный путь с именем файла (расширение файла необязательно):
Теперь изменим файл greeting.js :
Вообще объект module представляет ссылку на текущий модуль, а его свойство exports определяет все свойства и методы модуля, которые могут быть экспортированы и использованы в других модулях. Подробнее определение загрузки модуля и все его функции можно посмотреть на странице https://github.com/nodejs/node/blob/master/lib/module.js.
Далее изменим файл app.js :
Определение конструкторов и объектов в модуле¶
Кроме определения простейших функций или свойств в модуле могут определяться сложные объекты или функции конструкторов, которые затем используются для создания объектов. Так, добавим в папку проекта новый файл user.js :
Модули в JavaScript
Фронтенд-разработчики каждый день используют модули. Это может быть функция из локального файла или сторонняя библиотека из node_modules. Сегодня я кратко расскажу об основных модульных системах в JavaScript и некоторых нюансах их использования.
Синтаксис систем модулей
В современном JavaScript осталось два основных стандарта модульных систем. Это CommonJS, которая является основной для платформы Node.js, и ESM (ECMAScript 6 модули), которая была принята как стандарт для языка и внесена в спецификацию ES2015.
История развития модульных систем JavaScript хорошо описана в статьях «Эволюция модульного JavaScript» и «Путь JavaScript-модуля».
Если вам хорошо известен весь синтаксис модульных систем ESM и CommonJS, то можно пропустить следующую главу.
ESM-модули
Именованный импорт/экспорт
export можно использовать в момент объявления функции, переменной или класса:
Для больших модулей удобнее использовать группированный экспорт, это позволяет наглядно увидеть все экспортируемые сущности внутри модуля:
Импорт/Экспорт по умолчанию
В случае, когда из файла модуля экспортируется только одна сущность, удобнее использовать экспорт по умолчанию. Для этого необходимо добавить default после инструкции export :
Импорт модуля в случае экспорта по умолчанию:
Дополнительные возможности
Переименование. Для изменения имени метода в момент импорта/экспорта существует инструкция as :
Импорт этой функции будет доступен только по новому имени:
Переименование в момент импорта:
Этот синтаксис полезен для случаев, когда имя импортируемой части уже занято. Также можно сократить имя функции/переменной/класса, если она часто используется в файле:
Инициализация модуля без импорта его частей. Используется, когда необходимо выполнить импорт модуля для выполнения кода внутри него, но не импортировать какую-либо его часть:
Импорт всего содержимого модуля. Можно импортировать всё содержимое модуля в переменную и обращаться к частям модуля как к свойствам этой переменной:
Такой синтаксис не рекомендуется использовать, сборщик модулей (например, Webpack) не сможет корректно выполнить tree-shaking при таком использовании.
Реэкспорт. Существует сокращенный синтаксис для реэкспорта модулей. Это бывает полезно, когда нужно собрать модули из разных файлов в одном экспорте:
при таком реэкспорте наименования частей модуля будут сохраняться, но можно изменять их с помощью инструкции as :
Аналогичным способом можно реэкспортировать значения по умолчанию:
Использование модулей в браузере
Рассмотрим на примере небольшого проекта.
Импорт модуля внутри index.html:
По атрибуту type=»module» браузер понимает, что экспортирует файл с модулями, и корректно его обработает. Стоит отметить, что пути импорта, указанные в main.js (./dist/module1 и ./dist/module2), будут преобразованы в абсолютные пути относительно текущего расположения, и браузер запросит эти файлы у сервера по адресам /dist/module1 и /dist/module2 соответственно. Практического применения у этой возможности не так много, в основном в проектах используется сборщик (например Webpack), который преобразует ESM-модули в bundle. Однако использование ESM-модулей в браузере может позволить улучшить загрузку страницы за счет разбиения bundle-файлов на маленькие части и постепенной их загрузки.
CommonJS
В CommonJS cуществует что-то схожее с импортом по умолчанию, для этого необходимо просто присвоить module.exports значению экспортируемой функции:
Сохранение значения в exports напрямую, в отличие от именованного экспорта, не будет работать:
Стоит обратить внимание, что если были экспортированы части модуля, они затрутся и будет экспортировано только последнее значение module.exports :
Импорт. Для импорта необходимо воспользоваться конструкцией require() и указать путь до модуля:
Можно воспользоваться деструктуризацией и получить значение необходимой функции сразу после импорта:
Работа с модулями в Node.js
Поддержка ESM-модулей
До недавнего времени Node.js поддерживал только CommonJS, но с версии 13.2.0 команда разработчиков анонсировала поддержку ESM (с версии 8.5.0 поддержка модулей ECMAScript 6 была скрыта за экспериментальным флагом). Подробно о том, как работать с модулями ECMAScript 6 в Node.js, можно прочитать в анонсе команды разработчиков Node.js.
Поиск модулей
Все относительные пути, начинающиеся c ‘./’ или ‘../’ будут обрабатываться только относительно рабочей папки проекта. Пути с ‘/’ будут обрабатываться как абсолютные пути файловой системы. Для остальных случаев Node.js начинает поиск модулей в папке проекта node_modules (пример: /home/work/projectN/node_modules). В случае, если интересующий модуль не был найден, Node.js поднимается на уровень выше и продолжает свой поиск там. И так до самого верхнего уровня файловой системы. Поиск необходимой библиотеки будет выглядеть следующим образом:
Дополнительные свойства у module и require
У module и require есть дополнительные свойства, которые могут быть полезны.
module.id — уникальный идентификатор модуля. Обычно это полностью определенный путь до модуля.
module.children — объект, содержащий модули, которые импортированы в текущем файле. Ключами объекта являются module.id :
require.cache — представляет из себя объект с информацией о каждом импортированном модуле. Если при импорте модуля Node.js находит его в кеше, код модуля не будет выполняться повторно, а экспортируемые сущности будут взяты из закешированного значения. При необходимости повторного «чистого» импорта модуля необходимо сбросить закешированное значение, удалив его из кеша:
Что происходит в момент импорта ES-модуля
В момент выполнения файла Javascript-движок выполняет несколько этапов загрузки модулей:
Структура данных, содержащая информацию о модуле (уникальный идентификатор, список зависимостей и состояния всех экспортируемых значений) называется Module Records.
При выполнении скрипта строится граф зависимостей и создается запись по каждому импортируемому модулю внутри него. В момент каждого импорта, вызывается метод Evaluate() внутри модуля Module Records. При первом вызове этой функции выполняется сценарий для получения и сохранения состояния модуля. Подробнее об этом процессе можно прочитать в статье «Глубокое погружение в ES-модули в картинках».
Что происходит при повторном импорте модуля
Но остался открытым вопрос, создаётся ли новая сущность Module Records при повторном импорте? Например в данном случае:
За получение Module Records для каждого import отвечает метод HostResolveImportedModule, который принимает два аргумента:
В спецификации говорится, что для одинаковых парах значений referencingScriptOrModule и specifier возвращается один и тот же экземпляр Module Records.
Рассмотрим еще один пример, когда один и тот же модуль импортируется в нескольких файлах:
Multiple different referencingScriptOrModule, specifier pairs may map to the same Module Record instance. The actual mapping semantic is host-defined but typically a normalization process is applied to specifier as part of the mapping process. A typical normalization process would include actions such as alphabetic case folding and expansion of relative and abbreviated path specifiers
Таким образом, даже если referencingScriptOrModule отличается, а specifier одинаков, может быть возвращен одинаковый экземпляр Module Records.
Однако этой унификации не будут подвержены импорты с дополнительными параметрами в specifier :
Циклические зависимости
При большой вложенности модулей друг в друга может возникнуть циклическая зависимость:
Для наглядности, эту цепочку зависимостей можно упростить до:
ES-модули нативно умеют работать с циклическими зависимостями и корректно их обрабатывать. Принцип работы подробно описан в спецификации. Однако, ESM редко используются без обработки. Обычно с помощью транспилятор (Babel) сборщик модулей (например, Webpack) преобразует их в CommonJS для запуска на Node.js, или в исполнямый скрипт (bundle) для браузера. Циклические зависимости не всегда могут быть источником явных ошибок и исключений, но могут стать причиной некорректного поведения кода, которое трудно будет отловить.
Есть несколько хаков, как можно обходить циклические зависимости для некоторые ситуаций, но лучше просто не допускать их возниковения.
Заключение
В этой статье я собрал всю основную информацию о модульных системах в Javascript, чтобы у читателя не осталось пробелов относительно того, как их использовать и как они работают. Надеюсь, у меня это получилось, и статья оказалась вам полезной. Буду рад обратной связи!
Использование модулей Node.js с npm и package.json
Published on April 13, 2020
Автор выбрал фонд Open Internet/Free Speech для получения пожертвования в рамках программы Write for DOnations.
Введение
Благодаря таким функциям, как оперативное выполнение ввода/вывода и его широко известному синтаксису JavaScript, Node.js быстро стал популярной рабочей средой для разработки веб-приложений на стороне сервера. Но по мере роста интереса создаются более крупные приложения, а управление сложностью базы кода и ее зависимостей становится сложнее. Node.js организует эти сложные процессы с помощью модулей, которые являются любым отдельным файлом JavaScript, содержащим функции или объекты, используемые другими программами или модулями. Группа из одного или нескольких модулей часто называется пакетом, а эти пакеты организованы менеджерами пакетов.
При создании более сложных проектов Node.js управление своими метаданными и зависимостями при помощи файла package.json позволит обеспечить более предсказуемые сборки, поскольку все внешние зависимости одинаковы. Файл будет автоматически отслеживать эту информацию. Хотя вы можете изменять файл напрямую для обновления метаданных вашего проекта, вам будет редко требоваться взаимодействовать с ним напрямую для управления модулями.
Предварительные требования
Для данного обучающего руководства вам потребуется следующее:
Шаг 1 — Создание файла package.json
Начнем это обучающее руководство с проекта в качестве примера — гипотетический модуль локатора Node.js, получающий IP-адрес пользователя и возвращающий страну происхождения. Вы не будете кодировать модуль в этом обучающем руководстве. Однако пакеты, которыми вы управляете, будут актуальны, если вы их разрабатывали.
Использование команды init
Вначале настройте проект, чтобы можно было попрактиковаться в управлении модулями. В своей оболочке создайте новую папку под названием locator :
Затем перейдите в новую папку:
Теперь инициализируйте интерактивную командную строку, введя следующее:
Результат будет выглядеть следующим образом:
Затем команда init запросит репозиторий GitHub проекта. Вы не будете использовать его в данном примере, поэтому оставьте и его пустым.
Шаг 2 — Установка модулей
Обычно в разработке программного обеспечения используются внешние библиотеки для выполнения вспомогательных задач в проектах. Это позволяет разработчику сосредотачивать внимание на бизнес-логике и создавать приложения более быстро и эффективно.
Рассмотрим это на примере. В вашем приложении локатора вы будете использовать библиотеку axios, которая поможет вам выполнять запросы HTTP. Установите ее, введя следующее в оболочке:
После установки библиотеки вы увидите примерно следующее:
Теперь откройте файл package.json в текстовом редакторе по вашему выбору. В этом обучающем руководстве мы будем использовать nano :
Вы увидите новое свойство, как подчеркнуто ниже:
в начале номера версии, это означает, что только более высокие номера версий PATCH удовлетворяют это ограничение.
После завершения просмотра package.json выйдите из файла.
Зависимости разработки
Пакеты, используемые для разработки проекта, но не для его создания или запуска, называются зависимостями разработки. Они не требуются для вашего модуля или приложения в производственной среде, но могут оказаться полезными при написании кода.
Например, разработчики часто используют инструменты статического анализа кода, чтобы обеспечить соответствие кода передовым практикам и единообразие стиля. Хотя это полезно для разработки, это только увеличивает размер дистрибутива без предоставления ощутимых выгод при развертывании в производственной среде.
Установите инструмент статического анализа кода в качестве зависимости разработки для вашего проекта. Попробуйте это в оболочке:
В результате вы получите следующий вывод:
Автоматически сгенерированные файлы: node_modules и package-lock.json
Папка node_modules содержит все установленные зависимости для вашего проекта. В большинстве случаев вы не должны назначать эту папку вашему репозиторию с контролем версии. По мере того как вы будете устанавливать больше зависимостей, размер этой папки будет быстро расти. Кроме того, файл package-lock.json хранит записи точных версий, установленных более сжато, поэтому включение node_modules не требуется.
Установка из package.json
С помощью ваших файлов package.json и package-lock.json вы можете быстро задать те же самые зависимости проекта, прежде чем начать разработку нового проекта. Чтобы продемонстрировать это, перейдите на один уровень выше в дереве директорий и создайте новую папку с именем cloned_locator на том же уровне директории, что и locator :
Перейдите в новую директорию:
Теперь скопируйте файлы package.json и package-lock.json из locator в cloned_locator :
Чтобы установить требуемые модули для этого проекта, введите следующее:
При развертывании в производственную среду вы можете пропустить зависимости разработки. Вспомните, что зависимости разработки хранятся в разделе devDependencies файла package.json и не влияют на управление вашим приложением. При установке модулей в рамках процесса непрерывной интеграции и разработки, чтобы развернуть приложение, пропустите зависимости разработки, введя следующее:
Прежде чем перейти к следующему разделу, вернитесь в папку locator :
Глобальная установка
Проверьте, что пакет успешно установлен, введя следующее:
Вы увидите примерно следующий результат:
Теперь, когда вы можете устанавливать модули, в следующем разделе вы будете практиковать методы управления своими зависимостями.
Шаг 3 — Управление модулями
Полный менеджер пакетов способен на гораздо большее, чем установка модулей. В npm имеется 20 команд, связанных с управлением зависимостями. На этом шаге вы:
Указание модулей
Если вы хотите знать, какие модули установлены в проекте, проще использовать команду list или ls вместо чтения package.json напрямую. Для этого введите следующее:
Результат должен выглядеть следующим образом:
По умолчанию ls отображает все дерево зависимостей — модули, от которых зависит ваш проект, и модули, от которых зависят ваши зависимости. Это может быть довольно неудобно, если вы хотите получить общий обзор того, что установлено.
Чтобы вывести только модули, которые вы установили, без их зависимостей, введите следующее в оболочке:
Результат будет выглядеть следующим образом:
Обновление модулей
Вывод будет выглядеть следующим образом:
Похоже, вы можете обновить eslint до последней версии. Используйте команду update или up следующим образом:
Вывод команды будет содержать установленную версию:
Если хотите обновить все модули одновременно, введите следующее:
Удаление модулей
Удаление зависимостей из проекта — обычное мероприятие в жизненном цикле разработки программного обеспечения. Зависимость может не решить проблему, как заявлено, или может не предоставить удовлетворительный опыт разработки. В этих случаях может быть лучше удалить зависимость и создать собственный модуль.
Вывод будет выглядеть следующим образом:
Здесь не указано явно, что axios был удален. Чтобы убедиться, что он был удален, еще раз укажите зависимости:
Теперь мы видим только то, что установлен eslint :
Проверка модулей
После установки устаревшей версии request вы должны увидеть примерно следующий результат:
npm указывает, что у вас есть уязвимости в ваших зависимостях. Для получения более подробных сведений проверьте весь ваш проект:
Команда audit показывает таблицы вывода, указывающие на недостатки безопасности:
Вы увидите примерно следующий результат:
npm смог безопасно обновить два пакета, тем самым устранив две уязвимости. Но у вас все еще есть четыре уязвимости в ваших зависимостях. Команда audit fix не всегда устраняет каждую проблему. Хотя версия модуля может иметь уязвимость безопасности, если вы обновите ее до версии с другим API, то это может нарушить код выше в дереве зависимостей.
Как упоминалось ранее, это не рекомендуется, если вы не уверены, что это не нарушит функциональность.