Поиск по сайту:

Как использовать модули ECMAScript с Node.js


Стандартизированный способ упаковки кода в виде повторно используемых модулей отсутствовал в ECMAScript на протяжении большей части его истории. В отсутствие интегрированного решения подход CommonJS (CJS) стал стандартом де-факто для разработки Node.js. Это использует require и module.exports для использования и предоставления фрагментов кода:

// Import a module
const fs = require("fs");
 
// Provide an export
module.exports = () => "Hello World";

ES2015, также известный как ES6, наконец представил собственную встроенную модульную систему. ECMAScript или модули ES (ESM) используют синтаксис import и export:

// Import a default export
import fs from "fs";
 
// Provide a default export
export default () => "Hello World";
 
// Import a named export
import {helloWorld} from "./hello-world.js";
 
// Provide a named export
export const helloWorld = () => "Hello World";

Node.js предлагает поддержку ESM по умолчанию, начиная с v16. В более ранних версиях вам нужно было использовать флаг --experimental-modules, чтобы активировать эту возможность. Хотя модули ES теперь отмечены как стабильные и готовые к общему использованию, наличие двух разных механизмов загрузки модулей означает, что использование обоих видов кода в одном проекте затруднительно.

В этой статье мы рассмотрим, как использовать модули ES с Node и что вы можете сделать, чтобы максимизировать совместимость с пакетами CommonJS.

Основы

Все относительно просто, если вы начинаете новый проект и хотите положиться на ESM. Поскольку Node.js теперь предлагает полную поддержку, вы можете разделить свой код на отдельные файлы и использовать операторы import и export для доступа к вашим модулям.

К сожалению, вам нужно сделать сознательный выбор на ранней стадии. По умолчанию Node не поддерживает import и export внутри файлов, заканчивающихся расширением .js. Вы можете добавить к файлам суффикс .mjs, где ESM всегда доступен, или изменить файл package.json, включив в него \type\:\модуль\.

{
    "name": "example-package",
    "type": "module",
    "dependencies": {
        "..."
    }
}

Выбор последнего пути обычно более удобен для проектов, которые будут использовать исключительно ESM. Node использует поле type для определения системы модулей по умолчанию для вашего проекта. Эта модульная система всегда используется для обработки простых файлов .js. Если не установить вручную, CJS является системой модулей по умолчанию для максимальной совместимости с существующей экосистемой кода Node. Файлы с расширениями .cjs или .mjs всегда будут рассматриваться как исходные файлы в формате CJS и ESM соответственно.

Импорт модуля CommonJS из ESM

Вы можете импортировать модули CJS в файлы ESM с помощью обычного оператора import:

// cjs-module.cjs
module.exports.helloWorld = () => console.log("Hello World");
 
// esm-module.mjs
import component from "./cjs-module.cjs";
component.helloWorld();

component будет преобразован в значение module.exports модуля CJS. В приведенном выше примере показано, как вы можете получить доступ к именованным экспортам в качестве свойств объекта по имени вашего импорта. Вы также можете получить доступ к определенным экспортам, используя синтаксис именованного импорта ESM:

import {helloWorld} from "./cjs-module.cjs";
helloWorld();

Это работает с помощью системы статического анализа, которая сканирует файлы CJS для обработки предоставляемого ими экспорта. Такой подход необходим, потому что CJS не понимает концепцию «именованного экспорта». Все модули CJS имеют один экспорт — «именованные» экспорты на самом деле представляют собой объект с несколькими парами «свойство-значение».

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

Импорт модуля ESM из CJS

Все становится сложнее, когда вы хотите использовать новый модуль ESM в существующем коде CJS. Вы не можете написать оператор import в файлах CJS. Однако динамический синтаксис import() работает, и его можно сочетать с await для относительно удобного доступа к модулям:

// esm-module.mjs
const helloWorld = () => console.log("Hello World");
export {helloWorld};
 
// esm-module-2.mjs
export default = () => console.log("Hello World");
 
// cjs-module.cjs
const loadHelloWorld = async () => {
    const {helloWorld} = await import("./esm-module.mjs");
    return helloWorld;
};
const helloWorld = await loadHelloWorld();
helloWorld();
 
const loadHelloWorld2 = async() => {
    const helloWorld2 = await import("./esm-module-2.mjs");
    return helloWorld2;
};
const helloWorld2 = await loadHelloWorld2();
helloWorld2();

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

Получение пути к текущему модулю с помощью модулей ES

