[perf][bundle] Стартовая загрузка ~3.5 МБ JS: нет route-level code splitting; KaTeX, 45 грамматик lowlight, drawio, posthog и AI SDK грузятся всегда #342

Open
opened 2026-07-04 19:41:02 +03:00 by agent_vscode · 0 comments
Collaborator

Суть

На слабых машинах интерфейс «задумчивый» уже на старте: при каждой загрузке/перезагрузке страницы браузер скачивает и, главное, парсит/компилирует ~3.5 МБ минифицированного JS (~1 МБ gzip) на главном потоке. Замер реальной прод-сборки (pnpm --filter client build, rolldown):

Чанк Размер gzip Загружается на старте
index (app-код + posthog + highlight + diff + drawio-обвязка + i18next…) 1 920 КБ 561 КБ да (entry)
lib (ProseMirror/TipTap + lowlight) 759 КБ 240 КБ да (static import)
vendor-mantine 514 КБ 158 КБ да (modulepreload)
katex 257 КБ 77 КБ да, даже без единой формулы

Для сравнения: excalidraw (754 КБ), mermaid (~1 МБ суммой диаграммных чанков), emoji-data, onnxruntime/vad — уже корректно отделены и грузятся по требованию. Проблема — в том, что осталось в eager-графе.

Диагноз (по убыванию вклада)

  1. Нет route-level code splitting. App.tsx статически импортирует все 28 роутов, ни одного React.lazy. Цепочка page.tsx#L3FullEditorpage-editor.tsx#L26-L29extensions.ts втягивает весь TipTap, @hocuspocus/provider, yjs, y-indexeddb, все node views и пп. 2–4 ниже в стартовый граф. Пользователь на /login скачивает и компилирует весь редактор и все страницы настроек.

  2. KaTeX статически в eager-чанке редактора. math-inline.tsx#L1-L2 и math-block.tsx#L1-L2: import katex + css. Тянутся через extensions.ts#L84-L85. ~280 КБ JS+CSS платят все страницы, включая не содержащие формул.

  3. ~45 грамматик highlight.js регистрируются на верхнем уровне модуля. extensions.ts#L105-L140: createLowlight(common) (~35 языков) + 10 статических импортов (powershell, abap, elixir, erlang, dockerfile, clojure, fortran, haskell, scala, plaintext) и lowlight.register(...) выполняются в момент evaluate модуля — для любой страницы, даже без код-блоков. (Стоимость ре-подсветки при печати — #343.)

  4. react-drawio без lazy-обёртки — асимметрия с excalidraw. drawio-view.tsx#L14-L20 и drawio-menu.tsx#L37 импортируются статически через extensions.ts#L93 и page-editor.tsx#L62. У excalidraw при этом есть корректный образец: excalidraw-view-lazy.tsx.

  5. posthog-js (~150 КБ) статически даже для self-hosted. main.tsx#L17, main.tsx#L24: posthog.init под флагом isCloud() && isPostHogEnabled, но импорты статические, и PostHogProvider оборачивает дерево всегда.

  6. AI SDK (ai + @ai-sdk/react) — eager для всех авторизованных. global-app-shell.tsx#L18 статически монтирует AiChatWindowchat-thread.tsx#L2 тянет рантайм AI SDK даже при выключенном AI в воркспейсе.

Мелочь, уедет само при распиле: diff в eager-чанке через suggestion.ts#L1; socket.io-client (~90 КБ, реально нужен) — user-provider.tsx#L7.

