Начало работы с модулями Node.js: require, exports, imports и не только

Модули являются важной концепцией для понимания проектов Node.js. В этом посте мы рассмотрим модули Node: requireexports и будущее import .

Модули Node.js позволяют писать повторно используемый код. Вы можете вкладывать их друг в друга. Используя Node Package Manager (NPM) вы можете опубликовать свои модули и сделать их доступными для сообщества. Кроме того, NPM позволяет повторно использовать модули, созданные другими разработчиками.

Мы используем Node 12.x для примеров и синтаксис ES6 +. Тем не менее, концепции действительны для любой версии.

В этом разделе мы рассмотрим, как создавать модули Node и каждый из его компонентов:

  • Require
  • Exports
  • Module (module.exports vs. export)
  • Import

require — подключение модулей в Node.js

require используются для подключения модулей. Это позволяет вам включать модули в ваши программы. Вы можете добавить встроенные базовые модули Node.js, модули, написанные сообществом разработчиков из папки node_modules и локальные модули.

Допустим, мы хотим прочитать файл из файловой системы. Node.js имеет для этого встроенный модуль под названием «fs»:

const fs = require('fs');

fs.readFile('./file.txt', 'utf-8', (err, data) => {
  if(err) { throw err; }
  console.log('data: ', data);
});

Как видите, мы импортировали модуль «fs» в наш код. Это позволяет нам использовать любую прикрепленную к нему функцию, например «readFile» и многие другие.

Функция require будет искать файлы в следующем порядке:

  1. Built-in. Встроенные в Node.js модули (такие как fs)
  2. NPM Modules. Устанавливаемые модули из репозитория npm, их можно посмотреть в папке node_modules.
  3. Local Modules. Локальные модули, которых надо подгружать из вашего локального места через обращение к пути .// or ../. Модули соответствуют расширениям: *.js*.json*.mjs*.cjs*.wasm и *.node.

Давайте теперь объясним каждый чуть подробнее

1. Встроенные модули Node.js

Когда вы устанавливаете Node.js, он поставляется со многими встроенными модулями. Node.js поставляется с батарейками в комплекте :).

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

  • fs: позволяет манипулировать (создавать / читать / писать) файлами и каталогами .
  • path: утилиты для работы с файлами и каталогами путей.
  • http: создавать HTTP-серверы и клиенты для веб-разработки .
  • url: утилиты для разбора URL и извлечения из него элементов .

Эти модули вам не нужно устанавливать, вы можете импортировать их и использовать в своих программах из коробки.

2. Модули из репозитория NPM

Модули NPM — это сторонние модули, которые вы можете использовать после их установки. Назвать несколько:

  • lodash: коллекция служебных функций для манипулирования массивами, объектами и строками.
  • request: HTTP-клиент проще в использовании, чем встроенный модуль  http.
  • express: HTTP сервер для создания сайтов и API. Опять же, проще в использовании, чем встроенный модуль http.

Это вы должны сначала установить их, вот так:

$ npm install express

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

const express = require('express');
3. Собственные модули

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

exports — экспорт данных из модуля Node.js

Ключевое слово export дает вам возможность «экспортировать» ваши объекты и методы. Давайте сделаем пример:

const PI = 3.14159265359;

exports.area = radius => (radius ** 2) * PI;
exports.circumference = radius => 2 * radius * PI;

В приведенном ниже коде мы экспортируем функции area и circumference. Мы определили константу PI, но она доступна только внутри модуля. Только элементы, связанные с exports, доступны за пределами модуля.

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

const circle = require('./circle');

const r = 3;
console.log(`Circle with radius ${r} has
  area: ${circle.area(r)};
  circumference: ${circle.circumference(r)}`);

Заметил, что на этот раз мы добавляем имя модуля к ./. Это означает, что модуль является локальным файлом.

Обертка модуля в Node.js

Вы можете рассматривать каждый модуль Node.js как отдельную функцию, подобную следующей:

(function (exports, require, module, __filename, __dirname) {
  module.exports = exports = {};

  // Your module code ...

});

Мы уже описывали exports и require. Обратите внимание на связь между module.exports и exports. Они указывают на одну и ту же ссылку. Но если вы назначите что-то напрямую для exports, вы прервете его ссылку на module.exports — подробнее об этом в следующем разделе.

