From 34515032eeb3106bc75e37fe55489d19f99cbe52 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 05:38:35 +0000 Subject: [PATCH] web(memory): split Plate editor plugins into base + lazy heavy kits; idle-load heavy kits to reduce startup heap Co-Authored-By: nate@buster.so --- .../components/ui/report/base-editor-kit.ts | 23 +++++ .../ui/report/load-heavy-editor-kits.ts | 88 +++++++++++++++++++ .../components/ui/report/useReportEditor.tsx | 54 ++++++++---- 3 files changed, 149 insertions(+), 16 deletions(-) create mode 100644 apps/web/src/components/ui/report/base-editor-kit.ts create mode 100644 apps/web/src/components/ui/report/load-heavy-editor-kits.ts diff --git a/apps/web/src/components/ui/report/base-editor-kit.ts b/apps/web/src/components/ui/report/base-editor-kit.ts new file mode 100644 index 000000000..76b97a104 --- /dev/null +++ b/apps/web/src/components/ui/report/base-editor-kit.ts @@ -0,0 +1,23 @@ +'use client'; + +import { TrailingBlockPlugin } from 'platejs'; +import { AlignKit } from './plugins/align-kit'; +import { BasicBlocksKit } from './plugins/basic-blocks-kit'; +import { BasicMarksKit } from './plugins/basic-marks-kit'; +import { ExitBreakKit } from './plugins/exit-break-kit'; +import { FixedToolbarKit } from './plugins/fixed-toolbar-kit'; +import { LineHeightKit } from './plugins/line-height-kit'; +import { LinkKit } from './plugins/link-kit'; +import { ListKit } from './plugins/list-kit'; + +export const BaseEditorKit = [ + ...BasicBlocksKit, + ...BasicMarksKit, + ...ListKit, + ...AlignKit, + ...LineHeightKit, + ...LinkKit, + ...ExitBreakKit, + TrailingBlockPlugin, + ...FixedToolbarKit +]; diff --git a/apps/web/src/components/ui/report/load-heavy-editor-kits.ts b/apps/web/src/components/ui/report/load-heavy-editor-kits.ts new file mode 100644 index 000000000..5c58073ab --- /dev/null +++ b/apps/web/src/components/ui/report/load-heavy-editor-kits.ts @@ -0,0 +1,88 @@ +export const loadHeavyEditorKits = async () => { + const [ + ai, + blockMenu, + codeBlock, + table, + toggle, + toc, + media, + callout, + column, + math, + date, + mention, + font, + discussion, + comment, + suggestion, + slash, + autoformat, + cursorOverlay, + dnd, + emoji, + docx, + markdown, + blockPlaceholder, + floatingToolbar, + busterStream + ] = await Promise.all([ + import('./plugins/ai-kit'), + import('./plugins/block-menu-kit'), + import('./plugins/code-block-kit'), + import('./plugins/table-kit'), + import('./plugins/toggle-kit'), + import('./plugins/toc-kit'), + import('./plugins/media-kit'), + import('./plugins/callout-kit'), + import('./plugins/column-kit'), + import('./plugins/math-kit'), + import('./plugins/date-kit'), + import('./plugins/mention-kit'), + import('./plugins/font-kit'), + import('./plugins/discussion-kit'), + import('./plugins/comment-kit'), + import('./plugins/suggestion-kit'), + import('./plugins/slash-kit'), + import('./plugins/autoformat-kit'), + import('./plugins/cursor-overlay-kit'), + import('./plugins/dnd-kit'), + import('./plugins/emoji-kit'), + import('./plugins/docx-kit'), + import('./plugins/markdown-kit'), + import('./plugins/block-placeholder-kit'), + import('./plugins/floating-toolbar-kit'), + import('./plugins/buster-stream-kit').catch(() => ({ BusterStreamKit: [] as any[] })) + ]); + + const kits = [ + ...(ai.AIKit || []), + ...(blockMenu.BlockMenuKit || []), + ...(codeBlock.CodeBlockKit || []), + ...(table.TableKit || []), + ...(toggle.ToggleKit || []), + ...(toc.TocKit || []), + ...(media.MediaKit || []), + ...(callout.CalloutKit || []), + ...(column.ColumnKit || []), + ...(math.MathKit || []), + ...(date.DateKit || []), + ...(mention.MentionKit || []), + ...(font.FontKit || []), + ...(discussion.DiscussionKit || []), + ...(comment.CommentKit || []), + ...(suggestion.SuggestionKit || []), + ...(slash.SlashKit || []), + ...(autoformat.AutoformatKit || []), + ...(cursorOverlay.CursorOverlayKit || []), + ...(dnd.DndKit || []), + ...(emoji.EmojiKit || []), + ...(docx.DocxKit || []), + ...(markdown.MarkdownKit || []), + ...(blockPlaceholder.BlockPlaceholderKit || []), + ...(floatingToolbar.FloatingToolbarKit || []), + ...((busterStream as any).BusterStreamKit || []) + ]; + + return kits; +}; diff --git a/apps/web/src/components/ui/report/useReportEditor.tsx b/apps/web/src/components/ui/report/useReportEditor.tsx index 83b24e457..9483bcb92 100644 --- a/apps/web/src/components/ui/report/useReportEditor.tsx +++ b/apps/web/src/components/ui/report/useReportEditor.tsx @@ -1,9 +1,9 @@ import { type Value } from 'platejs'; import { useEditorRef, usePlateEditor, type TPlateEditor } from 'platejs/react'; -import { useMemo } from 'react'; - -import { EditorKit } from './editor-kit'; +import { useEffect, useMemo, useState } from 'react'; import { FIXED_TOOLBAR_KIT_KEY } from './plugins/fixed-toolbar-kit'; +import { BaseEditorKit } from './base-editor-kit'; +import { loadHeavyEditorKits } from './load-heavy-editor-kits'; import type { IReportEditor } from './ReportEditor'; export const useReportEditor = ({ @@ -17,27 +17,49 @@ export const useReportEditor = ({ onReady?: (editor: IReportEditor) => void; useFixedToolbarKit?: boolean; }) => { - const plugins = useMemo(() => { - const filteredKeys: string[] = []; - if (!useFixedToolbarKit) { - filteredKeys.push(FIXED_TOOLBAR_KIT_KEY); - } - - if (filteredKeys.length > 0) { - return EditorKit.filter((plugin) => !filteredKeys.includes(plugin.key)); - } - - return EditorKit; + const basePlugins = useMemo(() => { + if (useFixedToolbarKit) return BaseEditorKit; + return BaseEditorKit.filter((plugin) => plugin.key !== FIXED_TOOLBAR_KIT_KEY); }, [useFixedToolbarKit]); + const [plugins, setPlugins] = useState(basePlugins); + + useEffect(() => { + setPlugins(basePlugins); + }, [basePlugins]); + + useEffect(() => { + const schedule = (cb: () => void) => { + if (typeof window !== 'undefined' && 'requestIdleCallback' in window) { + (window as any).requestIdleCallback(cb, { timeout: 3000 }); + } else { + setTimeout(cb, 1500); + } + }; + let cancelled = false; + schedule(async () => { + const heavy = await loadHeavyEditorKits(); + if (!cancelled) { + setPlugins((prev) => { + const keys = new Set(prev.map((p) => p.key)); + const toAdd = heavy.filter((p) => !keys.has(p.key)); + return [...prev, ...toAdd]; + }); + } + }); + return () => { + cancelled = true; + }; + }, []); + return usePlateEditor({ plugins, value, readOnly: disabled, onReady: ({ editor }) => onReady?.(editor) - }); // Pass dependencies to usePlateEditor + }); }; -export type ReportEditor = TPlateEditor; +export type ReportEditor = TPlateEditor; export const useEditor = () => useEditorRef();