CommonJS va ES Modules — require, import, export, farqlari

~20 daq · Node.js · Kirish va Asoslar

Muammodan boshlaylik

Siz katta loyiha yozyapsiz. Hamma kodni bitta faylga yozsangiz — 5000 qatorlik fayl. Qayerda nima borligini topish qiyin. Bir narsa o'zgarsa — hamma narsa sinishi mumkin.

Yechim — kodni modullarga bo'lish. Har bir modul o'z ishini qiladi. Kerak bo'lganda import qilinadi.

Node.js da buning ikki yo'li bor: CommonJS (eski, lekin hali keng tarqalgan) va ES Modules (zamonaviy standart). Ikkalasini bilmasangiz — boshqa odamning kodini o'qiyotganda chalkashib qolasiz.


CommonJS — Node.js ning asl modul tizimi

Node.js 2009 yilda yaratilganda brauzerda modul tizimi yo'q edi. Node.js o'zining modul tizimini yaratdi — CommonJS. Hozir ham ko'plab loyihalar, npm paketlar CommonJS ishlatadi.

Eksport qilish — module.exports

// hisob.js
function qoshish(a, b) {
  return a + b;
}

function ayirish(a, b) {
  return a - b;
}

function kopaytirish(a, b) {
  return a * b;
}

module.exports = { qoshish, ayirish, kopaytirish };

module.exports — bu fayldan tashqariga chiqariladigan narsa. Boshqa fayl bu modulni import qilganda aynan module.exports da nima bo'lsa — shuni oladi.

Import qilish — require

// main.js
const hisob = require('./hisob');

console.log(hisob.qoshish(5, 3));      // 8
console.log(hisob.ayirish(10, 4));     // 6
console.log(hisob.kopaytirish(3, 7));  // 21

Yoki destructuring bilan:

const { qoshish, ayirish } = require('./hisob');

console.log(qoshish(5, 3));   // 8
console.log(ayirish(10, 4));  // 6

require qanday ishlaydi?

const hisob = require('./hisob');

Bu qator ishlayotganda Node.js quyidagilarni qiladi:

1. './hisob' ni qidiradi
   → './hisob.js' bormi? → ha → o'qiydi
   → './hisob/index.js' bormi? → ...

2. Faylni o'qiydi va bajaradi

3. module.exports ni qaytaradi

4. Natijani cache ga saqlaydi
   → Keyingi require('./hisob') — cache dan oladi
   → Fayl qayta o'qilmaydi!

Cache — muhim tushuncha. Bir modul bir marta yuklanadi. Ikkinchi marta require qilsangiz — xuddi shu ob'ekt qaytadi.

// a.js
const mod = require('./counter');
mod.count++;

// b.js
const mod = require('./counter');
console.log(mod.count); // a.js da oshirilgan qiymat!
// Chunki ikkalasi bir xil ob'ektni ko'rsatadi

Eksport qilishning boshqa usullari

// Usul 1 — to'liq ob'ekt
module.exports = { qoshish, ayirish };

// Usul 2 — bitta-bitta
module.exports.qoshish = function(a, b) { return a + b; };
module.exports.ayirish = function(a, b) { return a - b; };

// Usul 3 — exports (module.exports ning qisqartmasi)
exports.qoshish = function(a, b) { return a + b; };

// Usul 4 — bitta funksiya yoki class eksport
module.exports = function(a, b) { return a + b; };
// Ishlatish:
const qoshish = require('./hisob');
qoshish(5, 3); // 8

⚠️ Diqqat: exports va module.exports farqi bor. exports = { ... } deb yozsangiz — ishlamaydi! exports faqat module.exports ga property qo'shish uchun. To'liq ob'ekt berishda module.exports = { ... } ishlating.


ES Modules — zamonaviy standart

ES Modules (ESM) — 2015 yilda JavaScript standartiga kiritildi. Brauzer va Node.js ikkalasida ham ishlaydi. Node.js 12 dan boshlab to'liq qo'llab-quvvatlanadi.

Fayl kengaytmasi yoki package.json

Node.js da ESM ishlatish uchun ikki yo'l:

