require и import в JavaScript
import используется для импорта привязок, которые экспортируются другим модулем, а функция require() используется для загрузки модуля в приложении Node.js. Эти два механизма похожи, но у них есть важные отличия о которых следует знать. Мы обсудим их в этой статье.Введение
require vs import — два способа импорта модулей в JavaScript, которые принципиально отличаются временем загрузки, поддержкой tree shaking и областью применения. В статье на примерах разберём, когда использовать CommonJS, а когда — ES Modules. Оба механизма решают одну задачу — организацию модульного кода, — но используют разные спецификации, работают в разных средах и подчиняются разным правилам.
require() является частью системы CommonJS, которая долгое время была стандартом де-факто для Node.js. import — часть официальной спецификации ECMAScript модулей (ESM), которая поддерживается как современными версиями Node.js, так и всеми актуальными браузерами нативно.
Сравнительная таблица: require, import и import()
Для быстрой ориентации в различиях между тремя механизмами импорта ниже представлена сводная таблица. Детальное объяснение каждого аспекта приводится в последующих разделах.
| Критерий | require |
статический import |
динамический import() |
|---|---|---|---|
| Тип конструкции | Функция | Ключевое слово | Функция, возвращающая Promise |
| Система модулей | CommonJS | ECMAScript modules (ESM) | ESM (работает и в CommonJS) |
| Время загрузки | Синхронное, во время выполнения | На этапе парсинга, до выполнения кода | Асинхронное, во время выполнения |
| Условная загрузка | Да (внутри if, функций) |
Нет | Да |
| Top-level await | Нет | Да (в модулях с "type": "module") |
Да |
| Tree shaking | Не поддерживается | Поддерживается | Ограниченная поддержка |
| Импорт JSON | Да (require('./file.json')) |
Да, с синтаксисом with { type: 'json' } |
Да |
| Кэширование | Через require.cache (доступен для модификации) |
Внутренний кэш ESM-загрузчика (недоступен) | Использует тот же кэш, что и статический import |
| Поддержка в браузерах | Требует сборщика (webpack, Browserify) | Нативная, с type="module" |
Нативная |
| Поддержка в Node.js | Все версии | Начиная с Node.js 12 (с флагами), 14+ (стабильно) | Начиная с Node.js 13.2, 14+ (стабильно) |
| Обработка циклических зависимостей | Возвращает неполный exports | Требует осторожности, использует живые связки | Аналогично статическому import |
Важно понимать, что статический import и динамический import() — это не взаимоисключающие альтернативы, а два инструмента, предназначенных для разных сценариев. Статический import используется для зависимостей, известных на момент написания кода и необходимых для немедленной работы модуля. Динамический import() применяется для отложенной или условной загрузки.
Детальный разбор механизмов импорта
Функция require()
Загружает модули синхронно. Когда интерпретатор встречает вызов require(), он останавливает выполнение текущего модуля до тех пор, пока целевой модуль не будет полностью считан с диска, выполнен и возвращён его экспорт. Это свойство делает require() предсказуемым, но может влиять на производительность при загрузке тяжёлых модулей, поскольку блокируется весь поток выполнения.
Node.js оборачивает каждый модуль, загруженный через require(), в функцию-обёртку:
(function(exports, require, module, __filename, __dirname) {
// код модуля
});
Благодаря этой обёртке переменные, объявленные в модуле через var, let или const, остаются локальными, не попадая в глобальную область видимости. Также модуль получает доступ к специальным объектам: exports и module.exports для определения того, что будет возвращено при импорте, а также __filename и __dirname для получения информации о его расположении в файловой системе.
Модули кэшируются в require.cache. При повторном импорте возвращается объект из кэша без повторного выполнения кода. Это позволяет безопасно использовать модули с состоянием, но может создать ложное впечатление, если файл на диске изменился.
require() поддерживает загрузку файлов с расширениями .js, .json и .node (нативные аддоны). Если расширение не указано, Node.js последовательно пробует эти три варианта в указанном порядке. Также require() может загружать синхронные ES-модули (не содержащие top-level await) при определённых условиях, возвращая объект-пространство имён, где экспорт по умолчанию доступен через свойство default.
Статический import
Ключевое слово import является частью спецификации ECMAScript модулей. В отличие от require(), import не является функцией, вызываемой во время выполнения, а представляет собой декларативную конструкцию, анализируемую на этапе парсинга кода. Статический import определяет все зависимости до начала выполнения модуля.
Статический import имеет ряд ограничений, вытекающих из его природы. Он может использоваться только на верхнем уровне модуля — внутри функций, условных блоков или циклов его применение невозможно. Пути импорта должны быть статическими строками, не содержащими вычисляемых выражений. Эти ограничения являются следствием того, что анализатор кода должен однозначно определить все зависимости модуля без выполнения кода.
Преимуществом статического import является возможность инструментов статического анализа. Сборщики модулей (webpack, Rollup, esbuild) могут определять, какие экспорты из модуля реально используются, и исключать неиспользуемый код — этот процесс называется tree shaking. Кроме того, статическая структура импортов позволяет браузерам и Node.js загружать модули параллельно и эффективно, оптимизируя время загрузки приложения.
В Node.js для использования статического import необходимо, чтобы файл интерпретировался как ES-модуль. Это определяется тремя способами: расширением .mjs, полем "type": "module" в ближайшем package.json, или наличием синтаксиса ES-модулей в файле без явных маркеров. В браузерах для использования import тег script должен содержать атрибут type="module".
Динамический import()
Динамический import() представляет собой гибридный механизм, объединяющий синтаксис import с функциональной природой require(). Вызов import(modulePath) возвращает Promise, который разрешается в объект модуля, аналогичный получаемому при статическом импорте.
В отличие от статического import, динамический import() может использоваться в любом месте кода, включая условные блоки и функции. Путь модуля может быть вычислен динамически, что открывает возможности для реализации таких паттернов, как отложенная загрузка (lazy loading), загрузка по требованию (on-demand loading) и реализация плагинов.
Динамический import() поддерживается как в среде Node.js, так и в браузерах. В Node.js он доступен начиная с версии 13.2, причём может использоваться как в CommonJS-модулях, так и в ES-модулях. В браузерах динамический import() работает в любых скриптах, не требуя атрибута type="module", однако загружаемые модули по-прежнему должны соблюдать CORS-политику.
При импорте CommonJS-модуля через import() результатом является объект-пространство имён, где экспорт по умолчанию находится в свойстве default. Импорт ES-модуля через динамический import() возвращает объект, полностью соответствующий статическому импорту, включая живые связки.
Дерево решений: как выбрать механизм импорта
Выбор между require, статическим import и динамическим import() зависит от среды выполнения, архитектурных требований и характера зависимостей. Представленная ниже последовательность вопросов поможет принять обоснованное решение.
Вопрос первый: где будет выполняться код?
Если код предназначен для браузера и не проходит через этап сборки, единственным возможным вариантом является import (статический или динамический). Браузеры не поддерживают require нативно, и его использование потребовало бы включения сборщика модулей в процесс разработки.
Если код выполняется в Node.js, доступны все три варианта. Дополнительным преимуществом Node.js является возможность смешивания: CommonJS-модули могут динамически импортировать ES-модули через import(), а ES-модули могут загружать CommonJS-модули как через статический import, так и через динамический.
Вопрос второй: должны ли зависимости быть известны до выполнения?
Для библиотек и приложений, где критична возможность tree shaking (исключения неиспользуемого кода), предпочтителен статический import. Сборщики и минификаторы могут анализировать статическую структуру импортов и удалять код, который не используется в проекте. require и динамический import() не предоставляют такой возможности, поскольку их зависимости определяются во время выполнения.
Для сценариев, где модуль нужен всегда и сразу при запуске, подходит как статический import, так и require. Разница здесь не в функциональности, а в синхронности: require блокирует выполнение до полной загрузки модуля, в то время как статический import анализируется на этапе парсинга, и загрузка происходит до выполнения кода. На практике для критического пути запуска оба варианта приемлемы.
Вопрос третий: требуется ли условная или отложенная загрузка модуля?
Если модуль должен загружаться только при наступлении определённого условия (например, по клику пользователя, при наличии определённого флага конфигурации, или для реализации необязательной функциональности), динамический import() является единственным подходящим вариантом. Статический import не допускает условного использования, а require хотя и может быть помещён внутрь if, загружает модуль синхронно, что может привести к заметным паузам в интерфейсе.
Динамический import() также является предпочтительным выбором для реализации подключаемых модулей (плагинов), когда имена модулей заранее неизвестны и определяются во время выполнения из конфигурации или пользовательского ввода.
Вопрос четвёртый: требуется ли top-level await?
Top-level await — возможность использовать ключевое слово await на верхнем уровне модуля, дожидаясь выполнения асинхронных операций до того, как модуль станет доступен для импорта. Эта возможность доступна только в ES-модулях, загруженных через import. require не поддерживает top-level await, а попытка загрузить через require модуль, содержащий top-level await, приведёт к ошибке ERR_REQUIRE_ASYNC_MODULE.
Если модуль должен инициализироваться асинхронно (например, устанавливать соединение с базой данных, загружать конфигурацию из удалённого источника, или ожидать разрешения других асинхронных зависимостей) до того, как его экспорты станут доступны, использование ES-модулей с top-level await является единственным корректным решением.
Вопрос пятый: насколько важна совместимость с существующим кодом и экосистемой?
При работе с устаревшими проектами, использующими CommonJS, или с нативными аддонами Node.js (файлы .node), require остаётся наиболее надёжным выбором. Хотя ES-модули могут загружать CommonJS-модули, и require может загружать синхронные ES-модули, каждая такая граница между системами модулей создаёт дополнительные сложности и потенциальные точки отказа.
Для новых проектов, не ограниченных необходимостью поддержки старых версий Node.js (ранее 14.x), рекомендуется использовать ES-модули со статическим import для основных зависимостей и динамическим import() для сценариев отложенной загрузки. Это обеспечивает доступ к современным возможностям языка и лучшую совместимость с браузерным окружением.
require, статическим import и динамическим import(). Следуйте от начального вопроса к конечному узлу в зависимости от вашего сценария.Практические кейсы
Кейс А: Браузерное приложение с динамической загрузкой JSON-конфигурации
Требуется реализовать веб-страницу, которая отображает панель администратора. Панель содержит различные виджеты, но их набор и настройки определяются серверной конфигурацией, которая может меняться без пересборки приложения. Загружать конфигурацию при старте неэффективно, поскольку администратор может не понадобиться пользователю вовсе.
Решение: использование динамического import()
В браузере динамический импорт позволяет загружать модули по требованию. В данном случае конфигурация хранится в JSON-файле, который импортируется как модуль с использованием синтаксиса атрибутов импорта:
// admin-panel.js
document.getElementById('admin-button').addEventListener('click', async () => {
try {
// Динамическая загрузка конфигурации и модуля панели администратора
const config = await import('./config/admin-config.json', { with: { type: 'json' } });
const { AdminPanel } = await import('./AdminPanel.js');
const panel = new AdminPanel(config.default);
panel.render();
} catch (error) {
console.error('Не удалось загрузить панель администратора:', error);
}
});
Конфигурация загружается только при клике на кнопку администратора, что сокращает начальную загрузку страницы. Синтаксис with { type: 'json' } обязателен для импорта JSON в современных браузерах и Node.js. Обратите внимание, что импортированный JSON-модуль предоставляет экспорт по умолчанию (свойство default), содержащее распарсенный объект. Динамический import() возвращает Promise, разрешающийся в объект модуля, что позволяет использовать синхронный синтаксис с await.
Кейс Б: Node.js библиотека с поддержкой CommonJS и ES-модулей
Требуется опубликовать библиотеку для работы с датами, которая будет использоваться как в старых проектах на CommonJS, так и в новых проектах на ES-модулях. Библиотека должна корректно импортироваться обоими способами без ошибок и с полной поддержкой IntelliSense.
Решение: дуальная публикация с использованием поля exports.
Структура проекта и содержимое package.json:
{
"name": "date-utils",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"exports": {
".": {
"require": "./dist/index.cjs",
"import": "./dist/index.js"
},
"./package.json": "./package.json"
},
"files": ["dist"]
}
Код библиотеки пишется один раз на ES-модулях, затем транспилируется в CommonJS для создания файла .cjs:
// src/index.js (исходный код)
export function addDays(date, days) {
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;
}
export function format(date, locale = 'en-US') {
return date.toLocaleDateString(locale);
}
export default { addDays, format };
Использование в CommonJS-проекте:
const { addDays, format } = require('date-utils');
// или
const dateUtils = require('date-utils');
console.log(dateUtils.addDays(new Date(), 5));
Использование в ES-модулях:
import { addDays, format } from 'date-utils';
// или
import dateUtils from 'date-utils';
console.log(dateUtils.addDays(new Date(), 5));
Поле exports в package.json определяет отдельные точки входа для require и import. При загрузке через require Node.js использует CommonJS-версию (.cjs), при загрузке через import — ESM-версию (.js). Файлы .cjs и .js создаются из одного исходного кода с помощью сборщика (например, Rollup или esbuild), что гарантирует идентичность поведения. Поле main обеспечивает обратную совместимость со старыми версиями Node.js, не поддерживающими exports.
Кейс В: Миграция существующего проекта с require на import
Имеется проект Node.js на CommonJS с устоявшейся архитектурой. Требуется поэтапно перевести его на ES-модули, не останавливая разработку и не создавая конфликтов в системе контроля версий.
Решение: инкрементальная миграция с использованием гибридного режима.
-
Подготовка
package.jsonВ корневой
package.jsonдобавляется поле"type": "module", но при этом все существующие файлы переименовываются или остаются с расширением.cjsдля явного указания CommonJS:{
"name": "my-project",
"type": "module",
"scripts": {
"start": "node index.mjs"
}
}Существующие файлы получают расширение
.cjs, новые модули создаются с расширением.mjs. Это позволяет обеим системам сосуществовать. -
Замена глобальных переменных
В CommonJS доступны переменные
__dirname,__filename,requireиmodule.exports. В ES-модулях их необходимо заменить:// CommonJS (файл utils.cjs)
const path = require('path');
module.exports = {
getConfigPath: () => path.join(__dirname, 'config.json')
};
// ES-модуль (файл utils.mjs)
import { dirname } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
export const getConfigPath = () => path.join(__dirname, 'config.json'); -
Замена
requireна динамическийimportв критических местахДля модулей, которые ещё не переведены на ES-модули, но уже требуются из новых модулей, используется динамический
import():// module.mjs - новый ES-модуль
export async function useLegacyFeature(data) {
// Старый модуль ещё на CommonJS
const legacyModule = await import('./legacy-module.cjs');
return legacyModule.processData(data);
} -
Обновление импортов файлов с расширениями
ES-модули требуют указания полного имени файла, включая расширение:
// CommonJS
const helper = require('./helpers');
// ES-модуль
import helper from './helpers.mjs';
Поэтапная миграция возможна благодаря тому, что Node.js позволяет динамически импортировать CommonJS-модули из ES-модулей через import(). Обратное направление (импорт ES-модуля из CommonJS) также возможно, но только если ES-модуль не содержит top-level await и загружается синхронно. Наиболее безопасная стратегия — переводить модули с листьев графа зависимостей к корню: сначала утилиты и вспомогательные модули, затем сервисы и контроллеры.
Диагностика частых ошибок
В процессе работы с модулями в Node.js и браузерах разработчики регулярно сталкиваются с типовыми ошибками. Понимание причин их возникновения позволяет быстро диагностировать проблему и выбрать корректный способ исправления.
ERR_REQUIRE_ASYNC_MODULE
Симптом: При вызове require() для ES-модуля Node.js выбрасывает ошибку ERR_REQUIRE_ASYNC_MODULE.
Причина: Функция require() загружает модули синхронно. Если целевой ES-модуль (или любой модуль в его графе зависимостей) содержит top-level await, Node.js не может загрузить его через require(), поскольку top-level await требует асинхронного ожидания.
Решение: Существует несколько способов исправления в зависимости от контекста. Наиболее прямое решение — заменить require() на динамический import(), который возвращает Promise и может обрабатывать асинхронную загрузку. Альтернативно, можно реорганизовать ES-модуль, удалив top-level await, если это возможно по логике приложения. Для диагностики конкретного местоположения top-level await используйте флаг --experimental-print-required-tla, выводящий информацию о том, где именно в коде обнаружен проблемный await.
ERR_UNSUPPORTED_DIR_IMPORT
Симптом: При попытке импортировать директорию через import возникает ошибка ERR_UNSUPPORTED_DIR_IMPORT.
Причина: В отличие от require(), который поддерживает импорт директорий (автоматически ищет package.json с полем main или файл index.js), статический import требует явного указания полного пути к файлу, включая расширение. Импорт директории не поддерживается в спецификации ES-модулей.
Решение: Вместо импорта директории необходимо указать полный путь к файлу: import config from './config/index.js'. Если требуется сохранить возможность импорта через имя директории, следует использовать поле exports в package.json, где можно явно определить, какой файл соответствует запросу. В браузерах также можно использовать import maps для создания удобных псевдонимов путей.
MIME type validation error в браузере
Симптом: Браузер отклоняет загрузку модуля с сообщением о неверном MIME-типе, например: The server responded with a non-JavaScript MIME type.
Причина: Браузер проверяет заголовок Content-Type, возвращаемый сервером для файла модуля. Для JavaScript-модулей ожидается MIME-тип text/javascript или другой JavaScript-совместимый тип (например, application/javascript). Если сервер возвращает text/plain, text/html или любой другой тип, браузер блокирует выполнение по соображениям безопасности. Эта проблема особенно актуальна для файлов с расширением .mjs, которое не всегда корректно настроено на серверах.
Решение: Необходимо настроить веб-сервер для корректной обработки MIME-типов. Для сервера на Node.js с использованием http-server или express проблема обычно не возникает. При использовании статической файловой раздачи (например, через GitHub Pages) расширение .mjs работает корректно. В случае невозможности изменить конфигурацию сервера можно использовать расширение .js для модулей, указав type="module" в теге script.
Ошибки CORS при локальной разработке
Симптом: При открытии HTML-файла с модулями через file:// протокол браузер выводит CORS-ошибки в консоли, и модули не загружаются.
Причина: Модули в браузере подчиняются политике одинакового источника (same-origin policy). При загрузке через file:// протокол все файлы считаются разными источниками, и браузер блокирует импорт из соображений безопасности. Это сделано для предотвращения атак, использующих локальные файлы.
Решение: Необходимо использовать локальный веб-сервер. Простейший способ — запустить сервер из командной строки: для Node.js достаточно выполнить npx http-server в директории с проектом. Для Python — python -m http.server. Альтернативно, многие редакторы кода (VS Code, WebStorm) имеют встроенные возможности запуска локального сервера. После запуска страница открывается по адресу http://localhost:8080, и модули загружаются корректно.
__dirname is not defined в ES-модулях
Симптом: При попытке использовать __dirname или __filename в ES-модуле Node.js выбрасывает ошибку ReferenceError: __dirname is not defined.
Причина: Переменные __dirname и __filename предоставляются обёрткой, в которую Node.js помещает CommonJS-модули. В ES-модулях эта обёртка отсутствует, поскольку спецификация ECMAScript не предусматривает таких глобальных переменных. ES-модули имеют доступ к import.meta, который содержит информацию о текущем модуле.
Решение: В современных версиях Node.js (начиная с 20.11.0 для стабильного API) для получения директории модуля используется import.meta.dirname, для получения полного пути файла — import.meta.filename. В более старых версиях необходимо использовать комбинацию import.meta.url и функций из модуля url:
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Оба подхода работают только для файлов с протоколом file:. Для модулей, загруженных через другие протоколы (например, data: или node:), эти свойства недоступны.
Часто задаваемые вопросы
Можно ли использовать require в браузере?
Нет, браузеры не поддерживают require нативно. Функция require является частью системы CommonJS, реализованной в Node.js. Однако использование сборщиков модулей (webpack, Browserify, esbuild) позволяет писать код с require в браузере: сборщик анализирует зависимости и объединяет модули в один или несколько файлов, понятных браузеру. Без сборки единственным способом импорта модулей в браузере является import.
Можно ли использовать import в старых версиях Node.js?
Начиная с Node.js 12, поддержка ES-модулей доступна с использованием экспериментальных флагов (--experimental-modules). Стабильная поддержка без флагов появилась в Node.js 14. Для версий Node.js 8 и 10 существовала экспериментальная поддержка, но использовать её в продакшене не рекомендуется. Если проект работает на Node.js 10 или более ранней версии, единственным надёжным способом остаётся require.
Что произойдёт, если использовать import внутри условного оператора?
Статический import не может быть использован внутри if, циклов или функций — это приведёт к синтаксической ошибке. Спецификация ES-модулей требует, чтобы все статические импорты находились на верхнем уровне модуля. Если условная загрузка необходима, следует использовать динамический import(): if (condition) { const module = await import('./module.js'); }.
Как получить __dirname в ES-модуле?
В ES-модулях переменные __dirname и __filename отсутствуют. В Node.js 20 и выше доступны свойства import.meta.dirname и import.meta.filename. Для более старых версий используется комбинация import.meta.url с функциями из модуля url:
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Можно ли импортировать JSON через import?
Да, но с обязательным использованием синтаксиса атрибутов импорта: import config from './config.json' with { type: 'json' };. Без указания типа браузер или Node.js отклонит импорт, поскольку по умолчанию ожидается JavaScript-код. Альтернативой является чтение JSON через fetch в браузере или через fs.readFileSync в Node.js, но импорт как модуля обеспечивает более чистый синтаксис и автоматическое кэширование.
Почему import всегда должен быть в начале файла, а require можно использовать где угодно?
Статический import обрабатывается на этапе парсинга кода, до его выполнения. Интерпретатор должен знать все зависимости модуля до того, как начнётся выполнение любой инструкции. require — это обычная функция, которая вызывается во время выполнения, поэтому она может быть расположена в любом месте, включая условные блоки. Это не ограничение, а следствие разных подходов к организации модулей: статический анализ против динамического выполнения.
Как загрузить ES-модуль из CommonJS?
Из CommonJS-модуля можно загрузить ES-модуль с помощью динамического import(), который возвращает Promise:
// В CommonJS-файле (.cjs или .js без "type": "module")
async function loadESModule() {
const esModule = await import('./es-module.mjs');
console.log(esModule.default);
}
Статический import в CommonJS не поддерживается. Также важно, что загружаемый ES-модуль не должен требовать синхронного доступа к своему экспорту сразу после вызова — придётся работать с асинхронным кодом.
Влияют ли import и require на производительность по-разному?
Да. require загружает модули синхронно, что в случае большого количества или объёмных модулей может увеличить время запуска приложения. Статический import позволяет среде выполнения (Node.js или браузеру) начать загрузку модулей параллельно ещё до выполнения кода, что потенциально быстрее. Однако в правильно спроектированном приложении разница для большинства сценариев незначительна. Более существенное влияние оказывает tree shaking (доступный только для статического import), который уменьшает итоговый размер бандла за счёт исключения неиспользуемого кода.
Что такое «живые связки» (live bindings) в контексте import?
При импорте через import создаётся живая связка с экспортируемым значением из исходного модуля. Если исходный модуль изменяет значение экспортированной переменной, импортирующий модуль видит это изменение. В случае require импортируется значение, существовавшее на момент загрузки модуля, и последующие изменения экспорта не отслеживаются. Живые связки являются важным отличием ES-модулей от CommonJS и обеспечивают предсказуемое поведение при динамическом изменении состояния модуля.
Как отключить возможность загрузки ES-модулей через require в Node.js?
Начиная с некоторых версий Node.js, загрузка синхронных ES-модулей через require включена по умолчанию. Если это поведение вызывает проблемы (например, случайная загрузка ES-модуля, который не должен загружаться синхронно), его можно отключить флагом --no-require-module. Для диагностики мест, где используется эта возможность, предназначен флаг --trace-require-module, выводящий стек вызовов при каждой загрузке ES-модуля через require.
Заключение
Главное различие между require и import лежит не в синтаксисе, а в фундаментальных принципах. require — синхронная функция времени выполнения из CommonJS. import — декларативная конструкция из ES Modules, анализируемая на этапе парсинга и поддерживающая tree shaking.
Выбор механизма диктуется техническими требованиями:
- Статический
import— если нужен tree shaking, top-level await или код выполняется в браузере без сборки. require— для устаревших версий Node.js, нативных аддонов или совместимости с CommonJS-проектами.- Динамический
import()— для условной, отложенной загрузки или когда имя модуля определяется во время выполнения.
Обе системы будут существовать параллельно ещё долго. Понимание сильных и слабых сторон каждой — признак профессиональной зрелости.
Документация Node.js
-
Modules: CommonJS modules — Подробное описание системы CommonJS-модулей в Node.js. Содержит информацию о кэшировании, разрешении путей, циклических зависимостях и алгоритме работы
require(). -
Modules: ECMAScript modules — Исчерпывающее руководство по ES-модулям в Node.js. Описывает способы включения ESM, взаимодействие с CommonJS, top-level await и формальную спецификацию алгоритмов разрешения модулей.
Документация MDN Web Docs
-
JavaScript modules (руководство) — Практическое руководство по использованию ES-модулей в браузерах. Охватывает экспорт и импорт, динамическую загрузку, import maps, top-level await, циклические зависимости и написание изоморфных модулей. Содержит множество рабочих примеров и рекомендаций по отладке.
-
import (справочная страница, русский язык) — Детальная справочная информация по синтаксису статического и динамического import. Включает описание всех вариантов импорта, особенности поведения импортированных переменных и примеры использования в различных сценариях.
При работе с документацией следует учитывать, что возможности Node.js и браузеров развиваются независимо. Рекомендуется проверять актуальность информации относительно используемой версии Node.js через официальные каналы — блог Node.js и примечания к выпускам (changelogs). Для браузерных возможностей наиболее надёжным источником является MDN Web Docs с индикаторами совместимости (Baseline), показывающими уровень поддержки в различных браузерах.