@react-email/render 渲染引擎演进全解:从 render 到纯文本转换的完整实践指南 📅 发布时间:2026/9/13 20:33:15 👁 浏览次数: react-email/render 渲染引擎演进全解从 render 到纯文本转换的完整实践指南【免费下载链接】react-email Build and send emails using React项目地址: https://gitcode.com/GitHub_Trending/re/react-emailreact-email/render是 React Email 生态中将 React 组件转换为可用于真实发送的 HTML 邮件模板的核心包。本文以该包在仓库中的 CHANGELOG.md 为主线结合 packages/render 下的源码、测试与配置系统梳理render函数的异步化演进、运行时分支、纯文本转换能力、输出后处理preload 剥离与格式化以及多字节字符处理等关键技术点帮助你在 Next.js、Node.js、Edge 与浏览器等不同环境中正确使用并理解其底层行为。一、包概览一个包三种运行时react-email/render的核心职责只有一个把 React 组件渲染成邮件 HTML 字符串。在 package.json 中可以看到它面向不同运行时暴露了不同的构建产物nodeNode.js 服务端渲染默认导出edgeEdge Runtime含workerd、edge-light、convex条件导出browser浏览器端与 Deno、Worker 环境。此外还通过exports字段中的deno、worker、browser等条件精确控制模块解析。其中convex条件在 2.0.7 中单独修复了与node条件的导出顺序问题见 CHANGELOG 2.0.7并在 1.2.3 中改为在 Convex 运行时使用 edge 导出这些细节说明该包对不同部署平台的兼容性做了专门打磨。版本要求方面engines.node要求20.0.0peerDependencies声明react与react-dom为^18.0 || ^19.0 || ^19.0.0-rc即支持 React 18 与 React 19含 RC。从 CHANGELOG 可以看到对 React 19 的兼容从 1.0.0 起便通过放宽 peer 依赖逐步引入。二、核心 APIrender 的异步化演进2.1 从同步到异步1.0.0 的重大变更CHANGELOG 中 1.0.0 版本标记为Major Changes将render从同步 API 改为始终返回 Promise并同时弃用了renderAsync。CHANGELOG 给出的理由有三点更好地支持 Next.js 最新版本为未来 React API 的弃用做准备支持 Suspense从而允许在组件内部使用 async 能力。对升级用户而言迁移成本被刻意压低旧render的调用需要await结果而原来使用renderAsync的代码可以直接替换为render属于 drop-in 替换。2.0.0 则正式移除了已弃用的renderAsync并顺带清理了不再使用的react-promise-suspense依赖API 表面收敛为单一的render。2.2 现代 render 的实现路径在 src/node/render.tsx 中render的签名是export const render async (node: React.ReactNode, options?: Options): Promisestring其内部实现要点如下动态导入react-dom/server通过import(react-dom/server)并做m.default回退处理对应 2.0.1 的add fallback for m.default修复兼容不同打包器与模块格式优先使用renderToReadableStream当宿主环境存在WritableStream时走可读流路径先await stream.allReady再读取完整输出对应 2.0.6 的await stream.allReady before reading renderToReadableStream output修复回退到renderToPipeableStream在WritableStream不可用的环境如部分 Node 版本或老式容器回退到管道流对应 1.3.2 的fallback to renderToPipeableSream修复包裹 Suspense 与 ErrorBoundary所有模板在渲染前被包进Suspense与自建的 ErrorBoundary见 src/shared/error-boundary.tsx1.0.1 引入包裹 Suspense1.0.5 之前一系列修复解决了错误被吞掉、错误被写进输出、甚至进入客户端渲染CSR导致邮件静默损坏的问题progressiveChunkSize: Number.POSITIVE_INFINITY让 React 一次输出完整文档避免渐进式刷新产生的分块噪声onError立即 reject防止错误发生时 React 退化为 CSR 回退确保错误被抛出而不是被吞进 HTML对应 2.0.3、2.0.5 的修复。2.3 流读取与多字节字符处理渲染输出的流式读取集中在 src/node/read-stream.ts。它同时处理两类流可读流pipeTo到WritableStream管道流pipe到 NodeWritable。关键实现是使用单一TextDecoder实例并以{ stream: true }模式解码这是针对多字节字符如 CJK、emoji 等高密度字符在流分块时被截断问题的核心修复对应 CHANGELOG 中的多项记录1.0.2Fix null characters in between chunks when using high-density characters1.3.1fixed multi-byte characters causing problems during stream reading2.0.8Strip nul bytes from React 18 renderToPipeableStream output to prevent emails with multi-byte characters from being truncated。renderToPipeableStream路径还会额外执行replaceAll(\0, )这是对 React 18 已知问题facebook/react#26228。三、Options 配置全解render的第二个参数options类型定义在 src/shared/options.ts 中是理解全部可选行为的钥匙选项类型默认值说明prettybooleanfalse是否用 Prettier 格式化输出 HTML1.1.0 起弃用render上的 pretty 选项改为独立的pretty函数此处保留为便捷开关plainTextbooleanfalse是否返回纯文本而非 HTMLhtmlToTextOptionsHtmlToTextOptions见下透传给html-to-text库的选项仅当plainText: true且unstableTextConversion为false时有效unstableTextConversionbooleanfalse2.1.0 新增为true时使用包内自研的纯文本格式化器忽略htmlToTextOptions类型层面通过联合类型约束了合法组合plainText: false时不允许再传htmlToTextOptions或unstableTextConversionunstableTextConversion: true时不再接受htmlToTextOptions。在 src/node/render.tsx 中render的返回逻辑按以下顺序处理if (options?.plainText) { return options.unstableTextConversion ? unstableToPlainText(html) : toPlainText(html, options.htmlToTextOptions); } // 否则拼上 XHTML 1.0 Transitional doctype 后返回 const doctype !DOCTYPE html PUBLIC ...XHTML 1.0 Transitional...; const document ${doctype}${html.replace(/!DOCTYPE.*?/, )}; if (options?.pretty) return pretty(document); return document;注意render输出的 HTML 会自动剥离 React 注入的 doctype并替换为邮件客户端兼容性最好的XHTML 1.0 Transitionaldoctype开启pretty时整个文档含 doctype会经 Prettier 格式化。四、纯文本转换toPlainText 与 unstableToPlainText邮件通常需要同时提供 HTML 与纯文本两种版本纯文本转换因此是render的重要能力。4.1 toPlainText基于 html-to-text 的默认实现src/shared/utils/to-plain-text.ts 基于html-to-text库的convert函数内置了一套默认选择器规则export const plainTextSelectors: SelectorDefinition[] [ { selector: img, format: skip }, // 图片默认跳过 { selector: [data-skip-in-texttrue], format: skip }, // 显式标记跳过 { selector: a, options: { linkBrackets: false, hideLinkHrefIfSameAsText: true } }, { selector: [data-text-formatdataTable], format: dataTable }, ];对应 CHANGELOG 与测试to-plain-text.spec.ts可以确认以下行为img与alt文本默认不进入纯文本任何元素可通过data-skip-in-texttrue属性在纯文本中隐藏链接不带方括号包裹linkBrackets: false且当链接文字与 href 相同时只保留一份hideLinkHrefIfSameAsText1.3.0 修复了纯文本模式下链接重复输出的问题1.4.0 起默认关闭自动换行wordwrap: false2.1.0 新增data-text-formatdataTable渲染为对齐的数据表格列其行为由html-to-text的dataTableformat 提供测试用例should render tables as aligned rows with># npm npm install react-email/render -E # 或 yarn yarn add react-email/render -E要求 Node.js 20且项目中已安装react/react-dom18 或 19。7.2 基础渲染import { MyTemplate } from ../components/MyTemplate; import { render } from react-email/render; // render 始终返回 Promise必须 await const html await render(MyTemplate firstNameJim /);7.3 纯文本与格式化const text await render(MyTemplate /, { plainText: true, // htmlToTextOptions: { wordwrap: 80 }, // 默认路径可用 // unstableTextConversion: true, // 2.1.0 自研转换器 }); const formatted await render(MyTemplate /, { pretty: true }); // 也可以单独使用导出的工具函数 import { toPlainText, pretty } from react-email/render; const text toPlainText(html); const htmlPretty pretty(html);7.4 实践建议迁移注意若你仍在用renderAsync直接替换为render并await即可当前版本已彻底移除该 API多字节内容包含中文、日文、emoji 等内容的邮件无需特殊处理流读取与\0剥离逻辑已内置处理纯文本表格希望纯文本中以对齐列呈现的表格给table加上data-text-formatdataTable属性隐藏内容不希望出现在纯文本中的元素如装饰性图片添加data-skip-in-texttrue图片本身默认即被跳过选择器的取舍toPlainText的稳定 API 与html-to-text生态兼容性更好unstableToPlainText减少了第三方依赖体积但行为仍在演进投入生产前请结合自身模板验证输出。结语从 1.0.0 的render异步化到 2.1.0 引入自研纯文本转换器react-email/render的 CHANGELOG 记录了一条清晰的技术演进路线围绕异步流式渲染、跨运行时兼容、多字节安全与输出净化持续打磨。理解这些变更背后的动机与实现能帮助你在使用render、toPlainText、pretty等 API 时做出更合理的技术选型也能在遇到渲染异常时更快定位问题根因。更多组件与用法可参考仓库根目录的 README.md 及 packages/react-email 的相关文档。【免费下载链接】react-email Build and send emails using React项目地址: https://gitcode.com/GitHub_Trending/re/react-email创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考