Для нашего удобства определены __filename и __dirname. Они предоставляют полный путь к текущему файлу и каталогу. Последний исключает имя файла и печатает путь к каталогу.

Например, для нашего модуля ./circle.js это будет примерно так:

  • __filename : /User/websofter/code/circle.js
  • __dirname : /User/websofter/code

Хорошо, мы рассмотрели exports, require, __filename и __dirname. Единственное, что мы не рассмотрели, это module. Давай сделаем это!

Что использовать module.exports vs. exports ?

Модуль module не является глобальным и является локальным для каждого модуля. Он содержит метаданные о модуле, такие как id, exports, parent, children и так далее.

exports — это псевдоним module.exports. Следовательно, все, что вы назначаете для exports, также доступно в module.exports. Однако, если вы назначите что-то напрямую для экспорта, вы потеряете ярлык для module.exports. Например.

class Cat {
  makeSound() {
    return `${this.constructor.name}: Meowww`;
  }
}

// exports = Cat; // It will not work with `new Cat();`
// exports.Cat = Cat; // It will require `new Cat.Cat();` to work (yuck!)
module.exports = Cat;

Попробуйте следующий случай с exports, а затем с module.exports.

const Cat = require('./cat');

const cat = new Cat();
console.log(cat.makeSound());

Подводя итог, когда использовать module.exports против exports:

Используйте exports для:

  • Экспорт именованной функции. например exports.area, exports.circumference.

Используйте module.exports для:

  • если вы хотите экспортировать объект, класс, функцию на корневом уровне (например, module.exports = Cat)
  • Если вы предпочитаете возвращать один объект, который предоставляет несколько назначений. например module.exports = {area, circumference};

imports — новые возможности импорта модулей

Начиная с версии 8.5.0+, Node.js нативно поддерживает модули ES с флагом функции и новым расширением файла * .mjs.

Например, наш предыдущий circle.js можно переписать как circle.mjs следующим образом:

const PI = 3.14159265359;

export function area(radius) {
  return (radius ** 2) * PI;
}

export function circumference(radius) {
  return 2 * radius * PI;
}

Затем мы можем использовать импорт:

import { area, circumference } from './circle.mjs';

const r = 3;

console.log(`Circle with radius ${r} has
  area: ${area(r)};
  circunference: ${circumference(r)}`);

И, наконец, вы можете запустить его, используя флаг функции экспериментального модуля:

$ node --experimental-modules main.mjs

Если вам не нравятся экспериментальные модули, другой альтернативой является использование транспилятора. Это преобразует современный JavaScript в более старые версии для вас. Хорошими вариантами являются TypeScriptBabel и Rollup.

Устранение неполадок при использовании imports и require

Экспериментальный Флаг

Если вы не используете экспериментальный флаговый узел --experimental-modules и пытаетесь использовать import, вы получите ошибку, подобную этой:

internal/modules/cjs/loader.js:819
  throw new ERR_REQUIRE_ESM(filename);
  ^

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: bla bla blah
Расширения файлов .mjs vs .js (или .cjs)

Если у вас есть код модуля написан в файле с расширением *.mjs, то вы не можете использовать require, он выдаст ошибку (ReferenceError: require не определен). .mjs предназначен для импорта через imports в соответствии ECMAScript, а .js — для регулярного использования require.

Однако с помощью *.mjs вы можете загружать оба вида модулей!

import { area, circumference } from './circle.mjs';
import Cat from './cat.js';

const r = 3;
console.log(`Circle with radius ${r} has
  area: ${area(r)};
  circumference: ${circumference(r)}`);

const cat = new Cat();
console.log(cat.makeSound());

Обратите внимание, что cat.js использует модули commonJS.

Резюме

Мы узнали о том, как создавать модули Node.js, и использовали этот опыт в нашем коде. Модули позволяют легко повторно использовать код. Они обеспечивают функциональность, которая изолирована от других модулей. Функция require используется для загрузки модулей. exports и module.exports позволяют нам определить, какие части нашего кода мы хотим представить. Мы также исследовали разницу между module.exports и exports. Наконец, мы быстро выбрали, что будет с модулями, использующими imports.