fix(web): drop react-dom/server from inline-mention-editor bundle

Replace ReactDOMServer.renderToString with a tiny createRoot + flushSync
helper so react-dom/server is no longer pulled into the client bundle on
every chat page load. react-dom is already in the bundle (React itself
uses it), so this adds zero new runtime weight.

Fixes #1189.
This commit is contained in:
Tim Ren 2026-04-15 20:01:16 +08:00
parent 656e061f84
commit 2727aebcc6

View file

@ -1,6 +1,7 @@
"use client";
import { X } from "lucide-react";
import type { ReactElement } from "react";
import {
createElement,
forwardRef,
@ -10,11 +11,27 @@ import {
useRef,
useState,
} from "react";
import ReactDOMServer from "react-dom/server";
import { flushSync } from "react-dom";
import { createRoot } from "react-dom/client";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import type { Document } from "@/contracts/types/document.types";
import { cn } from "@/lib/utils";
// Render a React element to an HTML string on the client without pulling
// `react-dom/server` into the bundle. `createRoot` + `flushSync` use the
// same `react-dom` package React itself imports, so this adds zero new
// runtime weight.
function renderElementToHTML(element: ReactElement): string {
const container = document.createElement("div");
const root = createRoot(container);
flushSync(() => {
root.render(element);
});
const html = container.innerHTML;
root.unmount();
return html;
}
export interface MentionedDocument {
id: number;
title: string;
@ -213,7 +230,7 @@ export const InlineMentionEditor = forwardRef<InlineMentionEditorRef, InlineMent
const iconSpan = document.createElement("span");
iconSpan.className = "flex items-center text-muted-foreground";
iconSpan.innerHTML = ReactDOMServer.renderToString(
iconSpan.innerHTML = renderElementToHTML(
getConnectorIcon(doc.document_type ?? "UNKNOWN", "h-3 w-3")
);
@ -222,7 +239,7 @@ export const InlineMentionEditor = forwardRef<InlineMentionEditorRef, InlineMent
removeBtn.className =
"size-3 items-center justify-center rounded-full text-muted-foreground transition-colors";
removeBtn.style.display = "none";
removeBtn.innerHTML = ReactDOMServer.renderToString(
removeBtn.innerHTML = renderElementToHTML(
createElement(X, { className: "h-3 w-3", strokeWidth: 2.5 })
);
removeBtn.onclick = (e) => {