IlmHamroh
JavaScript Full-stack/16-qism. Frontend asboblari: npm, bundlerlar, config24/48-dars13 daqiqa
Mundarija (21)

Source map'lar: minifikatsiya qilingan koddagi xatoni asl qatorga qaytarish

Qisqacha: Source map (manba xaritasi) — .map fayli: u siqilgan koddagi har joy asl fayldagi qaysi qator va ustunga mos kelishini yozib qo'yadi. Build fayli oxiridagi //# sourceMappingURL=… izohi xaritani ko'rsatadi, DevTools (va node --enable-source-maps) uni o'qib, kassa.js:1:31 o'rniga narx.ts:8:15 ni ko'rsatadi. Vite'da build.sourcemap: true (fayl + havola), "hidden" (fayl bor, havola yo'q — xato kuzatish xizmatiga yuklash uchun), "inline" (xarita JS ichida), false. Xaritada asl kod ham bor — yopiq loyihada uni ochiq qoldirmang.

Bu darsda

  • Source map nima ekanini va .map faylining tuzilishini o'qiysiz.
  • Bir xil xatoni source map'siz va bilan solishtirasiz (Node'da, haqiqiy stack trace).
  • build.sourcemap ning to'rt qiymatini va har birining hajmini bilasiz.
  • Production'da source map xavfini va Sentry kabi xizmatlarga "yashirin" yuklashni tushunasiz.
  • vazifalar ga source map qo'shasiz va Service Worker uni keshlamasligini ta'minlaysiz.

Oldin bilishingiz kerak: Stack trace va JavaScript xato turlari, Tree shaking va minifikatsiya, Bundler nima.

1. Nega bu kerak?

Oldingi darsda minifikatsiyani ko'rdik: applyDiscount(orderTotal, discountPercent) → e(e,t), butun fayl — bitta qator. Hajm ikki baravar kamaydi. Lekin bitta muammo tug'ildi.

Tasavvur qiling: «Bahor» saytida mijozda xato chiqdi. Sardor xato xabarini ko'rdi: RangeError … at e (kassa.js:1:31). "1-qator, 31-ustun" — butun fayl bitta qator, e esa nimadir funksiya. Qaysi .ts fayl? Qaysi funksiya? Bilib bo'lmaydi.

Stack trace darsida bunday holat uchun bir va'da bor edi: siqilgan fayl oxirida xaritaga havola turadi, DevTools uni topsa — asl faylni ko'rsatadi. Bugun shu xaritani ochamiz.

Hayotiy o'xshatish: kitobning qisqartirilgan nashri. Unda sahifa raqamlari boshqa. Kimdir "qisqa nashrning 12-sahifasi" desa, to'liq nashrda qayerligini topish uchun moslik jadvali kerak: "qisqa 12 → to'liq 47–49". Source map — aynan shu jadval.

Maslahat: Chiqishlar 2026-10-10 da Node 24.21.0 va Vite 8.3.3 bilan. Stack trace'lardagi to'liq yo'llarni … bilan qisqartirdik.

2. Xato — xaritasiz va xarita bilan

2.1 Kichik kassa

Ikki fayl. narx.ts:

ts
// narx.ts — buyurtma summasi
export function orderTotal(
  prices: number[],
  discount: number,
): number {
  if (discount > 50) {
    // Chegirma 50% dan oshmasin — kassir xatosi
    throw new RangeError(`Chegirma juda katta: ${discount}%`);
  }
  const sum = prices.reduce((a, b) => a + b, 0);
  return sum - (sum * discount) / 100;
}

console.log(orderTotal([35000, 28000], 10));
console.log(orderTotal([35000], 70));

Konsolda:

text
56700
RangeError: Chegirma juda katta: 70%

Loyihada oxirgi ikki qator alohida main.ts faylida (import { orderTotal } from "./narx.ts"). Vite bilan build.sourcemap: true qilib yig'dik (Node'da ishga tushirish uchun bitta kassa.js chiqadigan qilib sozladik):

text
dist/kassa.js  0.20 kB │ gzip: 0.18 kB │ map: 0.94 kB

dist/kassa.js ning to'liq matni — ikki qator:

text
function e(e,t){if(t>50)throw RangeError(`Chegirma juda katta: ${t}%`);let n=e.reduce((e,t)=>e+t,0);return n-n*t/100}console.log(e([35e3,28e3],10)),console.log(e([35e3],70));
//# sourceMappingURL=kassa.js.map

Oxirgi qator — sourceMappingURL izohi: "bu faylning xaritasi yonidagi kassa.js.map da". JavaScript uchun bu oddiy izoh, bajarilmaydi. Uni faqat xaritani tushunadigan vositalar o'qiydi.

2.2 Xaritasiz

bash
node dist/kassa.js
text
56700
file:///…/dist/kassa.js:1
function e(e,t){if(t>50)throw RangeError(`Chegirma juda katta: ${t}%`);let n=e.reduce((e,t)=>e+t,0);return n-n*t/100}console.log(e([35e3,28e3],10)),console.log(e([35e3],70));
                              ^

RangeError: Chegirma juda katta: 70%
    at e (file:///…/dist/kassa.js:1:31)
    at file:///…/dist/kassa.js:1:161

at e (kassa.js:1:31) — funksiya e, 1-qator, 31-ustun. Xabar matni saqlangan (satrlar minifikatsiyada o'zgarmaydi), lekin joy — foydasiz.

2.3 Xarita bilan

Node xaritani sukut bo'yicha o'qimaydi — --enable-source-maps bayrog'i kerak:

bash
node --enable-source-maps dist/kassa.js
text
56700
…\src\narx.ts:8
    throw new RangeError(`Chegirma juda katta: ${discount}%`);
              ^

RangeError: Chegirma juda katta: 70%
    at orderTotal (…\src\narx.ts:8:15)
    at <anonymous> (…\src\main.ts:5:13)

Nima bo'ldi? Bir xil fayl, bir xil xato — lekin endi: narx.ts, 8-qator, asl matn (discount, new RangeError) va asl funksiya nomi orderTotal. Ikkinchi qator — chaqirgan joy: main.ts:5:13. Brauzerning DevTools'i ham xuddi shunday qiladi — Console'dagi xato havolasi va Sources paneli asl .ts faylni ko'rsatadi.

Tekshirib ko'ring: dist/kassa.js dagi RangeError oldida new yo'q (throw RangeError(…)), asl kodda esa bor. Bu xato emasmi?

Javob

Yo'q. RangeError(…) va new RangeError(…) bir xil natija beradi — o'rnatilgan xato klasslari new siz chaqirilsa ham obyekt yaratadi. Minifikator buni biladi va 4 belgini tejaydi. Source map esa asl matnni ko'rsatadi — new bilan. Xarita "o'girilgan kod qayerdan kelgan"ni aytadi, kodni qayta tiklamaydi.

3. .map fayli ichida

3.1 Tuzilma

kassa.js.map — JSON. Boshi (qisqartirilgan):

text
{"version":3,"file":"kassa.js","names":["orderTotal","discount","sum","prices","a","b"],"sources":["../src/narx.ts","../src/main.ts"],"sourcesContent":["// narx.ts — buyurtma summasi\nexport function orderTotal(
  prices: number[], …","…"],"mappings":"AACA,SAAgBA,EACd,EACA,EACQ,CACR,GAAIC,…"}
Kalit Nima
version format versiyasi — doim 3
file qaysi faylning xaritasi
sources asl fayllar (xaritaga nisbatan yo'l)
sourcesContent asl fayllarning to'liq matni
names asl nomlar: orderTotal, discount…
mappings moslik jadvali, siqilgan ko'rinishda

3.2 mappings: siqilgan jadval

AACA,SAAgBA,EACd,… — tasodifiy harflar emas. Har vergul orasidagi bo'lak — bitta moslik: "siqilgan fayldagi N-ustun → sources dagi K-fayl, L-qator, M-ustun, names dagi J-nom". Raqamlar Base64 VLQ kodlashda (raqamni harflar bilan qisqa yozish usuli) va oldingisiga nisbatan farq sifatida saqlanadi — shuning uchun xarita kichik. ; — siqilgan fayldagi yangi qator.

Uni qo'lda o'qish shart emas. Node'da tayyor SourceMap klassi bor (node:module):

js
import { SourceMap } from "node:module";

const map = new SourceMap(mapJson);
// SourceMap 0 dan sanaydi, stack trace esa 1 dan
const entry = map.findEntry(0, 30); // 1-qator, 31-ustun
console.log(entry.originalSource, entry.originalLine + 1);

Natija: ../src/narx.ts 8. 3-mashqda shu klass bilan stack trace qatorini asl joyga qaytaradigan kichik vosita yozasiz.

flowchart LR
  E["xato: kassa.js:1:31"] --> U["sourceMappingURL<br/>→ kassa.js.map"]
  U --> M["mappings:<br/>1:31 → 0-fayl, 8:15"]
  M --> S["sources[0]<br/>../src/narx.ts"]
  S --> C["sourcesContent:<br/>asl matn"]

4. build.sourcemap: to'rt qiymat

Kassa loyihasini har qiymat bilan yig'dik:

Qiymat kassa.js .map fayl sourceMappingURL
false (sukut) 0.17 kB yo'q yo'q
true 0.20 kB 0.94 kB bor
"hidden" 0.17 kB 0.94 kB yo'q
"inline" 1.49 kB yo'q (JS ichida) data: manzil
  • true — xarita alohida faylda, havola JS oxirida. Brauzer .map ni faqat DevTools ochiq bo'lganda yuklaydi — oddiy foydalanuvchi uni yuklab olmaydi, sahifa sekinlashmaydi.
  • "hidden" — xarita yaratiladi, lekin JS unga ishora qilmaydi. DevTools uni topmaydi. Xarita sizning xato kuzatish xizmatingizga yuklanadi va u yerda ishlatiladi (pastda).
  • "inline" — xarita //# sourceMappingURL=data:application/json;base64,… ko'rinishida JS'ning ichida. JS 7 baravar kattalashdi — har foydalanuvchi uni yuklaydi. Faqat dev uchun. Vite'ning dev server'i o'zi o'zgartirgan modullarga aynan shunday data: xarita qo'shadi — Bundler nima darsida ko'rgan /src/main.js javobining oxiriga qarasangiz, shu qatorni topasiz.
  • false — xarita yo'q. Xato kelsa, faqat e (kassa.js:1:31).

Dev server (npm run dev) xaritani har doim beradi — sozlash shart emas. build.sourcemap faqat production build uchun.

5. Xavfsizlik: xaritada nima bor

sourcesContent ga qayta qarang: unda asl fayllar to'liq — izohlar ham (// Chegirma 50% dan oshmasin — kassir xatosi), o'zgaruvchi nomlari ham, tur nomlari ham. .map ochiq bo'lsa, istalgan odam DevTools'da sizning manba kodingizni o'qiydi.

Bu har doim ham muammo emas:

  • Ochiq repo (GitHub'da hamma ko'radi) — xaritada yangi narsa yo'q. vazifalar — shunday.
  • Yopiq loyiha — biznes mantig'i, ichki API manzillari, izohlardagi "TODO: bu yerda tekshiruv yo'q" kabi yozuvlar raqobatchi yoki hujumchiga ochiladi.

Ikkinchi holatda: "hidden" yoki false. Va bir qoida hamma loyihaga: frontend kodida sir bo'lmaydi. Xaritasiz ham siqilgan JS'ni o'qish mumkin — API kalitlari, parollar u yerda bo'lmasligi kerak (Vite III: env darsida VITE_ o'zgaruvchilari bilan buni yana ko'ramiz).

Diqqat: .map fayllarni o'chirib qo'yish kodni "yashirmaydi". Minifikatsiya qilingan JS baribir brauzerda. Source map faqat o'qishni osonlashtiradi — u himoya emas, uni o'chirish ham himoya emas.

6. Production'dagi xatolar: Sentry

Foydalanuvchilarda chiqqan xatolarni kim ko'radi? Buning uchun xato kuzatish (error tracking) xizmatlari bor — eng mashhuri Sentry. Saytga kichik kod ulanadi, u har xatoni stack trace bilan serverga yuboradi. Server stack'ni source map orqali asl qatorlarga qaytaradi va sizga ko'rsatadi.

Ish tartibi:

  1. build.sourcemap: "hidden" — xarita bor, havola yo'q.
  2. Build'dan keyin .map fayllar Sentry'ga yuklanadi (Vite plagini @sentry/vite-plugin, 2026-10 holatiga 5.4.1).
  3. .map fayllar dist/ dan o'chiriladi — saytga chiqmaydi.

Sentry hujjatidagi sozlama namunasi (biz uni ishga tushirmadik — Sentry hisobi va maxfiy token kerak):

ts
// vite.config.ts — Sentry hujjatidan namuna
import { sentryVitePlugin } from "@sentry/vite-plugin";
import { defineConfig } from "vite";

export default defineConfig({
  build: { sourcemap: "hidden" },
  plugins: [
    sentryVitePlugin({
      org: "bahor",
      project: "menyu",
      // token — muhit o'zgaruvchisidan, kodda EMAS
      authToken: process.env.SENTRY_AUTH_TOKEN,
      sourcemaps: { filesToDeleteAfterUpload: ["./dist/**/*.map"] },
    }),
  ],
});

Natija: foydalanuvchi hech qachon xaritani ko'rmaydi, siz esa Sentry'da narx.ts:8 ni ko'rasiz. Monitoringni keyingi qismlarda (production) chuqur o'rganamiz.

Tekshirib ko'ring: Nega Sentry'ga yuklangan .map fayllarni dist/ dan o'chirish kerak, agar "hidden" rejimda JS ularga baribir ishora qilmasa?

Javob

Ishora yo'q — lekin fayl serverda turibdi. Kimdir kassa.js.map manzilini taxmin qilib (nom — JS nomi + .map) to'g'ridan-to'g'ri so'rashi mumkin va asl kodni yuklab oladi. "hidden" faqat DevTools'ning avtomatik topishini o'chiradi; faylning o'zini o'chirmasangiz, u ochiq.

7. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
DevTools'da asl fayl yo'q build.sourcemap yo'q yoki "hidden" true (ochiq loyiha)
xarita bor, qatorlar noto'g'ri build'dan keyin JS'ni boshqa vosita o'zgartirgan, xarita eskirgan o'zgartirishni build ichida qiling
Node'da minifikatsiya qilingan stack --enable-source-maps yo'q bayroqni qo'shing
"inline" production'da har foydalanuvchiga ortiqcha yuk true yoki "hidden"
.map Service Worker keshida oflayn uchun keraksiz megabaytlar qobiq ro'yxatidan chiqaring

Oxirgi qator — bugungi vazifalar qadamining bir qismi.

8. Mashqlar

Mashqlar kurs/mashqlar/16/24-source-map/ da.

1-mashq (oson): Qaysi qiymat?

  • Ochiq repo, DevTools'da asl kod ko'rinsin:
  • Yopiq loyiha, xatolar Sentry'da:
  • Dev server'dagidek, xarita JS ichida:
  • Xarita umuman kerak emas:
Yechim

true — xarita + havola (DevTools o'zi topadi). "hidden" — xarita bor, havola yo'q (xizmatga yuklanadi, keyin dist/ dan o'chiriladi). "inline" — JS ichida, faqat dev'da. false — sukut. Biror loyihada tanlovni sabab bilan yozing — masalan, vite.config.ts dagi izohda.

2-mashq (o'rta): Ikki stack trace

Darsdagi kassa loyihasini yarating (narx.ts, main.ts, build.sourcemap: true, chiqish fayli kassa.js). So'ng:

  1. node dist/kassa.js va node --enable-source-maps dist/kassa.js — ikkala stack'ning birinchi at qatorini yozing.
  2. sourcemap: "hidden" qilib, --enable-source-maps bilan qayta ishga tushiring. Nima o'zgardi?
Yechim
  1. Xaritasiz: at e (…/dist/kassa.js:1:31). Xarita bilan: at orderTotal (…\src\narx.ts:8:15).
  2. "hidden" da kassa.js oxirida sourceMappingURL yo'q — Node xaritani topa olmaydi va yana at e (…/dist/kassa.js:1:31) ko'rsatadi, garchi kassa.js.map fayli yonida turgan bo'lsa ham. Hidden xarita faqat uni aniq so'raydigan vosita (Sentry, 3-mashqdagi skript) uchun.

3-mashq (qiyin): Xaritadan joyni topish

qaytar.mjs yozing: node qaytar.mjs dist/kassa.js:1:31 — fayl oxiridagi sourceMappingURL dan xaritani topib, asl joyni chiqarsin: { source, line, column, name }.

Ishoralar: import { SourceMap } from "node:module"; new SourceMap(json).findEntry(line, column) — 0 dan sanaydi, stack trace esa 1 dan. Natijada originalSource, originalLine, originalColumn, name. sourceMappingURL ni regex bilan oling: /\/\/# sourceMappingURL=(\S+)\s*$/. Argumentni "dist/kassa.js:1:31" ni bo'lishda ehtiyot bo'ling: Windows yo'lida ham : bor (C:\…) — faqat raqamdan oldingi : bo'yicha bo'ling (split(/:(?=\d)/), RegExp lookahead).

Yechim
js
// qaytar.mjs — minifikatsiya qilingan joyni (fayl:qator:ustun)
// source map orqali asl fayl va qatorga qaytaradi
import { readFileSync } from "node:fs";
import { SourceMap } from "node:module";
import { dirname, join } from "node:path";

export function originalPosition(mapJson, line, column) {
  const map = new SourceMap(mapJson);
  // SourceMap 0 dan sanaydi, stack trace esa 1 dan
  const entry = map.findEntry(line - 1, column - 1);
  if (!entry.originalSource) return null;
  return {
    source: entry.originalSource,
    line: entry.originalLine + 1,
    column: entry.originalColumn + 1,
    name: entry.name ?? null,
  };
}

// node qaytar.mjs dist/kassa.js:1:31
if (import.meta.main) {
  const [file, line, column] = process.argv[2].split(/:(?=\d)/);
  const code = readFileSync(file, "utf8");
  const url = code.match(/\/\/# sourceMappingURL=(\S+)\s*$/)?.[1];
  if (!url) throw new Error(`${file} da sourceMappingURL yo'q`);
  const mapJson = JSON.parse(readFileSync(join(dirname(file), url)));
  const pos = originalPosition(mapJson, Number(line), Number(column));
  console.log(pos);
}
bash
node qaytar.mjs dist/kassa.js:1:31
node qaytar.mjs dist/kassa.js:1:161
text
{ source: '../src/narx.ts', line: 8, column: 15, name: null }
{ source: '../src/main.ts', line: 5, column: 13, name: 'orderTotal' }

Ikkalasi --enable-source-maps ko'rsatgan joylar bilan bir xil. Ikkinchida name: 'orderTotal' — names massividan: siqilgan koddagi e aslida orderTotal edi. Testlar (qaytar.test.mjs, saqlangan .map fayl bilan) — ℹ tests 2, ℹ pass 2. Sentry'ning serveri ham aynan shu ishni qiladi — faqat millionlab xato uchun.

4-mashq: Vazifalar qadami — source map'lar

  • Branch: build/source-map
  • Commit: build: source map'lar; qobiq .map fayllarni keshlamaydi
  1. vite.config.ts — build.sourcemap: true (izoh bilan: nega true).
  2. skriptlar/qobiq.js — qobiqRoyxati .map fayllarni Service Worker qobig'iga qo'shmasin.
  3. tekshiruv/qobiq.test.js — test ro'yxatiga .map fayl qo'shing va u natijada yo'qligini tekshiring.
Yechim

vite.config.ts (build ichida):

ts
    // Source map (16/#24): DevTools minifikatsiya qilingan koddagi
    // xatoni asl .ts fayl va qatorga qaytaradi. Repo ochiq
    // (Pages) — xaritada sir yo'q; .map faqat DevTools ochilganda
    // yuklanadi
    sourcemap: true,

skriptlar/qobiq.js:

js
export function qobiqRoyxati(fayllar) {
  const qolgani = fayllar
    .filter((yol) => yol !== "sw.js" && !yol.endsWith(".map"))
    .toSorted();
  return ["./", ...qolgani];
}

tekshiruv/qobiq.test.js — test nomi "sahifa birinchi, sw.js va .map yo'q, qolgani tartiblangan", kirish ro'yxatiga "assets/index-B3x9.js.map" qo'shildi, kutilgan natija o'zgarmadi.

Natija (build chiqishidan):

text
dist/assets/js/tema.js           0.16 kB │ gzip: 0.15 kB │ map:   0.94 kB
dist/assets/olchov-G4bloHwN.js   8.92 kB │ gzip: 3.42 kB │ map:  22.87 kB
dist/assets/index-Dvsf2Sj6.js   25.37 kB │ gzip: 9.51 kB │ map: 115.37 kB

index chunk 25.33 → 25.37 kB: farq faqat oxirgi qatorda — //# sourceMappingURL=index-Dvsf2Sj6.js.map. Fayl nomi (hash) o'zgarmadi: hash kodning o'zidan hisoblanadi. Xaritada sources — 16 ta .ts fayl, sourcesContent — 16 (asl matn ichida). .map jami ~139 kB — lekin u faqat DevTools ochilganda yuklanadi. Service Worker qobig'i — o'sha 12 fayl (.map siz).

Nega true: vazifalar reposi ochiq (GitHub Pages), xaritada yangi narsa yo'q. Yopiq loyihada — "hidden" va xato kuzatish xizmati yoki false.

Testlar: 145/145. Brauzer: 159/159. Diff: 3 fayl, +11 −3.

bash
git switch -c build/source-map
npm run build && npm test
git add vite.config.ts skriptlar/qobiq.js tekshiruv/qobiq.test.js
git commit -m \
"build: source map'lar; qobiq .map fayllarni keshlamaydi"
git push -u origin build/source-map
gh pr create --fill
gh pr merge --merge

Commit sarlavhasi — 55 belgi.

9. Real ishda

  • Har production loyihada savol bor: "source map ochiqmi?". Ko'p kompaniyalarda javob: "hidden" + Sentry (yoki Datadog, Bugsnag) + .map ni saytdan o'chirish.
  • Node serverlarda (24-qismdan) TypeScript build'i bilan --enable-source-maps — xato loglari to'g'ridan-to'g'ri .ts qatorlarini ko'rsatadi.
  • Intervyu savollari: "Source map nima?", "hidden source map nima uchun?", "Production'da source map xavflimi?", "Brauzer .map ni qachon yuklaydi?".

Xulosa

  • Source map — siqilgan joy → asl fayl, qator, ustun, nom jadvali (version, sources, sourcesContent, names, mappings).
  • //# sourceMappingURL= izohi xaritani ko'rsatadi; DevTools va node --enable-source-maps uni o'qiydi.
  • build.sourcemap: true (havola bilan), "hidden" (havolasiz, xizmatga yuklash uchun), "inline" (JS ichida, faqat dev), false.
  • sourcesContent da asl kod to'liq — yopiq loyihada .map ochiq qolmasin; lekin xaritani yashirish himoya emas: frontend'da sir bo'lmaydi.
  • vazifalar: sourcemap: true (ochiq repo), Service Worker .map ni keshlamaydi; index hash o'zgarmadi.

Keyingi dars: Code splitting, chunk nomlari va hash bilan keshlash — olchov chunk'i nega alohida, fayl nomidagi hash keshni qanday xavfsiz qiladi va "deploydan keyin eski chunk topilmadi" xatosi qayerdan keladi.

Manbalar

  • Source Map spetsifikatsiyasi (ECMA-426) — tc39.es/ecma426
  • Vite: "Build Options → build.sourcemap" — vite.dev/config/build-options
  • Node.js: "Command-line → --enable-source-maps", "Modules → Source map v3 support (SourceMap)" — nodejs.org/api
  • Chrome DevTools: "Map preprocessed code to source code" — developer.chrome.com/docs/devtools/javascript/source-maps
  • Sentry: "Vite → Source Maps" (@sentry/vite-plugin) — docs.sentry.io/platforms/javascript/sourcemaps/uploading/vite
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Source map'lar: minifikatsiya qilingan koddagi xatoni asl qatorga qaytarish — IlmHamroh