В чем разница между require(x) и import x в TypeScript/Node.js?
Основное различие: синтаксис и стандарты
require() и import — это два разных подхода к работе с модулями в JavaScript/TypeScript.
- require() — это функция из CommonJS (CJS), старого стандарта модулей Node.js. Она синхронная, загружает модуль "на лету" и возвращает его содержимое.
- import — это ключевое слово из ES-модулей (ESM), современного стандарта ECMAScript. Он асинхронный, статический (анализируется на этапе компиляции) и поддерживает tree-shaking.
Почему возникает путаница в TypeScript?
Проблема в том, что TypeScript — это надстройка над JavaScript. Он компилируется в JS, и то, во что превратится ваш import, зависит от настройки module в tsconfig.json:
// tsconfig.json
{
"compilerOptions": {
"module": "commonjs" // или "esnext", "es2015", "node16" и т.д.
}
}
- Если "module": "commonjs" — TypeScript преобразует
importвrequire(). Например:
// Исходный TS
import express from 'express';
// Станет в JS:
const express_1 = require("express");
const express = express_1.default;
- Если "module": "esnext" —
importостанется как есть, и Node.js будет пытаться загрузить файл как ES-модуль. Для этого вам нужно либо добавить"type": "module"вpackage.json, либо использовать расширение.mjs.
Когда что использовать?
Используйте import (ESM), если:
- Вы пишете новый проект на TypeScript.
- Вы хотите использовать современные возможности: tree-shaking, статический анализ, асинхронную загрузку.
- Ваш проект не требует совместимости со старыми CommonJS-пакетами.
Используйте require (CommonJS), если:
- Вы работаете с легаси-кодом, который написан на CommonJS.
- Вы используете старые версии Node.js (< 12).
- Вам нужно динамически загружать модули (например, в условии).
import()тоже может это делать, но синтаксис другой.
Практические примеры
Пример 1: Экспорт по умолчанию (default export)
// myModule.ts
export default function greet(name: string): string {
return `Hello, ${name}!`;
}
// Правильный импорт:
import greet from './myModule'; // ✅ Работает
const greet = require('./myModule').default; // ✅ Тоже работает, но нужно .default
const greet = require('./myModule'); // ❌ Вернет объект { default: function }
Пример 2: Именованный экспорт (named export)
// utils.ts
export const add = (a: number, b: number) => a + b;
export const multiply = (a: number, b: number) => a * b;
// Правильный импорт:
import { add, multiply } from './utils'; // ✅
const { add, multiply } = require('./utils'); // ✅ Тоже работает
Пример 3: Динамический импорт
// CommonJS (синхронный)
const moduleA = require('./moduleA');
// ESM (асинхронный)
const moduleA = await import('./moduleA');
Рекомендация для TypeScript
Установите в tsconfig.json:
{
"compilerOptions": {
"module": "node16", // или "nodenext" — это самый современный режим
"moduleResolution": "node16",
"esModuleInterop": true // важно для совместимости с CommonJS
}
}
Опция esModuleInterop позволяет вам писать import express from 'express' даже для CommonJS-пакетов. Без нее вам пришлось бы писать import * as express from 'express'.
В итоге: для новых проектов используйте import. Это современный стандарт, который лучше поддерживается инструментами и дает больше возможностей. require() оставьте для старых проектов или для случаев, когда нужна динамическая загрузка (но и для этого есть import()).
Дополнение: Как это работает под капотом
Стоит понимать, что require() — это просто функция, которую Node.js добавляет в каждый CommonJS-модуль. Она загружает код модуля, оборачивает его в функцию и выполняет. Все это происходит синхронно и в момент вызова.
import же работает иначе. Это не функция, а языковая конструкция. Модули анализируются до выполнения кода (на этапе парсинга). Поэтому import нельзя использовать внутри условий или функций:
// ❌ Так нельзя
if (condition) {
import { something } from './module';
}
// ✅ А так можно
if (condition) {
const module = await import('./module');
}
Для динамической загрузки существует import() (с круглыми скобками, как функция). Он возвращает Promise и работает и в ESM, и в CommonJS.
Еще один важный момент: кэширование. Обе системы кэшируют модули после первого require/import. Но в ESM кэш изолирован — если вы импортируете модуль через import и через require(), это могут быть два разных экземпляра модуля!
Поэтому не смешивайте два подхода в одном проекте без крайней необходимости. Выберите один (лучше ESM) и придерживайтесь его.
Ответов пока нет.
Войдите чтобы оставить ответ