Проверено и уже правильно (не трогать): mermaid (code-block-view.tsx#L11-L13), excalidraw (lazy-обёртки), emoji-mart (+data — динамические import()), onnxruntime/vad (import() при старте диктовки, ассеты из public/vad/), локали i18n (i18next-http-backend, load: 'currentOnly').

Границы изменения

Только apps/client: App.tsx, lazy-обёртки компонентов, vite.config.ts (группы чанков). Никаких изменений API, поведения фич, серверной части. Функциональность 1:1, меняется только момент загрузки кода.

Решение

  1. React.lazy + Suspense на уровне роутов в App.tsx — минимум: Page (редактор), все settings/*, share-страницы, SpaceHome/SpaceTrash/Home. Fallback — существующий скелетон/пустой лоадер. Это само по себе выносит редактор, katex, lowlight, drawio и diff из стартового пути для всех не-редакторских маршрутов.
  2. Lazy node views для KaTeX — по образцу mermaid: React.lazy-обёртки для MathInlineView/MathBlockView, katex-чанк грузится при первом появлении math-узла в документе.
  3. Ленивая инициализация lowlight — вынести createLowlight из top-level: либо инициализировать при первом рендере код-блока, либо сократить eager-набор до короткого списка популярных языков и дорегистрировать остальные динамическим import() по факту встречи языка.
  4. Lazy-обёртки для drawiodrawio-view-lazy.tsx / drawio-menu-lazy.tsx зеркально excalidraw; заменить ссылки в extensions.ts и page-editor.tsx.
  5. posthog — условный динамический импорт: if (isCloud() && isPostHogEnabled) { const { default: posthog } = await import("posthog-js"); … }; PostHogProvider монтировать только в cloud-режиме (или заменить на no-op обёртку).
  6. AiChatWindow — React.lazy за флагом «AI включён» или по первому открытию окна.
  7. (Опционально) добавить в vite.config.ts группы advancedChunks для @tiptap|prosemirror|yjs и katex, чтобы кэш браузера переживал деплои app-кода.

Крайние случаи

  • Suspense в дереве редактора: math/drawio lazy-узлы не должны ронять весь редактор — локальные Suspense с плейсхолдером размера узла (как у mermaid).
  • Share-страницы (/share/*) используют readonly-редактор — проверить, что распил не ломает их отдельный layout и что нужные extension-чанки догружаются.
  • Коллаборация: PageEditor грузится лениво → провайдер hocuspocus создаётся позже на десятки мс; порядок «сначала статический рендер, потом live-подключение» уже реализован (showStatic), регрессии быть не должно — проверить явно.
  • Первый переход на страницу после холодного старта: редакторный чанк грузится в момент навигации — допустимо (скелетон), но стоит добавить modulepreload/префетч редакторного чанка после idle на авторизованных роутах.
  • Vite dev-режим: барельные именованные импорты @tabler/icons-react (158 файлов) в prod тришейкаются нормально, но замедляют dev-сервер — можно добавить в optimizeDeps, отдельная мелкая задача.

Тесты / проверка

  • pnpm --filter client build до/после: размер entry-чанка и суммарный eager-JS (цель: entry < ~700 КБ, eager суммарно < ~1.5 МБ min).
  • dist/index.html: в modulepreload не должно быть katex.
  • Смок: логин → home → открытие страницы с формулами/код-блоками/drawio → share-страница → настройки; всё работает, чанки догружаются по требованию (Network-панель).
  • Lighthouse/Performance-трейс на троттлинге CPU 6x: TBT до/после.

Вне скоупа

  • Рантайм-стоимость печати в редакторе (меню, getJSON, ре-подсветка) — #343 (слой B).
  • Фоновые ре-рендеры (дерево, socket, инвалидации, навигация) — #344 (слой C).
  • Панель комментариев — #340.
  • Серверные изменения — нет.

План работ

  1. React.lazy роуты в App.tsx + Suspense-fallback'и, проверка share-layout.
  2. Lazy KaTeX node views (по образцу mermaid).
  3. Lazy lowlight (инициализация при первом код-блоке / сокращение eager-набора).
  4. Lazy drawio (зеркально excalidraw).
  5. Условный динамический posthog; lazy AiChatWindow.
  6. (Опция) группы advancedChunks + префетч редакторного чанка после idle.
  7. Замер до/после (build-размеры + TBT на троттлинге), смок-прогон.
# Суть На слабых машинах интерфейс «задумчивый» уже на старте: при каждой загрузке/перезагрузке страницы браузер скачивает и, главное, **парсит/компилирует ~3.5 МБ минифицированного JS (~1 МБ gzip) на главном потоке**. Замер реальной прод-сборки (`pnpm --filter client build`, rolldown): | Чанк | Размер | gzip | Загружается на старте | |---|---|---|---| | `index` (app-код + posthog + highlight + diff + drawio-обвязка + i18next…) | 1 920 КБ | 561 КБ | да (entry) | | `lib` (ProseMirror/TipTap + lowlight) | 759 КБ | 240 КБ | да (static import) | | `vendor-mantine` | 514 КБ | 158 КБ | да (modulepreload) | | `katex` | 257 КБ | 77 КБ | **да, даже без единой формулы** | Для сравнения: excalidraw (754 КБ), mermaid (~1 МБ суммой диаграммных чанков), emoji-data, onnxruntime/vad — уже корректно отделены и грузятся по требованию. Проблема — в том, что осталось в eager-графе. # Диагноз (по убыванию вклада) 1. **Нет route-level code splitting.** [App.tsx](apps/client/src/App.tsx) статически импортирует все 28 роутов, ни одного `React.lazy`. Цепочка [page.tsx#L3](apps/client/src/pages/page/page.tsx#L3) → `FullEditor` → [page-editor.tsx#L26-L29](apps/client/src/features/editor/page-editor.tsx#L26-L29) → [extensions.ts](apps/client/src/features/editor/extensions/extensions.ts) втягивает весь TipTap, `@hocuspocus/provider`, `yjs`, `y-indexeddb`, все node views и пп. 2–4 ниже в стартовый граф. Пользователь на `/login` скачивает и компилирует весь редактор и все страницы настроек. 2. **KaTeX статически в eager-чанке редактора.** [math-inline.tsx#L1-L2](apps/client/src/features/editor/components/math/math-inline.tsx#L1-L2) и [math-block.tsx#L1-L2](apps/client/src/features/editor/components/math/math-block.tsx#L1-L2): `import katex` + css. Тянутся через [extensions.ts#L84-L85](apps/client/src/features/editor/extensions/extensions.ts#L84-L85). ~280 КБ JS+CSS платят все страницы, включая не содержащие формул. 3. **~45 грамматик highlight.js регистрируются на верхнем уровне модуля.** [extensions.ts#L105-L140](apps/client/src/features/editor/extensions/extensions.ts#L105-L140): `createLowlight(common)` (~35 языков) + 10 статических импортов (`powershell, abap, elixir, erlang, dockerfile, clojure, fortran, haskell, scala, plaintext`) и `lowlight.register(...)` выполняются в момент evaluate модуля — для любой страницы, даже без код-блоков. (Стоимость ре-подсветки при печати — #343.) 4. **`react-drawio` без lazy-обёртки — асимметрия с excalidraw.** [drawio-view.tsx#L14-L20](apps/client/src/features/editor/components/drawio/drawio-view.tsx#L14-L20) и [drawio-menu.tsx#L37](apps/client/src/features/editor/components/drawio/drawio-menu.tsx#L37) импортируются статически через [extensions.ts#L93](apps/client/src/features/editor/extensions/extensions.ts#L93) и [page-editor.tsx#L62](apps/client/src/features/editor/page-editor.tsx#L62). У excalidraw при этом есть корректный образец: [excalidraw-view-lazy.tsx](apps/client/src/features/editor/components/excalidraw/excalidraw-view-lazy.tsx). 5. **`posthog-js` (~150 КБ) статически даже для self-hosted.** [main.tsx#L17](apps/client/src/main.tsx#L17), [main.tsx#L24](apps/client/src/main.tsx#L24): `posthog.init` под флагом `isCloud() && isPostHogEnabled`, но **импорты статические**, и `PostHogProvider` оборачивает дерево всегда. 6. **AI SDK (`ai` + `@ai-sdk/react`) — eager для всех авторизованных.** [global-app-shell.tsx#L18](apps/client/src/components/layouts/global/global-app-shell.tsx#L18) статически монтирует `AiChatWindow` → [chat-thread.tsx#L2](apps/client/src/features/ai-chat/components/chat-thread.tsx#L2) тянет рантайм AI SDK даже при выключенном AI в воркспейсе. Мелочь, уедет само при распиле: `diff` в eager-чанке через [suggestion.ts#L1](apps/client/src/features/comment/utils/suggestion.ts#L1); `socket.io-client` (~90 КБ, реально нужен) — [user-provider.tsx#L7](apps/client/src/features/user/user-provider.tsx#L7). **Проверено и уже правильно (не трогать):** mermaid ([code-block-view.tsx#L11-L13](apps/client/src/features/editor/components/code-block/code-block-view.tsx#L11-L13)), excalidraw (lazy-обёртки), emoji-mart (+data — динамические import()), onnxruntime/vad (import() при старте диктовки, ассеты из `public/vad/`), локали i18n (`i18next-http-backend`, `load: 'currentOnly'`). # Границы изменения Только `apps/client`: App.tsx, lazy-обёртки компонентов, vite.config.ts (группы чанков). Никаких изменений API, поведения фич, серверной части. Функциональность 1:1, меняется только момент загрузки кода. # Решение 1. **`React.lazy` + `Suspense` на уровне роутов в App.tsx** — минимум: `Page` (редактор), все `settings/*`, share-страницы, `SpaceHome`/`SpaceTrash`/`Home`. Fallback — существующий скелетон/пустой лоадер. Это само по себе выносит редактор, katex, lowlight, drawio и diff из стартового пути для всех не-редакторских маршрутов. 2. **Lazy node views для KaTeX** — по образцу mermaid: `React.lazy`-обёртки для `MathInlineView`/`MathBlockView`, katex-чанк грузится при первом появлении math-узла в документе. 3. **Ленивая инициализация lowlight** — вынести `createLowlight` из top-level: либо инициализировать при первом рендере код-блока, либо сократить eager-набор до короткого списка популярных языков и дорегистрировать остальные динамическим import() по факту встречи языка. 4. **Lazy-обёртки для drawio** — `drawio-view-lazy.tsx` / `drawio-menu-lazy.tsx` зеркально excalidraw; заменить ссылки в extensions.ts и page-editor.tsx. 5. **posthog — условный динамический импорт**: `if (isCloud() && isPostHogEnabled) { const { default: posthog } = await import("posthog-js"); … }`; `PostHogProvider` монтировать только в cloud-режиме (или заменить на no-op обёртку). 6. **AiChatWindow — `React.lazy`** за флагом «AI включён» или по первому открытию окна. 7. (Опционально) добавить в [vite.config.ts](apps/client/vite.config.ts) группы `advancedChunks` для `@tiptap|prosemirror|yjs` и `katex`, чтобы кэш браузера переживал деплои app-кода. # Крайние случаи - **Suspense в дереве редактора**: math/drawio lazy-узлы не должны ронять весь редактор — локальные `Suspense` с плейсхолдером размера узла (как у mermaid). - **Share-страницы** (`/share/*`) используют readonly-редактор — проверить, что распил не ломает их отдельный layout и что нужные extension-чанки догружаются. - **Коллаборация**: `PageEditor` грузится лениво → провайдер hocuspocus создаётся позже на десятки мс; порядок «сначала статический рендер, потом live-подключение» уже реализован (`showStatic`), регрессии быть не должно — проверить явно. - **Первый переход на страницу после холодного старта**: редакторный чанк грузится в момент навигации — допустимо (скелетон), но стоит добавить `modulepreload`/префетч редакторного чанка после idle на авторизованных роутах. - **Vite dev-режим**: барельные именованные импорты `@tabler/icons-react` (158 файлов) в prod тришейкаются нормально, но замедляют dev-сервер — можно добавить в `optimizeDeps`, отдельная мелкая задача. # Тесты / проверка - `pnpm --filter client build` до/после: размер entry-чанка и суммарный eager-JS (цель: entry < ~700 КБ, eager суммарно < ~1.5 МБ min). - `dist/index.html`: в modulepreload не должно быть katex. - Смок: логин → home → открытие страницы с формулами/код-блоками/drawio → share-страница → настройки; всё работает, чанки догружаются по требованию (Network-панель). - Lighthouse/Performance-трейс на троттлинге CPU 6x: TBT до/после. # Вне скоупа - Рантайм-стоимость печати в редакторе (меню, getJSON, ре-подсветка) — #343 (слой B). - Фоновые ре-рендеры (дерево, socket, инвалидации, навигация) — #344 (слой C). - Панель комментариев — #340. - Серверные изменения — нет. # План работ 1. `React.lazy` роуты в App.tsx + Suspense-fallback'и, проверка share-layout. 2. Lazy KaTeX node views (по образцу mermaid). 3. Lazy lowlight (инициализация при первом код-блоке / сокращение eager-набора). 4. Lazy drawio (зеркально excalidraw). 5. Условный динамический posthog; lazy AiChatWindow. 6. (Опция) группы advancedChunks + префетч редакторного чанка после idle. 7. Замер до/после (build-размеры + TBT на троттлинге), смок-прогон.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#342