Conversation
Speed up the simulated SSE stream player used by both alipay/wechat component + JS demo pages: default chunkSize 1→4, interval 120→30ms, and newline/punctuation pauses +180/+100 → +40/+20. The demo now streams at a realistic LLM cadence instead of a slow per-character crawl. Both integration pages inherit it (neither passes overrides). Update the two characterization tests that locked the old "one char / 120ms" contract to assert the new brisk defaults. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…flicker Real-device (Minifish 2.0 consumer) findings: the package's local /katex-fonts/*.ttf paths and base64 data URIs do NOT resolve on Alipay真机 in a real consumer app (they only worked in this repo's own examples, which sync katex-fonts to the project root). loadFontFace on device only accepts a whitelisted https network font. loadKatexFonts.ts: - Add shared KATEX_FONT_CDN map (20 mdn.alipayobjects.com ttf URLs). - Alipay branch = CDN ttf only; drop ALIPAY_LOCAL_BASES, loadInlineFontData and the base64 katex-font-data.js generator (smaller package). - WeChat keeps local-first (its /miniprogram_npm/.../katex-fonts is a real resolvable 构建npm path) + shared CDN fallback. - Replace loadKatexFonts(onReady) with ensureKatexFonts() / onKatexFontsReady() (fires at most once, only when not-yet-ready) / areKatexFontsReady(). Flicker: Alipay Markdown/MiniNodeRenderer now reflow at most once, gated on idle (skip when no formula / fonts already ready / mid-stream), instead of re-registering a reflow on every streamed chunk — kills the whole-page flash during streaming. Add ensureKatexFonts() to WeChat MiniNodeRenderer for JS-接入 parity (rich-text resolves KaTeX fonts via loadFontFace). Build: externalize loadKatexFonts.js as dist/shared/loadKatexFonts.js (like flattenInline.js) so the 11KB loader is shipped once per root, not inlined into every component wrapper. Ship ttf to miniprogram_dist only. exports: add no-extension subpath maps ./es/Markdown/index and ./es/MiniNodeRenderer/index → .js so Minifish (which honors package.json exports; Node's * glob won't auto-append extensions) can load <markdown>. Consumers must whitelist the font CDN domain in 小程序下载合法域名. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The streaming text animation splits a text leaf into per-character <text>
boxes. For a list marker ("1." / "•") this created a line-break opportunity
between "1" and ".", and since .md-list-marker is a 28rpx-wide flex item,
the two char boxes overflowed and wrapped to separate lines.
Markers are structural atomic text, not typed content, so exclude them from
the per-char animation branch (gate on !u.isMarker(node)) — they now render
as a single <text>, matching WeChat's atomic isListItem marker rendering.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Free Run ID: 📒 Files selected for processing (18)
📝 WalkthroughWalkthrough本次变更重构了 KaTeX 字体加载机制,从内联 base64/本地字体方案改为基于 CDN 映射与就绪回调(ensureKatexFonts/areKatexFontsReady/onKatexFontsReady)的方案,并为支付宝/微信 Markdown 与 MiniNodeRenderer 组件引入重排门控逻辑;同时新增列表 marker 动画例外、调整流式播放默认节奏、更新构建脚本与 exports 映射。 ChangesKaTeX 字体加载重构
列表 Marker 动画例外处理
流式播放默认节奏调整
Exports 映射补充
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant MarkdownComponent as Markdown 组件
participant loadKatexFonts as loadKatexFonts.ts
participant Platform as my/wx loadFontFace
MarkdownComponent->>loadKatexFonts: ensureKatexFonts()
loadKatexFonts->>Platform: resolveApi() + startLoading()
Platform-->>loadKatexFonts: 所有字体加载完成
loadKatexFonts->>loadKatexFonts: flushReadyCallbacks()
MarkdownComponent->>MarkdownComponent: onPatch -> _maybeKatexReflow()
alt 字体已就绪且非流式
MarkdownComponent->>MarkdownComponent: _katexFontReflow() 立即重排
else 未就绪
MarkdownComponent->>loadKatexFonts: onKatexFontsReady(cb)
loadKatexFonts-->>MarkdownComponent: 就绪后回调触发重排(仅一次)
end
Poem
Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login. Comment |
There was a problem hiding this comment.
Code Review
This pull request optimizes KaTeX font loading and streaming animations for Alipay and WeChat mini-programs. Key changes include switching Alipay to a whitelist of HTTPS CDN TTF fonts to resolve real-device rendering issues, removing base64 font data modules to reduce package size, and implementing a once-only, idle-gated reflow mechanism to eliminate page flickering during streaming. Additionally, it introduces explicit subpath mappings in package.json for better module resolution and prevents list markers from being split across lines during animations. The review feedback recommends executing the callback immediately in onKatexFontsReady if fonts are already loaded to avoid silent failures, and adding Array.isArray checks within the hasKatexNodes helper functions to prevent potential runtime errors.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| export function onKatexFontsReady(cb: () => void): void { | ||
| ensureKatexFonts(); | ||
| if (ready) return; | ||
| readyCallbacks.push(cb); | ||
| } |
There was a problem hiding this comment.
If onKatexFontsReady is called when ready is already true, the callback is currently ignored and never executed. This violates the standard contract of 'onReady' style APIs, where listeners registered after the resource is ready should be executed immediately.
While the current components check areKatexFontsReady() before calling this, any future callers or third-party integrations that do not perform this check will experience silent failures where their callbacks are never executed.
Additionally, the flicker prevention is already fully handled by the guards inside _maybeKatexReflow (checking this._katexReflowed and areKatexFontsReady()), so making onKatexFontsReady return early without calling the callback is redundant for flicker prevention and introduces a major API footgun.
Executing the callback immediately when already ready makes the API robust and standard.
| export function onKatexFontsReady(cb: () => void): void { | |
| ensureKatexFonts(); | |
| if (ready) return; | |
| readyCallbacks.push(cb); | |
| } | |
| export function onKatexFontsReady(cb: () => void): void { | |
| ensureKatexFonts(); | |
| if (ready) { | |
| callSafe(cb); | |
| return; | |
| } | |
| readyCallbacks.push(cb); | |
| } |
| function hasKatexNodes(nodes: any[] | undefined): boolean { | ||
| return !!nodes && nodes.some((node: any) => | ||
| !!node && (String((node.attrs || {}).class || '').indexOf('katex') > -1 || | ||
| hasKatexNodes(node.children))); | ||
| } |
There was a problem hiding this comment.
To prevent potential runtime errors, it is safer to verify that nodes is actually an array using Array.isArray(nodes) before calling .some(). If node.children is ever parsed as a non-array object or other unexpected type, calling .some() directly on it would throw a TypeError.
| function hasKatexNodes(nodes: any[] | undefined): boolean { | |
| return !!nodes && nodes.some((node: any) => | |
| !!node && (String((node.attrs || {}).class || '').indexOf('katex') > -1 || | |
| hasKatexNodes(node.children))); | |
| } | |
| function hasKatexNodes(nodes: any[] | undefined): boolean { | |
| return !!nodes && Array.isArray(nodes) && nodes.some((node: any) => | |
| !!node && (String((node.attrs || {}).class || '').indexOf('katex') > -1 || | |
| hasKatexNodes(node.children))); | |
| } |
| function hasKatexNodes(nodes: any[] | undefined): boolean { | ||
| return !!nodes && nodes.some((node: any) => | ||
| !!node && (String((node.attrs || {}).class || '').indexOf('katex') > -1 || | ||
| hasKatexNodes(node.children))); | ||
| } |
There was a problem hiding this comment.
To prevent potential runtime errors, it is safer to verify that nodes is actually an array using Array.isArray(nodes) before calling .some(). If node.children is ever parsed as a non-array object or other unexpected type, calling .some() directly on it would throw a TypeError.
| function hasKatexNodes(nodes: any[] | undefined): boolean { | |
| return !!nodes && nodes.some((node: any) => | |
| !!node && (String((node.attrs || {}).class || '').indexOf('katex') > -1 || | |
| hasKatexNodes(node.children))); | |
| } | |
| function hasKatexNodes(nodes: any[] | undefined): boolean { | |
| return !!nodes && Array.isArray(nodes) && nodes.some((node: any) => | |
| !!node && (String((node.attrs || {}).class || '').indexOf('katex') > -1 || | |
| hasKatexNodes(node.children))); | |
| } |
🤔 This is a ...
🔗 Related Issues
Surfaced while integrating
@ant-design/x-markdown-mini@1.0.1into a real Minifish 2.0 consumer (appId2021006167675338) and testing KaTeX on the Alipay real device.💡 Background and Solution
Four issues, all exposed only on real devices / real consumers (invisible in the simulator or in this repo's own examples):
Alipay real-device KaTeX rendered in the system font (PingFang SC), not KaTeX. The loader tried package-local
/katex-fonts/*.ttfpaths and base64data:URIs first — but on a real devicemy.loadFontFaceonly accepts a whitelisted https network font, and local/base64 paths don't resolve in a real consumer bundle (they only worked because this repo's ownexamples/synckatex-fontsto the project root). Fix: ship a sharedKATEX_FONT_CDNmap (20mdn.alipayobjects.comttf URLs, CORS*), and make the Alipay branch load CDN ttf only. WeChat keeps its local-first (/miniprogram_npm/.../katex-fontsis a real 构建npm path) + CDN fallback. Consumers must add the font CDN domain to their mini-program 下载合法域名.Whole-page flicker while streaming formulas.
MiniNodeRenderer.didUpdatere-registered a font-ready reflow on every streamed chunk, and the ready callback fired synchronously once fonts were cached — so the root renderer unmounted/remounted the entire tree every chunk. Fix:ensureKatexFonts()/onKatexFontsReady()(fires at most once, only when not-yet-ready) /areKatexFontsReady(); components reflow at most once, idle-gated (skip when no formula / fonts ready / mid-stream).List markers split across lines while streaming. Per-char streaming animation broke
1.into two<text>boxes; inside the 28rpx-wide.md-list-markerflex item they wrapped. Fix: exclude markers from the per-char animation branch (render atomically, matching WeChat).<markdown>failed to load under Minifish. Minifish honorspackage.json#exports, and Node's*subpath glob doesn't auto-append extensions, so.../es/Markdown/index(no extension, asusingComponentsrequires) didn't resolve. Fix: add explicit./es/Markdown/index+./es/MiniNodeRenderer/index→.jsmaps.Also: the loader is now externalized to
dist/shared/loadKatexFonts.js(shipped once per package root, likeflattenInline.js) instead of inlined into every component wrapper; the base64katex-font-data.jsgenerator and the Alipay-root ttf copy are removed (smaller package). Demo streaming pace was sped up to a realistic LLM cadence (defaultchunkSize1→4,interval120→30ms).Version bump + changelog + republishing the WeChat example fixture are intentionally left to the separate release step.
📝 Change Log
mdn.alipayobjects.com) to your mini-program download allowlist. Fixed whole-page flicker and split list markers while streaming formulas. Added./es/Markdown/indexand./es/MiniNodeRenderer/indexexport subpaths so bundlers that honorpackage.json#exports(e.g. Minifish) can resolve the components. Smaller package (removed inlined base64 fonts; loader shipped once per root).mdn.alipayobjects.com)。修复流式渲染公式时整页闪动、以及列表序号(如1.)换行的问题。新增./es/Markdown/index与./es/MiniNodeRenderer/index导出子路径,使尊重package.json#exports的构建工具(如 Minifish)能正确解析组件。包体减小(移除内联 base64 字体;loader 每个包根仅发一份)。Summary by CodeRabbit
新功能
Bug 修复