Mundarija (28)
- Bu darsda
- 1. Nega bu kerak?
- 2. Yoritish ichkaridan: tokenlar
- 2.1 O'quv modeli: o'z tokenizer'imiz
- 2.2 Grammatika va mavzu
- 2.3 Yorug' va qorong'i mavzu
- 3. Uchta kutubxona: satr yoki element
- 3.1 highlight.js — HTML satr
- 3.2 prism-react-renderer — React elementlari
- 3.3 Shiki — VS Code sifati
- 4. Markdown ichidagi kod bloklari
- 5. Nusxalash, qator raqamlari va ajratish
- 5.1 Qator oralig'ini o'qish
- 5.2 Kod bloki: raqamlar, ajratish, nusxalash
- 6. Build vaqtida yoritish: brauzerga 0 KB
- 7. Biz sinab ko'rdik: hajm va CSP
- 7.1 Kanon o'lchovi
- 7.2 Bizning qo'shimcha sinovimiz
- 7.3 Nega vazifalar o'zgarmaydi
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): bo'sh joylarni to'ldiring
- 2-mashq (o'rta): tokenlar statistikasi
- 3-mashq (qiyin): meta satrdan sarlavha va til
- 4-mashq: tajriba qadami (ixtiyoriy)
- 10. Real ishda
- Xulosa
- Manbalar
Sintaksis yoritish React'da: Prism, Shiki, highlight.js, nusxalash tugmasi va qator raqamlari
Qisqacha: Sintaksis yoritish — kodni tokenlarga bo'lib (kalit so'z, son, satr, izoh), har biriga rang berish. Kutubxonalar ikki xil natija beradi. HTML satr (highlight.js, Shiki
codeToHtml) —innerHTMLtalab qiladi vavazifalarning Trusted Types siyosatida yiqildi. React elementlari (prism-react-renderer, ShikicodeToHast+ React) — CSP 0. Eng yaxshisi — yoritishni build vaqtida qilish (rehype-pretty-code, Shiki): brauzerga 0 KB JS. Kanon o'lchovi: highlight.js +10.0 KB, Prism +26.2, Shiki +70.5.
Bu darsda
- Tokenlash va yoritish qanday ishlashini kutubxonasiz modelda ko'rasiz.
- prism-react-renderer, Shiki va highlight.js ni ulaysiz va natija turini (satr yoki element) ajratasiz.
- Markdown ichidagi kod bloklarini
components.codeorqali yoritasiz. - Nusxalash tugmasi, qator raqamlari va ajratilgan qatorlarni a11y bilan qo'shasiz.
- Yoritishni brauzerda yoki build vaqtida qilishni narx bo'yicha tanlaysiz.
Oldin bilishingiz kerak: Markdown render qilish, RegExp asoslari va metodlari, Clipboard API, Brauzer xavfsizlik modeli.
1. Nega bu kerak?
O'tgan darsda vazifalar ga Markdown izoh qo'shdik. Sardor dasturchi-stajyor — izohlarga kod parchalarini ham yozadi:
Narxni shunday hisoblaymiz:
~~~js
const jami = osh * 2 + choy;
~~~(~~~ — kod blokining ikkinchi yozilishi, ``` bilan bir xil.) Hozir u bir xil rangli matn. Kod rangsiz bo'lsa, o'qish qiyin: qayerda kalit so'z, qayerda satr, qayerda izoh — ko'z ajratmaydi. Bu kursdagi har bir kod bloki rangli ekanini sezgandirsiz — bu ham sintaksis yoritish.
Ikkinchi ehtiyoj — nusxalash. Telefonda kodni barmoq bilan belgilash azob. "Nusxalash" tugmasi bitta bosishda ishni bitiradi.
Hayotiy o'xshatish: maktab daftaridagi grammatik tahlil. O'qituvchi ega ostiga bitta, kesim ostiga ikkita chiziq tortadi. Gap o'sha-o'sha, lekin tuzilishi ko'rinib qoladi. Yoritgich ham shunday: kod o'zgarmaydi, har "so'z turi" o'z rangini oladi.
2. Yoritish ichkaridan: tokenlar
2.1 O'quv modeli: o'z tokenizer'imiz
Yoritgich ikki ish qiladi. Avval kodni tokenlarga bo'ladi (tokenlash, tokenization): har bo'lakka tur beradi. Keyin har turga rang yoki CSS sinfi qo'yadi. Buni bir nechta muntazam ifoda bilan o'zimiz yozamiz:
import { useEffect } from "react";
// har tur — bitta regex; tartib muhim: avval izoh va satr
const RULES = [
["comment", /^\/\/.*/],
["string", /^"[^"]*"/],
["keyword", /^(?:const|let|return|function)\b/],
["number", /^\d+/],
["plain", /^[\s\S]/], // qolgan har bitta belgi
];
function tokenize(code) {
const tokens = [];
let rest = code;
while (rest) {
for (const [type, re] of RULES) {
const m = rest.match(re);
if (m) {
tokens.push({ type, text: m[0] });
rest = rest.slice(m[0].length);
break;
}
}
}
return tokens;
}
const COLORS = {
keyword: "#00009f",
number: "#36acaa",
string: "#e3116c",
comment: "#6a737d",
};
const code = 'const osh = 35000; // "narx"';
export default function App() {
const tokens = tokenize(code);
useEffect(() => {
const typed = tokens.filter((t) => t.type !== "plain");
console.log(typed.map((t) => `${t.type}:${t.text}`).join(" | "));
}, [tokens]);
return (
<pre>
<code>
{tokens.map((t, i) => (
<span key={i} style={{ color: COLORS[t.type] }}>
{t.text}
</span>
))}
</code>
</pre>
);
}Konsolda:
keyword:const | number:35000 | comment:// "narx"Ekranda:
const osh = 35000; // "narx"Ekrandagi matn kod bilan bir xil — yoritish faqat rang qo'shadi. Konsolga qarang: izoh ichidagi "narx" satr emas, izohning bir qismi. Chunki comment qoidasi birinchi turadi va butun qatorni yeb qo'yadi. Qoidalar tartibini almashtirsangiz, // izoh bo'lib, "narx" — satr bo'lib chiqadi. Haqiqiy yoritgichlar shunday chalkashliklarni yuzlab qoidalar bilan hal qiladi.
Rang style orqali, React elementida berildi — innerHTML yo'q. Bu muhim nuqta: keyingi bo'limda kutubxonalar aynan shu joyda ikkiga bo'linadi.
2.2 Grammatika va mavzu
Haqiqiy yoritgichda ikki qism bor:
- Grammatika (grammar) — tilning tokenlash qoidalari. JavaScript, Python, CSS uchun alohida.
- Mavzu (theme) — token turi → rang.
github-light,dracula,one-dark.
Ular alohida bo'lgani uchun bitta grammatika bilan yuzlab mavzuni almashtirish mumkin. Shiki VS Code'ning o'zidagi TextMate grammatikalari va mavzularini ishlatadi — kod VS Code'dagi bilan bir xil ranglanadi. Prism va highlight.js — o'z grammatikalari bilan, sodda va tezroq.
2.3 Yorug' va qorong'i mavzu
Sayt qorong'i temaga o'tsa, kod bloki ham o'tishi kerak. Yorug' fon uchun tanlangan ko'k kalit so'z qora fonda ko'rinmay qoladi. Uch yo'l bor:
- CSS sinflari + ikki fayl. highlight.js va Prism (sinf rejimida) tokenlarga sinf beradi (
hljs-keyword,token keyword). Ranglar CSS'da,@media (prefers-color-scheme: dark)yoki[data-tema="qorongi"]ichida boshqa qiymat. Eng arzon yo'l: JS o'zgarmaydi, faqat CSS. - CSS o'zgaruvchilari. Shiki'da ikki mavzuni birdan berish mumkin:
themes: { light: "github-light", dark: "github-dark" }. Natijada har tokenda yorug' rang va--shiki-darko'zgaruvchisi bo'ladi. Qorong'i temada CSScolor: var(--shiki-dark)qo'yadi. - Qayta yoritish. Tema o'zgarganda kodni boshqa mavzu bilan qayta hisoblash. Eng qimmati: har o'tishda hamma bloklar qayta tokenlanadi.
Qaysi biri bo'lmasin, kontrastni tekshiring: kod matni ham oddiy matn kabi WCAG 1.4.3 (kamida 4.5:1) talabiga bo'ysunadi. Ko'p tayyor mavzular izohlarni (// …) juda och kulrangda beradi — kontrast tekshiruvchisi buni darhol ushlaydi.
Tekshirib ko'ring: Nega mavzuni CSS sinflari bilan berish "qayta yoritish" dan arzon?
Javob
Tokenlash — eng og'ir ish: matnni grammatika qoidalari bilan bo'lish. Sinflar bilan tokenlar bir marta hisoblanadi, tema almashganda brauzer faqat CSS qoidasini qayta qo'llaydi — JS ishlamaydi. Qayta yoritishda esa har blok yana tokenlanadi va React elementlari qayta yaratiladi.
3. Uchta kutubxona: satr yoki element
3.1 highlight.js — HTML satr
Eng eski va eng keng tarqalgani (190+ til). Natija — HTML satr. Biz highlight.js 11.12.0 ni Node 24 da ishga tushirdik:
import hljs from "highlight.js/lib/core";
import javascript from "highlight.js/lib/languages/javascript";
hljs.registerLanguage("javascript", javascript);
const html = hljs.highlight("const osh = 35000; // narx", {
language: "javascript",
}).value;html qiymati:
<span class="hljs-keyword">const</span> osh = <span class="hljs-number">35000</span>; <span class="hljs-comment">// narx</span>Ranglar — CSS sinflari (hljs-keyword), mavzu alohida CSS fayl. React'da bu satr dangerouslySetInnerHTML bilan qo'yiladi — kanon tajribasida vazifalar CSP'si ostida TrustedHTML talabi bilan yiqildi.
Satrning o'zi xavfli emas: highlight.js kod matnini ekranlaydi (< → <). Muammo — innerHTML yo'li va Trusted Types. Ko'p saytlar yoritishni serverda qiladi va tayyor HTML'ni yuboradi — bu kurs sayti ham darslarni serverda marked + highlight.js bilan HTML'ga aylantiradi. Shunda brauzerga yoritgich kodi umuman kelmaydi.
hljs.highlightAuto(code) — tilni o'zi taxmin qiladi. Qulay, lekin har grammatikani sinab ko'radi (sekin) va qisqa parchada ko'p adashadi. Tilni har doim aniq bering.
3.2 prism-react-renderer — React elementlari
prism-react-renderer — Prism grammatikalari asosida, lekin HTML satr emas, tokenlar beradi. Siz ularni o'zingiz chizasiz (render prop). Kanon tajribasidagi kod:
import { Highlight, themes } from "prism-react-renderer";
<Highlight theme={themes.github} code={code} language="js">
{({ tokens, getLineProps, getTokenProps }) => (
<pre>
{tokens.map((line, i) => (
<div key={i} {...getLineProps({ line })}>
{line.map((token, j) => (
<span key={j} {...getTokenProps({ token })} />
))}
</div>
))}
</pre>
)}
</Highlight>;tokens — qatorlar massivi, har qator — tokenlar massivi. Biz const osh = 35000; // narx ning tokenlarini Node'da chiqardik:
[["keyword","const"],["plain"," osh "],["operator","="],["plain"," "],["number","35000"],["punctuation",";"],["plain"," "],["comment","// narx"]]O'quv modelimizdagi g'oyaning o'zi, faqat operator va punctuation turlari ham bor. getTokenProps har tokenga className (token keyword) va style ({ color: "#00009f" }) beradi. style React orqali qo'yiladi — CSP 0, kanon o'lchovi ham, bizning Chrome 155 dagi qayta sinovimiz ham (kalit so'z rangi rgb(0, 0, 159)).
Narx — +26.2 KB. Paket Prism'ning tanlangan tillari bilan keladi; boshqa til kerak bo'lsa, grammatikani alohida qo'shasiz.
3.3 Shiki — VS Code sifati
Shiki — eng aniq yoritgich: TextMate grammatikalari, VS Code mavzulari. Sukut bo'yicha Oniguruma regex dvigatelini WebAssembly sifatida yuklaydi. Kanon WASM'siz, JavaScript regex dvigateli bilan o'lchadi — +70.5 KB (faqat JS va bitta mavzu).
codeToHtml — HTML satr, ranglar style atributlarida:
<pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">const</span>…Bu satr innerHTML bilan qo'yilsa — kanonda TT buzilishi. Hatto Trusted Types bo'lmasa ham, CSP qoidasiga ko'ra style-src 'self' HTML ichidagi style="…" atributlarini to'sadi — kod rangsiz qoladi (bu holatni alohida o'lchamadik — hujjat bo'yicha).
Lekin Shiki ikkinchi yo'lni ham beradi: codeToHast — HTML daraxti (hast), o'tgan darsdagi unified ekotizimining o'zi. Daraxtni hast-util-to-jsx-runtime React elementlariga aylantiradi:
import { Fragment } from "react";
import { jsx, jsxs } from "react/jsx-runtime";
import { toJsxRuntime } from "hast-util-to-jsx-runtime";
const hast = highlighter.codeToHast(code, {
lang: "javascript",
theme: "github-light",
});
const element = toJsxRuntime(hast, { Fragment, jsx, jsxs });Biz shu variantni vazifalar CSP'si bilan Chrome 155 da sinadik: buzilish 0, kalit so'z rangi rgb(215, 58, 73) (#D73A49) — ranglar qo'yildi. Sabab: React style ni atribut sifatida emas, DOM xususiyati (element.style.color = …) orqali qo'yadi, CSP esa buni to'smaydi.
Tekshirib ko'ring: Nega highlight.js satri xavfsiz (kod ekranlangan), lekin baribir Trusted Types'da yiqiladi?
Javob
Trusted Types satrning ichiga qaramaydi — u qaysi yo'l bilan kelganini tekshiradi. innerHTML ga oddiy satr berish — taqiq, satr qanchalik toza bo'lmasin. Faqat siyosatdan o'tgan TrustedHTML obyekti qabul qilinadi. Bu qattiq tuyuladi, lekin aynan shu qoida "bu satrni kimdir tekshirgandir" degan taxminni yo'q qiladi.
4. Markdown ichidagi kod bloklari
Izohdagi ```js bloki react-markdown'da <pre><code className="language-js"> bo'lib chiqadi. components.code ni almashtirsak, yoritishni o'zimiz qo'shamiz. Natija oynasida Prism yo'q, shuning uchun o'quv modelimizdagi tokenize ni ishlatamiz — g'oya bir xil:
import Markdown from "react-markdown";
const RULES = [
["comment", /^\/\/.*/],
["keyword", /^(?:const|let|return)\b/],
["number", /^\d+/],
["plain", /^[\s\S]/],
];
const COLORS = {
keyword: "#00009f",
number: "#36acaa",
comment: "#6a737d",
};
function tokenize(code) {
const out = [];
let rest = code;
while (rest) {
const [type, re] = RULES.find(([, r]) => r.test(rest));
const text = rest.match(re)[0];
out.push({ type, text });
rest = rest.slice(text.length);
}
return out;
}
function Code({ className, children }) {
const lang = /language-(\w+)/.exec(className ?? "")?.[1];
if (!lang) return <code>{children}</code>; // `inline` kod
const tokens = tokenize(String(children).replace(/\n$/, ""));
return (
<code data-lang={lang}>
{tokens.map((t, i) => (
<span key={i} style={{ color: COLORS[t.type] }}>
{t.text}
</span>
))}
</code>
);
}
const note =
"Hisob: `jami`\n\n```js\nconst jami = 35000 * 2; // 2 osh\n```";
export default function App() {
return <Markdown components={{ code: Code }}>{note}</Markdown>;
}Ekranda:
Hisob: jami
const jami = 35000 * 2; // 2 oshIkki narsaga qarang. Til — className dan (language-js): bo'lmasa, bu qator ichidagi `jami` — yoritmaymiz. Oxirgi \n — Markdown blok matnining oxirida yangi qator bor, uni kesamiz, aks holda pastda bo'sh qator chiqadi.
Prism bilan xuddi shunday: Code ichida tokenize o'rniga <Highlight code={…} language={lang}>. Tayyor plaginlar ham bor: rehype-highlight (highlight.js grammatikalari, lekin natija — hast daraxti, satr emas) va Shiki'ning @shikijs/rehype plagini.
5. Nusxalash, qator raqamlari va ajratish
5.1 Qator oralig'ini o'qish
Hujjat saytlarida qatorlarni ajratish odatda blok sarlavhasida yoziladi: ```js {2,4-5}. Bu — meta satr. Uni o'qish — oddiy JS:
function parseLines(meta) {
const m = /\{([\d,\s-]+)\}/.exec(meta);
const lines = new Set();
if (!m) return lines;
for (const part of m[1].split(",")) {
const [from, to = from] = part.trim().split("-").map(Number);
for (let n = from; n <= to; n++) lines.add(n);
}
return lines;
}
console.log(parseLines('js {2,4-5} title="narx.js"'));
console.log(parseLines("js"));Konsolda:
Set(3) { 2, 4, 5 }
Set(0) {}to = from — standart qiymat: "2" bo'lsa, oraliq 2 dan 2 gacha. Set — tez tekshirish uchun (lines.has(n)).
5.2 Kod bloki: raqamlar, ajratish, nusxalash
Endi hammasini bitta komponentga yig'amiz. Nusxalash funksiyasini prop qilib beramiz: haqiqiy ilovada u navigator.clipboard.writeText (Clipboard API), natija oynasida esa — konsolga yozadigan soxta funksiya:
import { useState } from "react";
function CodeBlock({ code, highlight = new Set(), copy }) {
const [status, setStatus] = useState("");
async function handleCopy() {
try {
await copy(code);
setStatus("Nusxalandi");
} catch {
setStatus("Nusxalab bo'lmadi — qo'lda belgilang");
}
}
return (
<figure>
<pre>
{code.split("\n").map((line, i) => (
<div
key={i}
style={{ background: highlight.has(i + 1) ? "#ff6" : "" }}
>
<span aria-hidden="true" style={{ userSelect: "none" }}>
{String(i + 1).padStart(2)}{" "}
</span>
{line}
</div>
))}
</pre>
<button type="button" onClick={handleCopy}>
Nusxalash
</button>{" "}
<span role="status">{status}</span>
</figure>
);
}
const fakeCopy = async (text) =>
console.log("clipboard:", text.length, "belgi");
export default function App() {
return (
<CodeBlock
code={[
"const osh = 35000;",
"const choy = 5000;",
"console.log(osh + choy);",
].join("\n")}
highlight={new Set([3])}
copy={fakeCopy}
/>
);
}Konsolda:
clipboard: 62 belgiEkranda:
1 const osh = 35000;
2 const choy = 5000;
3 console.log(osh + choy);
Nusxalash NusxalandiUchta a11y qarori:
- Qator raqamlari
aria-hidden— ekran o'quvchi "bir, const osh…" demaydi.user-select: none— sichqoncha bilan belgilaganda raqamlar nusxaga tushmaydi. Nusxalash tugmasi esacodening o'zini beradi (62 belgi — raqamsiz). - "Nusxalandi"
role="status"ichida — ekran o'quvchi uni o'qiydi. Faqat ikonka o'zgarsa, Malika tugma ishladimi-yo'qmi bilmaydi. - Xato holati —
writeTextHTTPS'siz yoki ruxsat bo'lmasa rad etadi. Jim qolmang: "qo'lda belgilang" deng.
Ajratilgan qator — fon rangi. Rang yolg'iz emas: ko'p saytlar chap chetga chiziq ham qo'shadi (kontrast va rang darsidagi qoida).
6. Build vaqtida yoritish: brauzerga 0 KB
Hujjat sayti, blog, kurs — kod bloklari oldindan ma'lum. Ularni har mehmon brauzerida qayta yoritish — isrof. Build vaqtida yoritish: sayt yig'ilayotganda kod rangli HTML'ga aylanadi, brauzerga faqat HTML va CSS boradi.
flowchart LR
subgraph B["Build (bir marta)"]
M["Markdown / MDX"] --> P["rehype-pretty-code (Shiki)"]
P --> H["rangli HTML"]
end
subgraph R["Brauzer (har mehmon)"]
H --> V["ko'rinadi: 0 KB yoritgich JS"]
endDiagrammaning ikkinchi qismiga qarang: brauzer hech narsa hisoblamaydi. rehype-pretty-code — Shiki ustidagi rehype plagini: qator raqamlari, {2,4-5} ajratish, title="…" sarlavha — hammasi meta satrdan. Next.js'da server komponentlari bilan Shiki serverda ishlaydi va natija HTML bo'lib keladi — 23-qismda.
Qachon brauzerda kerak? Foydalanuvchi kodni o'zi yozsa — izoh, chat, onlayn muharrir. Unda yengil yoritgich (highlight.js yoki Prism) va kech yuklash.
7. Biz sinab ko'rdik: hajm va CSP
7.1 Kanon o'lchovi
- build vaqtida (rehype-pretty-code)hisob — build'da0 KB
- highlight.js 11.12 (core + js)10 KB
- prism-react-renderer 2.4.126,2 KB
- Shiki 4.5.0 (JS dvigatel, 1 til)70,5 KB
Manba: O'lchandi: kanon tajribasi, Vite 8.3.3, React 19.3.0, gzip -9, Chrome 155, natijalar/tajriba/kutubxonalar-21.txt, 2026-10-17; build vaqtidagi 0 — brauzer yoritgich JS yuklamaydi (ta'rif bo'yicha)
Xom natija (xato matnlari qisqartirilgan):
kod-hljs +10.0 KB | CSP require-trusted-types-for×2 | … 'TrustedHTML' …
kod-prism +26.2 KB | CSP 0
kod-shiki +70.5 KB | CSP require-trusted-types-for×2 | … 'TrustedHTML' …CSP ostida highlight.js va Shiki misollari ekranga hech narsa chizmadi (0 element) — xato React daraxtini yiqitdi. CSP'siz ikkalasi ham const osh = 35000; ni chizdi.
7.2 Bizning qo'shimcha sinovimiz
Kanon Shiki'ni codeToHtml + dangerouslySetInnerHTML bilan sinagan. Biz codeToHast + toJsxRuntime variantini qo'shdik (esbuild, Chrome 155, kanon CSP'si): 0 buzilish, ranglar joyida. Demak, Shiki'ning o'zi emas, ulash usuli CSP'ni buzadi.
7.3 Nega vazifalar o'zgarmaydi
Kanon izohda kod bloklarini yoritmadi. Izohlar qisqa, kod parchasi kam uchraydi. Bitta funksiya uchun +26 KB (Prism) yoki +70 KB (Shiki) — o'tgan darsdagi 46 KB Markdown chunk'iga yana bir yarmi. Kerak bo'lsa — Prism, faqat components.code da va kech yuklanadigan chunk ichida.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
kod rangsiz, konsolda TrustedHTML |
HTML satr innerHTML bilan |
React elementlari (Prism, Shiki hast) |
| Shiki ranglari yo'q, TT yo'q | style="…" atributini CSP to'sdi |
codeToHast + toJsxRuntime |
| til noto'g'ri aniqlandi | highlightAuto |
tilni aniq bering |
| Python bloki yoritilmadi | grammatika yuklanmagan | tilni ro'yxatdan o'tkazing |
| nusxada qator raqamlari ham bor | raqamlar matn ichida | user-select: none + tugma code ni beradi |
| ekran o'quvchi raqamlarni o'qiydi | aria-hidden yo'q |
raqam span iga aria-hidden |
| "Nusxalash" jim ishlamaydi | HTTP yoki ruxsat yo'q | catch + foydalanuvchiga xabar |
| kod blok ostida bo'sh qator | Markdown blok oxiridagi \n |
.replace(/\n$/, "") |
| izohdagi qator ichidagi kod ham blok bo'ldi | language- tekshirilmagan |
sinf bo'lmasa — oddiy <code> |
9. Mashqlar
Mashqlar kurs/mashqlar/21/09-kod-yoritish/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.
1-mashq (oson): bo'sh joylarni to'ldiring
- Tokenlash qoidalari to'plami:
- Token turi → rang xaritasi:
- Shiki'ning React elementlariga yo'l ochadigan metodi:
- Qator raqamlari nusxaga tushmasligi uchun CSS:
user-select: - Yoritgich JS'i brauzerga umuman kelmaydigan usul: vaqtida yoritish
Yechim
- grammatika — tilning tokenlash qoidalari (TextMate, Prism grammatikasi).
- mavzu —
github-light,dracula. - codeToHast — HTML daraxti;
toJsxRuntimebilan React elementlari. - none — sichqoncha bilan belgilanmaydi.
- build — rehype-pretty-code yoki serverda Shiki.
2-mashq (o'rta): tokenlar statistikasi
O'quv modelimizdagi RULES va tokenize dan foydalanib, komponent yozing: kod const narx = 35000;\nconst soni = 3;\n// jami\nreturn narx * soni;. Ekranda har tur uchun bitta <li>: keyword: 3, number: 2, comment: 1 (faqat plain dan boshqalari, birinchi uchrash tartibida).
Ishora: sanash — Map yoki Object.groupBy (guruhlash).
Yechim
const RULES = [
["comment", /^\/\/.*/],
["string", /^"[^"]*"/],
["keyword", /^(?:const|let|return|function)\b/],
["number", /^\d+/],
["plain", /^[\s\S]/],
];
function tokenize(code) {
const tokens = [];
let rest = code;
while (rest) {
const [type, re] = RULES.find(([, r]) => r.test(rest));
const text = rest.match(re)[0];
tokens.push({ type, text });
rest = rest.slice(text.length);
}
return tokens;
}
const code = [
"const narx = 35000;",
"const soni = 3;",
"// jami",
"return narx * soni;",
].join("\n");
export default function App() {
const counts = new Map();
for (const t of tokenize(code)) {
if (t.type === "plain") continue;
counts.set(t.type, (counts.get(t.type) ?? 0) + 1);
}
return (
<ul>
{[...counts].map(([type, n]) => (
<li key={type}>
{type}: {n}
</li>
))}
</ul>
);
}Ekranda:
keyword: 3
number: 2
comment: 1Map kalitlarni qo'shilish tartibida saqlaydi — birinchi const (keyword), keyin 35000 (number), keyin izoh. return ham kalit so'z — shuning uchun 3 ta. narx va soni — plain: modelimizda "o'zgaruvchi" turi yo'q. Haqiqiy grammatikalarda bor.
3-mashq (qiyin): meta satrdan sarlavha va til
parseMeta(info) funksiyasini yozing: ```ts {1,3} title="narx.ts" ning info satridan (ts {1,3} title="narx.ts") obyekt qaytarsin: { lang: "ts", lines: [1, 3], title: "narx.ts" }. Sarlavha yoki qatorlar bo'lmasa — title: null, lines: [].
Ishora: til — birinchi so'z; sarlavha — /title="([^"]*)"/; qatorlar — «Qator oralig'ini o'qish» bo'limidagi parseLines.
Yechim
function parseLines(meta) {
const m = /\{([\d,\s-]+)\}/.exec(meta);
const lines = new Set();
if (!m) return lines;
for (const part of m[1].split(",")) {
const [from, to = from] = part.trim().split("-").map(Number);
for (let n = from; n <= to; n++) lines.add(n);
}
return lines;
}
function parseMeta(info) {
const lang = info.trim().split(/\s+/)[0] || null;
const title = /title="([^"]*)"/.exec(info)?.[1] ?? null;
return { lang, lines: [...parseLines(info)], title };
}
console.log(parseMeta('ts {1,3} title="narx.ts"'));
console.log(parseMeta("js"));Konsolda:
{ lang: 'ts', lines: [ 1, 3 ], title: 'narx.ts' }
{ lang: 'js', lines: [], title: null }?.[1] ?? null — moslik bo'lmasa exec null qaytaradi, ?. xatoni oldini oladi, ?? esa undefined o'rniga null beradi. rehype-pretty-code ham meta satrni shunday o'qiydi — faqat ko'proq imkoniyat bilan (so'z ajratish, diff).
4-mashq: tajriba qadami (ixtiyoriy)
vazifalar ga tegmang. Alohida Vite loyihada (kurs/tajriba/kod/) react-markdown + components.code + prism-react-renderer bilan izoh chizuvchisini yasang. Keyin xuddi shuni Shiki bilan — avval codeToHtml + dangerouslySetInnerHTML, keyin codeToHast + toJsxRuntime. Har variantni vazifalar CSP <meta> i bilan oching.
Yechim
Tekshiruv ro'yxati:
- Prism:
npm i -E prism-react-renderer@2.4.1; CSP bilan konsol toza, ranglar bor. - Shiki
codeToHtml+dangerouslySetInnerHTML:This document requires 'TrustedHTML' assignment. - Shiki
codeToHast+toJsxRuntime(npm i -E shiki@4.5.0 hast-util-to-jsx-runtime): konsol toza, ranglar bor. npm run build: kanonda Prism +26.2 KB, Shiki (JS dvigatel, 1 til) +70.5 KB; sizning raqamingiz tillar va mavzular soniga bog'liq.
10. Real ishda
- Hujjat saytlari (React, Vite, Next.js hujjatlari) — build vaqtida Shiki, ko'pincha rehype-pretty-code yoki shunga o'xshash plagin.
- Bloglar va kurslar — serverda yoki build'da; bu kurs sayti — serverda highlight.js.
- Chat va forum — foydalanuvchi kodi, brauzerda: Prism yoki highlight.js, kech yuklanadi.
- Kod muharrirlari — yoritish muharrirning o'zida (Monaco, CodeMirror) — keyingi darslar.
- Intervyu savollari: "Sintaksis yoritish qanday ishlaydi?", "nega
dangerouslySetInnerHTMLsiz yoritish yaxshiroq?", "build vaqtida va runtime'da yoritish farqi?", "nusxalash tugmasini qanday a11y qilasiz?".
Xulosa
- Yoritish = tokenlash (grammatika) + rang (mavzu); kod matni o'zgarmaydi.
- highlight.js va Shiki
codeToHtml— HTML satr,innerHTML→vazifalarTT'da yiqildi; prism-react-renderer va ShikicodeToHast+toJsxRuntime— React elementlari, CSP 0. - Markdown'da —
components.code: tillanguage-…sinfidan, oxirgi\nkesiladi. - Kod bloki: raqamlar
aria-hiddenvauser-select: none, nusxa —codening o'zi, "Nusxalandi" —role="status", xato holati ham. - Oldindan ma'lum kod — build vaqtida (0 KB JS); foydalanuvchi kodi — brauzerda, yengil va kech.
- Kanon: highlight.js +10.0, Prism +26.2, Shiki +70.5 KB;
vazifalarda yoritish yo'q — narxi foydasidan katta.
Keyingi dars: Rich text muharrir I: Tiptap — Markdown belgilarisiz, "Word kabi" tahrir: toolbar, qalin tugmasi, JSON va HTML'da saqlash va nega bu vazifalar ga kirmadi.
Manbalar
- prism-react-renderer 2: README (
Highlight,themes,getTokenProps) — github.com/FormidableLabs/prism-react-renderer - Shiki: "Installation", "Bundles", "JavaScript RegExp Engine",
codeToHast— shiki.style - highlight.js 11: "Usage", "Importing the library" (core + tillar) — highlightjs.org
- rehype-pretty-code — rehype-pretty.pages.dev; rehype-highlight — github.com/rehypejs/rehype-highlight
- hast-util-to-jsx-runtime — github.com/syntax-tree/hast-util-to-jsx-runtime
- MDN: Clipboard API
writeText, CSPstyle-src— developer.mozilla.org - Kanon:
natijalar/tajriba/kutubxonalar-21.txt,tajriba/kutubxonalar/src/k/kod-*.tsx(2026-10-17)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!