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

Vite plaginlari va o'z plaginingizni yozish

Qisqacha: Vite plagini — name va bir nechta hook (Vite ma'lum paytda chaqiradigan funksiya) bor oddiy obyekt. Asosiy hook'lar: resolveId — import yo'lini topadi, load — modul matnini beradi, transform — matnni o'zgartiradi. Yana: configureServer — dev serverga yo'l qo'shadi, closeBundle — build tugagach ishlaydi. Sozlama enforce: "pre" plaginni Vite'ning o'z plaginlaridan oldinga qo'yadi, apply: "build" — faqat build'da yoqadi. Ekotizimdagi plaginlarning (React, SVG, PWA) hammasi shu hook'lardan qurilgan.

Bu darsda

  • Mashhur Vite plaginlari nima qilishini va qachon kerakligini bilasiz.
  • Plagin hook'lari qaysi tartibda chaqirilishini o'z ko'zingiz bilan ko'rasiz.
  • Virtual modul, fayl o'giruvchi (transform) va dev server yo'li bo'lgan plaginlar yozasiz.
  • enforce va apply farqini tushuntira olasiz.
  • vazifalar ning Service Worker qobig'ini postbuild skriptidan Vite plaginiga ko'chirasiz.

Oldin bilishingiz kerak: Vite II: vite.config, Vite V: production build, scripts chuqur, Kesh strategiyalari va offline ishlash.

1. Nega bu kerak?

vazifalar ning package.json ida shunday qator bor:

json
{
  "scripts": {
    "postbuild": "node skriptlar/qobiq.js"
  }
}

Bundler nima darsida yozgan edik: build tugagach npm postbuild ni o'zi ishga tushiradi (pre va post skriptlar), skript esa dist/ dagi hash'li fayl nomlarini Service Worker'ning QOBIQ ro'yxatiga yozadi. Ishlaydi. Lekin bir kuni Sardor build'ni boshqacha boshladi — npx vite build (npm skriptisiz). Natija:

text
dist/sw.js:  const VERSIYA = "manba";   const QOBIQ = ["./", "index.html"];

postbuild ishlamadi — u faqat npm run build ga bog'langan. Service Worker endi faqat ikki faylni oldindan keshlaydi. Brauzer tekshiruvida:

text
❌ SW: qobiqdagi hamma fayl keshda: 7 (kutilgan 2)

158 ta , 1 ta . Oflayn rejim baribir ishladi (network-first strategiya fayllarni ishlatilganda keshlaydi), shuning uchun xato ko'zga tashlanmasdi. Lekin birinchi tashrifdagi oldindan keshlash to'liq emas.

Muammo ildizi: build'ning bir qismi Vite'dan tashqarida. Yechim — uni ichkariga olish: Vite'ga "build tugaganda shu ishni ham qil" deb o'rgatish. Buni plagin qiladi.

Oshxona o'xshatishi: Vite — oshpaz, uning ish kuni bosqichlardan iborat (mahsulot olish, to'g'rash, pishirish, suzish). Plagin — oshpazga beriladigan qo'shimcha ko'rsatma: "to'g'rashdan keyin piyozni alohida qo'y", "suzgandan keyin idishga yorliq yopishtir". Har ko'rsatma ma'lum bosqichga bog'langan — bu bosqichlar hook'lar.

2. Ekotizim: tayyor plaginlar

O'z plaginingizni yozishdan oldin — tayyorlari. Haftalik yuklanishlar — npm statistikasidan (api.npmjs.org, 2026-09-29…10-05), versiyalar — npm view (2026-10).

Plagin Nima qiladi Haftalik yuklanish
@vitejs/plugin-react 6.1.2 JSX, React Fast Refresh (HMR) 116.9 mln
vite-tsconfig-paths 6.1.1 tsconfig dagi paths ni Vite'ga o'rgatadi 36.2 mln
@vitejs/plugin-react-swc 4.3.3 xuddi shu, SWC bilan 13.4 mln
vite-plugin-svgr 5.2.0 SVG'ni React komponenti qiladi 6.7 mln
vite-plugin-pwa 2.0.0 Service Worker va manifest (Workbox) 5.1 mln
vite-plugin-checker 0.14.5 tsc/ESLint'ni dev server yonida ishlatadi 4.7 mln

Ba'zilari haqida bir-ikki gap:

  • plugin-react va plugin-react-swc — React 17-qismda; hozir bilish shart emas. 6.x versiya (Vite 8 uchun) Babel'ni o'zi bilan olib yurmaydi: dependencies da faqat @rolldown/pluginutils, Babel va React Compiler — ixtiyoriy peerDependencies. 5.x da esa @babel/core majburiy edi. Ya'ni Vite 8 da JSX'ni Oxc o'giradi. SWC varianti esa @swc/core ni oladi — Vite 8 bilan uning afzalligi kamaydi.
  • vite-tsconfig-paths — @/ taxalluslari uchun (Path alias).
  • vite-plugin-svgr — SVG va ikonka pipeline darsida.
  • vite-plugin-pwa — Kesh strategiyalari darsida "self.__WB_MANIFEST ga qobiq ro'yxatini Vite plagini yoki workbox-build qo'yadi" degan edik. Mana u. Biz esa xuddi shu ishni 30 qatorli o'z plaginimiz bilan qilamiz — bu darsning oxirida.
  • vite-plugin-checker — Vite turlarni tekshirmaydi (Transpilyatsiya); bu plagin tsc xatolarini dev'da brauzerda ko'rsatadi.
  • unplugin-* — unplugin (3.4.0) — bitta plaginni Vite, Rolldown, webpack, esbuild uchun bir yo'la yozish kutubxonasi. unplugin-icons, unplugin-auto-import kabi paketlar shu asosda.

Ulash har doim bir xil: npm i -D …, keyin vite.config.ts da plugins: [react()]. Plagin — funksiya chaqiruvi natijasi (obyekt), funksiyaning o'zi emas.

3. Plagin — hook'li obyekt

3.1 Eng kichik plagin

vazifalar ning vite.config.ts ida allaqachon bitta plagin bor — biz uni Bundler nima darsida yozgan edik:

ts
{
  name: "blocking-render",
  transformIndexHtml: {
    order: "post",
    handler: (html) =>
      html.replace(
        '<script type="module" crossorigin',
        '<script type="module" blocking="render" crossorigin',
      ),
  },
},

Plagin = obyekt. name — majburiy, xato xabarlarida va ogohlantirishlarda shu nom chiqadi. Qolgan har kalit — hook: Vite ma'lum paytda chaqiradigan funksiya. Hook transformIndexHtml — "index.html tayyor bo'lganda, matnini menga ber, men o'zgartirib qaytaraman". Kalit order: "post" — "Vite o'z o'zgarishlarini qilib bo'lgach" (aks holda Vite <script> ni keyin qayta yozib, atributni yo'qotardi).

3.2 Hook'lar tartibi — o'lchab ko'ramiz

Hook'lar ko'p. Qaysi biri qachon chaqirilishini ko'rish uchun "kuzatuvchi" plagin yozdik — har hook faqat o'z nomini chiqaradi:

ts
// plaginlar/kuzatuvchi.ts — hook'lar qaysi tartibda chaqiriladi
import type { Plugin } from "vite";

export function watcher(): Plugin {
  const log = (hook: string, extra = "") =>
    console.log(`[kuzatuvchi] ${hook} ${extra}`.trimEnd());
  return {
    name: "kuzatuvchi",
    enforce: "pre",
    config() {
      log("config");
    },
    configResolved(config) {
      log("configResolved", config.command);
    },
    buildStart() {
      log("buildStart");
    },
    resolveId(id) {
      log("resolveId", id);
      return null;
    },
    load(id) {
      if (id.endsWith("menyu.ts")) log("load", "…/src/menyu.ts");
      return null;
    },
    transform(code, id) {
      if (id.endsWith("menyu.ts")) {
        log("transform", `${code.length} belgi`);
      }
      return null;
    },
    transformIndexHtml() {
      log("transformIndexHtml");
    },
    closeBundle() {
      log("closeBundle");
    },
  };
}

import type { Plugin } — plagin obyektining turi: muharrir har hook'ning argumentlarini biladi. return null — "men bu bilan ishlamayman, keyingi plaginga ber". bahor-menyu da npx vite build (Vite qatorlari olib tashlangan, yo'l qisqartirilgan):