1. Fayl kengaytmasi: .mjs
   hisob.mjs → ESM

2. package.json da:
   { "type": "module" }
   → barcha .js fayllar ESM bo'ladi

Eksport qilish — ikki tur

ESM da eksportning ikki turi bor. Bu farqni tushunmasdan import da chalkashib qolasiz.

Birinchi tur — nomli eksport. Bir moduldan xohlagancha narsa chiqarish mumkin. Har birining o'z nomi bor.

// hisob.js
export function qoshish(a, b) {
  return a + b;
}

export function ayirish(a, b) {
  return a - b;
}

export const PI = 3.14159;

Yoki oxirida bir joyda:

function qoshish(a, b) { return a + b; }
function ayirish(a, b) { return a - b; }
const PI = 3.14159;

export { qoshish, ayirish, PI };

Nomli eksportni import qilganda — nom aniq yozilishi shart, { } ichida:

import { qoshish, ayirish, PI } from './hisob.js';
// Nom noto'g'ri yozilsa — xato!
// import { qoshish2 } from './hisob.js' → xato

Ikkinchi tur — asosiy eksport (default). Har bir modulda faqat bitta bo'lishi mumkin. Modulning "bosh" narsasi.

// logger.js
export default function log(xabar) {
  console.log(`[LOG] ${xabar}`);
}

default eksportni import qilganda — nom o'zingiz belgilaysiz, { } shart emas:

import log from './logger.js';         // "log" — o'zingiz qo'ydingiz
import xabarYoz from './logger.js';    // bu ham ishlaydi, xuddi shu funksiya

Ikkalasini birgalikda ishlatish:

// api.js
export const BASE_URL = 'https://api.example.com';
export const TIMEOUT = 5000;

export default class ApiClient {
  get(url) { /* ... */ }
  post(url, data) { /* ... */ }
}
import ApiClient, { BASE_URL, TIMEOUT } from './api.js';
//     ↑ default      ↑ nomli eksportlar

Qachon qaysinisini ishlatish kerak?

default export:
  ✓ Modulning asosiy maqsadi bitta narsa bo'lganda
  ✓ Class, bitta funksiya, bitta ob'ekt eksport qilganda
  Misol: React komponenti, Express router, Mongoose model

Nomli export:
  ✓ Bir moduldan bir nechta narsa chiqarganda
  ✓ Utility funksiyalar, konstantalar
  Misol: hisob.js, format.js, helpers.js

Import qilish — import

// main.js

// Nomli import — { } ichida, nom aniq bo'lishi shart
import { qoshish, ayirish } from './hisob.js';

// Default import — { } siz, nom o'zingiz belgilaysiz
import log from './logger.js';

// Ikkalasi birgalikda
import ApiClient, { BASE_URL, TIMEOUT } from './api.js';

// Hammasini bitta ob'ektga — namespace import
import * as hisob from './hisob.js';
// hisob.qoshish(5, 3)
// hisob.PI

console.log(qoshish(5, 3));    // 8
console.log(hisob.PI);         // 3.14159
log('Dastur boshlandi');        // [LOG] Dastur boshlandi

import * as hisob — moduldan hamma narsani hisob ob'ektiga yuklaydi. Qachon foydali? Bir moduldan juda ko'p narsa import qilganda, yoki nom to'qnashuvi bo'lganda.

💡 ESM da fayl kengaytmasini yozish shart: './hisob.js' CommonJS da './hisob' yetarli edi.

Dynamic import — kerak bo'lganda yuklash

// Faqat kerak bo'lganda yuklash
async function yuklash() {
  const { qoshish } = await import('./hisob.js');
  console.log(qoshish(5, 3));
}

yuklash();

Bu — katta loyihalarda performance uchun muhim. Hamma modulni boshida yuklamasdan, kerak bo'lganda yuklanadi.


CommonJS vs ES Modules — asosiy farqlar

CommonJS ES Modules
Sintaksis require / module.exports import / export
Yuklash Sinxron (kutadi) Asinxron (kutmaydi)
Qachon bajariladi Runtime da Parse vaqtida
Top-level await Yo'q Bor
Tree shaking Qiyin Oson
Brauzerda Yo'q Ha
Node.js da Ha (default) Ha (.mjs yoki "type":"module")

