CommonJS (CJS) — система модулей Node.js: require() синхронный, exports — обычный объект, разрешение зависимостей в рантайме. ES Modules (ESM) — стандарт ECMAScript: import/export статические (анализируются до выполнения), поддерживают tree shaking, работают в браузере нативно, динамический import() для ленивой загрузки. Современные проекты используют ESM, Node.js поддерживает оба формата.
// math.js (CommonJS)
function add(a, b) { return a + b }
function multiply(a, b) { return a * b }
module.exports = { add, multiply }
// или по одному:
// module.exports.add = add
// exports.add = add (shorthand)
// main.js (CommonJS)
const { add, multiply } = require('./math')
// или: const math = require('./math')
// Ключевые особенности CJS:
// 1. Синхронный: require() блокирует поток до загрузки модуля
// 2. Динамический: можно использовать внутри условий и функций
if (process.env.NODE_ENV === 'development') {
const devTools = require('./dev-tools') // динамически!
}
// 3. Кэширование: повторный require() возвращает тот же объект
const a = require('./math')
const b = require('./math')
console.log(a === b) // true — один и тот же объект из кэша
// 4. Объект exports — живая ссылка на module.exports
// exports.foo = 'bar' работает
// exports = { foo: 'bar' } НЕ работает (переприсваивание ссылки)// math.js (ES Modules)
export function add(a, b) { return a + b }
export function multiply(a, b) { return a * b }
export const PI = 3.14159
// default export — один на файл
export default class Calculator {
// ...
}
// main.js (ES Modules)
import Calculator, { add, multiply, PI } from './math.js'
// named imports + default import
// Реэкспорт (barrel file pattern):
// index.js
export { add, multiply } from './math.js'
export { default as Calculator } from './math.js'
// Переименование при импорте:
import { add as sum, multiply as mul } from './math.js'
// Импорт всего как namespace:
import * as MathUtils from './math.js'
MathUtils.add(1, 2)// DEFAULT — один главный экспорт, импортируется с любым именем
// Когда: один класс, одна функция, одна константа — главное в файле
export default function fetchUser(id) { /* ... */ }
import fetchUser from './user' // любое имя
import getUser from './user' // тоже работает!
// NAMED — несколько именованных экспортов, имена фиксированы
// Когда: набор утилит, константы, несколько функций
export const API_URL = 'https://api.example.com'
export function formatDate(date) { /* ... */ }
import { API_URL, formatDate } from './utils' // имена точные
// ЛУЧШАЯ ПРАКТИКА: предпочитай named exports
// Они явные, лучше работают с tree shaking и IDE autocomplete// a.js
import { b } from './b.js'
export const a = 'A: ' + b // b может быть undefined при старте!
// b.js
import { a } from './a.js'
export const b = 'B: ' + a // a может быть undefined при старте!
// ESM: живые привязки (live bindings) — значения обновляются
// CJS: копии значений на момент require()
// Решение: рефакторить, чтобы избежать циклов, или использовать функции// utils.js — 3 функции, но используем только одну
export function formatDate(d) { /* ... */ }
export function parseDate(s) { /* ... */ }
export function addDays(d, n) { /* ... */ }
// main.js
import { formatDate } from './utils.js'
// С ESM (статический анализ): бандлер увидит, что parseDate и addDays
// не используются, и исключит их из бандла (tree shaking)
// С CJS это НЕВОЗМОЖНО:
const utils = require('./utils')
// Бандлер не знает, какие свойства объекта будут использоваться в рантайме
utils.formatDate(new Date()) // весь utils.js попадает в бандл// Статический import — загружается всегда при старте
import { heavyChart } from './chart.js' // загружается сразу
// Динамический import() — Promise, загружается по требованию
async function showChart() {
// Загружается только когда нужен (code splitting)
const { heavyChart } = await import('./chart.js')
heavyChart.render('#container')
}
// В React (lazy loading компонентов):
// const ChartPage = React.lazy(() => import('./ChartPage'))
// Webpack/Vite автоматически создадут отдельный чанк
// Условная загрузка (нельзя со статическим import):
async function loadPlugin(name) {
const plugin = await import(`./plugins/${name}.js`)
return plugin.default
}{
"type": "module",
"exports": {
".": {
"import": "./dist/index.esm.js",
"require": "./dist/index.cjs.js"
}
}
}Начни с главного отличия: «CJS — синхронный, динамический, runtime-разрешение; ESM — статический, async, compile-time анализ, что и делает tree shaking возможным». Объясни практические последствия: tree shaking уменьшает размер бандла, динамический import() позволяет code splitting. Упомяни live bindings в ESM vs. копии значений в CJS.
Демонстрация обеих модульных систем, динамический import, паттерн barrel-файла
// ===== ЭМУЛЯЦИЯ CommonJS В СРЕДЕ ВЫПОЛНЕНИЯ =====
console.log('=== CommonJS паттерны ===')
// Эмулируем module.exports / require для демонстрации
function createCJSModule(factory) {
const module = { exports: {} }
const exports = module.exports
factory(module, exports)
return module.exports
}
// math.cjs.js — CommonJS стиль
const mathCJS = createCJSModule((module, exports) => {
function add(a, b) { return a + b }
function multiply(a, b) { return a * b }
const PI = 3.14159
// Два способа экспорта в CJS:
exports.add = add // через exports shorthand
exports.multiply = multiply
module.exports.PI = PI // через module.exports
})
console.log('add(2, 3):', mathCJS.add(2, 3)) // 5
console.log('multiply(4, 5):', mathCJS.multiply(4, 5)) // 20
console.log('PI:', mathCJS.PI) // 3.14159
// Деструктурирование при импорте (CJS стиль)
const { add, multiply, PI } = mathCJS
console.log('Деструктурирование:', add(1, 2), multiply(3, 4), PI)
// ===== ES MODULES ПАТТЕРНЫ (статические) =====
console.log('\n=== ES Modules паттерны ===')
// Named exports — несколько утилит
// В реальном ESM файле: export function formatDate(d) { ... }
const dateUtils = (() => {
function formatDate(date) {
const d = new Date(date)
return d.toLocaleDateString('ru-RU')
}
function formatTime(date) {
const d = new Date(date)
return d.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' })
}
function isWeekend(date) {
const day = new Date(date).getDay()
return day === 0 || day === 6
}
// export { formatDate, formatTime, isWeekend }
return { formatDate, formatTime, isWeekend }
})()
const now = new Date('2024-03-15T14:30:00')
console.log('formatDate:', dateUtils.formatDate(now))
console.log('formatTime:', dateUtils.formatTime(now))
console.log('isWeekend(пятница):', dateUtils.isWeekend(now))
// Default export — один главный класс/функция
// В реальном ESM: export default class EventBus { ... }
class EventBus {
constructor() { this.listeners = new Map() }
on(event, fn) { /* ... */ }
emit(event, data) { /* ... */ }
}
// import EventBus from './event-bus.js'
// import Bus from './event-bus.js' — любое имя для default
// ===== ДИНАМИЧЕСКИЙ IMPORT (ЭМУЛЯЦИЯ) =====
console.log('\n=== Динамический import() — ленивая загрузка ===')
// Реальный динамический import():
// const module = await import('./heavy-module.js')
// Эмулируем через Promise для демонстрации
function mockDynamicImport(moduleName) {
return new Promise((resolve) => {
setTimeout(() => {
// Имитируем загрузку чанка
const modules = {
'chart': {
default: { render: (el) => `Chart rendered in ${el}` }
},
'pdf-generator': {
generatePDF: (data) => `PDF generated with ${JSON.stringify(data)}`
}
}
resolve(modules[moduleName] || {})
}, 10)
})
}
// Code splitting: загружаем только когда нужно
async function showChart(elementId) {
console.log('Загружаем чарт-библиотеку...')
const { default: chart } = await mockDynamicImport('chart')
console.log(chart.render(elementId))
}
async function exportToPDF(data) {
console.log('Загружаем PDF генератор...')
const { generatePDF } = await mockDynamicImport('pdf-generator')
console.log(generatePDF(data))
}
showChart('#dashboard-chart')
exportToPDF({ title: 'Отчёт', rows: 150 })
// ===== BARREL FILE PATTERN =====
console.log('\n=== Barrel file паттерн ===')
// components/index.js (barrel):
// export { Button } from './Button.js'
// export { Input } from './Input.js'
// export { Modal } from './Modal.js'
// Использование:
// import { Button, Modal } from './components'
// Вместо:
// import { Button } from './components/Button'
// import { Modal } from './components/Modal'
// Эмуляция для демонстрации
const Button = { render: () => '<button>' }
const Input = { render: () => '<input>' }
const Modal = { render: () => '<div class=modal>' }
const components = { Button, Input, Modal }
const { Button: Btn, Modal: Mdl } = components
console.log('Button:', Btn.render())
console.log('Modal:', Mdl.render())
// ===== CJS vs ESM: TREE SHAKING =====
console.log('\n=== Tree Shaking: почему ESM лучше ===')
const bigLibrary = {
// Только formatDate используется в коде
formatDate: (d) => new Date(d).toLocaleDateString('ru-RU'),
// Эти функции НИКОГДА не используются в приложении:
generateReport: () => { /* 50KB кода */ },
exportToExcel: () => { /* 100KB кода */ },
renderPDF: () => { /* 200KB кода */ },
}
// CJS: const { formatDate } = require('big-library')
// Бандлер включает ВСЁ в бандл (не знает что нужно в рантайме)
// ESM: import { formatDate } from 'big-library'
// Бандлер ЗНАЕТ что нужно только formatDate → 350KB не войдут в бандл!
console.log('Tree shaking исключит неиспользуемый код из бандла')
console.log('Это возможно ТОЛЬКО с ESM (статический анализ импортов)')CommonJS (CJS) — система модулей Node.js: require() синхронный, exports — обычный объект, разрешение зависимостей в рантайме. ES Modules (ESM) — стандарт ECMAScript: import/export статические (анализируются до выполнения), поддерживают tree shaking, работают в браузере нативно, динамический import() для ленивой загрузки. Современные проекты используют ESM, Node.js поддерживает оба формата.
// math.js (CommonJS)
function add(a, b) { return a + b }
function multiply(a, b) { return a * b }
module.exports = { add, multiply }
// или по одному:
// module.exports.add = add
// exports.add = add (shorthand)
// main.js (CommonJS)
const { add, multiply } = require('./math')
// или: const math = require('./math')
// Ключевые особенности CJS:
// 1. Синхронный: require() блокирует поток до загрузки модуля
// 2. Динамический: можно использовать внутри условий и функций
if (process.env.NODE_ENV === 'development') {
const devTools = require('./dev-tools') // динамически!
}
// 3. Кэширование: повторный require() возвращает тот же объект
const a = require('./math')
const b = require('./math')
console.log(a === b) // true — один и тот же объект из кэша
// 4. Объект exports — живая ссылка на module.exports
// exports.foo = 'bar' работает
// exports = { foo: 'bar' } НЕ работает (переприсваивание ссылки)// math.js (ES Modules)
export function add(a, b) { return a + b }
export function multiply(a, b) { return a * b }
export const PI = 3.14159
// default export — один на файл
export default class Calculator {
// ...
}
// main.js (ES Modules)
import Calculator, { add, multiply, PI } from './math.js'
// named imports + default import
// Реэкспорт (barrel file pattern):
// index.js
export { add, multiply } from './math.js'
export { default as Calculator } from './math.js'
// Переименование при импорте:
import { add as sum, multiply as mul } from './math.js'
// Импорт всего как namespace:
import * as MathUtils from './math.js'
MathUtils.add(1, 2)// DEFAULT — один главный экспорт, импортируется с любым именем
// Когда: один класс, одна функция, одна константа — главное в файле
export default function fetchUser(id) { /* ... */ }
import fetchUser from './user' // любое имя
import getUser from './user' // тоже работает!
// NAMED — несколько именованных экспортов, имена фиксированы
// Когда: набор утилит, константы, несколько функций
export const API_URL = 'https://api.example.com'
export function formatDate(date) { /* ... */ }
import { API_URL, formatDate } from './utils' // имена точные
// ЛУЧШАЯ ПРАКТИКА: предпочитай named exports
// Они явные, лучше работают с tree shaking и IDE autocomplete// a.js
import { b } from './b.js'
export const a = 'A: ' + b // b может быть undefined при старте!
// b.js
import { a } from './a.js'
export const b = 'B: ' + a // a может быть undefined при старте!
// ESM: живые привязки (live bindings) — значения обновляются
// CJS: копии значений на момент require()
// Решение: рефакторить, чтобы избежать циклов, или использовать функции// utils.js — 3 функции, но используем только одну
export function formatDate(d) { /* ... */ }
export function parseDate(s) { /* ... */ }
export function addDays(d, n) { /* ... */ }
// main.js
import { formatDate } from './utils.js'
// С ESM (статический анализ): бандлер увидит, что parseDate и addDays
// не используются, и исключит их из бандла (tree shaking)
// С CJS это НЕВОЗМОЖНО:
const utils = require('./utils')
// Бандлер не знает, какие свойства объекта будут использоваться в рантайме
utils.formatDate(new Date()) // весь utils.js попадает в бандл// Статический import — загружается всегда при старте
import { heavyChart } from './chart.js' // загружается сразу
// Динамический import() — Promise, загружается по требованию
async function showChart() {
// Загружается только когда нужен (code splitting)
const { heavyChart } = await import('./chart.js')
heavyChart.render('#container')
}
// В React (lazy loading компонентов):
// const ChartPage = React.lazy(() => import('./ChartPage'))
// Webpack/Vite автоматически создадут отдельный чанк
// Условная загрузка (нельзя со статическим import):
async function loadPlugin(name) {
const plugin = await import(`./plugins/${name}.js`)
return plugin.default
}{
"type": "module",
"exports": {
".": {
"import": "./dist/index.esm.js",
"require": "./dist/index.cjs.js"
}
}
}Начни с главного отличия: «CJS — синхронный, динамический, runtime-разрешение; ESM — статический, async, compile-time анализ, что и делает tree shaking возможным». Объясни практические последствия: tree shaking уменьшает размер бандла, динамический import() позволяет code splitting. Упомяни live bindings в ESM vs. копии значений в CJS.
Демонстрация обеих модульных систем, динамический import, паттерн barrel-файла
// ===== ЭМУЛЯЦИЯ CommonJS В СРЕДЕ ВЫПОЛНЕНИЯ =====
console.log('=== CommonJS паттерны ===')
// Эмулируем module.exports / require для демонстрации
function createCJSModule(factory) {
const module = { exports: {} }
const exports = module.exports
factory(module, exports)
return module.exports
}
// math.cjs.js — CommonJS стиль
const mathCJS = createCJSModule((module, exports) => {
function add(a, b) { return a + b }
function multiply(a, b) { return a * b }
const PI = 3.14159
// Два способа экспорта в CJS:
exports.add = add // через exports shorthand
exports.multiply = multiply
module.exports.PI = PI // через module.exports
})
console.log('add(2, 3):', mathCJS.add(2, 3)) // 5
console.log('multiply(4, 5):', mathCJS.multiply(4, 5)) // 20
console.log('PI:', mathCJS.PI) // 3.14159
// Деструктурирование при импорте (CJS стиль)
const { add, multiply, PI } = mathCJS
console.log('Деструктурирование:', add(1, 2), multiply(3, 4), PI)
// ===== ES MODULES ПАТТЕРНЫ (статические) =====
console.log('\n=== ES Modules паттерны ===')
// Named exports — несколько утилит
// В реальном ESM файле: export function formatDate(d) { ... }
const dateUtils = (() => {
function formatDate(date) {
const d = new Date(date)
return d.toLocaleDateString('ru-RU')
}
function formatTime(date) {
const d = new Date(date)
return d.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' })
}
function isWeekend(date) {
const day = new Date(date).getDay()
return day === 0 || day === 6
}
// export { formatDate, formatTime, isWeekend }
return { formatDate, formatTime, isWeekend }
})()
const now = new Date('2024-03-15T14:30:00')
console.log('formatDate:', dateUtils.formatDate(now))
console.log('formatTime:', dateUtils.formatTime(now))
console.log('isWeekend(пятница):', dateUtils.isWeekend(now))
// Default export — один главный класс/функция
// В реальном ESM: export default class EventBus { ... }
class EventBus {
constructor() { this.listeners = new Map() }
on(event, fn) { /* ... */ }
emit(event, data) { /* ... */ }
}
// import EventBus from './event-bus.js'
// import Bus from './event-bus.js' — любое имя для default
// ===== ДИНАМИЧЕСКИЙ IMPORT (ЭМУЛЯЦИЯ) =====
console.log('\n=== Динамический import() — ленивая загрузка ===')
// Реальный динамический import():
// const module = await import('./heavy-module.js')
// Эмулируем через Promise для демонстрации
function mockDynamicImport(moduleName) {
return new Promise((resolve) => {
setTimeout(() => {
// Имитируем загрузку чанка
const modules = {
'chart': {
default: { render: (el) => `Chart rendered in ${el}` }
},
'pdf-generator': {
generatePDF: (data) => `PDF generated with ${JSON.stringify(data)}`
}
}
resolve(modules[moduleName] || {})
}, 10)
})
}
// Code splitting: загружаем только когда нужно
async function showChart(elementId) {
console.log('Загружаем чарт-библиотеку...')
const { default: chart } = await mockDynamicImport('chart')
console.log(chart.render(elementId))
}
async function exportToPDF(data) {
console.log('Загружаем PDF генератор...')
const { generatePDF } = await mockDynamicImport('pdf-generator')
console.log(generatePDF(data))
}
showChart('#dashboard-chart')
exportToPDF({ title: 'Отчёт', rows: 150 })
// ===== BARREL FILE PATTERN =====
console.log('\n=== Barrel file паттерн ===')
// components/index.js (barrel):
// export { Button } from './Button.js'
// export { Input } from './Input.js'
// export { Modal } from './Modal.js'
// Использование:
// import { Button, Modal } from './components'
// Вместо:
// import { Button } from './components/Button'
// import { Modal } from './components/Modal'
// Эмуляция для демонстрации
const Button = { render: () => '<button>' }
const Input = { render: () => '<input>' }
const Modal = { render: () => '<div class=modal>' }
const components = { Button, Input, Modal }
const { Button: Btn, Modal: Mdl } = components
console.log('Button:', Btn.render())
console.log('Modal:', Mdl.render())
// ===== CJS vs ESM: TREE SHAKING =====
console.log('\n=== Tree Shaking: почему ESM лучше ===')
const bigLibrary = {
// Только formatDate используется в коде
formatDate: (d) => new Date(d).toLocaleDateString('ru-RU'),
// Эти функции НИКОГДА не используются в приложении:
generateReport: () => { /* 50KB кода */ },
exportToExcel: () => { /* 100KB кода */ },
renderPDF: () => { /* 200KB кода */ },
}
// CJS: const { formatDate } = require('big-library')
// Бандлер включает ВСЁ в бандл (не знает что нужно в рантайме)
// ESM: import { formatDate } from 'big-library'
// Бандлер ЗНАЕТ что нужно только formatDate → 350KB не войдут в бандл!
console.log('Tree shaking исключит неиспользуемый код из бандла')
console.log('Это возможно ТОЛЬКО с ESM (статический анализ импортов)')Конвертируй код с CommonJS в формат ES Modules. Перепиши функции createValidator и validateUser используя ES Module синтаксис (export/import вместо module.exports/require). Также реализуй функцию loadModule(name) которая возвращает Promise — эмуляцию динамического import().
isEmail: str.includes("@") && str.slice(str.indexOf("@")).includes(".") isPhone: /^\+?\d{10,15}$/.test(str) createValidator: итерируй Object.entries(rules), вызывай каждый валидатор loadModule: new Promise((resolve, reject) => setTimeout(() => modules[name] ? resolve(modules[name]) : reject(new Error(...)), 10))
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке