Замена require на import
## Причины отказа от `require`
Система модулей CommonJS долгое время была стандартом в экосистеме Node.js. Для подключения зависимостей использовалась функция `require`, а экспорт выполнялся через `module.exports`.
```js
const fs = require('fs')
module.exports = function sum(a, b) {
return a + b
}
```
С развитием JavaScript появился стандарт ECMAScript Modules (ESM), использующий ключевые слова `import` и `export`.
```js
import fs from 'fs'
export default function sum(a, b) {
return a + b
}
```
Vite ориентирован именно на ESM. Его архитектура строится вокруг нативных ES-модулей браузера, поэтому использование `import` становится обязательной частью современной разработки.
---
## Отличия `require` и `import`
### Синтаксическая природа
`require` является обычной функцией.
```js
const moduleA = require('./moduleA')
```
`import` — часть синтаксиса языка.
```js
import moduleA from './moduleA.js'
```
Из-за этого `import` анализируется ещё до выполнения кода, что позволяет Vite:
* строить граф зависимостей;
* выполнять tree shaking;
* ускорять Hot Module Replacement;
* оптимизировать загрузку модулей.
---
## Статический анализ импортов
Одно из ключевых преимуществ ESM — статичность.
`import` нельзя помещать в условия или функции обычным способом:
```js
if (condition) {
import moduleA from './moduleA.js'
}
```
Такой код вызовет ошибку.
`require` подобных ограничений не имеет:
```js
if (condition) {
const moduleA = require('./moduleA')
}
```
Однако именно предсказуемость `import` делает возможной высокую скорость Vite.
---
## Базовая замена `require` на `import`
### Импорт по умолчанию
#### CommonJS
```js
const lodash = require('lodash')
```
#### ESM
```js
import lodash from 'lodash'
```
---
### Именованный импорт
#### CommonJS
```js
const map = require('lodash/map')
```
#### ESM
```js
import map from 'lodash/map'
```
---
### Несколько импортов
#### CommonJS
```js
const fs = require('fs')
const path = require('path')
```
#### ESM
```js
import fs from 'fs'
import path from 'path'
```
---
## Замена `module.exports`
### Экспорт по умолчанию
#### CommonJS
```js
module.exports = function greet() {
console.log('Hello')
}
```
#### ESM
```js
export default function greet() {
console.log('Hello')
}
```
---
### Именованный экспорт
#### CommonJS
```js
exports.sum = (a, b) => a + b
exports.mul = (a, b) => a * b
```
#### ESM
```js
export const sum = (a, b) => a + b
export const mul = (a, b) => a * b
```
---
## Импорт именованных экспортов
```js
export const apiUrl = 'https://example.com'
export const timeout = 5000
```
Импорт:
```js
import { apiUrl, timeout } from './config.js'
```
---
## Переименование импортов
```js
import { apiUrl as baseUrl } from './config.js'
```
---
## Импорт всего модуля
```js
import * as utils from './utils.js'
```
Использование:
```js
utils.sum(1, 2)
utils.mul(3, 4)
```
---
## Динамический импорт вместо условного `require`
### Проблема
В CommonJS часто встречается такой код:
```js
const adapter = require(`./adapters/${name}`)
```
Для Vite подобный подход проблематичен, поскольку путь вычисляется динамически.
---
### Решение через `import()`
```js
const adapter = await import(`./adapters/${name}.js`)
```
`import()` возвращает Promise.
---
## Асинхронная природа `import()`
```js
const module = await import('./math.js')
console.log(module.sum(2, 3))
```
Либо:
```js
import('./math.js').then(module => {
console.log(module.sum(2, 3))
})
```
---
## Использование `import.meta`
В Vite объект `import.meta` играет важную роль.
### Получение переменных окружения
```js
console.log(import.meta.env.VITE_API_URL)
```
---
### Проверка режима
```js
if (import.meta.env.DEV) {
console.log('Development mode')
}
```
---
## Замена `__dirname`
В ESM отсутствуют глобальные переменные:
* `__dirname`
* `__filename`
### CommonJS
```js
console.log(__dirname)
```
---
### ESM-эквивалент
```js
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
console.log(__dirname)
```
---
## Замена JSON-импортов
### CommonJS
```js
const config = require('./config.json')
```
---
### ESM
Во многих случаях Vite поддерживает прямой импорт:
```js
import config from './config.json'
```
---
## Особенности расширений файлов
В CommonJS расширение часто опускается:
```js
const utils = require('./utils')
```
В ESM рекомендуется явно указывать расширение:
```js
import utils from './utils.js'
```
Это особенно важно при работе вне Node.js и внутри браузерной среды.
---
## Совместимость CommonJS и ESM
Некоторые старые библиотеки всё ещё используют CommonJS.
### Импорт CommonJS-модуля
```js
import chalk from 'chalk'
```
Vite автоматически пытается обеспечить совместимость.
---
## Проблемы с именованными импортами
Иногда код выглядит так:
```js
import { something } from 'old-library'
```
Но библиотека экспортируется через `module.exports`.
В таком случае может потребоваться:
```js
import oldLibrary from 'old-library'
oldLibrary.something()
```
---
## Интероперабельность модулей
### CommonJS внутри ESM
```js
import pkg from 'some-commonjs-package'
```
---
### ESM внутри CommonJS
Для старого Node.js это сложнее:
```js
async function load() {
const module = await import('./module.js')
}
```
---
## Использование `type: "module"`
Чтобы Node.js воспринимал файлы как ESM, в `package.json` указывается:
```json
{
"type": "module"
}
```
После этого:
* `.js` трактуется как ESM;
* `require` становится недоступен;
* необходимо использовать `import`.
---
## Использование расширения `.mjs`
Альтернативный вариант:
```txt
server.mjs
```
Такой файл автоматически считается ESM.
---
## Как Vite обрабатывает модули
Vite использует браузерные ES-модули напрямую во время разработки.
При импорте:
```js
import { sum } from './math.js'
```
сервер Vite:
* анализирует зависимости;
* переписывает пути;
* кэширует преобразования;
* отправляет отдельные модули браузеру.
Это позволяет избежать полной пересборки проекта.
---
## Tree Shaking
ESM позволяет удалять неиспользуемый код.
### Модуль
```js
export function used() {}
export function unused() {}
```
---
### Импорт
```js
import { used } from './utils.js'
```
В production-сборку попадёт только `used`.
При использовании CommonJS это значительно сложнее.
---
## Побочные эффекты модулей
### Пример побочного эффекта
```js
console.log('Module loaded')
```
При импорте:
```js
import './logger.js'
```
код выполнится сразу после загрузки модуля.
---
## Порядок выполнения импортов
Импорты поднимаются вверх автоматически.
```js
console.log('A')
import './module.js'
console.log('B')
```
Фактический порядок:
1. Выполнение импортов
2. Выполнение основного кода
---
## Циклические зависимости
### Пример проблемы
#### a.js
```js
import { b } from './b.js'
export const a = 'A'
```
#### b.js
```js
import { a } from './a.js'
export const b = 'B'
```
ESM обрабатывает циклические зависимости лучше CommonJS, но сложные циклы всё равно способны вызывать ошибки и `undefined`.
---
## Импорт CSS
Vite позволяет импортировать стили прямо в JavaScript.
```js
import './styles.css'
```
---
## Импорт ресурсов
### Изображения
```js
import logo from './logo.png'
```
---
### SVG
```js
import icon from './icon.svg'
```
---
## URL-импорты
```js
const imageUrl = new URL('./image.png', import.meta.url).href
```
Подход полезен для:
* web workers;
* динамических ресурсов;
* библиотек.
---
## Импорт Web Worker
```js
import Worker from './worker.js?worker'
const worker = new Worker()
```
---
## Импорт как строка
```js
import text from './file.txt?raw'
```
---
## Ленивая загрузка модулей
### Обычный импорт
```js
import Chart from './Chart.js'
```
Модуль загружается сразу.
---
### Ленивая загрузка
```js
const Chart = await import('./Chart.js')
```
Загрузка произойдёт только в момент необходимости.
---
## Code Splitting
Dynamic import автоматически создаёт отдельные чанки.
```js
const module = await import('./heavy-module.js')
```
Vite вынесет модуль в отдельный файл.
---
## Миграция старого проекта
### Исходный CommonJS-код
```js
const express = require('express')
const path = require('path')
const app = express()
module.exports = app
```
---
### Версия для ESM
```js
import express from 'express'
import path from 'path'
const app = express()
export default app
```
---
## Миграция util-файлов
### До миграции
```js
const formatDate = () => {}
const parseDate = () => {}
module.exports = {
formatDate,
parseDate
}
```
---
### После миграции
```js
export const formatDate = () => {}
export const parseDate = () => {}
```
---
## Ошибка `require is not defined`
Одна из самых частых ошибок после перехода на Vite:
```txt
Uncaught ReferenceError: require is not defined
```
Причина — браузер не поддерживает CommonJS.
### Неправильно
```js
const axios = require('axios')
```
---
### Правильно
```js
import axios from 'axios'
```
---
## Ошибка `module is not defined`
### Неправильно
```js
module.exports = config
```
---
### Правильно
```js
export default config
```
---
## Ошибка смешивания систем модулей
### Проблемный код
```js
import fs from 'fs'
module.exports = {}
```
Нельзя смешивать ESM и CommonJS в одном файле.
---
## Автоматическая миграция
Для больших проектов часто используются:
* Babel codemods;
* jscodeshift;
* ESLint autofix;
* TypeScript refactoring tools.
---
## ESLint для ESM
Пример конфигурации:
```js
export default [
{
rules: {
'no-var': 'error'
}
}
]
```
---
## TypeScript и ESM
### tsconfig.json
```json
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "Bundler"
}
}
```
---
## Использование alias-импортов
### vite.config.js
```js
import { defineConfig } from 'vite'
import path from 'path'
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
}
})
```
---
### Использование
```js
import Button from '@/components/Button.js'
```
---
## Импорт переменных окружения
### `.env`
```txt
VITE_API_URL=https://api.example.com
```
---
### Использование
```js
console.log(import.meta.env.VITE_API_URL)
```
---
## Практика организации импортов
Распространённая структура:
```js
import fs from 'fs'
import express from 'express'
import { sum } from './utils.js'
import config from './config.js'
import './styles.css'
```
Порядок:
1. Встроенные модули
2. Внешние зависимости
3. Локальные файлы
4. Стили
---
## Реэкспорт модулей
### index.js
```js
export { sum } from './sum.js'
export { mul } from './mul.js'
```
---
### Использование
```js
import { sum, mul } from './math/index.js'
```
---
## Импорт по умолчанию и именованный экспорт
### Модуль
```js
export default function app() {}
export const version = '1.0.0'
```
---
### Импорт
```js
import app, { version } from './app.js'
```
---
## Ограничения ESM
### Нельзя использовать условные статические импорты
```js
if (condition) {
import moduleA from './moduleA.js'
}
```
---
### Нельзя импортировать после выполнения кода
```js
console.log('Start')
import fs from 'fs'
```
Импорты всегда располагаются сверху файла.
---
## Преимущества перехода на `import`
* нативная поддержка браузерами;
* высокая скорость Vite;
* поддержка tree shaking;
* code splitting;
* улучшенная оптимизация;
* предсказуемый граф зависимостей;
* лучшая интеграция с TypeScript;
* совместимость с современной экосистемой JavaScript.