text
[kuzatuvchi] config
[kuzatuvchi] configResolved build
[kuzatuvchi] buildStart
[kuzatuvchi] resolveId …\bahor-menyu\index.html
[kuzatuvchi] resolveId /src/main.ts
[kuzatuvchi] resolveId ./style.css
[kuzatuvchi] resolveId ./menyu.ts
[kuzatuvchi] load …/src/menyu.ts
[kuzatuvchi] transform 379 belgi
[kuzatuvchi] transformIndexHtml
[kuzatuvchi] closeBundle

Diagramma — shu tartib, guruhlab:

flowchart TB
  A["config, configResolved<br/>sozlama"] --> B["buildStart"]
  B --> C["har import uchun:<br/>resolveId → load → transform"]
  C --> D["chunk'lar yig'iladi"]
  D --> E["transformIndexHtml"]
  E --> F["fayllar diskka yoziladi"]
  F --> G["closeBundle"]

O'rtadagi uchlik — eng muhimi. Har import uchun Vite uch savol beradi:

Hook Savol Misol javob
resolveId(id) bu import qaysi fayl? "./menyu.ts" → C:/…/src/menyu.ts
load(id) uning matni qanday? fayl tarkibi (diskdan)
transform(code, id) matnni qanday o'zgartiray? TS → JS

