news 2026/9/13 20:33:13

@react-email/render 渲染引擎演进全解:从 render 到纯文本转换的完整实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
@react-email/render 渲染引擎演进全解:从 render 到纯文本转换的完整实践指南

@react-email/render 渲染引擎演进全解:从 render 到纯文本转换的完整实践指南

【免费下载链接】react-email💌 Build and send emails using React项目地址: https://gitcode.com/GitHub_Trending/re/react-email

@react-email/render是 React Email 生态中将 React 组件转换为可用于真实发送的 HTML 邮件模板的核心包。本文以该包在仓库中的 CHANGELOG.md 为主线,结合 packages/render 下的源码、测试与配置,系统梳理render函数的异步化演进、运行时分支、纯文本转换能力、输出后处理(preload 剥离与格式化)以及多字节字符处理等关键技术点,帮助你在 Next.js、Node.js、Edge 与浏览器等不同环境中正确使用并理解其底层行为。

一、包概览:一个包,三种运行时

@react-email/render的核心职责只有一个:把 React 组件渲染成邮件 HTML 字符串。在 package.json 中可以看到它面向不同运行时暴露了不同的构建产物:

  • node:Node.js 服务端渲染(默认导出);
  • edge:Edge Runtime(含workerdedge-lightconvex条件导出);
  • browser:浏览器端与 Deno、Worker 环境。

此外还通过exports字段中的denoworkerbrowser等条件精确控制模块解析。其中convex条件在 2.0.7 中单独修复了与node条件的导出顺序问题(见 CHANGELOG 2.0.7),并在 1.2.3 中改为在 Convex 运行时使用 edge 导出,这些细节说明该包对不同部署平台的兼容性做了专门打磨。

版本要求方面:engines.node要求>=20.0.0peerDependencies声明reactreact-dom^18.0 || ^19.0 || ^19.0.0-rc,即支持 React 18 与 React 19(含 RC)。从 CHANGELOG 可以看到,对 React 19 的兼容从 1.0.0 起便通过放宽 peer 依赖逐步引入。

二、核心 API:render 的异步化演进

2.1 从同步到异步:1.0.0 的重大变更

CHANGELOG 中 1.0.0 版本标记为Major Changes,将render从同步 API 改为始终返回 Promise,并同时弃用了renderAsync。CHANGELOG 给出的理由有三点:

  1. 更好地支持 Next.js 最新版本;
  2. 为未来 React API 的弃用做准备;
  3. 支持 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): Promise<string>

其内部实现要点如下:

  • 动态导入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.tsx),1.0.1 引入包裹 Suspense,1.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。它同时处理两类流:

  • 可读流(pipeToWritableStream);
  • 管道流(pipe到 NodeWritable)。

关键实现是使用单一TextDecoder实例并以{ stream: true }模式解码,这是针对多字节字符(如 CJK、emoji 等高密度字符)在流分块时被截断问题的核心修复,对应 CHANGELOG 中的多项记录:

  • 1.0.2:Fix null characters in between chunks when using high-density characters
  • 1.3.1:fixed multi-byte characters causing problems during stream reading
  • 2.0.8:Strip 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 格式化输出 HTML(1.1.0 起弃用render上的 pretty 选项,改为独立的pretty函数,此处保留为便捷开关)
plainTextbooleanfalse是否返回纯文本而非 HTML
htmlToTextOptionsHtmlToTextOptions见下透传给html-to-text库的选项,仅当plainText: trueunstableTextConversionfalse时有效
unstableTextConversionbooleanfalse2.1.0 新增;为true时使用包内自研的纯文本格式化器,忽略htmlToTextOptions

类型层面通过联合类型约束了合法组合:plainText: false时不允许再传htmlToTextOptionsunstableTextConversionunstableTextConversion: 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-text=true]', format: 'skip' }, // 显式标记跳过 { selector: 'a', options: { linkBrackets: false, hideLinkHrefIfSameAsText: true } }, { selector: '[data-text-format="dataTable"]', format: 'dataTable' }, ];

对应 CHANGELOG 与测试(to-plain-text.spec.ts)可以确认以下行为:

  • imgalt文本默认不进入纯文本;
  • 任何元素可通过data-skip-in-text="true"属性在纯文本中隐藏;
  • 链接不带方括号包裹(linkBrackets: false),且当链接文字与 href 相同时只保留一份(hideLinkHrefIfSameAsText);
  • 1.3.0 修复了纯文本模式下链接重复输出的问题;
  • 1.4.0 起默认关闭自动换行(wordwrap: false);
  • 2.1.0 新增data-text-format="dataTable":渲染为对齐的数据表格列,其行为由html-to-textdataTableformat 提供(测试用例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-dom(18 或 19)。

    7.2 基础渲染

    import { MyTemplate } from "../components/MyTemplate"; import { render } from "@react-email/render"; // render 始终返回 Promise,必须 await const html = await render(<MyTemplate firstName="Jim" />);

    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,直接替换为renderawait即可;当前版本已彻底移除该 API;
    • 多字节内容:包含中文、日文、emoji 等内容的邮件无需特殊处理,流读取与\0剥离逻辑已内置处理;
    • 纯文本表格:希望纯文本中以对齐列呈现的表格,给<table>加上data-text-format="dataTable"属性;
    • 隐藏内容:不希望出现在纯文本中的元素(如装饰性图片)添加data-skip-in-text="true",图片本身默认即被跳过;
    • 选择器的取舍toPlainText的稳定 API 与html-to-text生态兼容性更好;unstableToPlainText减少了第三方依赖体积,但行为仍在演进,投入生产前请结合自身模板验证输出。

    结语

    从 1.0.0 的render异步化,到 2.1.0 引入自研纯文本转换器,@react-email/render的 CHANGELOG 记录了一条清晰的技术演进路线:围绕异步流式渲染、跨运行时兼容、多字节安全与输出净化持续打磨。理解这些变更背后的动机与实现,能帮助你在使用rendertoPlainTextpretty等 API 时做出更合理的技术选型,也能在遇到渲染异常时更快定位问题根因。更多组件与用法可参考仓库根目录的 README.md 及 packages/react-email 的相关文档。

    【免费下载链接】react-email💌 Build and send emails using React项目地址: https://gitcode.com/GitHub_Trending/re/react-email

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 20:27:25

数据库三大范式详解:从函数依赖到反范式设计实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 20:25:21

lucide 原生 JS 图标在 Web Components 的 Shadow DOM 中如何渲染?

lucide 原生 JS 图标在 Web Components 的 Shadow DOM 中如何渲染&#xff1f; 【免费下载链接】lucide Beautiful & consistent icon toolkit made by the community. Open-source project and a fork of Feather Icons. 项目地址: https://gitcode.com/GitHub_Trending…

作者头像 李华
网站建设 2026/9/13 20:21:04

2012-2015老Mac装最新macOS:OpenCore Legacy Patcher手把手教程

2012-2015老Mac装最新macOS&#xff1a;OpenCore Legacy Patcher手把手教程 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 2012 到 2015 年买的 MacBook Pro…

作者头像 李华
网站建设 2026/9/13 20:20:44

多传感器融合定位:从传感器特性到工程落地的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华