Компоненты
Что такое компонент
Компонент — переиспользуемый блок UI на React. В Docusaurus это функция или класс, возвращающая JSX; в статьях подключают через import в MDX или из swizzle-темы.
React-компоненты в it-knowledge-base — мост между ~3000 MDX-статей и интерактивом, поиском, подборками, PDF. Глобальной регистрации MDX-компонентов нет — каждая статья импортирует виджеты явно. Исключение — article chrome в theme/DocItem/Layout (PDF, hero, related автоматически).
Учебник — React, компоненты и JSX.
Три способа появления на странице
| Способ | Примеры | Где настраивается |
|---|---|---|
| Import в MDX | ExternalPlayEmbed, CollectionHub | Сама статья .mdx |
| Theme layout | ArticlePdfExport, TechArticleHero | src/theme/DocItem/Layout/index.tsx |
| Pages / Navbar | UniverseMap, DocSearchBar | src/pages/index.js, NavbarItem/DocSearch |
Связь между собой — shared кирпичи, данные из src/data/, константы URL, клиентский движок поиска.
Импорт компонентов в MDX
---
title: Моя статья
description: "Краткое описание."
slug: /encyclopedia/example/1
---
import ExternalPlayEmbed from '@site/src/components/ExternalPlayEmbed';
# Заголовок
Текст параграфа.
<ExternalPlayEmbed example="about/data-types-play" title="Типы данных" />
Три правила при подключении в MDX.
- Пустая строка после
---frontmatter. - Алиас
@site/src/components/...(относительный путь изdocs/не резолвится). - PascalCase в JSX —
<ExternalPlayEmbed />.
Remark lazyMdxDemoImports на сборке заменит import на lazy-обёртку. В исходнике пишите обычный import — как в примере выше.
MDX и добавление нового виджета
- Файл в
src/components/MyWidget.jsx(или.tsx). importв нужных.mdx.npm start— проверить Network (async chunk при скролле или клике).- Тяжёлый интерактив — play.spirzen.ru +
ExternalPlayEmbedдля демо вне репозитория.
Как всё подключается — цепочка embed
| Этап | Слой | Действие |
|---|---|---|
| 1 | Автор | Пишет <ExternalPlayEmbed example="slug" /> в MDX |
| 2 | remark | Переписывает import → lazyExternalEmbed(...) |
| 3 | lazy-обёртка | EmbedClickGate — click-to-load |
| 4 | React | import() грузит chunk ExternalPlayEmbed |
| 5 | BrowserOnly | Рендер только в браузере |
| 6 | iframe | src="https://play.spirzen.ru/p/embed/slug/?theme=dark" |
| 7 | play → parent | postMessage высоты; trusted origin |
| 8 | DOM | Контейнер подстраивает высоту, опционально fullscreen |
Подробнее об архитектуре — Архитектура, интерактив.
ExternalPlayEmbed — эталон embed
Назначение — встроить демо с play.spirzen.ru в iframe с авто-высотой, click-to-load, fullscreen.
Props
| Prop | Тип | Описание |
|---|---|---|
example | string | Slug демо → /p/embed/<slug>/ |
title | string | Подпись для a11y и UI |
minHeight | number | Минимальная высота до postMessage (default 320) |
autoLoad | boolean | Автоматическая загрузка iframe без клика |
playProps | object | Query-параметры для play |
embedData | object | Данные в iframe через postMessage |
src | string | Полный URL вместо example — escape hatch |
Схема интеграции
const url = new URL(buildPlayEmbedUrl(baseUrl, example));
url.searchParams.set('theme', theme);
baseUrl — siteConfig.customFields.playExamplesUrl (prod или localhost:4322). API URL в src/constants/playExamples.js.
Живой пример на этой странице
ExternalCodeEmbed
Аналог для code.spirzen.ru — пути /e/embed/<slug>/. Trusted origin для postMessage — src/constants/codeExamples.js.
import ExternalCodeEmbed from '@site/src/components/ExternalCodeEmbed';
<ExternalCodeEmbed example="python/hello-world" title="Hello World" />
Lazy-обёртка — как делать
Автор не пишет lazy вручную — remark подставляет обёртку при сборке.
lazyExternalEmbed (play/code)
// после remark в скомпилированном MDX
import __itLazyExternalEmbed from '@site/src/components/shared/lazyExternalEmbed';
const ExternalPlayEmbed = __itLazyExternalEmbed(
() => import('@site/src/components/ExternalPlayEmbed'),
{ kind: 'play' }
);
Как работает lazyExternalEmbed.js.
- До активации — только
EmbedClickGate(нет iframe, нет внешних запросов). - После клика —
React.lazy+ Suspense +autoLoadна внутренний компонент. - Обёртка снаружи — BrowserOnly.
lazyDemo / lazyDemoInView (остальные)
const Foo = __itLazyDemoInView(() => import('@site/src/components/Foo'));
| Обёртка | Когда |
|---|---|
lazyExternalEmbed | iframe play/code — отдельный chunk, сериализация через embedLoadQueue |
lazyDemoInView | Остальные @site/src/components/* — chunk при попадании в viewport |
lazyDemo | В theme layout — без viewport, сразу lazy при монтировании layout |
Не импортируйте shared/ из MDX — remark оборачивает только корень components/.
Создать свою lazy-обёртку (редко)
Паттерн как в lazyDemo.js.
import React, {lazy, Suspense} from 'react';
export default function lazyMyWidget(importFn) {
const Lazy = lazy(importFn);
return function Wrapped(props) {
return (
<Suspense fallback={<div role="status">Загрузка…</div>}>
<Lazy {...props} />
</Suspense>
);
};
}
Для MDX достаточно положить файл в components/ — lazyMdxDemoImports подхватит автоматически.
shared/ — кирпичи инфраструктуры
| Модуль | Роль |
|---|---|
lazyDemo.js | React.lazy + Suspense |
lazyDemoInView.js | Lazy + IntersectionObserver |
lazyExternalEmbed.js | Очередь iframe, BrowserOnly |
EmbedClickGate.jsx | UI gate "Запустить демо" |
embedLoadQueue.js | Сериализация — max 2 iframe одновременно |
useEmbedViewport.js | Стабильный src, высота, in-view mount |
embedScrollLock.js | Блокировка скролла в fullscreen |
deferredIdle.js | idle — requestIdleCallback |
demoFallback.jsx | Скелетоны загрузки |
Кирпич — мелкий модуль без публичного MDX-API; обёртка в корне components/ — то, что импортируют статьи.
click-to-load и autoLoad
| Режим | Когда | Сеть при открытии статьи |
|---|---|---|
| click-to-load (default) | Embed play/code через lazyExternalEmbed | Нет запросов к play/code до клика |
| autoLoad | Prop на ExternalPlayEmbed после активации gate | iframe грузится сразу после клика на gate |
| lazyDemoInView | Обычные виджеты | Подгрузка chunk при скролле к блоку |
Автоматическая загрузка всего iframe без gate перегружает страницу с десятками демо — в проде gate обязателен; autoLoad={true} только для осознанных случаев.
postMessage, API и trusted origin
Контракт spirzen ↔ play/code — postMessage + URL slug, без отдельного REST на каждую статью.
| Сообщение | Направление | Смысл |
|---|---|---|
it-play-embed-height | play → parent | Высота для DOM |
it-play-theme | parent → play | Синхронизация light/dark |
it-play-embed-data | parent → play | Произвольный payload |
it-play-fullscreen | play → parent | Режим на весь экран |
Trusted origin — whitelist в PLAY_TRUSTED_ORIGINS / CODE_EXAMPLES_TRUSTED_ORIGINS; чужой event.origin игнорируется.
Query-параметры — ?theme=light|dark, доп. ключи через playProps в URL.
BrowserOnly — подробно
BrowserOnly — компонент Docusaurus (@docusaurus/BrowserOnly), который рендерит детей только на клиенте, после гидратации. На сервере (SSR) при docusaurus build React генерирует HTML без кода, требующего window, document, localStorage, iframe.
Зачем нужен
| Проблема без BrowserOnly | Пример |
|---|---|
window is not defined | postMessage, IntersectionObserver |
| Расхождение SSR/клиент | Случайные id, размеры viewport |
| Лишняя работа на сервере | DocSearch, PDF export |
Docusaurus — SSG + гидратация. Компонент с прямым доступом к window в теле функции упадёт при prerender.
Два способа использования
1. Fallback на сервере
<BrowserOnly fallback={<div role="status">Загрузка…</div>}>
{() => <ExternalPlayEmbedInner {...props} />}
</BrowserOnly>
На SSR и до гидратации показывается fallback (скелетон, пустой null). После mount в браузере вызывается функция-ребёнок () => <Inner />.
2. Children как render prop
Дети должны быть функцией () => ReactNode, не готовым JSX. Иначе код с window выполнится при импорте модуля на сервере.
Где используется в проекте
| Компонент | Fallback |
|---|---|
ExternalPlayEmbed / ExternalCodeEmbed | Скелетон с role="status" |
lazyExternalEmbed | Сообщение "Подготовка embed…" |
ArticlePdfExport | null |
DocSearchBar | Упрощённая кнопка без модалки |
LabTrainersHub, RandomArticle | demoLoadingFallback |
DeveloperExamPlay | Текст загрузки экзамена |
BrowserOnly vs useEffect
| Подход | Когда |
|---|---|
| BrowserOnly | Весь компонент бесполезен без DOM (embed, поиск, PDF) |
| useEffect | Часть логики клиентская (TechArticleHero вставляет иконку в h1 после mount) |
| typeof window !== 'undefined' | В clientModules, отдельно от MDX-компонентов |
TechArticleHero без BrowserOnly — при SSR возвращает null, в useEffect ищет h1 в DOM и монтирует createRoot.
a11y с BrowserOnly
Fallback должен иметь role="status" или aria-live="polite", чтобы скринридер не молчал при подгрузке тяжёлого блока.
Theme layout — без MDX
const ArticlePdfExport = lazyDemo(() => import('@site/src/components/ArticlePdfExport'));
const TechArticleHero = lazyDemo(() => import('@site/src/components/TechArticleHero'));
В layout после контента — <ArticlePdfExport />, <TechArticleHero />, <ArticleRelated />, <ArticleSeeAlso />.
TechArticleHero props из MDX не принимает — читает useDoc(), techArticlePages.js, вставляет иконку у h1 через DOM.
articleMetaEnhancement.ts после idle делает теги кликабельными → /tags/....
Хабы, каталоги, виджеты
CollectionHub — каталог подборки
import CollectionHub from '@site/src/components/CollectionHub';
<CollectionHub label="Первые шаги" />
sidebarCollections.js + collectionDocTitles.json → полный список статей по label.
GettingStartedPaths, LabTrainersHub
Хабы маршрутов и тренажёров — карточки, ссылки на /lab/, play-демо.
RandomChecklistItem / RandomQuestionFromArticle
Парсинг списков "Проверь себя" из DOM (articleExtract.js), случайность одного пункта.
DocSearch
Не в MDX. Цепочка — Root → DocSearchProvider → navbar → Ctrl+K → docSearchEngine.js + индекс.
DesignThemePicker
TSX в navbar — стилизация через applyItDesign.
Толстые и тонкие компоненты
| Тип | Примеры | Где логика |
|---|---|---|
| Тонкие | CollectionHub, SpirzenOnlineToolLink | Рендер данных |
| Толстые | ExternalPlayEmbed, LabTrainersHub | iframe, postMessage, state, оптимизация |
| Вынесенные | WebGL, эмуляция, длинный код | play.spirzen.ru / code, в статье только embed |
Виджеты — самодостаточный блок в тексте (демо, случайный вопрос, хаб). Интерактив с WebGL или тяжёлой эмуляцией — отдельный сервис play, не src/components/.
Стилизация и глобальные классы
| Подход | Когда |
|---|---|
CSS module (*.module.css) | Изолированный UI компонента — предпочтительно |
| Глобальные классы | Система статей — .callout, .article-tags, .wiki-link в custom.css |
Infima --ifm-* | Согласование с темой |
Стилизация компонентов не должна ломать типографику .theme-doc-markdown.
Бандл, chunks и оптимизация
docusaurus.config.js → splitChunks.
vendor-react,vendor-docusaurus,vendor-prismitEmbed— async ExternalPlayEmbed / ExternalCodeEmbeditDemoAsync— остальные компоненты до ~200 KBitMermaid— диаграммы
Перегруз страницы — десятки синхронных демо; лечится gate + lazyDemoInView + embedLoadQueue. Оптимизация — ленивая подгрузка, idle enhancement, prefetch limit в clientModules.
Как создать новый компонент — чеклист
| Сценарий | Решение |
|---|---|
| WebGL, эмулятор, тяжёлый UI | play.spirzen.ru + ExternalPlayEmbed |
| Виджет в 1–2 статьях | src/components/MyWidget.jsx |
| Блок на всех статьях | DocItem/Layout + lazyDemo |
| Данные + список ссылок | data/*.json + тонкий компонент |
// src/components/MyWidget.jsx
import React from 'react';
import BrowserOnly from '@docusaurus/BrowserOnly';
import styles from './MyWidget.module.css';
/** @param {{ label: string }} props */
export default function MyWidget({ label }) {
return (
<BrowserOnly fallback={<span>{label}</span>}>
{() => <div className={styles.root}>{label}</div>}
</BrowserOnly>
);
}
npm start
npm run build
Частые ошибки
| Ошибка | Симптом | Исправление |
|---|---|---|
| Нет пустой строки после frontmatter | MDX compile error | Пустая строка после --- |
@theme вместо @site | Module not found | Проектные компоненты — @site/src/... |
window is not defined | Падение SSR | BrowserOnly или useEffect |
| iframe пустой локально | play/code не запущены | localhost:4321/4322 или env |
| postMessage высота 0 | Неверный origin | TRUSTED_ORIGINS в constants |
| slug не в play | 404 в iframe | docs:demo-registry, синхрон slug |
import из shared/ в MDX | Нет lazy-обёртки | Только корень components/ |
Реестр демо
info/demo-registry.md (docs:demo-registry). Slug в example="..." должен существовать на play.
Шпаргалка импортов
import ExternalPlayEmbed from '@site/src/components/ExternalPlayEmbed';
import ExternalCodeEmbed from '@site/src/components/ExternalCodeEmbed';
import CollectionHub from '@site/src/components/CollectionHub';
import LabTrainersHub from '@site/src/components/LabTrainersHub';
import GettingStartedPaths from '@site/src/components/GettingStartedPaths';
import RandomChecklistItem from '@site/src/components/RandomChecklistItem';
import RandomQuestionFromArticle from '@site/src/components/RandomQuestionFromArticle';
import DeveloperExamPlay from '@site/src/components/DeveloperExamPlay';
import DocCardList from '@theme/DocCardList';
DocCardList — компонент темы; remark не оборачивает @theme/* в lazy.
Глоссарий
Компонент
Переиспользуемый блок UI на React.
React-компоненты
Функции/классы с JSX в src/components/ и src/theme/.
Мост
Связь MDX-контента с интерактивом, данными и темой.
Интерактив
Исполняемые демо, тренажёры, play/code embeds.
Импорт компонентов
import X from '@site/src/components/X' в MDX.
Связь между собой
Компоненты → shared → data/constants → внешние сервисы.
embed
Встраивание play/code через iframe.
Эталон
ExternalPlayEmbed — образец для новых embed.
Демо
Интерактивный пример в статье.
iframe
Вложенный документ play/code.
play.spirzen.ru
Сервис интерактива, /p/embed/<slug>/.
lazy-обёртка
lazyExternalEmbed, lazyDemoInView — отложенный import.
props
Входные параметры JSX (example, title, …).
click-to-load
Загрузка embed только после клика (EmbedClickGate).
Автоматическая загрузка
autoLoad — iframe сразу после gate.
a11y
Доступность — role, aria-label, клавиатура. CSS a11y.
postMessage
API обмена сообщениями с iframe.
API
Контракт URL/postMessage между spirzen и play/code.
Подгрузка
Async загрузка chunk или iframe.
Запросы
HTTP к play/code — только после активации gate.
query-параметры
?theme=dark и playProps в URL iframe.
URL
Адрес embed, полной страницы play, статьи.
escape hatch
Prop src — полный URL вместо example.
trusted origin
Whitelist доменов для postMessage.
Кирпич
Мелкий модуль в shared/.
shared
src/components/shared/ — инфраструктура embed/lazy.
Обёртка
HOC вокруг lazy/import (lazyDemo, lazyExternalEmbed).
import
Статический в MDX; динамический import() в lazy.
lazyDemo
React.lazy + Suspense для theme layout.
Сериализация
embedLoadQueue — не более 2 iframe параллельно.
Блокировка скролла
embedScrollLock при fullscreen embed.
Suspense
React — показ fallback пока lazy chunk грузится.
autoLoad
Prop — пропустить внутренний gate после внешнего клика.
gate
EmbedClickGate — UI до загрузки iframe.
MDX
Markdown + JSX; точка вставки компонентов.
idle
scheduleIdleWork — отложенный DOM enhancement.
DOM
Дерево страницы в браузере.
Хаб
Агрегатор ссылок (CollectionHub, LabTrainersHub).
Каталог
Список демо или статей с метаданными.
label
Подпись подборки или кнопки.
Рендер
Отрисовка React в DOM.
Парсинг
Разбор DOM/markdown (RandomQuestion, articleExtract).
Случайность
RandomArticle, RandomChecklistItem — pick из набора.
Индекс
doc-search-index, demo-registry.
TS и TSX
TypeScript-компоненты (DesignThemePicker, theme).
WebGL
3D в браузере — на play.spirzen.ru, вне энциклопедии.
Эмуляция
Эмуляторы CPU/сети — play.spirzen.ru.
Тренажёры
Интерактивные упражнения лаборатории.
Виджеты
Вставляемые блоки в тексте статьи.
Толстые и тонкие компоненты
Много логики vs тонкий рендер данных.
BrowserOnly
Docusaurus — рендер только на клиенте, защита SSR.
Глобальные классы
.callout, .wiki-link в custom.css.
SSR
Server-Side Rendering при docusaurus build.
Синхронизация
theme/colorMode ↔ iframe через postMessage и query.
Стилизация компонентов
CSS modules + Infima variables.
Бандл
Собранный JS; splitChunks в config.
Перегруз
Слишком много sync iframe/chunks на одной странице.
Оптимизация
lazy, gate, queue, idle, viewport.
chunk
Отдельный JS-файл async import.
viewport
Видимая область; IntersectionObserver.
Связь с другими главами
- Интерактив — витрина для читателя.
- Архитектура — iframe, play, code, postMessage.
- Данные и скрипты — lazyMdxDemoImports.
- Структура src/ — components/, shared/.
- TypeScript — TSX в theme и picker.
- Темы и стили — theme query в embed.