Har savolga plaginlar navbat bilan javob beradi. Birinchi bo'lib javob (null emas) qaytargan plagin yutadi; transform esa zanjir — har plagin oldingisining natijasini oladi.

3.3 enforce — navbatdagi o'rin

Kuzatuvchida enforce: "pre" bor. Uni olib tashlasak, natija o'zgaradi: resolveId qatorlari umuman yo'qoladi, transform esa 379 belgi emas, 268 belgi ni ko'radi. Nega?

Vite'ning o'z ichki plaginlari bor (yo'l topuvchi, TS o'giruvchi, CSS…). Foydalanuvchi plaginlari sukut bo'yicha ulardan keyin turadi. Ichki yo'l topuvchi har importni o'zi topib, javob qaytaradi — navbat bizgacha yetmaydi. TS o'giruvchi esa bizdan oldin turlarni olib tashlaydi: 379 belgilik TypeScript 268 belgilik JavaScript'ga aylanadi.

enforce Navbat Qachon kerak
"pre" Vite'ning ichki plaginlaridan oldin asl manbani ko'rish, importni o'zi topish
yo'q (sukut) ichki plaginlardan keyin JS bilan ishlash (eng ko'p uchraydi)
"post" build plaginlaridan ham keyin yakuniy natijani o'zgartirish

3.4 apply — qachon yoqiladi

apply: "build" — plagin faqat vite build da ishlaydi, apply: "serve" — faqat dev'da. Funksiya ham bo'lishi mumkin: apply: (config, { command }) => …. Qobiq plaginimiz apply: "build" bo'ladi: dev'da dist/ yo'q.

3.5 Umumiy hook'lar va Vite'ning o'z hook'lari

Hook'lar ikki oiladan. Birinchisi — universal: ular Rollup'dan kelgan, Rolldown ham, Vite ham tushunadi. Shuning uchun ko'p Rollup plagini Vite'da o'zgarishsiz ishlaydi (rollup-plugin-visualizer ni oldingi darsda shunday ishlatdik). Ikkinchisi — faqat Vite'niki: ular dev server va HTML bilan bog'liq, bundler'da bunday tushuncha yo'q.

Universal (Rollup/Rolldown) Vite'ning o'zi
buildStart, buildEnd config, configResolved
resolveId, load, transform configureServer, configurePreviewServer
renderChunk, generateBundle transformIndexHtml
writeBundle, closeBundle handleHotUpdate (HMR)

Dev rejimda bundler ishlamaydi, lekin Vite universal hook'larni o'zi chaqiradi: brauzer faylni so'raganda — resolveId, load, transform. Shuning uchun bitta plagin dev'da ham, build'da ham ishlaydi. Faqat chunk bilan bog'liqlari (renderChunk, generateBundle) dev'da chaqirilmaydi — u yerda chunk yo'q.

3.6 Hook filtri — tezlik uchun

Vite 8 da bundler Rust'da (Rolldown), plaginlar esa JavaScript'da. Har transform chaqiruvi — Rust va JavaScript orasida ma'lumot tashish. Plagin 100 ta moduldan bittasi bilan ishlasa ham, u 100 marta chaqiriladi va 99 marta return null qiladi. Buning oldini olish uchun hook obyekt bo'lishi mumkin — filter va handler bilan:

ts
transform: {
  filter: { id: /\.csv$/ },
  handler(code) {
    return `export default ${JSON.stringify(code.split("\n"))};`;
  },
},

Endi filtrni Rolldown o'zi (Rust tomonida) tekshiradi va handler faqat .csv fayllar uchun chaqiriladi. Sinadik: bahor-menyu da 7 ta modul bor, hisoblagich handler ni 1 marta chaqirilganini ko'rsatdi. Kichik loyihada farq sezilmaydi, minglab modulli loyihada — sekundlar. Yangi plagin yozsangiz, filtrdan foydalaning; ichidagi if (!id.endsWith(...)) tekshiruvini esa qoldirish mumkin — eski Vite versiyalari filtrni bilmaydi.

