Замена 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.