Yuklash farqi — muhim

CommonJS sinxron yuklaydi. Ya'ni require chaqirilganda — fayl o'qilib, bajarilguncha keyingi qator kutadi.

// CommonJS — sinxron
const a = require('./a'); // kutadi
const b = require('./b'); // a tayyor bo'lgandan keyin

ES Modules asinxron yuklaydi. Barcha importlar parallel yuklanishi mumkin — bu tezroq.

// ESM — asinxron, parallel
import a from './a.js';
import b from './b.js';
// Ikkalasi parallel yuklanadi

Tree shaking

ESM da tree shaking ishlaydi — ishlatilmagan eksportlar bundle dan chiqarib tashlanadi. CommonJS da bu qiyin, chunki require dinamik — runtime da qaysi eksport kerakligini bilish qiyin.

// hisob.js — 3 ta funksiya eksport qiladi
export function qoshish(a, b) { return a + b; }
export function ayirish(a, b) { return a - b; }
export function bolish(a, b) { return a / b; }

// main.js — faqat qoshish ishlatiladi
import { qoshish } from './hisob.js';
// Tree shaking: ayirish va bolish bundle ga kirmaydi

Amaliy misol — modul tuzilmasi

Kichik loyihada modullarni qanday tashkil qilish:

my-project/
├── package.json
├── index.js
├── utils/
│   ├── hisob.js
│   ├── format.js
│   └── index.js    ← barcha utils ni re-export qiladi
└── config/
    └── index.js
// utils/hisob.js
export function qoshish(a, b) { return a + b; }
export function ayirish(a, b) { return a - b; }

// utils/format.js
export function pulFormat(son) {
  return son.toLocaleString('uz-UZ') + " so'm";
}

// utils/index.js — barrel export
export { qoshish, ayirish } from './hisob.js';
export { pulFormat } from './format.js';

// index.js — faqat bitta joydan import
import { qoshish, pulFormat } from './utils/index.js';

console.log(qoshish(100, 200));        // 300
console.log(pulFormat(1500000));       // 1 500 000 so'm

Bu barrel export pattern — katta loyihalarda import larni tartibli saqlash uchun.


CommonJS va ESM ni aralashtirib ishlatish

Ba'zan CommonJS paketni ESM loyihada ishlatish kerak bo'ladi. Bu mumkin, lekin ehtiyot bo'lish kerak.

// ESM faylda CommonJS paketni import qilish
import express from 'express'; // ko'plab paketlar CJS, lekin bu ishlaydi

// CJS faylda ESM modulni import qilish — to'g'ridan-to'g'ri MUMKIN EMAS
// Yechim: dynamic import
async function main() {
  const { default: chalk } = await import('chalk'); // ESM paketi
  console.log(chalk.green('Yashil matn'));
}

⚠️ CJS faylda import kalit so'zi ishlamaydi. ESM faylda require ishlamaydi. Aralashtirishda dynamic import() yordamga keladi.


Qaysi birini tanlash?

Yangi loyiha boshlayapsizmi? — ES Modules. Zamonaviy standart, brauzer va Node.js ikkalasida ishlaydi, tree shaking bor.

Mavjud Node.js loyiha? — CommonJS ni saqlab qoling. Hamma narsani ESM ga o'tkazish katta ish, foyda kam.

npm paket yozyapsizmi? — Ikkalasini ham qo'llab-quvvatlang (dual package). Ko'plab zamonaviy paketlar shunday qiladi.


Xulosa

CommonJS — Node.js ning asl modul tizimi. require va module.exports. Sinxron yuklaydi. Hali keng tarqalgan.

ES Modules — zamonaviy standart. import va export. Asinxron yuklaydi. Tree shaking ishlaydi. Brauzerda ham ishlaydi.

Ikkalasini bilish shart — chunki npm da ikkalasi ham uchraydi. Yangi loyihalarda ESM, eski loyihalarda CJS.


Keyingi dars

5-dars: package.json va npm — scripts, dependencies, npx