Tekshirib ko'ring: .ts fayllardagi // TODO izohlarini sanaydigan plagin yozmoqchisiz. transform hook'i enforce siz izohlarni ko'radimi?

Javob

Ishonib bo'lmaydi. enforce siz plagin Vite'ning TS o'giruvchisidan keyin turadi — u allaqachon o'girilgan JavaScript'ni oladi, unda izohlar saqlanishi kafolatlanmagan (o'giruvchi turlarni olib tashlaydi, kodni qayta yozadi). Asl manbani ko'rish uchun — enforce: "pre". Kuzatuvchidagi 379 va 268 belgi farqi aynan shuni ko'rsatadi.

4. O'z plaginlarimiz

4.1 Virtual modul va dev server yo'li

Virtual modul — diskda yo'q, lekin import qilinadigan modul: uning matnini plagin "o'ylab chiqaradi". Masalan, build sanasi:

ts
// plaginlar/bahor-info.ts — virtual modul va dev server yo'li
import type { Plugin } from "vite";

const VIRTUAL_ID = "virtual:bahor-info";
// "\0" — boshqa plaginlar bu id'ni fayl deb o'ylamasin (kelishuv)
const RESOLVED_ID = "\0" + VIRTUAL_ID;

export function bahorInfo(): Plugin {
  return {
    name: "bahor-info",
    resolveId(id) {
      if (id === VIRTUAL_ID) return RESOLVED_ID;
      return null;
    },
    load(id) {
      if (id !== RESOLVED_ID) return null;
      const date = new Date().toISOString().slice(0, 10);
      return `export const buildDate = ${JSON.stringify(date)};`;
    },
    configureServer(server) {
      // faqat npm run dev: /__holat — server tirikmi?
      server.middlewares.use("/__holat", (_req, res) => {
        res.setHeader("Content-Type", "text/plain; charset=utf-8");
        res.end("«Bahor» dev server ishlayapti");
      });
    },
  };
}

