CommonJS …what, why and how
What is CommonJS? CommonJS is a module formatting system. It is a standard for structuring and organizing JavaScript code. CJS assists in the server-side development of apps and it’s format has heavily influenced NodeJS’s module management.
Ok… So, what is a module? A module is just a bit of code encapsulated in a file, and exported to another file. Modules focus on a single part of functionality and remain loosely coupled with other filed in an application. This is because there are no global or shared variables between modules, as they only communicate via the module.exports object. Any code that you want to be accessible in another file can be a module!
How can I use CommonJS? CommonJS wraps each module in a function called ‘require’, and includes an object called ‘module.exports’, which exports code for availability to be required by other modules. All you have to do is add whatever you want accessible to other files onto the ‘exports’ object and require the module in the dependent file. the syntax for the require function is “var VariableName = require(‘moduleId_or_pathToModule’);”. Here’s an example…
Why would I need CommonJS?? CommonJS allows for code encapsulation, as modules with no global variables won’t conflict with each other when your application is run. CommonJS aids in dependancy-injection management. Modules are loaded synchronously, so modules that are dependent on other modules must be read further down in the code. The separation of functionality makes for much easier testing and debugging of code. Without module systems like CommonJS, dependancies had to be loaded in tags in the header of an HTML file, OR all code had to be lumped together which is incredibly slow and inefficient for file loading. Not to mention, your app would completely crash if there was a bug in any part of the code! See the difference in managing dependencies here..
In conclusion, CJS modules are reusable code made available for dependent files. CommonJS is an easy-to-understand way to implement dependency management, separation of concerns, and effective testing of a single part of your application.
Cjs что это
What’s on this Page
Что такое файл CJS?
Файл CommonJS (CJS) — это файл, содержащий код JavaScript, написанный с использованием синтаксиса CommonJS. CommonJS — это система модулей, предназначенная для работы в средах, выходящих за рамки веб-браузеров, и она часто используется в серверных средах JavaScript, таких как Node.js.
Формат файла CJS – дополнительная информация
Файлы CJS написаны с использованием синтаксиса CommonJS и могут редактироваться в любом текстовом редакторе, например Microsoft Notepad или Apple TextEdit. Модули CommonJS обычно хранятся в отдельных файлах и предназначены для инкапсуляции и модуляризации кода для лучшей организации и удобства обслуживания. Эти модули используют требуемую функцию для импорта зависимостей и объект Module.exports или Exports для предоставления значений и функций, которые могут использоваться другими частями кода.
Пример кода CJS
Модули CommonJS имеют особый синтаксис и структуру, которая включает в себя такие функции, как обязательная функция для импорта других модулей и модуль.exports или экспортирует объекты для экспорта значений, функций или объектов из модуля. Эти модули используются для инкапсуляции и разделения фрагментов кода, что упрощает управление и поддержку больших баз кода JavaScript. Вот базовый пример модуля CommonJS:
// Module definition in a file named "myModule.js" const someValue = 42; function add(a, b) < return a + b; >module.exports = < someValue, add, >; // Using the module in another file const myModule = require('./myModule'); console.log(myModule.someValue); // 42 console.log(myModule.add(10, 20)); // 30
Использованная литература
Модули CommonJS¶
АПИ является удовлетворительным. Совместимость с NPM имеет высший приоритет и не будет нарушена кроме случаев явной необходимости.
Модули CommonJS — это оригинальный способ упаковки кода JavaScript для Node.js. Node.js также поддерживает стандарт ECMAScript modules, используемый браузерами и другими средами выполнения JavaScript.
В Node.js каждый файл рассматривается как отдельный модуль. Например, рассмотрим файл с именем foo.js :
1 2 3 4
const circle = require('./circle.js'); console.log( `Площадь круга радиуса 4 равна $circle.area(4)>` );
В первой строке foo.js загружает модуль circle.js , который находится в том же каталоге, что и foo.js .
Вот содержимое circle.js :
1 2 3 4 5
const PI > = Math; exports.area = (r) => PI * r ** 2; exports.circumference = (r) => 2 * PI * r;
Модуль circle.js экспортировал функции area() и circumference() . Функции и объекты добавляются в корень модуля путем указания дополнительных свойств специального объекта exports .
Переменные, локальные для модуля, будут приватными, поскольку модуль обернут в функцию Node.js (см. module wrapper). В этом примере переменная PI является приватной для circle.js .
Свойству module.exports может быть присвоено новое значение (например, функция или объект).
Ниже, bar.js использует модуль square , который экспортирует класс Square:
1 2 3
const Square = require('./square.js'); const mySquare = new Square(2); console.log(`Площадь mySquare равна $mySquare.area()>`);
Модуль square определен в square.js :
1 2 3 4 5 6 7 8 9 10
// Назначение на exports не изменит модуль, необходимо использовать module.exports module.exports = class Square constructor(width) this.width = width; > площадь() return this.width ** 2; > >;
Система модулей CommonJS реализована в модуле module core module.
Включение¶
Node.js имеет две системы модулей: CommonJS модули и ECMAScript модули.
По умолчанию Node.js будет считать модулями CommonJS следующее:
- Файлы с расширением .cjs ;
- Файлы с расширением .js , если ближайший родительский файл package.json содержит поле верхнего уровня «type» со значением «commonjs» .
- Файлы с расширением .js , если ближайший родительский package.json файл не содержит поля верхнего уровня «type» . Авторы пакетов должны включать поле тип , даже в пакетах, где все источники являются CommonJS. Явное указание типа пакета облегчит инструментам сборки и загрузчикам определение того, как следует интерпретировать файлы в пакете.
- Файлы с расширением не .mjs , .cjs , .json , .node или .js (если ближайший родительский файл package.json содержит поле верхнего уровня «type» со значением «module» , эти файлы будут распознаны как модули CommonJS только в том случае, если они включаются через require() , но не при использовании в качестве точки входа программы в командной строке).
Более подробную информацию смотрите в Определение системы модулей.
Вызов require() всегда использует загрузчик модулей CommonJS. Вызов import() всегда использует загрузчик модулей ECMAScript.
Доступ к главному модулю¶
Когда файл запускается непосредственно из Node.js, require.main устанавливается в его модуль . Это означает, что можно определить, был ли файл запущен напрямую, проверив require.main === module .
Для файла foo.js это будет true , если он запущен через node foo.js , но false , если запущен через require(‘./foo’) .
Если точка входа не является модулем CommonJS, require.main будет undefined , и главный модуль будет недоступен.
Советы по работе с менеджером пакетов¶
Семантика функции Node.js require() была разработана достаточно общей, чтобы поддерживать разумные структуры каталогов. Программы менеджеров пакетов, такие как dpkg , rpm и npm , надеюсь, найдут возможность собирать собственные пакеты из модулей Node.js без модификации.
Ниже мы приводим предлагаемую структуру каталогов, которая может работать:
Допустим, мы хотим, чтобы папка по адресу /usr/lib/node// содержала содержимое определенной версии пакета.
Пакеты могут зависеть друг от друга. Для того чтобы установить пакет foo , может потребоваться установить определенную версию пакета bar . Пакет bar может сам иметь зависимости, и в некоторых случаях они могут даже сталкиваться или образовывать циклические зависимости.
Поскольку Node.js просматривает realpath всех загружаемых модулей (то есть, разрешает симлинки), а затем ищет их зависимости в папках node_modules , эта ситуация может быть разрешена с помощью следующей архитектуры:
- /usr/lib/node/foo/1.2.3/ : Содержимое пакета foo , версия 1.2.3.
- /usr/lib/node/bar/4.3.2/ : Содержимое пакета bar , от которого зависит foo .
- /usr/lib/node/foo/1.2.3/node_modules/bar : Символическая ссылка на /usr/lib/node/bar/4.3.2/ .
- /usr/lib/node/bar/4.3.2/node_modules/* : Символические ссылки на пакеты, от которых зависит bar .
Таким образом, даже если встретится цикл или возникнут конфликты зависимостей, каждый модуль сможет получить версию своей зависимости, которую он может использовать.
Когда код в пакете foo выполняет require(‘bar’) , он получит версию, которая находится по симлинку в /usr/lib/node/foo/1.2.3/node_modules/bar . Затем, когда код в пакете bar вызовет require(‘quux’) , он получит версию, которая находится по симлинку в /usr/lib/node/bar/4.3.2/node_modules/quux .
Более того, чтобы сделать процесс поиска модулей еще более оптимальным, вместо того, чтобы помещать пакеты непосредственно в /usr/lib/node , мы можем поместить их в /usr/lib/node_modules// . Тогда Node.js не будет утруждать себя поиском отсутствующих зависимостей в /usr/node_modules или /node_modules .
Чтобы сделать модули доступными для Node.js REPL, может быть полезно также добавить папку /usr/lib/node_modules в переменную окружения $NODE_PATH . Поскольку поиск модулей с помощью папок node_modules является относительным и основан на реальном пути к файлам, выполняющим вызовы require() , сами пакеты могут находиться где угодно.
Расширение .mjs ¶
Из-за синхронной природы require() невозможно использовать его для загрузки файлов модулей ECMAScript. Попытка сделать это приведет к ошибке ERR_REQUIRE_ESM . Вместо этого используйте import() .
Расширение .mjs зарезервировано для Модулей ECMAScript, которые не могут быть загружены через require() . Смотрите раздел Определение системы модулей для получения дополнительной информации о том, какие файлы разбираются как модули ECMAScript.
Все вместе¶
Чтобы получить точное имя файла, который будет загружен при вызове require() , используйте функцию require.resolve() .
Если собрать воедино все вышесказанное, вот высокоуровневый алгоритм в псевдокоде того, что делает require() :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
require(X) from module at path Y 1. If X is a core module, a. return the core module b. STOP 2. If X begins with '/' a. set Y to be the file system root 3. If X begins with './' or '/' or '../' a. LOAD_AS_FILE(Y + X) b. LOAD_AS_DIRECTORY(Y + X) c. THROW "not found" 4. If X begins with '#' a. LOAD_PACKAGE_IMPORTS(X, dirname(Y)) 5. LOAD_PACKAGE_SELF(X, dirname(Y)) 6. LOAD_NODE_MODULES(X, dirname(Y)) 7. THROW "not found" LOAD_AS_FILE(X) 1. If X is a file, load X as its file extension format. STOP 2. If X.js is a file, load X.js as JavaScript text. STOP 3. If X.json is a file, parse X.json to a JavaScript Object. STOP 4. If X.node is a file, load X.node as binary addon. STOP LOAD_INDEX(X) 1. If X/index.js is a file, load X/index.js as JavaScript text. STOP 2. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP 3. If X/index.node is a file, load X/index.node as binary addon. STOP LOAD_AS_DIRECTORY(X) 1. If X/package.json is a file, a. Parse X/package.json, and look for "main" field. b. If "main" is a falsy value, GOTO 2. c. let M = X + (json main field) d. LOAD_AS_FILE(M) e. LOAD_INDEX(M) f. LOAD_INDEX(X) DEPRECATED g. THROW "not found" 2. LOAD_INDEX(X) LOAD_NODE_MODULES(X, START) 1. let DIRS = NODE_MODULES_PATHS(START) 2. for each DIR in DIRS: a. LOAD_PACKAGE_EXPORTS(X, DIR) b. LOAD_AS_FILE(DIR/X) c. LOAD_AS_DIRECTORY(DIR/X) NODE_MODULES_PATHS(START) 1. let PARTS = path split(START) 2. let I = count of PARTS - 1 3. let DIRS = [] 4. while I >= 0, a. if PARTS[I] = "node_modules" CONTINUE b. DIR = path join(PARTS[0 .. I] + "node_modules") c. DIRS = DIR + DIRS d. let I = I - 1 5. return DIRS + GLOBAL_FOLDERS LOAD_PACKAGE_IMPORTS(X, DIR) 1. Find the closest package scope SCOPE to DIR. 2. If no scope was found, return. 3. If the SCOPE/package.json "imports" is null or undefined, return. 4. let MATCH = PACKAGE_IMPORTS_RESOLVE(X, pathToFileURL(SCOPE), ["node", "require"]) defined in the ESM resolver. 5. RESOLVE_ESM_MATCH(MATCH). LOAD_PACKAGE_EXPORTS(X, DIR) 1. Try to interpret X as a combination of NAME and SUBPATH where the name may have a @scope/ prefix and the subpath begins with a slash (`/`). 2. If X does not match this pattern or DIR/NAME/package.json is not a file, return. 3. Parse DIR/NAME/package.json, and look for "exports" field. 4. If "exports" is null or undefined, return. 5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH, `package.json` "exports", ["node", "require"]) defined in the ESM resolver. 6. RESOLVE_ESM_MATCH(MATCH) LOAD_PACKAGE_SELF(X, DIR) 1. Find the closest package scope SCOPE to DIR. 2. If no scope was found, return. 3. If the SCOPE/package.json "exports" is null or undefined, return. 4. If the SCOPE/package.json "name" is not the first segment of X, return. 5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(SCOPE), "." + X.slice("name".length), `package.json` "exports", ["node", "require"]) defined in the ESM resolver. 6. RESOLVE_ESM_MATCH(MATCH) RESOLVE_ESM_MATCH(MATCH) 1. let RESOLVED_PATH = fileURLToPath(MATCH) 2. If the file at RESOLVED_PATH exists, load RESOLVED_PATH as its extension format. STOP 3. THROW "not found"
Кэширование¶
Модули кэшируются после первой загрузки. Это означает (помимо прочего), что при каждом вызове require(‘foo’) будет возвращен точно такой же объект, если он будет разрешен в тот же файл.
При условии, что require.cache не изменен, многократные вызовы require(‘foo’) не приведут к многократному выполнению кода модуля. Это важная особенность. С ее помощью можно возвращать «частично выполненные» объекты, что позволяет загружать переходные зависимости, даже если они могут вызвать циклы.
Чтобы модуль выполнял код несколько раз, экспортируйте функцию и вызовите ее.
Предостережения по кэшированию модулей¶
Модули кэшируются на основе их разрешенного имени файла. Поскольку модули могут разрешаться в разные имена файлов в зависимости от расположения вызывающего модуля (загрузка из папок node_modules ), это не гарантия того, что require(‘foo’) всегда будет возвращать точно такой же объект, если он разрешается в разные файлы.
Кроме того, в файловых системах или операционных системах, не чувствительных к регистру, разные разрешенные имена файлов могут указывать на один и тот же файл, но кэш все равно будет рассматривать их как разные модули и будет перезагружать файл несколько раз. Например, require(‘./foo’) и require(‘./FOO’) возвращают два разных объекта, независимо от того, являются ли ./foo и ./FOO одним и тем же файлом.
Основные модули¶
Node.js имеет несколько модулей, скомпилированных в двоичный файл. Эти модули более подробно описаны в других разделах этой документации.
Основные модули определены в исходном коде Node.js и находятся в папке lib/ .
Основные модули могут быть определены с помощью префикса node: , в этом случае они обходят кэш require . Например, require(‘node:http’) всегда будет возвращать встроенный модуль HTTP, даже если в require.cache есть запись с таким именем.
Некоторые модули ядра всегда загружаются предпочтительно, если их идентификатор передан в require() . Например, require(‘http’) всегда будет возвращать встроенный модуль HTTP, даже если существует файл с таким именем. Список основных модулей, которые могут быть загружены без использования префикса node: , раскрывается как module.builtinModules .
Циклы¶
Когда есть циклические вызовы require() , модуль может не закончить выполнение, когда он будет возвращен.
Рассмотрим такую ситуацию:
1 2 3 4 5 6
console.log('a starting'); exports.done = false; const b = require('./b.js'); console.log('в a, b.done = %j', b.done); exports.done = true; console.log('a done');
1 2 3 4 5 6
console.log('b started'); exports.done = false; const a = require('./a.js'); console.log('в b, a.done = %j', a.done); exports.done = true; console.log('b done');
1 2 3 4 5 6 7 8
console.log('main starting'); const a = require('./a.js'); const b = require('./b.js'); console.log( 'in main, a.done = %j, b.done = %j', a.done, b.done );
Когда main.js загружает a.js , то a.js в свою очередь загружает b.js . В этот момент b.js пытается загрузить a.js . Чтобы предотвратить бесконечный цикл, незавершенная копия объекта экспорта a.js возвращается в модуль b.js . Затем b.js завершает загрузку, и его объект exports передается модулю a.js .
К тому времени, когда main.js загрузит оба модуля, они оба завершат работу. Вывод этой программы будет выглядеть следующим образом:
1 2 3 4 5 6 7 8 9
$ node main.js main starting a starting b starting in b, a.done = false b done in a, b.done = true a done in main, a.done = true, b.done = true
Для того чтобы циклические зависимости модулей корректно работали в приложении, необходимо тщательное планирование.
Модули файлов¶
Если точное имя файла не найдено, то Node.js попытается загрузить требуемое имя файла с добавленными расширениями: .js , .json , и, наконец, .node . При загрузке файла, имеющего другое расширение (например, .cjs ), его полное имя должно быть передано в require() , включая расширение файла (например, require(‘./file.cjs’) ).
Файлы .json анализируются как текстовые файлы JSON, файлы .node интерпретируются как скомпилированные модули аддонов, загруженные с помощью process.dlopen() . Файлы, использующие любое другое расширение (или вообще без расширения), разбираются как текстовые файлы JavaScript. Обратитесь к разделу Определение системы модулей, чтобы понять, какая цель разбора будет использоваться.
Требуемый модуль с префиксом ‘/’ — это абсолютный путь к файлу. Например, require(‘/home/marco/foo.js’) загрузит файл по адресу /home/marco/foo.js .
Требуемый модуль с префиксом ./’ является относительным к файлу, вызывающему require() . То есть, circle.js должен находиться в том же каталоге, что и foo.js , чтобы require(‘./circle’) нашел его.
Без ведущего ‘/’ , ‘/’ или ‘/. /’ для указания файла, модуль должен быть либо основным модулем, либо загружаться из папки node_modules .
Если указанный путь не существует, require() выдаст ошибку MODULE_NOT_FOUND .
Папки как модули¶
Стабильность: 3 – Закрыто
Принимаются только фиксы, связанные с безопасностью, производительностью или баг-фиксы. Пожалуйста, не предлагайте изменений АПИ в разделе с таким индикатором, они будут отклонены.
Существует три способа передачи папки в require() в качестве аргумента.
Первый — создать в корне папки файл package.json , который определяет главный модуль. Пример файла package.json может выглядеть следующим образом:
"name": "some-library", "main": "./lib/some-library.js" >
Если бы это находилось в папке ./some-library , то require(‘./some-library’) попытался бы загрузить ./some-library/lib/some-library.js .
Если в каталоге нет файла package.json , или если запись «main» отсутствует или не может быть разрешена, то Node.js попытается загрузить файл index.js или index.node из этого каталога. Например, если в предыдущем примере не было файла package.json , то require(‘./some-library’) попытается загрузить:
- ./some-library/index.js
- ./some-library/index.node .
Если эти попытки не увенчаются успехом, то Node.js сообщит об отсутствии всего модуля с ошибкой по умолчанию:
Ошибка: Cannot find module 'some-library'
Во всех трех вышеприведенных случаях вызов import(‘./some-library’) приведет к ошибке ERR_UNSUPPORTED_DIR_IMPORT . Использование пакетов subpath exports или subpath imports может обеспечить те же преимущества организации содержимого, что и папки, и модули, и работать как для require , так и для import .
Загрузка из папок node_modules ¶
Если идентификатор модуля, переданный в require() , не является модулем core и не начинается с ‘/’ , ‘../’ или ‘./’ , то Node.js начинает с каталога текущего модуля, добавляет /node_modules и пытается загрузить модуль из этого места. Node.js не будет добавлять node_modules к пути, который уже заканчивается на node_modules .
Если модуль не найден там, то он переходит в родительский каталог, и так далее, пока не будет достигнут корень файловой системы.
Например, если файл по адресу ‘/home/ry/projects/foo.js’ вызывает require(‘bar.js’) , то Node.js будет искать в следующих местах, в таком порядке:
- /home/ry/projects/node_modules/bar.js
- /home/ry/node_modules/bar.js
- /home/node_modules/bar.js
- /node_modules/bar.js
Это позволяет программам локализовать свои зависимости, чтобы они не конфликтовали.
Можно потребовать определенные файлы или подмодули, распространяемые вместе с модулем, включив суффикс пути после имени модуля. Например, require(‘example-module/path/to/file’) разрешит path/to/file относительно того места, где находится example-module . Путь с суффиксом следует той же семантике разрешения модуля.
Загрузка из глобальных папок¶
Если переменная окружения NODE_PATH установлена в список абсолютных путей, разделенных двоеточием, то Node.js будет искать модули по этим путям, если они не найдены в других местах.
В Windows NODE_PATH разграничивается точками с запятой ( ; ) вместо двоеточий.
NODE_PATH был изначально создан для поддержки загрузки модулей из различных путей до того, как был определен текущий алгоритм разрешения модулей.
NODE_PATH все еще поддерживается, но теперь, когда экосистема Node.js пришла к соглашению о расположении зависимых модулей, необходимость в нем отпала. Иногда развертывания, которые полагаются на NODE_PATH , показывают неожиданное поведение, когда люди не знают, что NODE_PATH должен быть установлен. Иногда зависимости модуля меняются, в результате чего при поиске по NODE_PATH загружается другая версия (или даже другой модуль).
Кроме того, Node.js будет искать в следующем списке GLOBAL_FOLDERS:
- 1: $HOME/.node_modules .
- 2: $HOME/.node_libraries
- 3: $PREFIX/lib/node .
Где $HOME — это домашний каталог пользователя, а $PREFIX — это настроенный в Node.js node_prefix .
Это в основном для исторических целей.
Настоятельно рекомендуется размещать зависимости в локальной папке node_modules . Они будут загружаться быстрее и надежнее.
Обертка модуля¶
Прежде чем код модуля будет выполнен, Node.js обернет его функцией-оберткой, которая выглядит следующим образом:
1 2 3 4 5 6 7 8 9
(function ( exports, require, module, __filename, __dirname ) // Код модуля на самом деле находится здесь >);
Делая это, Node.js достигает нескольких вещей:
- Переменные верхнего уровня (определенные с помощью var , const или let ) привязываются к модулю, а не к глобальному объекту.
- Это помогает обеспечить некоторые глобальные на вид переменные, которые на самом деле специфичны для модуля, например:
- Объекты module и exports , которые исполнитель может использовать для экспорта значений из модуля.
- Удобные переменные __filename и __dirname , содержащие абсолютное имя файла и путь к каталогу модуля.
Область применения модуля¶
__dirname ¶
Имя каталога текущего модуля. Это то же самое, что path.dirname() из __filename .
Пример: запуск node example.js из /Users/mjr .
1 2 3 4
console.log(__dirname); // Prints: /Users/mjr console.log(path.dirname(__filename)); // Печатает: /Users/mjr__filename ¶
Имя файла текущего модуля. Это абсолютный путь к файлу текущего модуля с разрешенными симлинками.
Для основной программы это имя не обязательно совпадает с именем файла, используемым в командной строке.
Имя каталога текущего модуля смотрите в __dirname .
Запуск node example.js из /Users/mjr .
1 2 3 4
console.log(__filename); // Печатает: /Users/mjr/example.js console.log(__dirname); // Печатает: /Users/mjrДаны два модуля: a и b , где b является зависимостью от a , а структура каталогов имеет вид:
- /Users/mjr/app/a.js
- /Users/mjr/app/node_modules/b/b.js .
Ссылки на __filename в пределах b.js вернут /Users/mjr/app/node_modules/b/b.js , а ссылки на __filename в пределах a.js вернут /Users/mjr/app/a.js .
exports ¶
Ссылка на module.exports , который короче по типу. Смотрите раздел о ярлыке exports для подробностей о том, когда использовать exports и когда использовать module.exports .
module ¶
Ссылка на текущий модуль, см. раздел об объекте module . В частности, module.exports используется для определения того, что модуль экспортирует и делает доступным через require() .
require(id) ¶
Используется для импорта модулей, JSON и локальных файлов. Модули могут быть импортированы из node_modules . Локальные модули и JSON файлы могут быть импортированы с использованием относительного пути (например, ./ , ./foo , ./bar/baz , ../foo ), который будет разрешен относительно каталога, названного __dirname (если определен) или текущего рабочего каталога. Относительные пути в стиле POSIX разрешаются независимо от ОС, то есть приведенные выше примеры будут работать на Windows так же, как и на Unix-системах.
1 2 3 4 5 6 7 8 9
// Импортирование локального модуля с путем относительно `__dirname` или текущей // рабочему каталогу. (В Windows это будет выглядеть как .\path\myLocalModule). const myLocalModule = require('./path/myLocalModule'); // Импортируем файл JSON: const jsonData = require('./path/filename.json'); // Импортирование модуля из node_modules или встроенного модуля Node.js: const crypto = require('node:crypto');require.cache ¶
Модули кэшируются в этом объекте, когда они требуются. Если удалить значение ключа из этого объекта, то при следующем require модуль будет перезагружен. Это не относится к родным аддонам, для которых перезагрузка приведет к ошибке.
Также возможно добавление или замена записей. Этот кэш проверяется перед встроенными модулями, и если в кэш добавлено имя, совпадающее со встроенным модулем, то только node: -префиксные вызовы require будут получать встроенный модуль. Используйте с care!
1 2 3 4 5 6 7 8
const assert = require('node:assert'); const realFs = require('node:fs'); const fakeFs = <>; require.cache.fs = exports: fakeFs >; assert.strictEqual(require('fs'), fakeFs); assert.strictEqual(require('node:fs'), realFs);require.extensions ¶
Стабильность: 0 – устарело или набрало много негативных отзывов
Эта фича является проблемной и ее планируют изменить. Не стоит полагаться на нее. Использование фичи может вызвать ошибки. Не стоит ожидать от нее обратной совместимости.
Указывает require , как обрабатывать определенные расширения файлов.
Обрабатывать файлы с расширением .sjs как .js :
require.extensions['.sjs'] = require.extensions['.js'];Удалено. В прошлом этот список использовался для загрузки в Node.js не-JavaScript модулей путем их компиляции по требованию. Однако на практике существуют гораздо лучшие способы сделать это, например, загрузить модули через какую-либо другую программу Node.js или скомпилировать их в JavaScript заранее.
Избегайте использования require.extensions . Его использование может вызвать тонкие ошибки, а разрешение расширений становится медленнее с каждым зарегистрированным расширением.
require.main ¶
Объект Module , представляющий сценарий входа, загруженный при запуске процесса Node.js, или undefined , если точка входа программы не является модулем CommonJS. Смотрите «Доступ к главному модулю».
В сценарии entry.js :
console.log(require.main);node entry.js1 2 3 4 5 6 7 8 9 10 11 12
Module id: '.', path: '/absolute/path/to', exports: <>, filename: '/absolute/path/to/entry.js', loaded: false, children: [], paths: [ '/absolute/path/to/node_modules', '/absolute/path/node_modules', '/absolute/node_modules', '/node_modules' ] >require.resolve(request[, options]) ¶
- request Путь к модулю для разрешения.
- options
- paths Пути для разрешения местоположения модуля. Если эти пути присутствуют, они используются вместо путей разрешения по умолчанию, за исключением GLOBAL_FOLDERS, таких как $HOME/.node_modules , которые всегда включаются. Каждый из этих путей используется как начальная точка для алгоритма разрешения модулей, что означает, что иерархия node_modules проверяется с этого места.
Использует внутренний механизм require() для поиска местоположения модуля, но вместо загрузки модуля возвращает только имя разрешенного файла.
Если модуль не может быть найден, выдается ошибка MODULE_NOT_FOUND .
require.resolve.paths(request) ¶
- request Путь модуля, пути поиска которого извлекаются.
- Возвращает:
Возвращает массив, содержащий пути, найденные при разрешении request , или null , если строка request ссылается на основной модуль, например http или fs .
Объект module ¶
В каждом модуле свободная переменная module является ссылкой на объект, представляющий текущий модуль. Для удобства, module.exports также доступен через exports module-global. На самом деле module не является глобальным, а скорее локальным для каждого модуля.
module.children ¶
Объекты модуля, впервые требуемые этим модулем.
module.exports ¶
Объект module.exports создается системой Module . Иногда это неприемлемо; многие хотят, чтобы их модуль был экземпляром какого-либо класса. Чтобы сделать это, назначьте нужный объект экспорта в module.exports . Присвоение нужного объекта в exports просто перепривяжет локальную переменную exports , что, вероятно, не является желаемым.
Например, предположим, что мы создаем модуль под названием a.js :
1 2 3 4 5 6 7 8 9
const EventEmitter = require('node:events'); module.exports = new EventEmitter(); // Выполняем некоторую работу, и через некоторое время выдаем // событие 'ready' из самого модуля. setTimeout(() => module.exports.emit('ready'); >, 1000);Затем в другом файле мы можем сделать следующее:
1 2 3 4
const a = require('./a'); a.on('ready', () => console.log('модуль "a" готов'); >);Присвоение module.exports должно быть сделано немедленно. Это не может быть сделано ни в каких обратных вызовах. Это не работает:
1 2 3
setTimeout(() => module.exports = a: 'hello' >; >, 0);const x = require('./x'); console.log(x.a);Ярлык exports ¶
Переменная exports доступна в области видимости модуля на уровне файлов, и ей присваивается значение module.exports перед оценкой модуля.
Она позволяет сократить время, так что module.exports.f = . может быть записано более кратко как exports.f = . . Однако имейте в виду, что, как и любая переменная, если присвоить exports новое значение, оно больше не будет связано с module.exports :
module.exports.hello = true; // Экспортируется из require модуля exports = hello: false >; // Не экспортируется, доступен только в модулеКогда свойство module.exports полностью заменяется новым объектом, обычно также переназначают exports :
1 2 3
module.exports = exports = function Constructor() // . и т.д. >;Чтобы проиллюстрировать поведение, представьте эту гипотетическую реализацию require() , которая очень похожа на то, что на самом деле делает require() :
1 2 3 4 5 6 7 8 9 10 11 12 13 14
function require(/* . */) const module = exports: <> >; ((module, exports) => // Код модуля здесь. В этом примере определите функцию. function someFunc() <> exports = someFunc; // На данный момент exports больше не является сокращением до module.exports, и // этот модуль по-прежнему будет экспортировать пустой объект по умолчанию. module.exports = someFunc; // Теперь модуль будет экспортировать someFunc, а не объект по умолчанию. // объекта по умолчанию. >)(module, module.exports); return module.exports; >module.filename ¶
Полностью разрешенное имя файла модуля.
module.id ¶
Идентификатор для модуля. Обычно это полностью разрешенное имя файла.
module.isPreloading ¶
module.loaded ¶
Завершил ли модуль загрузку или находится в процессе загрузки.
module.parent ¶
Стабильность: 0 – устарело или набрало много негативных отзывов
Эта фича является проблемной и ее планируют изменить. Не стоит полагаться на нее. Использование фичи может вызвать ошибки. Не стоит ожидать от нее обратной совместимости.
Пожалуйста, используйте require.main и module.children вместо этого.
Модуль, который первым потребовал данный модуль, или null , если текущий модуль является точкой входа текущего процесса, или undefined , если модуль был загружен чем-то, что не является модулем CommonJS (например: REPL или import ).
module.path ¶
Имя каталога модуля. Обычно оно совпадает с path.dirname() из module.id .
module.paths ¶
Пути поиска для модуля.
module.require(id) ¶
- id
- Возвращает: экспортированное содержимое модуля
Метод module.require() предоставляет возможность загрузить модуль так, как если бы require() был вызван из исходного модуля.
Для этого необходимо получить ссылку на объект module . Поскольку require() возвращает module.exports , а module обычно только доступен в коде конкретного модуля, для использования он должен быть явно экспортирован.
Глубокое погружение в ES-модули в картинках

ES-модули приносят в JavaScript официальную, унифицированную модульную систему. Однако, чтобы прийти к этому, потребовалось почти 10 лет работы по стандартизации.
Но ожидание почти закончилось. С выходом Firefox 60 в мае (пока в бете) все основные браузеры будут поддерживать ES-модули, а Рабочая группа Node Modules сейчас работает над добавлением поддержки ES-модулей в Node.js. Также идет интеграция ES-модулей в WebAssembly.
Многие JavaScript-разработчики знают, что ES-модули были противоречивыми. Но мало кто действительно понимает, как они работают. Давайте рассмотрим, какую проблему решают ES-модули и чем они отличаются от модулей в других модульных системах.
Какую проблему решают модули? Скопировать ссылку
Написание кода на JavaScript состоит в работе с переменными — в присвоении значений переменным или добавлении чисел в переменные или объединении двух переменных вместе и помещении их в другую переменную.

Поскольку большая часть вашего кода связана с изменением переменных, то их организация будет напрямую влиять на качество вашего кода… и на то, насколько хорошо вы сможете поддерживать его в дальнейшем.
Небольшое количества переменных, о которых вам нужно думать, значительно упростило бы ситуацию. У JavaScript есть способ помочь вам в этом — область видимости (scope). Из-за того, как области видимости работают в JavaScript, функции не могут обращаться к переменным, определенным в других функциях.

Это хорошо. Значит когда вы работаете над одной функцией, вы можете сосредоточиться только на ней. Вам не нужно беспокоиться о том, что другие функции могут делать с вашими переменными.
Однако у такого подхода есть и недостаток. Он затрудняет обмен переменными между различными функциями.
Что делать, если вы захотите использовать переменную вне текущей области видимости? Обычно способ справиться с этим заключается в том, чтобы положить её в область выше, например, в глобальную область видимости.

Это работает, но появляются некоторые проблемы.
Во-первых, все ваши скрипты должны быть расположены в определенном порядке. И вы должны будете внимательно следить за тем, чтобы никто не испортил этот порядок.
Если этот порядок нарушится, то в середине работы ваше приложение выдаст ошибку. Когда функция ищет jQuery там, где она ожидает его — на глобальном уровне — и не находит его, она выбрасывает ошибку и прекращает выполнение.

Это делает поддержку кода сложным, а удаление старого кода или скриптов игрой в рулетку. Вы не знаете, что может сломаться. Зависимости между этими различными частями кода неявны. Любая функция может использовать что угодно на глобальном уровне, поэтому вы точно не можете знать, какие функции от чего зависят.
Вторая проблема заключается в том, что поскольку эти переменные находятся в глобальной области видимости, каждая часть кода, находящаяся внутри этой области, может изменить переменную. Вредоносный код может изменить эту переменную специально с целью изменения работы функций или другой код может случайно удалить вашу переменную.
Как нам помогут модули? Скопировать ссылку
Модули дают вам лучший способ организовать эти переменные и функции. С модулями вы группируете переменные и функции, которые имеет смысл объединить.
Это помещает эти функции и переменные в область видимости модуля. Область модуля может использоваться для обмена переменными между функциями в модуле.
Но в отличие от областей видимости функций, области модулей позволяют сделать их переменные доступными и для других модулей. Они могут явно указать, какие переменные, классы или функции в модуле должны быть доступны.
Когда что-то становится доступным для других модулей, это называется export. После экспорта другие модули могут явно сказать, что они зависят от этой переменной, класса или функции.

Поскольку экспорт указывается явно, это позволяет определить, какие модули будут нарушены при удалении другого.
Когда у вас появляется возможность экспортировать и импортировать переменные между модулями, это значительно облегчает разбиение вашего кода на небольшие куски, которые могут работать независимо друг от друга. Затем вы можете комбинировать и рекомбинировать эти куски, вроде блоков Lego, для создания любых приложений из одного и того же набора модулей.
Поскольку модули настолько полезны, было несколько попыток добавить модульность в JavaScript. Сегодня активно используются две системы
- CommonJS (CJS) — это то, что Node.js использовал изначально.
- ESM (модули ECMAScript) — это более новая система, добавленная в спецификацию JavaScript. Браузеры уже поддерживают ES-модули, а Node.js добавляет поддержку.
Давайте подробно рассмотрим, как работает эта новая модульная система.
Как работают ES-модули Скопировать ссылку
При разработке с помощью модулей, вы строите граф зависимостей. Связи между различными зависимостями берутся из любых инструкций import, которые вы используете.
Эти операторы импорта определяют, какой код браузеру или Node.js нужно загрузить. Вы даете ему файл для использования в качестве точки входа в граф. Оттуда он просто следует за любым из операторов импорта, чтобы найти остальную часть кода.

Но сами файлы не являются тем, что браузер может использовать. Ему необходимо разобрать все эти файлы, чтобы превратить их в структуры данных, называемые записями модулей (module records). Таким образом, он действительно узнает, что происходит в файле.

После этого запись модуля необходимо превратить в экземпляр модуля. Экземпляр объединяет две вещи: код и состояние.
Код обычно представляет собой набор инструкций. Это как рецепт, как сделать что-то. Но вы не можете просто использовать этот код. Вам нужны исходные материалы для использования с этими инструкциями.
Что такое состояние? Состояние дает вам эти исходные материалы. Состояние — это фактические значения переменных в любой момент времени. Конечно, эти переменные — это просто псевдонимы для ячеек памяти, которые содержат значения.
Таким образом, экземпляр модуля объединяет код (список инструкций) с состоянием (значениями всех переменных).

Нам нужен экземпляр для каждого модуля. Процесс загрузки модуля происходит от файла точки входа к полному графу экземпляров модуля.
Для ES-модулей это происходит в три этапа.
- Построение (constuction) — поиск, загрузка и парсинг всех файлов в записях модулей.
- Создание экземпляра (instantiation) — поиск ячеек в памяти для размещения всех экспортируемых значений (но пока без заполнения их значениями) Затем связывание — экспорт и импорт этих полей в памяти.
- Оценка (evaluation) — запуск кода для заполнения этих полей фактическими значениями переменных.

Люди говорят о том, что ES-модули являются асинхронными. Вы можете думать об этом как о асинхронном процессе, т.к работа делится на три разные фазы:
- Построение
- Создание экземпляров
- Оценка
Все эти этапы могут выполняться отдельно. Это означает, что спецификация вводит какую-то асинхронность, которой не было в CommonJS. Я объясню это позже, но в CJS-модуль и его зависимости загружаются, создаются и анализируются сразу, без каких-либо перерывов.
Однако сами по себе действия не обязательно являются асинхронными. Они могут быть выполнены синхронным способом — зависит от того, что делает загрузка. Это потому, что не все контролируется спецификацией ESM. На самом деле есть два этапа работы, которые покрываются различными спецификациями.
Спецификация ES-модулей говорит о том, как следует анализировать файлы в записях модулей, и как следует создавать экземпляры и оценивать этот модуль. Тем не менее, в спецификации не описывается, как изначально получить эти файлы.
Это загрузчик, который извлекает файлы. И загрузчик указан в другой спецификации. Для браузеров он описан спецификацией HTML. Но у вас могут быть разные загрузчики, основанные на той платформе, которую вы используете.

Загрузчик также точно контролирует загрузку модулей. Он вызывает методы ES-модуля: ParseModule , Module.Instantiate и Module.Evaluate .

Теперь давайте пройдемся по каждому шагу более подробно.
Построение (Construction) Скопировать ссылку
Во время этапа построения для каждого модуля происходят три вещи:
- Определение, где загрузить файл, содержащий модуль (module resolution).
- Загрузка файла (по URL или из файловой системы).
- Синтаксический анализ файла в записи модуля.
Поиск и получение файла (fetching) Скопировать ссылку
Загрузчик позаботится о поиске файла и его загрузке. Сначала ему необходимо найти файл точки входа. В HTML вы указываете загрузчику, где его найти, используя тег .

Но как он найдет следующую группу модулей — от которых напрямую зависят модули main.js?
В этом случае используются операторы import. Одна часть оператора импорта называется спецификатором модуля. Он сообщает загрузчику, где он может найти каждый следующий модуль.

Одно замечание о спецификаторах модулей: иногда их нужно обрабатывать по-разному в браузере и Node.js. Каждый хост имеет свой собственный способ интерпретации спецификатора модуля. Для этого он использует решение, называемое алгоритмом разрешения модулей, которое отличается между платформами. В настоящее время некоторые спецификаторы модулей, которые работают в Node.js, не будут работать в браузере, но сейчас ведутся работы по устранению этой проблемы.
Пока это не исправлено, браузеры принимают только URL в качестве спецификаторов модуля. Они загружают файл модуля с этого URL. Но это не происходит для всего графа одновременно. Вы не знаете, какие зависимости модуль должен получить, пока вы не проанализировали файл… и вы не сможете проанализировать файл, пока не получите его.
Это означает, что мы должны пройти через дерево поэтапно, слой за слоем, анализировать один файл, выяснить его зависимости, а затем найти и загрузить их.

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

Блокировка основного потока сделает использование модулей в вашем приложении очень медленным. Это одна из причин того, что спецификация ES-модулей разбивает алгоритм на несколько этапов. Разбиение на фазы позволяет браузерам извлекать файлы и строить свое понимание графа модуля перед тем, как приступить к синхронной работе по созданию экземпляров.
Этот подход — разделение алгоритма на фазы — является одним из ключевых различий между ES-модулями и модулями CommonJS.
CommonJS может делать всё по-другому, потому что загрузка файлов из файловой системы занимает гораздо меньше времени, чем загрузка через интернет. Это означает, что Node.js может блокировать основной поток при загрузке файла. И раз файл уже загружен, есть смысл сразу провести построение (construction) и создание экземпляров (без разбивки на фазы). Это также означает, что вы идёте по всему графу, загружаете, создаете экземпляры и оцениваете зависимости перед возвратом экземпляра модуля.

Подход CommonJS имеет несколько последствий, и я расскажу об этом позже. Но одно это означает, что в Node.js с модулями CommonJS вы можете использовать переменные в вашем спецификаторе модуля. Вы выполняете весь код в этом модуле (до инструкции require ), прежде чем искать следующий модуль. Это означает, что переменная будет иметь значение при переходе к определению модулей.
Но с ES-модулями вы заранее строите весь этот граф модулей, прежде чем выполнять какую-либо оценку. Это означает, что вы не можете использовать переменные в своих спецификаторах модулей, поскольку эти переменные еще не имеют значений.

Но иногда очень полезно использовать переменные для путей модулей. Например, может потребоваться переключить загружаемый модуль в зависимости от того, что делает код или в какой среде он выполняется.
Чтобы сделать это возможным для ES модулей, есть предложение под названием динамический импорт. С его помощью можно использовать импорт вида import( $/foo.js ).
Это работает так: любой файл, загруженный с помощью import(), обрабатывается как точка входа в отдельный граф. Динамически импортируемый модуль запускает новый граф, который обрабатывается отдельно.

Однако следует отметить, что любой модуль, который находится в обоих этих графах, будет совместно использовать экземпляр модуля. Это происходит потому, что загрузчик кэширует экземпляры модулей. Для каждого модуля в определенной глобальной области будет только один экземпляр модуля.
Это уменьшает объём работы для движка. Например, это означает, что файл модуля будет извлечен только один раз, даже если несколько модулей зависят от него (это одна из причин для кэширования модулей. Мы увидим другой в разделе оценки.)
Загрузчик управляет этим кэшем с помощью так называемой карты модулей. Каждый глобальный модуль отслеживает свои модули в отдельной карте.
Когда загрузчик получает URL, он помещает этот URL в карту модуля и отмечает, что он в настоящее время извлекает файл (fetching). Затем он отправит запрос и перейдет к следующему файлу.

Что произойдет, если другой модуль зависит от того же файла? Загрузчик будет искать каждый URL в карте модуля. Если он увидит там fetching , он просто перейдет к следующему URL.
Но карта модуля не просто отслеживает, какие файлы извлекаются. Карта модуля также служит в качестве кэша для модулей, как мы увидим далее.
Парсинг Скопировать ссылку
Теперь, когда мы извлекли этот файл, нам нужно распарсить его в записи модуля. Это помогает браузеру понять, что представляют собой различные части модуля.

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

Есть одна деталь в парсинге, которая может показаться тривиальной, но на самом деле имеет довольно большие последствия. Все модули анализируются так, как если бы они имели use strict вверху. Есть и другие незначительные отличия. Например, ключевое слово await зарезервировано в коде верхнего уровня модуля, а значение this — undefined .
Другой способ парсинга называется целью парсинга (parse goal). Если вы анализируете один и тот же файл, но используете разные цели, вы получите разные результаты. Таким образом, вы хотите знать, прежде чем начать парсинг, какой файл вы анализируете — является ли он модулем или нет.
В браузерах это довольно легко. Вы просто добавляете type=»module» в тег . Это говорит браузеру, что этот файл должен быть проанализирован как модуль. И поскольку импортировать можно только модули, браузер знает, что любой импорт также является модулем.

Но в Node.js вы не можете использовать HTML-теги, поэтому у вас нет возможности использовать атрибут type. Сообщество пыталось решить эту проблему с помощью расширения .mjs. Это расширение говорит Node.js что этот файл является модулем. Сообщество говорит об этом, как о метке для цели парсинга. Обсуждение в настоящее время продолжается, поэтому неясно, какую метку сообщество решит использовать в конце.
В любом случае загрузчик определит, следует ли анализировать файл как модуль или нет. Если это модуль и есть импорт, он начнет процесс снова, пока все файлы не будут извлечены и распарсены.
И все готово! По окончании процесса загрузки вы перешли от простого файла точки входа к множеству записей модуля.

Следующий шаг — создать экземпляр этого модуля и связать Все экземпляры вместе.
Создание экземпляра Скопировать ссылку
Последний шаг — заполнение этих ячеек памяти. JS-движок делает это, выполняя код верхнего уровня — код, который находится вне функций.
Помимо простого заполнения этих ячеек памяти, оценка кода также может вызывать различные сайд-эффекты. Например, модуль может сделать запрос на сервер.

Из-за потенциальных побочных эффектов вы хотите только один раз оценить модуль. В отличие от линковки, которая происходит при создании экземпляра, которая может выполняться несколько раз с точно таким же результатом, оценка может иметь разные результаты в зависимости от того, сколько раз вы это делаете.
Это одна из причин наличия карты модулей. Карта модуля кэширует модуль по каноническому URL, так что для каждого модуля имеется только одна запись модуля. Это гарантирует, что каждый модуль выполняется только один раз.
А как насчет тех циклов, о которых мы говорили раньше?
В циклической зависимости вы в конечном итоге получаете цикл в графе. Обычно это длинная петля. Но чтобы объяснить проблему, я буду использовать пример с коротким циклом.

Давайте посмотрим, как это будет работать с модулями CommonJS. Во-первых, основной модуль выполнит оператор require. Затем будет загружен модуль counter.

Модуль counter попытается получить доступ к message из объекта exports . Но так как он еще не был оценен в основном модуле, вернется undefined . JS-движок выделит пространство в памяти для локальной переменной и установит значение undefined .

Оценка продолжается до конца кода верхнего уровня counter. Мы хотим узнать, получим ли мы правильное значение для сообщения в конце концов (после оценки main.js), поэтому мы настроим таймаут. Затем оценка возобновляется на main.js.

Переменная message будет инициализирована и добавлена в память. Но поскольку между ними нет никакой связи, она останется неопределенной в требуемом модуле.

Если экспорт будет обработан с использованием привязок в реальном времени, в конце концов counter увидит правильное значение. К моменту истечения таймаута оценка main.js завершиться и в переменную присвоится значение.
Поддержка этих циклов является большим основанием для разработки ES-модулей. Именно эта трехфазная архитектура и сделает её возможной.
Каков текущий статус ES-модулей ? Скопировать ссылку
С выходом Firefox 60 в начале мая, все основные браузеры будут поддерживать ES-модули по умолчанию. Node.js также добавляет поддержку, создана рабочая группа, занимающаяся выяснением проблем совместимости между CommonJS и ES-модулями.
Это означает, что вы сможете использовать тег , import и export . Однако еще больше возможностей впереди. Dynamic imports proposal находится на Stage 3 в процессе спецификации, также есть import.meta , а module resolution proposal поможет сгладить различия между браузерами и Node.js. Поэтому работа с модулями станет еще лучше в будущем.
Благодарность Скопировать ссылку
Спасибо всем, кто дал обратную связь на этот пост, или чьи письма или дискуссии прошлого года, в том числе Акселю Раухшмаеру, Бредли Фариасу, Дейву Хернану, Доменику Дениколе, Хави Хоффману, Джейсону Везерсби, Джей-Эф Бастьену, Йону Копперду, Люку Вагнеру, Майлсу Боринсу, Тиллю Шнайдериту, Тобаясу Копперсу, Йехуде Кацу, участникам сообщества WebAssembly, Рабочей группе Node Modules, а также TC39.