IlmHamroh
JavaScript Full-stack/16-qism. Frontend asboblari: npm, bundlerlar, config7/48-dars19 daqiqa
Mundarija (30)

package.json kirish nuqtalari: main, module, exports, imports va types

Qisqacha: import "paket" yozganingizda qaysi fayl ochilishini paketning package.json i belgilaydi. Eski usul — main (va bundler'lar uchun module), zamonaviy usul — exports. Unda har kirish yo'li va shart (import, require, types) uchun alohida fayl yoziladi. Ro'yxatda yo'q fayllar tashqaridan yopiq (ERR_PACKAGE_PATH_NOT_EXPORTED). imports paket ichidagi # taxalluslarni, files — nashrga tushadigan fayllarni, bin — terminal buyrug'ini belgilaydi.

Bu darsda

  • Node paketni qanday topishini: exports → shartlar → fayl, yo'q bo'lsa main.
  • exports dagi import, require, types, default shartlari va ularning tartibi.
  • Kichik yo'llar (subpath) va ERR_PACKAGE_PATH_NOT_EXPORTED xatosi.
  • imports (#narxlar), files, bin, sideEffects va module maydonlari.
  • web-vitals ning haqiqiy exports ini o'qish.

Oldin bilishingiz kerak: package.json II: bog'liqlik turlari, CommonJS va ESM bilan o'zaro ishlash, Declaration fayllari (.d.ts).

1. Nega bu kerak?

O'tgan darsda vazifalar ga npm run check qo'shdik. Endi Sardor boshqa vazifa oldi: Jasur aka «Bahor» narxlarini bir nechta loyihada ishlatmoqchi — sayt, Telegram bot, kassa dasturi. "Narxlarni bitta paketga yig', hamma loyiha o'shandan olsin."

Sardor bahor-narx paketini yozdi va birinchi foydalanuvchisi — ESM loyihadagi kassa — darhol xato oldi. Keyin kimdir paketning ichki fayliga to'g'ridan-to'g'ri ulanib oldi, Sardor esa o'sha faylni o'zgartirdi va begona loyiha sinib qoldi. Ikkala muammoning yechimi bitta maydonda — exports.

Siz kutubxona yozmasangiz ham, bu dars kerak. Har import … from "vite" yoki "web-vitals" ortida aynan shu qoidalar ishlaydi. ERR_PACKAGE_PATH_NOT_EXPORTED yoki TypeScript'ning TS7016 xatosini ko'rganingizda, sababini shu yerdan qidirasiz.

Maslahat: Chiqishlar 2026-10-10 da Windows 11, Node 24.21.0, npm 11.19.0 va TypeScript 7.0.2 bilan olingan. Yo'l boshi … bilan qisqartirilgan.

2. Node paketni qanday topadi

2.1 Umumiy yo'l

import { priceOf } from "bahor-narx" — bu bare specifier ("yalang'och nom"): ./ yoki / bilan boshlanmaydi. Node uni shunday qidiradi:

flowchart TD
  A["import 'bahor-narx'"] --> B["node_modules/bahor-narx<br/>package.json"]
  B --> C{"exports bormi?"}
  C -- "ha" --> D["yo'l va shart bo'yicha<br/>fayl tanlanadi"]
  C -- "yo'q" --> E{"main bormi?"}
  E -- "ha" --> F["main fayli"]
  E -- "yo'q" --> G["index.js"]
  D --> H["ro'yxatda yo'q:<br/>ERR_PACKAGE_PATH_NOT_EXPORTED"]

Muhim qoida: exports bor bo'lsa, Node main ga umuman qaramaydi. main faqat exports ni bilmaydigan eski vositalar uchun qoldiriladi. node_modules qayerdan qidirilishi — keyingi darslarning biri, node_modules ichida.

2.2 Mashq paketi

kurs/mashqlar/16/07-kirish/ da ikki papka: bahor-narx/ (paket) va kassa/ (uni ishlatadigan loyiha). Paketda build yo'q — fayllar qo'lda yozilgan:

Fayl Nima
dist/index.js ESM kirish: priceOf, format = "esm"
dist/index.cjs CommonJS kirish: o'sha funksiya, format: "cjs"
dist/narxlar.js ichki ma'lumot — narxlar obyekti
dist/format.js som(28000) → "28000 so'm"
Fayl Nima
dist/cli.js terminal buyrug'i
types/index.d.ts TypeScript turlari

format qiymati qaysi fayl ochilganini ko'rsatadi. CommonJS fayli:

js
// index.cjs — CommonJS kirish nuqtasi (require uchun)
const prices = { osh: 35000, "lag'mon": 28000, manti: 30000 };

function priceOf(name) {
  return prices[name] ?? null;
}
module.exports = { priceOf, format: "cjs" };

Kassa paketni papkadan o'rnatadi:

bash
npm i ../bahor-narx
text

added 1 package, and audited 3 packages in 461ms

found 0 vulnerabilities

package.json ga "bahor-narx": "file:../bahor-narx" yozildi. file: — "registrdan emas, diskdagi papkadan". npm node_modules/bahor-narx ni papkaga havola (symlink) qilib qo'yadi: paketni o'zgartirsangiz, kassa darhol yangisini ko'radi. Tajriba uchun qulay.

3. main: eski yo'l va uning muammolari

3.1 Faqat main

Paketning birinchi package.json i:

json
{
  "name": "bahor-narx",
  "version": "1.0.0",
  "type": "module",
  "main": "./dist/index.cjs"
}

CommonJS loyihadan (eski.cjs):

js
// eski.cjs — CommonJS loyihadan foydalanish
const { priceOf, format } = require("bahor-narx");

console.log("manti:", priceOf("manti"), "| format:", format);
bash
node eski.cjs
text
manti: 30000 | format: cjs

Ishladi. Endi ESM kassadan (kassa.js):

js
// kassa.js — bahor-narx paketidan foydalanadi
import { priceOf, format } from "bahor-narx";

console.log("osh:", priceOf("osh"), "| format:", format);
bash
node kassa.js
text
file:///…/kurs/mashqlar/16/07-kirish/kassa/kassa.js:2
import { priceOf, format } from "bahor-narx";
                  ^^^^^^
SyntaxError: Named export 'format' not found. The requested module 'bahor-narx' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'bahor-narx';
const { priceOf, format } = pkg;

(at … qatorlari olib tashlandi.) Tarjima: "format nomli eksport topilmadi. bahor-narx — CommonJS modul, u module.exports ning hamma qismini nomli eksport sifatida bermasligi mumkin". Node CommonJS faylni ishga tushirmasdan, matnini ko'zdan kechirib nomlarni taxmin qiladi. priceOf — qisqa yozuv, uni topdi. format: "cjs" — qiymati satr, uni taxmin qila olmadi. CommonJS va ESM darsidagi chegaralardan biri.

Bitta main ikki dunyoga ham xizmat qila olmaydi. ESM fayl bersak, CommonJS'dagi eski Node'lar sinadi; CommonJS bersak — yuqoridagi xato.

3.2 Ichki fayllar ochiq

main faqat "asosiy fayl qaysi" deydi, qolganini yopmaydi. Kassada:

js
// chuqur.js — paketning ichki fayliga to'g'ridan-to'g'ri kirish
import { prices } from "bahor-narx/dist/narxlar.js";

console.log(prices);
bash
node chuqur.js
text
{ osh: 35000, "lag'mon": 28000, manti: 30000 }

Ishladi — va aynan shu xavfli. Sardor ertaga narxlar.js ni menyu.js deb o'zgartirsa, bu loyiha sinadi. Muallif uchun har fayl "ommaviy API" bo'lib qoladi: hech narsani ko'chirib bo'lmaydi.

3.3 module maydoni

Eski paketlarda main yonida yana bir maydon uchraydi:

bash
npm view web-vitals@6.2.3 main module
text
main = 'dist/web-vitals.umd.cjs'
module = 'dist/web-vitals.js'

module — rasmiy Node maydoni emas. Uni bundler'lar (webpack, Rollup, Vite) o'ylab topgan: "ESM versiya shu yerda". Node unga qaramaydi. Bugun uning o'rnini exports dagi import sharti egalladi, lekin eski paketlar va bundler sozlamalarida (resolve.mainFields) hali ko'rasiz.

Tekshirib ko'ring: Faqat main bor paketda import pkg from "bahor-narx" (default import) ishlaydimi? pkg.format nima bo'ladi?

Javob

Ishlaydi. Xabarning o'zi shuni taklif qilgan edi: CommonJS modul ESM'dan doim default import bilan ochiladi va pkg — module.exports obyektining o'zi. pkg.format — "cjs".

4. exports: paketning eshiklari

4.1 Shartli eksport

exports — paketning "eshiklar ro'yxati". Ro'yxatdagi eshikdan kiriladi, qolgan devorlar yopiq. Yangi package.json dan:

json
{
  "exports": {
    ".": {
      "types": "./types/index.d.ts",
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    },
    "./format": "./dist/format.js",
    "./package.json": "./package.json"
  }
}

Kalitlarni o'qiymiz:

Kalit Ma'nosi
"." asosiy eshik: import "bahor-narx"
"./format" kichik yo'l: import "bahor-narx/format"
"./package.json" package.json ni o'qishga ruxsat

"." ichidagi kalitlar — shartlar (conditions). Node ularni yuqoridan pastga o'qiydi va birinchi mosini oladi:

Shart Kim ishlatadi
types TypeScript (Node e'tiborsiz qoldiradi)
import import va import()
require require()
default hammasi — oxirgi zaxira
Shart Kim ishlatadi
node faqat Node
browser bundler brauzer uchun yig'ganda
development, production Vite kabi bundler'lar rejimga qarab

Endi kassaning uchala fayli:

bash
node kassa.js
text
osh: 35000 | format: esm
bash
node eski.cjs
text
manti: 30000 | format: cjs

Bir paket, ikki dunyo: import ESM faylni, require CommonJS faylni oldi. Bunday paket dual package ("ikki formatli") deyiladi.

Diqqat: Ikki format — ikki nusxa kod. Bitta loyihada bir qism import, boshqa qism require qilsa, modul ikki marta yuklanadi va ichki holat (masalan, kesh) ikkiga bo'linadi. Node hujjati buni "dual package hazard" deb ataydi. Node 24 da require() ESM faylni ham yuklay oladi (pasport darsidagi chalk misoli), shuning uchun yangi paketlar tobora faqat ESM chiqmoqda.

4.2 Yopiq devor

chuqur.js endi:

bash
node chuqur.js
text
node:internal/modules/esm/resolve:315
  return new ERR_PACKAGE_PATH_NOT_EXPORTED(
         ^

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './dist/narxlar.js' is not defined by "exports" in …\kassa\node_modules\bahor-narx\package.json imported from …\kassa\chuqur.js
  code: 'ERR_PACKAGE_PATH_NOT_EXPORTED'
}

Node.js v24.21.0

Tarjima: "./dist/narxlar.js kichik yo'li exports da belgilanmagan". Paket muallifi endi ichki fayllarni bemalol ko'chira oladi. Foydalanuvchi uchun qoida: bu xato — "muallif bu faylni ochmagan". Paket hujjatidan ochiq yo'lni qidiring, package.json ni qo'lda tahrirlamang.

Kichik yo'l esa ishlaydi. format.js:

js
// format.js — kichik yo'l (subpath) orqali import
import { som } from "bahor-narx/format";
import pkg from "bahor-narx/package.json" with { type: "json" };

console.log(som(28000), "| versiya:", pkg.version);
bash
node format.js
text
28000 so'm | versiya: 1.0.0

with { type: "json" } — JSON faylni ESM'da import qilish yozuvi (Dinamik import darsidagi import atributlari). "./package.json" eshigi bo'lmasa, bu qator ham ERR_PACKAGE_PATH_NOT_EXPORTED berardi — ba'zi vositalar paket versiyasini shunday o'qiydi, shuning uchun uni ochiq qoldirish odat.

4.3 Naqsh: *

Ko'p kichik yo'l bo'lsa, ularni bittalab yozish shart emas:

json
{
  "exports": {
    "./taomlar/*": "./dist/taomlar/*.js"
  }
}

import "bahor-narx/taomlar/osh" → ./dist/taomlar/osh.js. * o'rniga kelgan qism o'ng tomondagi * ga qo'yiladi.

4.4 Shartlar tartibi

Node birinchi mos shartni oladi. Shuning uchun default doim oxirida bo'lishi kerak. Ataylab xato tartib bilan sinadik — { "default": "./a.js", "require": "./b.cjs" } va require("tartib"):

text
a.js (default)

require sharti hech qachon ishlamaydi — default uni oldinroq "yutib" yubordi. Xato yo'q, ogohlantirish yo'q, faqat noto'g'ri fayl.

types esa birinchi yoziladi — TypeScript hujjatining tavsiyasi. TypeScript ham shartlarni tartib bilan o'qiydi. Bizning sinovda tsc 7.0.2 types ni oxirida ham topdi, lekin boshqa vositalar va eski TypeScript'lar birinchi mos shartda to'xtaydi.

Tekshirib ko'ring: Paketda "exports": "./dist/index.js" (bitta qator). import "paket/dist/index.js" ishlaydimi?

Javob

Yo'q. Bitta qator — { ".": "./dist/index.js" } ning qisqa yozuvi: faqat asosiy eshik ochiq. paket/dist/index.js — kichik yo'l, u ro'yxatda yo'q, natija ERR_PACKAGE_PATH_NOT_EXPORTED. Fayl bir xil bo'lsa ham, Node faqat ochiq eshiklarni tan oladi.

5. Haqiqiy paket: web-vitals

vazifalar dagi web-vitals 6.2.3 ning exports ini o'qiymiz. npm view web-vitals@6.2.3 exports 13 ta kalit chiqaradi, boshi:

text
{
  '.': {
    types: './dist/modules/index.d.ts',
    default: './dist/web-vitals.js',
    require: './dist/web-vitals.umd.cjs'
  },
  './onCLS.js': {
    types: './dist/modules/onCLS.d.ts',
    default: './dist/modules/onCLS.js'
  },
…

Bir zum — default require dan oldin? Unda require hech qachon ishlamasligi kerak edi. node_modules dagi haqiqiy faylga qaraymiz:

bash
cd node_modules/web-vitals
node -p "require('./package.json').exports['.']"
text
{
  types: './dist/modules/index.d.ts',
  require: './dist/web-vitals.umd.cjs',
  default: './dist/web-vitals.js'
}

Faylda tartib to'g'ri. Kalitlarni registr javobi o'zgartirgan ekan. Saboq: exports tartibini tekshirishda npm view ga emas, node_modules dagi package.json ga ishoning.

Ildizga qaytib (cd ../..), Node'ning o'zidan so'raymiz — har ikki yo'l qaysi faylga olib boradi:

bash
node -p "require.resolve('web-vitals')"
text
…\kurs\vazifalar\node_modules\web-vitals\dist\web-vitals.umd.cjs
bash
node --input-type=module \
  -e "console.log(import.meta.resolve('web-vitals'))"
text
file:///…/kurs/vazifalar/node_modules/web-vitals/dist/web-vitals.js

require.resolve va import.meta.resolve — faylni ochmasdan "qaysi fayl?" savoliga javob beradi. Paket g'alati ishlasa, birinchi tekshiruv shu.

web-vitals ichki faylni yopgan. import("web-vitals/dist/web-vitals.js") ni sinadik (xato kodi va xabari):

text
ERR_PACKAGE_PATH_NOT_EXPORTED Package subpath './dist/web-vitals.js' is not defined by "exports" in …\kurs\vazifalar\node_modules\web-vitals\package.json

Lekin har o'lchovni alohida ochgan: import { onCLS } from "web-vitals/onCLS.js" — faqat bitta funksiya kerak bo'lsa. vazifalar hozircha vendor/ nusxasini ishlatadi (bog'liqlik turlari). Vite kelganda import … from "web-vitals" yoziladi va Vite "." eshigidan default shartini oladi.

6. types: TypeScript uchun eshik

kassa da narx.ts va tsconfig.json ("module": "nodenext", strict):

ts
// narx.ts — paket turlari bilan
import { priceOf } from "bahor-narx";

const price: number = priceOf("osh");
console.log(price);

Avval "types" shartini olib tashlab sinadik. .d.ts fayl types/ papkasida, .js esa dist/ da — TypeScript uni o'zi topa olmaydi:

bash
npx -p typescript@7.0.2 tsc
text
narx.ts(2,25): error TS7016: Could not find a declaration file for module 'bahor-narx'. '…/kurs/mashqlar/16/07-kirish/bahor-narx/dist/index.js' implicitly has an 'any' type.
  Try `npm i --save-dev @types/bahor-narx` if it exists or add a new declaration (.d.ts) file containing `declare module 'bahor-narx';`

Tarjima: "bahor-narx uchun e'lon fayli topilmadi, dist/index.js yashirincha any turini oldi". Declaration fayllari darsidan tanish xato. TypeScript avval .js ning yonidan (dist/index.d.ts) qidiradi. U yerda yo'q va types sharti ham yo'q.

"types": "./types/index.d.ts" ni qaytardik:

bash
npx -p typescript@7.0.2 tsc
text
narx.ts(4,7): error TS2322: Type 'number | null' is not assignable to type 'number'.
  Type 'null' is not assignable to type 'number'.

Endi turlar ishlayapti — va ular haqiqiy xatoni topdi: priceOf menyuda yo'q taom uchun null qaytaradi. Kassa buni hisobga olishi kerak.

Eski usul — ildizdagi "types" maydoni ("types": "./types/index.d.ts"). U main ga o'xshaydi: faqat asosiy eshik uchun ishlaydi va kichik yo'llarni bilmaydi. Paketda ham turlar bo'lmasa — @types/paket (15-qism).

7. imports: paket ichidagi # taxallus

dist/index.js narxlarni shunday oladi:

js
// index.js — ESM kirish nuqtasi
import { prices } from "#narxlar";

export function priceOf(name) {
  return prices[name] ?? null;
}
export const format = "esm";

#narxlar — package.json dagi imports maydonidan:

json
{
  "imports": {
    "#narxlar": "./dist/narxlar.js"
  }
}

imports — exports ning ichki egizagi. exports tashqariga "nimani beraman" deydi, imports esa paketning o'zi uchun qisqa nomlar beradi. # bilan boshlanishi majburiy. Bu taxallus faqat shu paket ichida ishlaydi. Kassada import … from "#narxlar" yozsak:

text
TypeError [ERR_PACKAGE_IMPORT_NOT_DEFINED]: Package import specifier "#narxlar" is not defined in package …\kassa\package.json imported from …\kassa\ichki.js

"#narxlar taxallusi kassaning package.json ida belgilanmagan". Node # ni har doim eng yaqin package.json dan qidiradi.

imports ilovada ham foydali: ../../../utils/format.js o'rniga #utils/format.js. Uni Node ham, TypeScript ham, Vite ham tushunadi. Path alias darsida uni @/ taxallusi bilan solishtiramiz.

8. Qolgan maydonlar: files, bin, sideEffects

8.1 files: arxivga nima tushadi

Paketda sinov/ papkasi bor (testlar). files siz npm pack --dry-run uni ham arxivga soldi (sinov/narx.test.js, jami 9 fayl). files — "faqat shularni sol" ro'yxati:

bash
npm pkg set "files[]=dist" "files[]=types"
npm pack --dry-run
text
npm notice
npm notice package: bahor-narx@1.0.0
npm notice Tarball Contents
npm notice 42B README.md
npm notice 269B dist/cli.js
npm notice 114B dist/format.js
npm notice 224B dist/index.cjs
npm notice 165B dist/index.js
npm notice 127B dist/narxlar.js
npm notice 492B package.json
npm notice 148B types/index.d.ts
…
npm notice total files: 8

sinov/ tushmadi. package.json va README.md esa files da bo'lmasa ham doim qo'shiladi. Oq ro'yxat (files) qora ro'yxatdan (.npmignore) xavfsizroq: yangi maxfiy fayl tasodifan nashrga tushmaydi.

8.2 bin: terminal buyrug'i

json
{
  "bin": {
    "bahor-narx": "./dist/cli.js"
  }
}

cli.js ning birinchi qatori — #!/usr/bin/env node ("bu faylni Node bilan ishga tushir"). Paket o'rnatilganda npm node_modules/.bin/ ga bahor-narx, bahor-narx.cmd va bahor-narx.ps1 ni yaratadi — har shell uchun bittadan. Kassada:

bash
npx bahor-narx manti
text
30000 so'm

vite, tsc, eslint buyruqlari ham aynan shu yo'l bilan paydo bo'ladi — scripts darsidagi .bin papkasi.

8.3 sideEffects: tree shaking uchun va'da

"sideEffects": false — "mening fayllarim import qilinganda o'z-o'zidan hech narsa qilmaydi (global o'zgaruvchi, CSS, console.log)". Bundler bu va'daga ishonib, ishlatilmagan fayllarni tashlab yuboradi — bu tree shaking (ishlatilmagan kodni "silkitib tushirish") ning bir qismi. Va'da yolg'on bo'lsa, kerakli kod ham yo'qoladi. Chuqur — Tree shaking darsida.

8.4 engines va packageManager

engines ni Node versiya menejerlari darsida o'rgangansiz. packageManager ("pnpm@12.10.1") — loyiha qaysi paket menejeri bilan ishlashini aytadi, uni Corepack o'qiydi (Yarn va Bun darsida).

Barcha maydonlar bir sxemada:

Maydon Kim o'qiydi Nima uchun
exports Node, bundler, TS eshiklar va shartlar
main / module eski vositalar / bundler eski kirish nuqtasi
types TypeScript eski turlar yo'li
imports Node, bundler, TS ichki # taxalluslar
Maydon Kim o'qiydi Nima uchun
files npm (pack, publish) arxiv tarkibi
bin npm (o'rnatishda) terminal buyruqlari
sideEffects bundler tree shaking
engines, packageManager npm, Corepack muhit talabi

Tekshirib ko'ring: Sardor bahor-narx ga dist/menyu.js qo'shdi, lekin exports ga yozmadi. npm pack --dry-run da u bo'ladimi? Kassa uni import qila oladimi?

Javob

Arxivda bo'ladi — files da dist papkasi butunligicha. Import qilib bo'lmaydi — exports da yo'q (ERR_PACKAGE_PATH_NOT_EXPORTED). files "nima yetkaziladi", exports "nimaga kirish mumkin" degan savolga javob beradi. Paketning o'zi esa menyu.js ni nisbiy yo'l bilan yoki # taxallusi orqali bemalol ishlata oladi.

9. Ko'p uchraydigan xatolar

Xabar Sabab Davosi
ERR_PACKAGE_PATH_NOT_EXPORTED yo'l exports da yo'q hujjatdagi ochiq yo'l
Named export '…' not found … CommonJS module ESM'dan CJS paket default import yoki import sharti
ERR_PACKAGE_IMPORT_NOT_DEFINED # taxallus bu package.json da yo'q imports ga qo'shing
TS7016: Could not find a declaration file turlar topilmadi types sharti yoki @types/…
Belgi Sabab Davosi
require boshqa fayl oldi default require dan oldin default — oxirida
arxivda testlar, maxfiy fayllar files yo'q files oq ro'yxati
buyruq topilmadi bin yo'q yoki shebang yo'q bin + #!/usr/bin/env node
npm view da tartib boshqacha registr javobi node_modules/…/package.json

10. Mashqlar

Mashqlar kurs/mashqlar/16/07-kirish/ da: bahor-narx/ va kassa/ — darsdagidek.

1-mashq (oson): web-vitals eshiklari

«Haqiqiy paket» bo'limidagi exports (fayldagi tartib bilan) bo'yicha javob bering:

  • import { onLCP } from "web-vitals" ochadigan fayl:
  • require("web-vitals") ochadigan fayl:
  • import "web-vitals/onCLS.js":
  • import "web-vitals/dist/modules/onCLS.js" — xato kodi:
Yechim

import sharti yo'q — default oladi. require — o'z sharti bor, u default dan oldin. ./onCLS.js — ochiq kichik yo'l. Oxirgisi fayl sifatida mavjud, lekin exports da yo'q. Tekshirish uchun import.meta.resolve("web-vitals/onCLS.js") yoki require.resolve dan foydalaning.

2-mashq (o'rta): Narxlarni ochiq qiling

Telegram bot narxlar ro'yxatini butunligicha olmoqchi: import { prices } from "bahor-narx/narxlar". Hozir ERR_PACKAGE_PATH_NOT_EXPORTED. bahor-narx ga bitta qator qo'shib, kassada narxlar.js ni ishlating. Ichki fayl nomi (dist/narxlar.js) foydalanuvchiga ko'rinmasin.

Yechim

exports ga:

json
{
  "exports": {
    "./format": "./dist/format.js",
    "./narxlar": "./dist/narxlar.js"
  }
}

(Qolgan kalitlar o'zgarmaydi.) Kassadagi narxlar.js:

js
// narxlar.js — 2-mashq yechimi: ochiq kichik yo'l orqali
import { prices } from "bahor-narx/narxlar";

console.log(prices);
bash
node narxlar.js
text
{ osh: 35000, "lag'mon": 28000, manti: 30000 }

Foydalanuvchi bahor-narx/narxlar ni biladi, dist/ ni emas. Ertaga fayl dist/data/narxlar.js ga ko'chsa, faqat exports dagi o'ng tomon o'zgaradi — bot sinmaydi.

3-mashq (qiyin): O'z resolver'ingiz

resolver/exports.mjs da resolveExport(exportsField, subpath, conditions) yozing — Node algoritmining soddalashtirilgan nusxasi:

  • exportsField — qator yoki obyekt. Hamma kalitlari . bilan boshlansa — bu yo'llar xaritasi, aks holda — "." ning qiymati.
  • Qiymat qator bo'lsa — javob shu. Obyekt bo'lsa — kalitlarni tartib bilan ko'rib, default yoki conditions dagi birinchi mosini oling (ichma-ich obyekt ham bo'lishi mumkin).
  • "./taomlar/*" kabi naqshlar: * o'rniga kelgan qism o'ng tomondagi * ga qo'yiladi.
  • Topilmasa — null.

exports.test.mjs da web-vitals ning haqiqiy exports parchasi bilan kamida 4 test yozing, jumladan "tartib muhim" testi.

Ishoralar: Object.entries kalitlarni yozilish tartibida beradi. Object.hasOwn(map, key) — kalit bormi. replaceAll — matnni o'zgartirish.

Yechim

exports.mjs:

js
// exports.mjs — package.json "exports" ni o'qiydigan resolver
// (Node algoritmining soddalashtirilgan nusxasi: massiv va null yo'q)

// Shartlar obyekti yoki qator → fayl yo'li (yoki null)
function pickTarget(target, conditions) {
  if (typeof target === "string") return target;
  for (const [key, value] of Object.entries(target)) {
    if (key === "default" || conditions.includes(key)) {
      const found = pickTarget(value, conditions);
      if (found !== null) return found;
    }
  }
  return null;
}

// exportsField — package.json dagi "exports"; subpath — "." yoki
// "./format"; conditions — masalan ["import", "node"]
export function resolveExport(exportsField, subpath, conditions) {
  const isMap =
    typeof exportsField === "object" &&
    Object.keys(exportsField).every((key) => key.startsWith("."));
  const map = isMap ? exportsField : { ".": exportsField };

  if (Object.hasOwn(map, subpath)) {
    return pickTarget(map[subpath], conditions);
  }
  // "./modules/*": "./dist/*.js" kabi naqshlar
  for (const [key, value] of Object.entries(map)) {
    const star = key.indexOf("*");
    if (star === -1) continue;
    const before = key.slice(0, star);
    const after = key.slice(star + 1);
    if (subpath.startsWith(before) && subpath.endsWith(after)) {
      const end = subpath.length - after.length;
      const middle = subpath.slice(before.length, end);
      const target = pickTarget(value, conditions);
      return target === null ? null : target.replaceAll("*", middle);
    }
  }
  return null;
}

const demo = { ".": { require: "./a.cjs", default: "./a.js" } };
console.log(resolveExport(demo, ".", ["import"])); // ./a.js
console.log(resolveExport(demo, ".", ["require"])); // ./a.cjs
console.log(resolveExport(demo, "./ichki", ["import"])); // null

Oxirgi to'rt qator — sinash uchun. pickTarget o'zini o'zi chaqiradi (rekursiya): { "node": { "import": "…" } } kabi ichma-ich shartlar uchun.

exports.test.mjs:

js
// exports.test.mjs — node --test exports.test.mjs
import { test } from "node:test";
import assert from "node:assert/strict";
import { resolveExport } from "./exports.mjs";

// web-vitals 6.2.3 ning haqiqiy "exports" idan parcha
const webVitals = {
  ".": {
    types: "./dist/modules/index.d.ts",
    require: "./dist/web-vitals.umd.cjs",
    default: "./dist/web-vitals.js",
  },
  "./onCLS.js": {
    types: "./dist/modules/onCLS.d.ts",
    default: "./dist/modules/onCLS.js",
  },
};

test("shartlar: import, require, types", () => {
  const imp = ["import", "node"];
  const req = ["require", "node"];
  assert.equal(
    resolveExport(webVitals, ".", imp),
    "./dist/web-vitals.js",
  );
  assert.equal(
    resolveExport(webVitals, ".", req),
    "./dist/web-vitals.umd.cjs",
  );
  assert.equal(
    resolveExport(webVitals, ".", ["types", "import"]),
    "./dist/modules/index.d.ts",
  );
});

test("kichik yo'llar va ochilmagan fayl", () => {
  const imp = ["import"];
  assert.equal(
    resolveExport(webVitals, "./onCLS.js", imp),
    "./dist/modules/onCLS.js",
  );
  const hidden = "./dist/web-vitals.js";
  assert.equal(resolveExport(webVitals, hidden, imp), null);
});

test("qator va naqsh (*)", () => {
  const only = resolveExport("./index.js", ".", ["import"]);
  assert.equal(only, "./index.js");
  const map = { "./taomlar/*": "./dist/taomlar/*.js" };
  assert.equal(
    resolveExport(map, "./taomlar/osh", ["import"]),
    "./dist/taomlar/osh.js",
  );
  const tea = resolveExport(map, "./ichimlik/choy", ["import"]);
  assert.equal(tea, null);
});

test("tartib muhim: birinchi mos shart yutadi", () => {
  const wrong = { default: "./a.js", require: "./b.cjs" };
  assert.equal(resolveExport(wrong, ".", ["require"]), "./a.js");
});
text
ℹ tests 4
ℹ pass 4
ℹ fail 0

Oxirgi test — «Shartlar tartibi» bo'limidagi haqiqiy Node xulqi (a.js (default)) bilan bir xil. Haqiqiy Node algoritmi yana massivlar, null qiymatlar va bir nechta * qoidalarini biladi — u Node hujjatida "Resolution algorithm" bo'limida to'liq yozilgan.

11. Real ishda

  • ERR_PACKAGE_PATH_NOT_EXPORTED paket yangilangandan keyin ko'p chiqadi: yangi versiya exports qo'shib, eski ichki yo'llarni yopgan. Yechim — changelog'dan yangi yo'lni topish, eski versiyada qolish emas.
  • Kutubxona chiqarganda exports + types + files — minimal to'plam. npm pack --dry-run ni har nashrdan oldin ko'ring. "Are The Types Wrong?" (arethetypeswrong.github.io) kabi vositalar paketingizning eshiklarini avtomatik tekshiradi.
  • Monorepolarda imports va exports ichki paketlar orasidagi chegarani belgilaydi.
  • Intervyu: "main va exports farqi?", "dual package nima va uning xavfi?", "sideEffects: false nima beradi?".

Xulosa

  • exports bor bo'lsa, Node main ga qaramaydi; ro'yxatda yo'q yo'l — ERR_PACKAGE_PATH_NOT_EXPORTED.
  • Shartlar tartib bilan o'qiladi: types birinchi, default oxirida; import/require — ikki formatli paket.
  • main — eski yagona kirish, module — bundler'larning eski maydoni, ildizdagi types — eski turlar yo'li.
  • imports — paket ichidagi # taxalluslar; Node uni eng yaqin package.json dan oladi.
  • files — arxiv oq ro'yxati, bin — node_modules/.bin dagi buyruq, sideEffects — tree shaking va'dasi.
  • "Qaysi fayl ochildi?" — require.resolve va import.meta.resolve.

Keyingi dars: Semver va versiya diapazonlari — ^, ~, >=, || va 0.x tuzog'i: package.json dagi versiya yozuvi qaysi yangilanishni o'tkazadi.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
package.json kirish nuqtalari: main, module, exports, imports va types — IlmHamroh