Qatorma-qator:

  • resolveId — import … from "virtual:bahor-info" ni ko'rsa, "bu meniki" deb belgilangan id qaytaradi. Boshqa importlarga — null.
  • "\0" — nol belgi. Bu Rollup/Rolldown kelishuvi: \0 bilan boshlangan id — "haqiqiy fayl emas". Boshqa plaginlar (masalan, CSS yoki rasm o'giruvchilar) unga tegmaydi.
  • load — shu id uchun modul matnini qaytaradi: oddiy JavaScript satri. JSON.stringify — define darsidagi sabab bilan.
  • configureServer(server) — faqat dev'da chaqiriladi. server.middlewares.use(yo'l, funksiya) — dev serverga yangi manzil qo'shadi. Middleware — so'rov va javob orasida ishlaydigan funksiya (backend qismlarida Express bilan ko'p yozasiz). Bu yerda (_req, res) — Node'ning oddiy so'rov va javob obyektlari.

TypeScript virtual:bahor-info ni bilmaydi — turini declaration fayli bilan beramiz, src/virtual.d.ts:

ts
// virtual:bahor-info — plaginlar/bahor-info.ts yasaydi
declare module "virtual:bahor-info" {
  export const buildDate: string;
}

Natija: tsc --noEmit toza; build ichida — var n=`2026-10-10`; dev'da curl http://localhost:5773/__holat — «Bahor» dev server ishlayapti. Import nomida xato bo'lsa (virtual:bahor-inf) — hech bir plagin javob bermaydi:

text
Error: [vite]: Rolldown failed to resolve import "virtual:bahor-inf" from "C:/…/src/xato.ts".
This is most likely unintended because it can break your application at runtime.

Tarjimasi: "Rolldown virtual:bahor-inf importini topa olmadi. Bu, ehtimol, kutilmagan holat — ilovani ishga tushganda buzishi mumkin". Har resolveId null qaytardi.

4.2 Fayl o'giruvchi: .csv → JS

Jasur aka menyuni Excel'da yuritadi va CSV qilib saqlaydi. Uni to'g'ridan-to'g'ri import qilishni xohlaymiz: import menuRows from "./menyu.csv". Vite CSV'ni tanimaydi — o'rgatamiz:

ts
// plaginlar/csv.ts — .csv faylni JS massivga aylantiradi
import type { Plugin } from "vite";

export function csv(): Plugin {
  return {
    name: "bahor-csv",
    transform(code, id) {
      if (!id.endsWith(".csv")) return null;
      const [header, ...lines] = code.trim().split(/\r?\n/);
      const keys = header!.split(",");
      const rows = lines.map((line, index) => {
        const cells = line.split(",");
        if (cells.length !== keys.length) {
          this.error(`${index + 2}-qator: ${cells.length} ta ustun`);
        }
        return Object.fromEntries(keys.map((k, i) => [k, cells[i]]));
      });
      return `export default ${JSON.stringify(rows)};`;
    },
  };
}
  • transform hamma modulni ko'radi — birinchi qator "faqat .csv" filtri. Filtrsiz plagin har TS faylni ham buzardi.
  • split(/\r?\n/) — Windows (\r\n) va Linux (\n) qatorlari (RegExp asoslari).
  • this.error(...) — plagin kontekstidagi xato: build to'xtaydi, xabarda plagin nomi va fayl ko'rinadi.
  • Qaytgan qiymat — JavaScript matni: export default [...].

src/menyu.csv:

text
nom,narx
Osh,35000
Lagmon,28000
Manti,30000

Build ichida: [{nom:`Osh`,narx:`35000`},{nom:`Lagmon`,narx:`28000`},{nom:`Manti`,narx:`30000`}]. Narx — satr (CSV'da tur yo'q, .env dagi kabi). Faylga ortiqcha ustunli qator qo'shsak (Choy,5000,issiq):

text
[plugin bahor-csv] C:/…/src/menyu.csv
RolldownError: 5-qator: 3 ta ustun

Xato aniq: qaysi plagin, qaysi fayl, qaysi qator. Plagin yozganda xabarni foydalanuvchi (Jasur aka!) tushunadigan qiling.

Tekshirib ko'ring: CSV plagini transform emas, load hook'ida yozilsa ham ishlaydimi?

Javob

Ha. load(id) da id.endsWith(".csv") bo'lsa, faylni o'zingiz o'qiysiz (readFileSync(id, "utf8")) va JS matnini qaytarasiz. Farq: transform da Vite faylni allaqachon o'qib bergan (code), load da o'qish sizning zimmangizda. "Bu turdagi faylni men tushunaman" holatida ikkalasi ham to'g'ri; mavjud JS'ni o'zgartirishda esa faqat transform.

5. Vazifalar qadami: qobiq plagini

Branch: build/qobiq-plagini

5.1 Skriptdan funksiyaga

skriptlar/qobiq.js da avval hamma ish if (import.meta.main) { … } ichida edi — fayl to'g'ridan-to'g'ri ishga tushganda. Endi uni ikki funksiyaga ajratamiz: biri ishni qiladi, ikkinchisi — plagin. Birinchisi:

js
/**
 * Build papkasidagi sw.js ni to'ldiradi
 * @param {string} papka build papkasi (dist)
 * @returns {{ soni: number, versiya: string }}
 */
export function qobiqniYoz(papka) {
  const yol = (fayl) => resolve(papka, fayl);
  // Windows'da ajratgich "\\" — URL va brauzer uchun "/"
  const fayllar = readdirSync(papka, { recursive: true })
    .map((fayl) => fayl.replaceAll("\\", "/"))
    .filter((fayl) => statSync(yol(fayl)).isFile());
  const royxat = qobiqRoyxati(fayllar);
  // Tarkib o'zgarsa — VERSIYA ham (yangi kesh): index.html,
  // manifest va ikonlar nomida hash yo'q, ular ham hisobga kiradi
  const hash = createHash("sha256");
  for (const fayl of royxat.slice(1)) {
    hash.update(fayl).update(readFileSync(yol(fayl)));
  }
  const versiya = `v6-${hash.digest("hex").slice(0, 8)}`;
  const sw = yol("sw.js");
  writeFileSync(sw, swgaYoz(readFileSync(sw, "utf8"), royxat, versiya));
  return { soni: royxat.length, versiya };
}

Bu — avvalgi kod, faqat papka endi parametr (new URL("../dist/", import.meta.url) o'rniga) va natija qaytariladi. Nega? Plagin dist qayerdaligini Vite sozlamasidan biladi (outDir boshqa bo'lishi mumkin), test esa vaqtinchalik papka beradi. qobiqRoyxati va swgaYoz — o'zgarmagan. Fayl boshiga import { resolve } from "node:path"; qo'shiladi.

  • readdirSync(papka, { recursive: true }) — ichki papkalar bilan hamma nom.
  • createHash("sha256") — tarkibdan "barmoq izi" (Bundler nima dagi VERSIYA g'oyasi): bitta bayt o'zgarsa — yangi VERSIYA, brauzer yangi kesh ochadi.

5.2 Plaginning o'zi

js
/**
 * Vite plagini: build oxirida (closeBundle — hamma fayl,
 * public/ dan ko'chirilgan sw.js ham, diskka yozilgan) qobiqni
 * yozadi. npm skriptiga (postbuild) bog'liq emas: "npx vite build"
 * ham to'liq
 * @returns {import("vite").Plugin}
 */
export function qobiqPlagini() {
  /** @type {import("vite").ResolvedConfig} */
  let sozlama;
  return {
    name: "vazifalar-qobiq",
    apply: "build", // npm run dev da ishlamaydi
    configResolved(tayyor) {
      sozlama = tayyor;
    },
    closeBundle() {
      const papka = resolve(sozlama.root, sozlama.build.outDir);
      const { soni, versiya } = qobiqniYoz(papka);
      sozlama.logger.info(
        `sw.js: QOBIQ — ${soni} ta, VERSIYA — ${versiya}`,
      );
    },
  };
}

Qatorma-qator:

Qator Nima qiladi
@returns {import("vite").Plugin} JSDoc tur: .js faylda ham muharrir plagin kalitlarini tekshiradi (JSDoc va @ts-check)
let sozlama; yopilish (closure): ikki hook bitta o'zgaruvchini ulashadi
name: "vazifalar-qobiq" xatolarda shu nom chiqadi
apply: "build" dev'da dist/ yo'q — plagin o'chiq
Qator Nima qiladi
configResolved(tayyor) yakuniy sozlamani saqlab qo'yamiz (root, outDir, logger)
closeBundle() build to'liq tugagach: JS, CSS va public/ dan ko'chgan sw.js — hammasi diskda
resolve(sozlama.root, sozlama.build.outDir) dist ning to'liq yo'li — outDir o'zgarsa ham to'g'ri
sozlama.logger.info(...) Vite'ning o'z logger'i: --logLevel silent ni hurmat qiladi (console.log — yo'q)

Nega aynan closeBundle? Avvalroq hook'lar (generateBundle, writeBundle) paytida public/ dagi fayllar hali ko'chirilmagan bo'lishi mumkin, sw.js esa aynan public/ dan keladi. closeBundle — oxirgi nuqta, "oshxona yopildi".

5.3 Ulash va tekshirish

vite.config.ts:

ts
import { defineConfig, loadEnv } from "vite";
import { qobiqPlagini } from "./skriptlar/qobiq.js";
ts
    plugins: [
      // Service Worker qobig'i: hash'li fayl nomlari dist/sw.js ga
      qobiqPlagini(),
      {
        name: "blocking-render",
        …

package.json dan "postbuild": "node skriptlar/qobiq.js" qatori o'chadi. Endi o'sha sinov:

bash
npx vite build
text
✓ built in 126ms
sw.js: QOBIQ — 12 ta, VERSIYA — v6-39c0ca88

Oxirgi qatorni plagin yozdi. VERSIYA — postbuild skripti bergan qiymat bilan aynan bir xil: xulq o'zgarmadi, faqat qayerda ishlashi. Brauzer tekshiruvi — 159/159.

Testga bitta holat qo'shamiz (tekshiruv/qobiq.test.js) — qobiqniYoz vaqtinchalik papkada:

js
describe("qobiqniYoz (plagin build oxirida chaqiradi)", () => {
  test("papkadagi fayllar QOBIQ ga, VERSIYA tarkibga bog'liq", (t) => {
    const papka = mkdtempSync(join(tmpdir(), "qobiq-"));
    t.after(() => rmSync(papka, { recursive: true, force: true }));
    mkdirSync(join(papka, "assets"));
    writeFileSync(join(papka, "sw.js"), SW);
    writeFileSync(join(papka, "index.html"), "<!DOCTYPE html>");
    writeFileSync(join(papka, "assets/index-B3x9.js"), "1");
    const birinchi = qobiqniYoz(papka);
    const sw = readFileSync(join(papka, "sw.js"), "utf8");
    assert.equal(birinchi.soni, 3);
    assert.ok(sw.includes(`const VERSIYA = "${birinchi.versiya}";`));
    assert.ok(sw.includes('"assets/index-B3x9.js"'));
    // Bitta fayl o'zgarsa — yangi VERSIYA (brauzer yangi kesh
    // ochadi)
    writeFileSync(join(papka, "sw.js"), SW);
    writeFileSync(join(papka, "index.html"), "<!DOCTYPE html>!");
    assert.notEqual(qobiqniYoz(papka).versiya, birinchi.versiya);
  });
});

t.after(...) — test tugagach (yiqilsa ham) vaqtinchalik papkani o'chiradi. Importlarga mkdirSync, mkdtempSync, rmSync, writeFileSync, tmpdir, join va qobiqniYoz qo'shiladi. Plaginning o'zini emas, uning ishini test qilamiz: plagin — yupqa qobiq, mantiq — oddiy funksiyada. Testlar 145 → 146.

bash
git add .
git commit -m \
"build: qobiq — postbuild skripti o'rniga Vite plagini (closeBundle)"

Diff: 4 fayl, +83 −20.

6. Hujumchi nigohi

Plagin — brauzerda emas, sizning kompyuteringizda, Node ichida ishlaydigan kod. Uning cheklovi yo'q: configResolved da .env fayllarni o'qiy oladi (loadEnv(mode, root, "") — VITE_ siz sirlar ham), closeBundle da istalgan faylni yozadi, fetch bilan ma'lumotni tashqariga yuboradi. Va bu npm run dev ni har bosganingizda — CI'da ham.

Hujumchi nima qiladi? Mashhur plagin nomiga o'xshash paket chiqaradi (bitta harfi farq qiladigan nom bilan) yoki haqiqiy plagin muallifining npm hisobini egallab, zararli versiya chiqaradi. Ta'minot zanjiri darsidagi hodisalar aynan shunday bo'lgan. Plagin hech qanday postinstall skriptisiz ham xavfli: zararli kod build vaqtida ishga tushadi.

Himoya — o'sha darsdagi qoidalar, plaginlarga alohida e'tibor bilan:

  • Plagin qo'shishdan oldin — nomi, yuklanishlar soni, repo va muallif (Paket tanlash). 10 qatorli ishni o'zingiz yozish ko'pincha xavfsizroq: vazifalar dagi ikkala plagin o'zimizniki.
  • Aniq versiya (save-exact) va lock-fayl — kutilmagan yangi versiya jim o'rnatilmaydi.
  • CI'da sirlar faqat kerakli qadamga beriladi: build qadamiga deploy kaliti kerak emas.

7. Ko'p uchraydigan xatolar

6.1 Plagin funksiyasini chaqirmaslik

plugins: [qobiqPlagini] — funksiyaning o'zi, obyekt emas. To'g'risi — qobiqPlagini(). Plagin "fabrika" funksiyasi parametr olishi mumkin (csv({ separator: ";" })) — shuning uchun hamma joyda shunday.

6.2 transform da filtr yo'q

Plagin har modulni (CSS, rasm, node_modules) o'zgartirib yuboradi. Birinchi qator — if (!id.endsWith(".csv")) return null;.

6.3 Natijani generateBundle da o'zgartirish va hash

Chunk kodini generateBundle hook'ida o'zgartirsangiz, fayl nomidagi hash eski qoladi — u oldinroq hisoblangan. Sinab ko'rdik: kodga izoh qo'shildi, hajm 1.02 → 1.05 kB, hash esa o'sha (index-BU9yMBgv.js). Kesh nuqtai nazaridan bu xavfli: tarkib boshqa, nom bir xil. Kodni o'zgartirish — transform yoki renderChunk da.

6.4 console.log o'rniga logger

console.log plagin ichida --logLevel silent da ham chiqadi va Vite'ning chiqishini buzadi. this.warn(), this.error() (hook ichida) yoki config.logger.

8. Mashqlar

Mashqlar kurs/mashqlar/16/31-plagin/ da — bahor-menyu nusxasi, plaginlar plaginlar/ papkasida.

1-mashq (oson): Kuzatuvchi

Darsdagi kuzatuvchi.ts ni yozing va npx vite build qiling. Keyin enforce: "pre" ni olib tashlab, yana build qiling. Ikki chiqishni solishtiring: qaysi qatorlar yo'qoldi? transform necha belgi ko'rdi?

Yechim

enforce: "pre" bilan — darsdagi ro'yxat (resolveId qatorlari, transform 379 belgi). enforce siz:

text
[kuzatuvchi] config
[kuzatuvchi] configResolved build
[kuzatuvchi] buildStart
[kuzatuvchi] load …/src/menyu.ts
[kuzatuvchi] transform 268 belgi
[kuzatuvchi] transformIndexHtml
[kuzatuvchi] closeBundle

resolveId umuman chaqirilmadi — Vite'ning ichki yo'l topuvchisi har importni bizdan oldin topdi. transform 268 belgi ko'rdi: turlar allaqachon olib tashlangan JavaScript. load esa ikkala holatda ham bor — u qatorni faqat ko'rdi (null qaytardi).

2-mashq (o'rta): virtual:bahor-info

Darsdagi bahor-info plaginini yozing, src/virtual.d.ts qo'shing va main.ts da buildDate ni sahifa pastiga chiqaring. Sinang: (1) npx tsc --noEmit toza; (2) build ichida bugungi sana; (3) npm run dev da http://localhost:5173/__holat ochiladi.

Yechim

main.ts ga:

ts
import { buildDate } from "virtual:bahor-info";

const footer = document.createElement("small");
footer.textContent = `Build: ${buildDate}`;
document.body.append(footer);

vite.config.ts — plugins: [bahorInfo()]. Natija: tsc toza; JS ichida 2026-10-10 (sizda o'z sanangiz); curl http://localhost:5173/__holat → «Bahor» dev server ishlayapti. E'tibor bering: dev'da buildDate server ishga tushgan kun, build'da — build kuni. load har safar chaqiriladi.

3-mashq (qiyin): CSV plagini

plaginlar/csv.ts ni yozing: .csv fayl — export default [...] (birinchi qator — kalitlar). Ustunlar soni mos kelmagan qatorda build qator raqami bilan to'xtasin. TypeScript uchun src/csv.d.ts da declare module "*.csv" yozing. menyu.csv dagi taomlarni sahifaga chiqaring.

Ishora: Declaration fayllari dagi declare module "*.svg" — xuddi shunday, faqat tur Record<string, string>[].

Yechim

Plagin — darsdagi csv.ts. Turlar:

ts
// .csv importi — plaginlar/csv.ts beradi
declare module "*.csv" {
  const rows: Record<string, string>[];
  export default rows;
}

main.ts:

ts
import menuRows from "./menyu.csv";

for (const row of menuRows) {
  const item = document.createElement("li");
  item.textContent = `${row.nom} — ${row.narx} so'm`;
  list.append(item);
}

Natija: build ichida [{nom:`Osh`,narx:`35000`},…]; ortiqcha ustunli qator bilan — RolldownError: 5-qator: 3 ta ustun. Qator raqami index + 2: massiv indeksi 0 dan, birinchi qator esa sarlavha. Narxni formatPrice ga berish uchun — Number(row.narx).

9. Real ishda

  • Har Vite loyihasida kamida bitta plagin bor (React, Vue). Ularni o'qiy olish — konfiguratsiya muammolarini tuzatishning kaliti: "nega bu fayl o'zgarmayapti?" — ko'pincha enforce yoki filtr.
  • O'z plaginlari katta jamoalarda odatiy: build ma'lumoti, tarjima fayllarini yig'ish, ichki API turlarini generatsiya qilish. Ko'pi 20–50 qator.
  • Plagin API Rollup'niki — Vite, Rolldown, unplugin uni bo'lishadi. Bir marta o'rgansangiz, boshqa vositalarda ham tanish.
  • Intervyu savollari: "Virtual modul nima?", "resolveId/load/transform farqi?", "Vite plagini va Rollup plagini farqi?" (javob: Vite qo'shimcha hook'lar — config, configureServer, transformIndexHtml — beradi).

Xulosa

  • Plagin — name va hook'lar bor obyekt; ekotizimdagi hamma plagin shu asosda.
  • Har import: resolveId → load → transform; null — "keyingisiga ber"; transform — zanjir.
  • enforce: "pre" — Vite'ning ichki plaginlaridan oldin (asl manba), sukut — keyin (JS); apply — faqat build yoki faqat dev.
  • Virtual modul (\0 id), configureServer (dev middleware), this.error (aniq xato) — o'z plaginlarimizda sinaldi.
  • vazifalar: Service Worker qobig'i postbuild o'rniga closeBundle plaginida — npx vite build ham to'liq, VERSIYA bir xil.

Keyingi dars: Dev server proxy, HTTPS va CORS dev muhitda — server.proxy bilan API so'rovlarini dev serverdan uzatamiz, telefondan sinash va HTTPS.

Manbalar

  • Vite hujjatlari: "Plugin API" (hook'lar, enforce, apply, virtual modullar) — vite.dev/guide/api-plugin
  • Rolldown hujjatlari: "Plugin API" — rolldown.rs
  • npm yuklanish statistikasi: api.npmjs.org/downloads/point/2026-09-29:2026-10-05/
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Vite plaginlari va o'z plaginingizni yozish — IlmHamroh