Skip to content

Conversation

@JUNERDD
Copy link

@JUNERDD JUNERDD commented Dec 16, 2025

中文版模板 / Chinese template

🤔 This is a ...

  • 🆕 New feature
  • 🐞 Bug fix
  • 📝 Site / documentation improvement
  • 📽️ Demo improvement
  • 💄 Component style improvement
  • 🤖 TypeScript definition improvement
  • 📦 Bundle size optimization
  • ⚡️ Performance optimization
  • ⭐️ Feature enhancement
  • 🌐 Internationalization
  • 🛠 Refactoring
  • 🎨 Code style optimization
  • ✅ Test Case
  • 🔀 Branch merge
  • ⏩ Workflow
  • ⌨️ Accessibility improvement
  • ❓ Other (about what?)

🔗 Related Issues

  • Fixes useStreaming 增加 output 的初始值 #1522 - useStreaming 增加 output 的初始值
  • Fix SSR compatibility issue with DOMPurify in server-side rendering environments
  • Fix streaming initial output rendering issue where content appears empty on first render

💡 Background and Solution

Problems Addressed

  1. SSR Compatibility Issue: DOMPurify was directly imported, which caused errors in server-side rendering (SSR) environments like Next.js because DOMPurify requires a browser window object. This prevented XMarkdown from working properly in SSR contexts.

  2. Streaming Initial Output Issue (related to useStreaming 增加 output 的初始值 #1522): When using streaming mode with hasNextChunk: true, the initial render would show empty content because the output was computed asynchronously in useEffect, causing a flash of empty content before the first update. This caused layout shift and content flickering in SSR scenarios or when other components rendered synchronously.

Solution

  1. Added getDOMPurify utility function: Created a utility that detects the environment and returns the appropriate DOMPurify instance:

    • On the server: Creates a DOMPurify instance with jsdom's window object
    • On the client: Returns the browser's DOMPurify instance
    • This ensures compatibility in both SSR and client-side environments
  2. Fixed streaming initial output: Modified useStreaming hook to compute the initial output synchronously using useState lazy initializer, ensuring content is rendered immediately on first mount without waiting for useEffect.

  3. Refactored Renderer: Updated Renderer to use the new getDOMPurify utility instead of direct import, ensuring consistent behavior across environments.

API Changes

No breaking changes. The API remains the same, but now works correctly in SSR environments.

📝 Change Log

Language Changelog
🇺🇸 English Fix SSR compatibility issue with DOMPurify and streaming initial output rendering
🇨🇳 Chinese 修复 DOMPurify 的 SSR 兼容性问题以及流式输出初始渲染问题

Summary by CodeRabbit

发布说明

  • 重构(Refactor)

    • 优化了内容净化机制以支持浏览器与服务器环境统一获取净化器
    • 重构了流式输出处理,提取纯函数以改善可测试性与可维护性
  • 依赖项(Chores)

    • 添加服务器端环境适配依赖以支持服务端净化处理

✏️ Tip: You can customize this high-level summary in your review settings.

- Add getDOMPurify utility function to support both server-side and client-side environments
- Fix streaming initial output by computing output synchronously on first render
- Refactor useStreaming hook to improve incomplete markdown placeholder handling
- Update Renderer to use getDOMPurify utility instead of direct import
@github-actions
Copy link
Contributor

github-actions bot commented Dec 16, 2025

Preview failed

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 16, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

将对 DOMPurify 的直接导入替换为跨环境工厂函数 getDOMPurify();重构 useStreaming 钩子,提取纯函数负责不完整标记占位与流式输出计算,并改为延迟初始化输出状态;更新测试与 utils 导出,新增 jsdom 依赖以支持服务端环境。

Changes

内聚体 / 文件 变更摘要
DOMPurify 跨环境工具
packages/x-markdown/src/XMarkdown/utils/getDOMPurify.ts, packages/x-markdown/src/XMarkdown/utils/index.ts
新增 getDOMPurify() 工厂函数并通过 utils 索引重导出;在服务端通过 JSDOM 构造 window 并初始化 DOMPurify,客户端返回全局实例。
Renderer 中 DOMPurify 使用更新
packages/x-markdown/src/XMarkdown/core/Renderer.ts, packages/x-markdown/src/XMarkdown/__test__/Renderer.test.ts
将直接导入 DOMPurify 替换为运行时通过 getDOMPurify() 获取实例;测试中改为先调用 getDOMPurify() 再对其 sanitize 进行 spy,确保每个测试块可控实例。
流式输出处理重构
packages/x-markdown/src/XMarkdown/hooks/useStreaming.ts
提取纯辅助函数 getIncompleteMarkdownPlaceholdercomputeStreamingOutput,将不完整标记占位与流处理逻辑集中;使用 useState(() => ...) 延迟初始化 output,并在 processStreaming 中委托新函数更新状态,移除原内联回调。
包依赖与类型
packages/x-markdown/package.json
新增运行时依赖 jsdom(^26.1.0)以及开发时类型 @types/jsdom(^27.0.0)。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 分钟

  • 重点审查项:
    • useStreaming.tscomputeStreamingOutput 的实现,确认流式输出、占位与 commit/缓存逻辑与原行为一致
    • getDOMPurify() 在不同运行时的环境检测与 JSDOM 窗口构造(服务器端初始化与客户端全局分支)
    • 测试中对 DOMPurify 实例的获取与 spy 时机,确保不会引入竞态或共享状态问题

Poem

🐇 我是小兔报新篇,
工厂一出两端连,
流式初值不再眠,
清净标记稳如磐,
代码翻飞耳畔甜。

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 标题准确概括了两个主要改动:SSR 对 DOMPurify 的支持和流式输出初始值的修复。
Linked Issues check ✅ Passed PR 完全满足问题 #1522 的需求:通过 useState 的懒初始化在 useStreaming 中同步计算初始输出。
Out of Scope Changes check ✅ Passed 所有改动均在范围内:getDOMPurify 工具、Renderer 重构和 useStreaming 流式处理逻辑改进,都直接支持 SSR 兼容性和流式输出修复。
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dosubot dosubot bot added the bug Something isn't working label Dec 16, 2025
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @JUNERDD, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the XMarkdown component's compatibility and user experience by resolving critical rendering issues. It ensures that the component functions correctly in Server-Side Rendering (SSR) environments by adapting DOMPurify for both client and server contexts, and it eliminates an undesirable "flash of empty content" during the initial load of streaming markdown, providing a smoother user experience.

Highlights

  • SSR Compatibility for DOMPurify: Resolved an issue where DOMPurify, a library used for sanitizing HTML, caused errors in Server-Side Rendering (SSR) environments like Next.js due to its reliance on the browser's window object. This was fixed by introducing a utility function that provides an environment-appropriate DOMPurify instance.
  • Streaming Initial Output Fix: Addressed a bug in the useStreaming hook where the initial render would display empty content when hasNextChunk: true was enabled. This "flash of empty content" was eliminated by computing the initial output synchronously using useState's lazy initializer.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively resolves two distinct issues: SSR compatibility for DOMPurify and a rendering glitch with initial streaming output. The introduction of the getDOMPurify utility is a clean and appropriate solution for handling environment differences. The refactoring within the useStreaming hook to use useState's lazy initializer is a solid fix for the initial empty content problem and improves the code's structure. I have one suggestion to improve performance in the new getDOMPurify utility.

Comment on lines 1 to 18
import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';

/**
* Returns the DOMPurify instance, compatible with both server-side (Node.js) and client-side (browser) environments.
*
* On the server, it creates a DOMPurify instance with a jsdom window; on the client, it returns the browser's DOMPurify.
*
* @see https://github.com/cure53/DOMPurify?tab=readme-ov-file#running-dompurify-on-the-server
*/
export function getDOMPurify() {
if (typeof window === 'undefined') {
const jsWindow = new JSDOM('').window;
return DOMPurify(jsWindow);
}

return DOMPurify;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Creating a new JSDOM instance on every call to getDOMPurify on the server can be inefficient. It's better to create the server-side DOMPurify instance once and cache it for subsequent calls. This will improve performance in an SSR environment by avoiding the overhead of creating a new JSDOM window repeatedly.

import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';

let serverDOMPurify: ReturnType<typeof DOMPurify>;

/**
 * Returns the DOMPurify instance, compatible with both server-side (Node.js) and client-side (browser) environments.
 *
 * On the server, it creates a DOMPurify instance with a jsdom window; on the client, it returns the browser's DOMPurify.
 *
 * @see https://github.com/cure53/DOMPurify?tab=readme-ov-file#running-dompurify-on-the-server
 */
export function getDOMPurify() {
  if (typeof window === 'undefined') {
    if (!serverDOMPurify) {
      const jsWindow = new JSDOM('').window;
      serverDOMPurify = DOMPurify(jsWindow as any);
    }
    return serverDOMPurify;
  }

  return DOMPurify;
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this cause a memory leak for the serverDOMPurify variable?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/x-markdown/src/XMarkdown/utils/getDOMPurify.ts (1)

11-18: 考虑缓存服务端的 DOMPurify 实例以提升性能。

当前实现在每次调用时都会创建新的 JSDOM 实例(在服务端),这可能导致性能开销。建议缓存服务端实例:

+let cachedServerDOMPurify: any;
+
 export function getDOMPurify() {
   if (typeof window === 'undefined') {
+    if (cachedServerDOMPurify) {
+      return cachedServerDOMPurify;
+    }
     const jsWindow = new JSDOM('').window;
-    return DOMPurify(jsWindow);
+    cachedServerDOMPurify = DOMPurify(jsWindow);
+    return cachedServerDOMPurify;
   }
 
   return DOMPurify;
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08490db and b9d7930.

📒 Files selected for processing (5)
  • packages/x-markdown/src/XMarkdown/__test__/Renderer.test.ts (3 hunks)
  • packages/x-markdown/src/XMarkdown/core/Renderer.ts (2 hunks)
  • packages/x-markdown/src/XMarkdown/hooks/useStreaming.ts (3 hunks)
  • packages/x-markdown/src/XMarkdown/utils/getDOMPurify.ts (1 hunks)
  • packages/x-markdown/src/XMarkdown/utils/index.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
packages/x-markdown/src/XMarkdown/utils/getDOMPurify.ts (1)
packages/x-markdown/src/XMarkdown/utils/index.ts (1)
  • getDOMPurify (1-1)
packages/x-markdown/src/XMarkdown/__test__/Renderer.test.ts (1)
packages/x-markdown/src/XMarkdown/utils/getDOMPurify.ts (1)
  • getDOMPurify (11-18)
packages/x-markdown/src/XMarkdown/core/Renderer.ts (1)
packages/x-markdown/src/XMarkdown/utils/getDOMPurify.ts (1)
  • getDOMPurify (11-18)
🔇 Additional comments (13)
packages/x-markdown/src/XMarkdown/__test__/Renderer.test.ts (3)

3-3: LGTM!测试文件正确引入了新的工具函数。

测试文件现在通过 getDOMPurify 工具函数获取 DOMPurify 实例,这与生产代码的变更保持一致。


779-780: 测试模式正确实现。

测试现在通过调用 getDOMPurify() 获取 DOMPurify 实例后再进行 spy,这与新的工具函数模式相匹配,确保了测试的准确性。


811-812: 测试模式正确实现。

与第 779 行类似,这里正确地通过 getDOMPurify() 获取实例后再创建 spy,保证了测试覆盖的有效性。

packages/x-markdown/src/XMarkdown/utils/index.ts (1)

1-1: LGTM!标准的模块导出。

通过 utils/index.ts 重新导出 getDOMPurify,使其可以从 utils 模块直接访问,这是标准的桶式导出模式。

packages/x-markdown/src/XMarkdown/core/Renderer.ts (2)

7-7: LGTM!正确引入跨环境工具函数。

Renderer 现在通过工具函数获取 DOMPurify 实例,这使得代码能够在 SSR 环境中正常工作。


146-148: SSR 支持正确实现。

通过在运行时调用 getDOMPurify() 获取实例而不是在模块加载时直接导入,成功解决了 SSR 环境中 window 对象不存在的问题。

packages/x-markdown/src/XMarkdown/utils/getDOMPurify.ts (1)

4-10: LGTM!文档清晰且引用了官方资源。

JSDoc 注释清楚地说明了函数的用途和跨环境行为,并引用了 DOMPurify 官方文档作为参考,这是良好的实践。

packages/x-markdown/src/XMarkdown/hooks/useStreaming.ts (6)

2-2: 导入添加正确。

新增的 StreamingOption 导入是后续新函数所必需的,符合预期。


200-231: 纯函数提取合理。

getIncompleteMarkdownPlaceholder 函数成功将不完整 Markdown 占位符逻辑提取为纯函数,提高了代码的可测试性和可维护性。特殊情况处理(Image 的单字符 ! 和 Table 的多行判断)逻辑正确。


233-287: 流式处理逻辑提取合理。

computeStreamingOutput 函数成功将核心流式处理逻辑集中化,提高了代码的可测试性。缓存重置机制(lines 243-247)可以正确处理输入流重置的情况,字符逐个处理的逻辑保持了原有行为。


300-316: 懒初始化修复了初始渲染为空的问题。

使用 useState 的懒初始化函数在首次渲染前同步计算初始输出,成功解决了流式模式下初始内容为空的问题。这是 PR 的核心修复。

需要注意的是,对于大型输入,首次挂载时的同步计算可能会有性能开销,但这是为了修复空白初始渲染而做出的必要权衡。


326-333: 重构提升了代码质量。

processStreaming 函数重构为使用提取的纯函数 computeStreamingOutput,代码更简洁且易于测试,同时保持了原有的处理逻辑。


335-335: 依赖数组更新正确,但需注意特定使用场景下的性能考虑。

在依赖数组中包含 components 是正确的,因为 processStreaming 回调在第 330 行的 computeStreamingOutput 调用中使用了该参数。

需要说明的是:如果父组件在每次渲染时都传递新的 components 对象引用(例如 components={{...}}),会导致 processStreaming 的依赖变化,进而触发第 346 行的 useEffect 重新执行。若 components 是稳定的对象引用,则不会产生额外性能开销。建议在父组件中使用 useMemo 缓存 components 对象,特别是当该对象包含复杂的组件映射时。

@socket-security
Copy link

socket-security bot commented Dec 16, 2025

No dependency changes detected. Learn more about Socket for GitHub.

👍 No dependency changes detected in pull request

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9d7930 and 1c9faaa.

📒 Files selected for processing (2)
  • packages/x-markdown/package.json (1 hunks)
  • packages/x-markdown/src/XMarkdown/utils/getDOMPurify.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/x-markdown/src/XMarkdown/utils/getDOMPurify.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test
  • GitHub Check: size
  • GitHub Check: build preview
🔇 Additional comments (1)
packages/x-markdown/package.json (1)

61-61: 类型定义依赖位置正确。

@types/jsdom 正确放置在 devDependencies 中。

@codecov
Copy link

codecov bot commented Dec 16, 2025

Bundle Report

Changes will increase total bundle size by 1.19MB (35.27%) ⬆️⚠️, exceeding the configured threshold of 5%.

Bundle name Size Change
antdx-array-push 2.94MB 1.19MB (67.78%) ⬆️⚠️

Affected Assets, Files, and Routes:

view changes for bundle: antdx-array-push

Assets Changed:

Asset Name Size Change Total Size Change (%)
antdx.js (New) 2.94MB 2.94MB 100.0% 🚀
antdx.min.js (Deleted) -1.75MB 0 bytes -100.0% 🗑️

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/x-markdown/src/XMarkdown/utils/getDOMPurify.ts (1)

3-4: 考虑添加注释说明 jsdomInstance 的用途。

jsdomInstance 在第 15 行被赋值后再未被读取。如果保留此引用是为了防止 jsdom window 对象被垃圾回收(确保 serverDOMPurify 依赖的 window 对象在模块生命周期内有效),建议添加注释说明;如果不需要保留引用,可以移除该变量。

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c9faaa and 2198be7.

📒 Files selected for processing (1)
  • packages/x-markdown/src/XMarkdown/utils/getDOMPurify.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-23T15:51:02.475Z
Learnt from: YumoImer
Repo: ant-design/x PR: 223
File: package.json:169-169
Timestamp: 2024-11-23T15:51:02.475Z
Learning: 在使用 pnpm 严格模式时,即使存在间接依赖,也需要在 package.json 中直接声明依赖包,否则项目无法正常启动。

Applied to files:

  • packages/x-markdown/src/XMarkdown/utils/getDOMPurify.ts
🧬 Code graph analysis (1)
packages/x-markdown/src/XMarkdown/utils/getDOMPurify.ts (1)
packages/x-markdown/src/XMarkdown/utils/index.ts (1)
  • getDOMPurify (1-1)
🔇 Additional comments (1)
packages/x-markdown/src/XMarkdown/utils/getDOMPurify.ts (1)

6-21: 实现方案合理。

动态加载 jsdom、处理多种导出模式以及错误处理逻辑都很稳健,能够适配不同的模块系统和运行环境。

Comment on lines +34 to +43
export function getDOMPurify(): ReturnType<typeof DOMPurify> {
if (typeof window === 'undefined') {
if (!serverDOMPurify) {
initializeServerDOMPurify();
}
return serverDOMPurify || DOMPurify;
}

return DOMPurify;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

服务端初始化失败时的回退逻辑存在问题。

第 39 行的回退逻辑 return serverDOMPurify || DOMPurify 在服务端环境下可能导致运行时错误:

  • 如果 initializeServerDOMPurify() 失败(例如 jsdom 加载失败),serverDOMPurifynull
  • 此时直接返回 DOMPurify 在服务端无法工作,因为 DOMPurify 需要一个 window 对象
  • 用户会收到关于缺少 window 的隐晦错误信息

建议改进回退策略:

 export function getDOMPurify(): ReturnType<typeof DOMPurify> {
   if (typeof window === 'undefined') {
     if (!serverDOMPurify) {
       initializeServerDOMPurify();
     }
-    return serverDOMPurify || DOMPurify;
+    if (!serverDOMPurify) {
+      throw new Error(
+        'Failed to initialize DOMPurify for SSR. Please ensure jsdom is installed.'
+      );
+    }
+    return serverDOMPurify;
   }
 
   return DOMPurify;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function getDOMPurify(): ReturnType<typeof DOMPurify> {
if (typeof window === 'undefined') {
if (!serverDOMPurify) {
initializeServerDOMPurify();
}
return serverDOMPurify || DOMPurify;
}
return DOMPurify;
}
export function getDOMPurify(): ReturnType<typeof DOMPurify> {
if (typeof window === 'undefined') {
if (!serverDOMPurify) {
initializeServerDOMPurify();
}
if (!serverDOMPurify) {
throw new Error(
'Failed to initialize DOMPurify for SSR. Please ensure jsdom is installed.'
);
}
return serverDOMPurify;
}
return DOMPurify;
}

"clsx": "^2.1.1",
"dompurify": "^3.2.6",
"html-react-parser": "^5.2.5",
"jsdom": "^26.1.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

浏览器场景不应该依赖 jsdom

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

useStreaming 增加 output 的初始值

2 participants