Модули ES не имеют доступа ко всем знакомым глобальным переменным Node.js, доступным в контекстах CJS. Помимо require() и module.exports, вы не сможете получить доступ к константам __dirname или __filename. или. Они обычно используются модулями CJS, которым необходимо знать путь к собственному файлу.

Файлы ESM могут читать import.meta.url, чтобы получить следующую информацию:

console.log(import.meta.url);
// file:///home/demo/module.mjs

Возвращаемый URL-адрес содержит абсолютный путь к текущему файлу.

Почему все несовместимости?

Различия между CJS и ESM гораздо глубже, чем просто синтаксические изменения. CJS — синхронная система; когда вы require() используете модуль, Node загружает его прямо с диска и выполняет его содержимое. ESM является асинхронным и разделяет импорт скрипта на несколько отдельных этапов. Импорты анализируются, асинхронно загружаются из места их хранения, а затем выполняются после того, как все их собственные импорты будут получены таким же образом.

ESM разработан как современное решение для загрузки модулей с широким спектром применения. Вот почему модули ESM подходят для использования в веб-браузерах: они асинхронны по своей конструкции, поэтому медленные сети не являются проблемой.

Асинхронный характер ESM также является причиной ограничений его использования в коде CJS. Файлы CJS не поддерживают await верхнего уровня, поэтому вы не можете использовать import самостоятельно:

// this...
import component from "component.mjs";
 
// ...can be seen as equivalent to this...
const component = await import("component.mjs");
 
// ...but top-level "await" isn't available in CJS

Следовательно, вы должны использовать динамическую структуру import() внутри функции async.

Стоит ли переходить на модули ES?

Простой ответ - да. Модули ES — это стандартизированный способ импорта и экспорта кода JavaScript. CJS дал Node модульную систему, когда в языке не было собственной. Теперь, когда он доступен, для долгосрочного здоровья сообщества лучше всего принять подход, описанный в стандарте ECMAScript.

ESM также является более мощной системой. Поскольку он асинхронный, вы получаете динамический импорт, удаленный импорт с URL-адресов и улучшенную производительность в некоторых ситуациях. Вы также можете повторно использовать свои модули с другими средами выполнения JavaScript, например с кодом, который доставляется прямо в веб-браузеры с помощью HTML-тегов .

Тем не менее миграция остается сложной задачей для многих существующих проектов Node.js. CJS не исчезнет в ближайшее время. Тем временем вы можете облегчить переход, предложив экспорт CJS и ESM для ваших собственных библиотек. Это лучше всего достигается путем написания тонкой оболочки ESM вокруг любого существующего экспорта CJS:

import demoComponent from "../cjs-component-demo.js";
import exampleComponent from "../cjs-component-example.js";
export {demoComponent, exampleComponent};

Поместите этот файл в новый подкаталог esm вашего проекта. Добавьте рядом с ним файл package.json, содержащий поле {\type\: \module\}.

Затем обновите package.json в корне вашего проекта, чтобы включить следующее содержимое:

{
    "exports": {
        "require": "./cjs-index.js",
        "import": "./esm/esm-index.js"
    }
}

Это говорит Node предоставить вашу точку входа CJS, когда пользователи вашего пакета require() экспортируют. При использовании import вместо него будет предложен сценарий-оболочка ESM. Имейте в виду, что эта функциональность карты импорта также имеет другой побочный эффект: пользователи будут ограничены только экспортом, предоставленным вашим файлом точки входа. Загрузка определенных файлов по путям внутри пакета (демонстрация импорта из \example-package/path/to/file.js) отключена при наличии карты импорта.

Краткое содержание

Ландшафт модулей Node.js может быть болезненным, особенно когда вам нужна совместимость как с CJS, так и с ESM. Хотя ситуация улучшилась со стабилизацией поддержки ESM, вам все равно нужно заранее решить, какая модульная система должна быть «по умолчанию» для вашего проекта. Загрузка кода из «другой» системы возможна, но часто неудобна, особенно когда CJS зависит от ESM.

Ситуация будет постепенно улучшаться по мере того, как все больше крупных проектов переходят на использование ESM в качестве предпочтительного подхода. Тем не менее, с таким большим количеством кода CJS массовая миграция нереалистична, и вполне вероятно, что оба механизма будут использоваться бок о бок — со всеми вытекающими из этого компромиссами — еще несколько лет.

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




Все права защищены. © Linux-Console.net • 2019-2024