docs: New version reminder notification modal#53777
docs: New version reminder notification modal#53777Wxh16144 wants to merge 24 commits intoant-design:masterfrom
Conversation
|
|
📝 WalkthroughSummary by CodeRabbit
Summary by CodeRabbit
Walkthrough本次变更新增了一个用于发布新版本公告的 React 组件 Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Browser
participant ChangeModal
participant localStorage
participant MarkdownFiles
Browser->>ChangeModal: 页面加载后渲染
ChangeModal->>localStorage: 读取 lastVisitedVersion
ChangeModal->>ChangeModal: 比较当前版本与上次访问版本
alt 新版本
ChangeModal->>MarkdownFiles: 加载对应语言 latest-changelog.md
ChangeModal->>User: 弹窗展示变更日志
User->>ChangeModal: 关闭弹窗
ChangeModal->>localStorage: 更新 lastVisitedVersion
else 不是新版本
ChangeModal-->>User: 不显示弹窗
end
Suggested reviewers
Poem
Tip ⚡️ Faster reviews with caching
Enjoy the performance boost—your workflow just got faster. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms (11)
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
👁 Visual Regression Report for PR #53777 Passed ✅
🎊 Congrats! No visual-regression diff found.
|
WalkthroughThis pull request introduces a new version reminder notification modal to the documentation site. It includes a modal that informs users about the latest version release and provides links to the full changelog and a Chinese mirror. The modal is integrated into the global layout and is triggered based on version comparison logic. Changes
🪧 TipsFor further assistance, please describe your question in the comments and @petercat-assistant to start a conversation with me. |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
scripts/generate-latest-changelog.ts (1)
1-56: 建议添加输出目录存在性检查脚本没有检查输出目录
.dumi/preset是否存在,如果目录不存在,写入文件时会失败。建议在写入文件前添加以下代码确保输出目录存在:
const main = async () => { + // 确保输出目录存在 + await fse.ensureDir(OUTPUT_DIR); + const lastENChangelog = getChangelog(originENChangelog); const lastCNChangelog = getChangelog(originCNChangelog);.dumi/theme/common/ChangeModal.tsx (2)
89-97: 溢出检测中的可读性与鲁棒性问题
containerRef.current?.scrollHeight!与可选链同时出现,可读性差且在current为空时仍会得到undefined > undefined的比较,虽然返回false,但语义不清晰。debounce未指定wait,默认0,实质上并未节流;浏览器resize频率较高时可能造成多次调用。-const hasOverflow = - containerRef.current?.scrollHeight! > containerRef.current?.clientHeight!; +const el = containerRef.current; +const hasOverflow = !!el && el.scrollHeight > el.clientHeight;可同时在
debounce中加入 100~200 ms 的等待时间来减少高频触发。
149-163:hasNewVersion依赖未参与showModal逻辑
useEffect监听了hasNewVersion,但在内部showModal没有检查。若首次渲染hasNewVersion为false,而后又变成true,仍会注册load事件;建议在showModal或useEffect外层提前判空:React.useEffect(() => { - let timer: NodeJS.Timeout | null = null; + if (!hasNewVersion) return; + + let timer: NodeJS.Timeout | null = null; function showModal() {这样能避免不必要的事件监听。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
.dumi/theme/common/ChangeModal.tsx(1 hunks).dumi/theme/layouts/GlobalLayout.tsx(2 hunks).gitignore(1 hunks)package.json(1 hunks)scripts/generate-latest-changelog.ts(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
scripts/generate-latest-changelog.ts (1)
typings/custom-typings.d.ts (1)
version(19-19)
⏰ Context from checks skipped due to timeout of 90000ms (16)
- GitHub Check: test-react-legacy (17, 2/2)
- GitHub Check: test-react-legacy (17, 1/2)
- GitHub Check: test-react-legacy (16, 2/2)
- GitHub Check: test-react-latest (dom, 2/2)
- GitHub Check: test-react-legacy (16, 1/2)
- GitHub Check: test-react-latest (dom, 1/2)
- GitHub Check: test lib/es module (es, 2/2)
- GitHub Check: test lib/es module (es, 1/2)
- GitHub Check: build
- GitHub Check: visual-diff snapshot (2/2)
- GitHub Check: visual-diff snapshot (1/2)
- GitHub Check: build preview
- GitHub Check: lint
- GitHub Check: size
- GitHub Check: build
- GitHub Check: WIP
🔇 Additional comments (8)
.gitignore (1)
62-62: 添加新的 gitignore 规则以忽略生成的更新日志文件这条规则正确地忽略了由新脚本
scripts/generate-latest-changelog.ts生成的更新日志文件。这些文件是在构建时生成的,不应该被 Git 跟踪。package.json (1)
65-65: 更新 lint:changelog 脚本以包含新的更新日志生成脚本脚本现在包含两个命令:原有的组件更新日志生成脚本和新增的最新版本更新日志提取脚本。这种集成方式正确,确保了在相关的生命周期中同时执行两个脚本。
.dumi/theme/layouts/GlobalLayout.tsx (2)
28-28: 导入新的版本提醒模态框组件正确导入了
ChangeModal组件以在全局布局中使用。
216-219: 将版本提醒模态框集成到全局布局中在
App组件内添加ChangeModal组件的位置正确,确保它在整个应用中都可用。scripts/generate-latest-changelog.ts (4)
1-6: 正确导入必要的依赖和版本信息导入了所需的模块和从
package.json中获取当前版本号,这是正确的做法。
7-11: 设置路径常量并读取更新日志文件代码正确设置了根路径和输出目录,并读取了英文和中文更新日志文件。
13-33: 实现提取特定版本更新日志的函数函数实现逻辑清晰,能够正确提取当前版本的更新日志内容。注释有助于理解代码功能,正则表达式的使用也很恰当。
46-56: 适当的错误处理和日志输出主函数执行和错误处理实现得很好,包括成功日志和失败时的进程退出。
More templates
commit: |
Bundle ReportBundle size has no change ✅ |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #53777 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 774 774
Lines 14012 14012
Branches 3711 3711
=========================================
Hits 14012 14012 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com> Signed-off-by: 𝑾𝒖𝒙𝒉 <wxh16144@qq.com>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
.dumi/theme/common/ChangeModal.tsx (2)
20-27: 避免 SSR 崩溃:在访问localStorage之前判断运行环境该段代码仍直接在函数顶层访问
localStorage,若在 dumi 的静态构建或任何 SSR 场景中执行,会因为window/localStorage不存在而抛出ReferenceError。请参考之前的保护写法,先判断typeof window !== 'undefined' && window.localStorage后再读取或写入。
125-129:navigator/window直接引用缺少环境判断组件初始化阶段直接读取
navigator.languages与window.location.hostname,SSR 时会导致错误。与之前评论相同,建议包裹typeof window !== 'undefined'判断或延迟到useEffect中执行。
🧹 Nitpick comments (2)
.dumi/theme/common/ChangeModal.tsx (2)
141-147: 按静态分析建议去掉可推断类型并使用const
lastVersion的类型可由初始化值推断,无需显式注解;lastVisitedVersion从未重新赋值,应声明为const以表达不可变语义。- let lastVersion: string = '1.0.0'; - let lastVisitedVersion = getLastVisitedVersion(); + let lastVersion = '1.0.0'; // 推断为 string + const lastVisitedVersion = getLastVisitedVersion();🧰 Tools
🪛 Biome (1.9.4)
[error] 143-143: This type annotation is trivially inferred from its initialization.
Safe fix: Remove the type annotation.
(lint/style/noInferrableTypes)
[error] 144-145: This let declares a variable that is only assigned once.
'lastVisitedVersion' is never reassigned.
Safe fix: Use const instead.
(lint/style/useConst)
🪛 GitHub Check: CodeFactor
[warning] 143-143: .dumi/theme/common/ChangeModal.tsx#L143
'lastVisitedVersion' is never reassigned. Use 'const' instead. (prefer-const)
93-94: 溢出判定缺乏空值保护,可能触发运行时异常当前写法依赖非空断言
!,在containerRef.current为空时表达式会变成undefined > undefined,虽不会抛错但逻辑含糊。可以在取值前先判断引用是否存在,让表达式更清晰健壮。- const hasOverflow = - containerRef.current?.scrollHeight! > containerRef.current?.clientHeight!; + const node = containerRef.current; + const hasOverflow = node ? node.scrollHeight > node.clientHeight : false;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
.dumi/theme/common/ChangeModal.tsx(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
.dumi/theme/common/ChangeModal.tsx
[error] 143-143: This type annotation is trivially inferred from its initialization.
Safe fix: Remove the type annotation.
(lint/style/noInferrableTypes)
[error] 144-145: This let declares a variable that is only assigned once.
'lastVisitedVersion' is never reassigned.
Safe fix: Use const instead.
(lint/style/useConst)
🪛 GitHub Check: CodeFactor
.dumi/theme/common/ChangeModal.tsx
[warning] 143-143: .dumi/theme/common/ChangeModal.tsx#L143
'lastVisitedVersion' is never reassigned. Use 'const' instead. (prefer-const)
⏰ Context from checks skipped due to timeout of 90000ms (12)
- GitHub Check: test-react-legacy (17, 2/2)
- GitHub Check: test-react-legacy (17, 1/2)
- GitHub Check: test-react-legacy (16, 2/2)
- GitHub Check: test-react-legacy (16, 1/2)
- GitHub Check: test-react-latest (dom, 2/2)
- GitHub Check: test-react-latest (dom, 1/2)
- GitHub Check: test lib/es module (es, 1/2)
- GitHub Check: visual-diff snapshot (2/2)
- GitHub Check: build
- GitHub Check: visual-diff snapshot (1/2)
- GitHub Check: build
- GitHub Check: WIP
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
.dumi/theme/common/ChangeModal.tsx (3)
125-129:⚠️ Potential issue添加环境检测以避免在 SSR/构建阶段使用 navigator 和 window 引发错误
hasChinesePreference和isChineseMirror在组件渲染时顶层直接引用navigator与window,同样会在 SSR 时导致崩溃。请改为带保护的写法,例如:- const hasChinesePreference = navigator.languages.some((lang) => lang.startsWith('zh')); - const isChineseMirror = ['ant-design.gitee.io', 'ant-design.antgroup.com'].includes( - window.location.hostname, - ); + const hasChinesePreference = + typeof navigator !== 'undefined' && + navigator.languages?.some((lang) => lang.startsWith('zh')); + const isChineseMirror = + typeof window !== 'undefined' && + ['ant-design.gitee.io', 'ant-design.antgroup.com'].includes(window.location.hostname);
100-112: 🛠️ Refactor suggestion补充取消防抖以避免潜在内存泄漏
checkOverflow使用了lodash/debounce,在组件卸载时仅断开了ResizeObserver,但未调用checkOverflow.cancel(),会导致定时器仍在运行且持有引用。建议在清理函数中增加取消调用:React.useEffect(() => { const container = containerRef.current; if (!container) return; checkOverflow(); const resizeObserver = new ResizeObserver(checkOverflow); resizeObserver.observe(container); return () => { - resizeObserver.disconnect(); + resizeObserver.disconnect(); + // 取消防抖定时器,防止组件卸载后仍保留引用 + checkOverflow.cancel(); }; }, []);
20-28:⚠️ Potential issue修复 SSR 环境下直接访问 localStorage 导致的构建错误
当前
getLastVisitedVersion在顶层直接调用localStorage,会在 Dumi 的静态构建或任何 SSR 场景下因window/localStorage不存在抛出ReferenceError。请在访问前添加运行时检测:function getLastVisitedVersion() { - const fallbackVersion = '1.0.0'; - const lastVisitedVersion = localStorage.getItem(STORAGE_KEY); + /* SSR 安全:仅在浏览器环境下访问 localStorage */ + if (typeof window === 'undefined' || !window.localStorage) { + return '0.0.0'; + } + const fallbackVersion = '1.0.0'; + const lastVisitedVersion = window.localStorage.getItem(STORAGE_KEY); if (lastVisitedVersion && VER_REGEX.test(lastVisitedVersion)) { return lastVisitedVersion; } - localStorage.setItem(STORAGE_KEY, fallbackVersion); + window.localStorage.setItem(STORAGE_KEY, fallbackVersion); return fallbackVersion; }
🧹 Nitpick comments (2)
.dumi/theme/common/ChangeModal.tsx (2)
142-143: 移除可推断类型的冗余注解
let lastVersion: string = '1.0.0';中的类型注解可被 TS 推断,建议简化:- let lastVersion: string = '1.0.0'; + let lastVersion = '1.0.0';🧰 Tools
🪛 Biome (1.9.4)
[error] 143-143: This type annotation is trivially inferred from its initialization.
Safe fix: Remove the type annotation.
(lint/style/noInferrableTypes)
10-13: 优化 Markdown 模块导入的类型声明,避免使用@ts-ignore当前为导入
.md?type=dumi-component添加了// @ts-ignore,建议新增全局类型声明(例如在typings.d.ts):declare module '*.md?type=dumi-component' { const Component: React.ComponentType; export default Component; }然后移除文件内的
@ts-ignore注释,以保持类型安全和代码可维护性。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
.dumi/theme/common/ChangeModal.tsx(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
.dumi/theme/common/ChangeModal.tsx
[error] 143-143: This type annotation is trivially inferred from its initialization.
Safe fix: Remove the type annotation.
(lint/style/noInferrableTypes)
⏰ Context from checks skipped due to timeout of 90000ms (9)
- GitHub Check: test-react-latest (dom, 2/2)
- GitHub Check: test-react-latest (dom, 1/2)
- GitHub Check: test-react-legacy (17, 2/2)
- GitHub Check: test-react-legacy (17, 1/2)
- GitHub Check: test-react-legacy (16, 2/2)
- GitHub Check: test-react-legacy (16, 1/2)
- GitHub Check: visual-diff snapshot (2/2)
- GitHub Check: visual-diff snapshot (1/2)
- GitHub Check: WIP
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (5)
.dumi/theme/common/ChangeModal.tsx (5)
21-29: 修复 SSR 兼容性问题直接访问
localStorage在服务器端渲染(SSR)环境中会导致错误,因为localStorage对象在服务器端不存在。
92-99: 防止内存泄漏,需要清理防抖函数
debounce创建的函数在组件卸载时需要被取消,否则可能导致内存泄漏和组件卸载后的无效状态更新。return () => { resizeObserver.disconnect(); + checkOverflow.cancel(); };Also applies to: 111-112
128-131: 修复 SSR 兼容性问题直接访问
navigator和window对象在服务器端渲染(SSR)环境中会导致错误。需要添加环境检查。-const hasChinesePreference = navigator.languages.some((lang) => lang.startsWith('zh')) || utils.isZhCN(pathname);; -const isChineseMirror = ['ant-design.gitee.io', 'ant-design.antgroup.com'].includes( - window.location.hostname, -); +const hasChinesePreference = + (typeof navigator !== 'undefined' && + navigator.languages?.some((lang) => lang.startsWith('zh'))) || + utils.isZhCN(pathname); +const isChineseMirror = + typeof window !== 'undefined' && + ['ant-design.gitee.io', 'ant-design.antgroup.com'].includes( + window.location.hostname, + );
135-137: 修复 SSR 兼容性问题直接访问
localStorage在服务器端渲染(SSR)环境中会导致错误。- function handleClose() { - localStorage.setItem(STORAGE_KEY, currentVersion); - updateOpen(false); - } + function handleClose() { + if (typeof window !== 'undefined') { + localStorage.setItem(STORAGE_KEY, currentVersion); + } + updateOpen(false); + }
202-209: 修正 Modal 样式属性使用Ant Design 的
Modal组件不支持styles属性,应使用官方提供的maskStyle和bodyStyle。- styles={{ - mask: { - backdropFilter: 'blur(2px)', - }, - body: { - padding: 0, - }, - }} + maskStyle={{ backdropFilter: 'blur(2px)' }} + bodyStyle={{ padding: 0 }}
🧹 Nitpick comments (3)
.dumi/theme/common/ChangeModal.tsx (3)
128-128: 修复代码中的多余分号这行代码末尾有两个分号。
-const hasChinesePreference = navigator.languages.some((lang) => lang.startsWith('zh')) || utils.isZhCN(pathname);; +const hasChinesePreference = navigator.languages.some((lang) => lang.startsWith('zh')) || utils.isZhCN(pathname);
144-146: 移除不必要的类型注解这里的类型注解是多余的,因为它可以从初始化值推断出来。
- const checkVersion = useEvent(async () => { - let lastVersion: string = '1.0.0'; + const checkVersion = useEvent(async () => { + let lastVersion = '1.0.0';🧰 Tools
🪛 Biome (1.9.4)
[error] 146-146: This type annotation is trivially inferred from its initialization.
Safe fix: Remove the type annotation.
(lint/style/noInferrableTypes)
219-223: 考虑使用更灵活的镜像切换逻辑当前代码硬编码了国内镜像地址。考虑使用配置项或环境变量来定义镜像URL,以便将来更改时更加灵活。
- window.location.href = window.location.href.replace( - window.location.host, - 'ant-design.antgroup.com', - ); + const CHINA_MIRROR = 'ant-design.antgroup.com'; + window.location.href = window.location.href.replace( + window.location.host, + CHINA_MIRROR, + );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
.dumi/theme/common/ChangeModal.tsx(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
.dumi/theme/common/ChangeModal.tsx
[error] 146-146: This type annotation is trivially inferred from its initialization.
Safe fix: Remove the type annotation.
(lint/style/noInferrableTypes)
⏰ Context from checks skipped due to timeout of 90000ms (16)
- GitHub Check: test-react-latest (dom, 2/2)
- GitHub Check: test-react-latest (dom, 1/2)
- GitHub Check: test lib/es module (es, 2/2)
- GitHub Check: test lib/es module (es, 1/2)
- GitHub Check: test-react-legacy (17, 2/2)
- GitHub Check: test-react-legacy (17, 1/2)
- GitHub Check: build
- GitHub Check: test-react-legacy (16, 2/2)
- GitHub Check: test-react-legacy (16, 1/2)
- GitHub Check: lint
- GitHub Check: visual-diff snapshot (2/2)
- GitHub Check: visual-diff snapshot (1/2)
- GitHub Check: build preview
- GitHub Check: size
- GitHub Check: build
- GitHub Check: WIP
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (4)
.dumi/theme/common/ChangeModal.tsx (4)
205-212:Modal并不支持styles属性,仍会导致样式无法生效该问题在上一轮已提及,仍未修复。请改用
maskStyle与bodyStyle:- styles={{ - mask: { - backdropFilter: 'blur(2px)', - }, - body: { - padding: 0, - }, - }} + maskStyle={{ backdropFilter: 'blur(2px)' }} + bodyStyle={{ padding: 0 }}
21-29: 仍然缺少对localStorage的环境检测虽然当前调用路径大概率只在浏览器端触发,但公共方法最好自带防护,避免未来复用时踩坑。建议参考此前评论加入
typeof window !== 'undefined'判断。
182-195: 🛠️ Refactor suggestion
useEffect中缺少checkVersion依赖且仍未做 SSR 保护
- 依赖数组为空会造成闭包引用旧的
checkVersion。window在 SSR 不存在,仍可能导致构建失败。- React.useEffect(() => { + React.useEffect(() => { let timer: NodeJS.Timeout | null = null; function showModal() { timer = setTimeout(() => { checkVersion().then(updateOpen); }, 1000); } - window.addEventListener('load', showModal); + if (typeof window !== 'undefined') { + window.addEventListener('load', showModal); + } return function cleanup() { - window.removeEventListener('load', showModal); + if (typeof window !== 'undefined') { + window.removeEventListener('load', showModal); + } timer && clearTimeout(timer); }; - }, []); + }, [checkVersion]);这样既能保证最新闭包,又避免 SSR 崩溃。
131-136:⚠️ Potential issueSSR 环境下直接访问
navigator/window,渲染阶段会抛ReferenceError组件首次渲染(包括静态构建/SSR 阶段)就会执行这段代码,
navigator与window在服务端均不存在,页面将直接构建失败。建议在运行时加环境判断,或推迟到useEffect中运行。- const hasChinesePreference = navigator.languages.some((lang) => lang.startsWith('zh')) || utils.isZhCN(pathname);; - const isChineseMirror = ['ant-design.gitee.io', 'ant-design.antgroup.com'].includes( - window.location.hostname, - ); + const hasChinesePreference = + (typeof navigator !== 'undefined' && + navigator.languages?.some((lang) => lang.startsWith('zh'))) || + utils.isZhCN(pathname); + + const isChineseMirror = + typeof window !== 'undefined' && + ['ant-design.gitee.io', 'ant-design.antgroup.com'].includes(window.location.hostname);如需进一步优化,可包裹在
React.useMemo中避免重复计算。
🧹 Nitpick comments (1)
.dumi/theme/common/ChangeModal.tsx (1)
148-149: 无必要的类型注解,可移除以简化代码
lastVersion的类型可由初始化值推断,静态分析已给出提示。- let lastVersion: string = '1.0.0'; + let lastVersion = '1.0.0';🧰 Tools
🪛 Biome (1.9.4)
[error] 149-149: This type annotation is trivially inferred from its initialization.
Safe fix: Remove the type annotation.
(lint/style/noInferrableTypes)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
.dumi/theme/common/ChangeModal.tsx(1 hunks)scripts/generate-latest-changelog.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- scripts/generate-latest-changelog.ts
🧰 Additional context used
🪛 Biome (1.9.4)
.dumi/theme/common/ChangeModal.tsx
[error] 149-149: This type annotation is trivially inferred from its initialization.
Safe fix: Remove the type annotation.
(lint/style/noInferrableTypes)
⏰ Context from checks skipped due to timeout of 90000ms (16)
- GitHub Check: test-react-legacy (17, 2/2)
- GitHub Check: test lib/es module (es, 2/2)
- GitHub Check: test-react-legacy (17, 1/2)
- GitHub Check: test-react-legacy (16, 2/2)
- GitHub Check: test lib/es module (es, 1/2)
- GitHub Check: test-react-legacy (16, 1/2)
- GitHub Check: build
- GitHub Check: lint
- GitHub Check: test-react-latest (dom, 2/2)
- GitHub Check: test-react-latest (dom, 1/2)
- GitHub Check: visual-diff snapshot (2/2)
- GitHub Check: visual-diff snapshot (1/2)
- GitHub Check: build preview
- GitHub Check: size
- GitHub Check: build
- GitHub Check: WIP
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
.dumi/theme/common/ChangeModal.tsx (2)
21-29: 确保 localStorage 在 SSR 环境下的安全访问直接使用
localStorage在服务器端渲染(SSR)环境中会导致运行时错误,因为服务器端不存在window对象。尽管已有关于此问题的评论,但代码仍未修复。建议添加运行时检测,如下所示:
function getLastVisitedVersion() { + /* istanbul ignore next */ + if (typeof window === 'undefined' || !window.localStorage) { + // SSR / 构建阶段:返回一个固定版本,避免崩溃 + return '0.0.0'; + } + const fallbackVersion = '1.0.0'; - const lastVisitedVersion = localStorage.getItem(STORAGE_KEY); + const lastVisitedVersion = window.localStorage.getItem(STORAGE_KEY); if (lastVisitedVersion && VER_REGEX.test(lastVisitedVersion)) { return lastVisitedVersion; } - localStorage.setItem(STORAGE_KEY, fallbackVersion); + window.localStorage.setItem(STORAGE_KEY, fallbackVersion); return fallbackVersion; }
211-218: 修正 Modal 样式属性使用,避免无效的styles配置Ant Design
Modal不支持styles属性,应使用官方提供的maskStyle和bodyStyle。- styles={{ - mask: { - backdropFilter: 'blur(2px)', - }, - body: { - padding: 0, - }, - }} + maskStyle={{ backdropFilter: 'blur(2px)' }} + bodyStyle={{ padding: 0 }}
🧹 Nitpick comments (4)
.dumi/theme/common/ChangeModal.tsx (4)
153-153: 移除冗余类型注解此处的类型注解可以由初始化值推断出来,建议移除以减少代码冗余。
- let lastVersion: string = '1.0.0'; + let lastVersion = '1.0.0';🧰 Tools
🪛 Biome (1.9.4)
[error] 153-153: This type annotation is trivially inferred from its initialization.
Safe fix: Remove the type annotation.
(lint/style/noInferrableTypes)
201-201: 添加依赖数组中缺少的 checkVersion虽然
checkVersion使用useEvent包装保证了引用稳定性,但为了代码的清晰性和一致性,建议将其添加到依赖数组中。- }, []); + }, [checkVersion]);
115-115: 添加 checkOverflow 的依赖项
useEffect的依赖数组中应包含checkOverflow,以避免潜在的闭包问题。- }, []); + }, [checkOverflow]);
99-99: 优化 debounce 的 deps 数组
useMemo创建的 debounce 函数依赖数组为空,这通常没问题,但明确添加setIsOverflowing作为依赖可以使代码更加清晰。- [], + [setIsOverflowing],
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
.dumi/theme/common/ChangeModal.tsx(1 hunks)package.json(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- package.json
🧰 Additional context used
🪛 Biome (1.9.4)
.dumi/theme/common/ChangeModal.tsx
[error] 153-153: This type annotation is trivially inferred from its initialization.
Safe fix: Remove the type annotation.
(lint/style/noInferrableTypes)
🪛 ESLint
.dumi/theme/common/ChangeModal.tsx
[error] 108-108: ResizeObserver is not supported in op_mini all, KaiOS 2.5
(compat/compat)
[error] 155-159: fetch is not supported in op_mini all
(compat/compat)
⏰ Context from checks skipped due to timeout of 90000ms (12)
- GitHub Check: test-react-legacy (17, 2/2)
- GitHub Check: test-react-legacy (17, 1/2)
- GitHub Check: test-react-legacy (16, 2/2)
- GitHub Check: test-react-legacy (16, 1/2)
- GitHub Check: test-react-latest (dom, 2/2)
- GitHub Check: test-react-latest (dom, 1/2)
- GitHub Check: build
- GitHub Check: visual-diff snapshot (2/2)
- GitHub Check: build preview
- GitHub Check: visual-diff snapshot (1/2)
- GitHub Check: build
- GitHub Check: WIP
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (4)
.dumi/theme/common/ChangeModal.tsx (4)
141-144:⚠️ Potential issue在 handleClose 中添加 localStorage 环境检查
与
getLastVisitedVersion函数类似,handleClose中也应先检查 localStorage 是否可用。function handleClose() { + if (typeof window === 'undefined' || !window.localStorage) { + updateOpen(false); + return; + } - localStorage.setItem(STORAGE_KEY, currentVersion); + window.localStorage.setItem(STORAGE_KEY, currentVersion); updateOpen(false); }
21-29:⚠️ Potential issue注意避免直接访问 localStorage,增加 SSR 环境判断
该函数直接访问
localStorage,会在服务端渲染 (SSR) 环境下导致错误。需要先检查运行环境。function getLastVisitedVersion() { + if (typeof window === 'undefined' || !window.localStorage) { + return '0.0.0'; // SSR 环境下返回默认值 + } const fallbackVersion = '1.0.0'; - const lastVisitedVersion = localStorage.getItem(STORAGE_KEY); + const lastVisitedVersion = window.localStorage.getItem(STORAGE_KEY); if (lastVisitedVersion && VER_REGEX.test(lastVisitedVersion)) { return lastVisitedVersion; } - localStorage.setItem(STORAGE_KEY, fallbackVersion); + window.localStorage.setItem(STORAGE_KEY, fallbackVersion); return fallbackVersion; }
190-211:⚠️ Potential issue优化 useEffect 的依赖和事件监听
该 useEffect 中使用了
checkVersion函数但未将其添加到依赖数组中,可能导致闭包问题。React.useEffect(() => { let timer: NodeJS.Timeout | null = null; let isLoadListenerAttached = false; function showModal() { timer = setTimeout(() => { checkVersion().then(updateOpen); }, 1000); } if (typeof window !== 'undefined') { if (document.readyState === 'complete') { showModal(); } else { isLoadListenerAttached = true; window.addEventListener('load', showModal); } } return function cleanup() { isLoadListenerAttached && window.removeEventListener('load', showModal); timer && clearTimeout(timer); }; - }, []); + }, [checkVersion]);
221-228:⚠️ Potential issue修正 Modal 样式属性,避免使用不支持的 styles 配置
Ant Design Modal 组件不支持
styles属性,应使用官方提供的maskStyle和bodyStyle。- styles={{ - mask: { - backdropFilter: 'blur(2px)', - }, - body: { - padding: 0, - }, - }} + maskStyle={{ backdropFilter: 'blur(2px)' }} + bodyStyle={{ padding: 0 }}
🧹 Nitpick comments (2)
.dumi/theme/common/ChangeModal.tsx (2)
90-98: 考虑添加 useCallback 并处理依赖关系
debounce创建的函数在每次渲染时都会重新创建,应使用useCallback包装并添加正确的依赖项。const checkOverflow = React.useMemo( () => debounce(() => { const hasOverflow = containerRef.current?.scrollHeight! > containerRef.current?.clientHeight!; setIsOverflowing(hasOverflow); - }), + }, 200), [], );此外,建议考虑使用
useCallback替代useMemo来创建函数,更符合 React hooks 最佳实践。🧰 Tools
🪛 GitHub Check: CodeFactor
[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)
[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)
238-243: 优化中国镜像跳转逻辑当前跳转方式会导致页面完全刷新,建议考虑使用 React Router 的方式导航,保持应用状态。
- window.location.href = window.location.href.replace( - window.location.host, - 'ant-design.antgroup.com', - ); + // 使用正则替换域名部分,保留路径和查询参数 + const newUrl = window.location.href.replace( + /^(https?:\/\/)([^\/]+)(\/.*)?$/, + '$1ant-design.antgroup.com$3' + ); + window.location.href = newUrl;如果确实需要保持站点状态,则可以考虑:
// 如果使用 react-router,可以尝试: const { pathname, search } = window.location; navigate(`https://ant-design.antgroup.com${pathname}${search}`);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
.dumi/theme/common/ChangeModal.tsx(1 hunks)
🧰 Additional context used
🪛 GitHub Check: CodeFactor
.dumi/theme/common/ChangeModal.tsx
[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)
[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)
⏰ Context from checks skipped due to timeout of 90000ms (10)
- GitHub Check: test-react-legacy (17, 2/2)
- GitHub Check: test-react-legacy (17, 1/2)
- GitHub Check: test-react-legacy (16, 2/2)
- GitHub Check: test-react-legacy (16, 1/2)
- GitHub Check: test-react-latest (dom, 2/2)
- GitHub Check: test-react-latest (dom, 1/2)
- GitHub Check: visual-diff snapshot (2/2)
- GitHub Check: visual-diff snapshot (1/2)
- GitHub Check: build preview
- GitHub Check: WIP
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (7)
.dumi/theme/common/ChangeModal.tsx (7)
159-180: 添加 fetch API 的兼容性处理
fetchAPI 在某些旧版浏览器中不被支持,应考虑添加 polyfill 或回退到 XMLHttpRequest。try { // eslint-disable-next-line compat/compat - await fetch( - isChineseMirror - ? 'https://registry.npmmirror.com/antd/latest' - : 'https://registry.npmjs.org/antd/latest', - ) + const fetchUrl = isChineseMirror + ? 'https://registry.npmmirror.com/antd/latest' + : 'https://registry.npmjs.org/antd/latest'; + + let response; + // 检查是否支持 fetch + if (typeof fetch !== 'undefined') { + response = await fetch(fetchUrl); + if (response.ok) { + const data = await response.json(); + if (typeof data?.version === 'string' && VER_REGEX.test(data.version)) { + lastVersion = data.version; + } + } + } else { + // 回退到 XMLHttpRequest + const data = await new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', fetchUrl); + xhr.onload = () => { + if (xhr.status >= 200 && xhr.status < 300) { + try { + resolve(JSON.parse(xhr.responseText)); + } catch (e) { + reject(e); + } + } else { + reject(new Error(`请求失败,状态码: ${xhr.status}`)); + } + }; + xhr.onerror = () => reject(new Error('网络请求失败')); + xhr.send(); + }); + + if (typeof data?.version === 'string' && VER_REGEX.test(data.version)) { + lastVersion = data.version; + } + } - .then((res) => { - if (res.ok) return res.json(); - throw new Error('Network response was not ok'); - }) - .then((data) => { - if (typeof data?.version === 'string' && VER_REGEX.test(data.version)) { - lastVersion = data.version; - } - }); } catch { lastVersion = '1.0.0'; }
19-27:⚠️ Potential issue
⚠️ localStorage 访问需要 SSR 兼容处理
getLastVisitedVersion函数直接访问localStorage,但在服务端渲染 (SSR) 环境中,localStorage不存在,会导致运行时错误。建议添加环境检测:
function getLastVisitedVersion() { + if (typeof window === 'undefined' || !window.localStorage) { + return '0.0.0'; // SSR 环境下返回一个安全的默认值 + } const fallbackVersion = '1.0.0'; - const lastVisitedVersion = localStorage.getItem(STORAGE_KEY); + const lastVisitedVersion = window.localStorage.getItem(STORAGE_KEY); if (lastVisitedVersion && VER_REGEX.test(lastVisitedVersion)) { return lastVisitedVersion; } - localStorage.setItem(STORAGE_KEY, fallbackVersion); + window.localStorage.setItem(STORAGE_KEY, fallbackVersion); return fallbackVersion; }
91-94:⚠️ Potential issue
⚠️ 修正非空断言在可选链后的使用在可选链 (
?.) 后使用非空断言 (!) 是不安全的,因为可选链可能返回undefined。const checkOverflow = React.useMemo( () => debounce(() => { - const hasOverflow = - containerRef.current?.scrollHeight! > containerRef.current?.clientHeight!; + const container = containerRef.current; + const hasOverflow = container + ? container.scrollHeight > container.clientHeight + : false; setIsOverflowing(hasOverflow); }), [], );🧰 Tools
🪛 GitHub Check: CodeFactor
[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)
[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)
99-113: 🛠️ Refactor suggestionuseEffect 依赖缺失和 ResizeObserver 兼容性
这个 useEffect 有两个问题:
- 依赖了
checkOverflow但未将其添加到依赖数组- 直接使用
ResizeObserver没有兼容性处理// 检测溢出状态 React.useEffect(() => { const container = containerRef.current; if (!container) return; checkOverflow(); // eslint-disable-next-line compat/compat - const resizeObserver = new ResizeObserver(checkOverflow); - resizeObserver.observe(container); + let resizeObserver: ResizeObserver | null = null; + + // 安全地使用 ResizeObserver + const initObserver = async () => { + try { + // 如果浏览器不支持,尝试加载 polyfill + const ResizeObserverConstructor = window.ResizeObserver || + (await import('resize-observer-polyfill')).default; + + resizeObserver = new ResizeObserverConstructor(checkOverflow); + resizeObserver.observe(container); + } catch (e) { + console.error('ResizeObserver 初始化失败', e); + // 回退方案:使用窗口 resize 事件 + window.addEventListener('resize', checkOverflow); + } + }; + + if (typeof window !== 'undefined') { + initObserver(); + } return function cleanup() { - resizeObserver.disconnect(); + if (resizeObserver) { + resizeObserver.disconnect(); + } else if (typeof window !== 'undefined') { + window.removeEventListener('resize', checkOverflow); + } checkOverflow.cancel(); }; -}, []); +}, [checkOverflow]);
141-144:⚠️ Potential issuehandleClose 函数中的 localStorage 访问也需要 SSR 保护
与
getLastVisitedVersion函数类似,handleClose函数中的 localStorage 访问也需要添加环境检测。function handleClose() { + if (typeof window === 'undefined' || !window.localStorage) { + updateOpen(false); + return; + } - localStorage.setItem(STORAGE_KEY, currentVersion); + window.localStorage.setItem(STORAGE_KEY, currentVersion); updateOpen(false); }
195-216: 🛠️ Refactor suggestionuseEffect 依赖项缺失和事件处理优化
此
useEffect依赖了checkVersion但未将其添加到依赖数组,并且可以改进事件监听逻辑:React.useEffect(() => { let timer: NodeJS.Timeout | null = null; let isLoadListenerAttached = false; function showModal() { timer = setTimeout(() => { checkVersion().then(updateOpen); }, 1000); } if (typeof window !== 'undefined') { if (document.readyState === 'complete') { showModal(); } else { isLoadListenerAttached = true; window.addEventListener('load', showModal); } } return function cleanup() { isLoadListenerAttached && window.removeEventListener('load', showModal); timer && clearTimeout(timer); }; -}, []); +}, [checkVersion]);
226-233:⚠️ Potential issue修正 Modal 样式属性使用
Ant Design 的
Modal组件不支持styles属性,应使用官方提供的maskStyle和bodyStyle:<Modal title={[locale.title, ' 🎉']} open={open} width={`min(90vw, 800px)`} centered onCancel={handleClose} onOk={gettingStarted} - styles={{ - mask: { - backdropFilter: 'blur(2px)', - }, - body: { - padding: 0, - }, - }} + maskStyle={{ backdropFilter: 'blur(2px)' }} + bodyStyle={{ padding: 0 }} okText={locale.gettingStarted} footer={(_, { OkBtn }) => (
🧹 Nitpick comments (3)
.dumi/theme/common/ChangeModal.tsx (3)
96-96: debounce 函数缺少延迟参数
debounce函数没有指定延迟时间,会使用默认的 0ms。这实际上只是将执行推迟到下一个事件循环,而不是真正的防抖动。const checkOverflow = React.useMemo( () => - debounce(() => { + debounce(() => { // ...检测逻辑 - }), + }, 100), // 添加合理的延迟时间,例如 100ms [], );
244-247: 优化导航处理方式当前代码直接修改
window.location.href进行导航,这会导致整个页面重新加载。由于组件已经有navigate函数,建议使用 React Router 的方式进行导航:onClick={() => { - window.location.href = window.location.href.replace( - window.location.host, - 'ant-design.antgroup.com', - ); + const newHost = 'ant-design.antgroup.com'; + const newPathname = window.location.pathname; + const newSearch = window.location.search; + + // 如果是在同一应用内导航,优先使用 navigate + if (typeof navigate === 'function') { + // 保存目标站点到会话存储 + sessionStorage.setItem('redirect_to_mirror', 'true'); + // 使用 navigate 重定向到中转页面 + navigate('/mirror-redirect'); + } else { + // 回退方案:直接修改 location + window.location.href = `${window.location.protocol}//${newHost}${newPathname}${newSearch}`; + } }}然后创建一个中转页面组件
MirrorRedirect,在useEffect中处理实际跳转。
1-270: 总体评价:功能完整但需优化兼容性此组件实现了版本更新通知的功能,并集成了国内镜像站点推荐,设计上考虑周全。但在代码实现上,主要存在以下几类问题需要解决:
- SSR 兼容性:多处直接访问浏览器 API(localStorage、window、navigator 等)需要增加环境检测
- 浏览器兼容性:ResizeObserver 和 fetch API 在旧浏览器中不支持,需要添加 polyfill
- TypeScript 安全性:修复可选链后的非空断言
- React 最佳实践:完善 useEffect 依赖、优化 Modal 属性使用
建议添加单元测试,并考虑将核心逻辑(如版本比较、数据获取)提取到单独的 hooks 中以提高可测试性和可维护性。
🧰 Tools
🪛 GitHub Check: CodeFactor
[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)
[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.dumi/theme/common/ChangeModal.tsx(1 hunks)package.json(2 hunks)typings/custom-typings.d.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- package.json
🧰 Additional context used
🪛 GitHub Check: CodeFactor
.dumi/theme/common/ChangeModal.tsx
[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)
[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)
⏰ Context from checks skipped due to timeout of 90000ms (11)
- GitHub Check: test-react-latest (dom, 2/2)
- GitHub Check: test-react-latest (dom, 1/2)
- GitHub Check: test-react-legacy (17, 2/2)
- GitHub Check: build
- GitHub Check: test-react-legacy (17, 1/2)
- GitHub Check: test-react-legacy (16, 2/2)
- GitHub Check: visual-diff snapshot (2/2)
- GitHub Check: test-react-legacy (16, 1/2)
- GitHub Check: visual-diff snapshot (1/2)
- GitHub Check: build preview
- GitHub Check: WIP
🔇 Additional comments (1)
typings/custom-typings.d.ts (1)
29-33: 新增 markdown 文件类型声明这个类型声明允许在 TypeScript 代码中直接导入 Markdown 文件作为 React 组件使用,配合 dumi 的功能支持。这很好地为
ChangeModal组件提供了类型安全的方式来导入和渲染变更日志内容。注意:此声明依赖于 dumi 的 PR(https://github.com/umijs/dumi/pull/2281)合并后才能正常工作。
| import EN from '../../preset/latest-changelog.en-US.md'; | ||
| import CN from '../../preset/latest-changelog.zh-CN.md'; |
There was a problem hiding this comment.
这个是否会大大增加打包体积?Modal 里放个版本提示和直达链接就好,我建议不放 markdown 内容。
There was a problem hiding this comment.
是否会大大增加打包体积?
不会大大, dumi 处理成 html 塞进去的。 就一些标签文本体积




中文版模板 / Chinese template
🤔 This is a ...
🔗 Related Issues
需要等 dumi 支持后才能生效:umijs/dumi#2281
background: #53759
💡 Background and Solution
内容过长时,添加一个遮罩 (如下图

内容较少时

📝 Change Log