diff --git a/.changeset/tall-cameras-cry.md b/.changeset/tall-cameras-cry.md
new file mode 100644
index 0000000000..a839374a8a
--- /dev/null
+++ b/.changeset/tall-cameras-cry.md
@@ -0,0 +1,5 @@
+---
+"@react-email/render": patch
+---
+
+Fix null characters in between chunks when using high-density characters
diff --git a/packages/react-email/next-env.d.ts b/packages/react-email/next-env.d.ts
index 4f11a03dc6..40c3d68096 100644
--- a/packages/react-email/next-env.d.ts
+++ b/packages/react-email/next-env.d.ts
@@ -2,4 +2,4 @@
///
Test Normal 情報Ⅰコース担当者様
平素よりお世話になっております。 情報Ⅰサポートチームです。 情報Ⅰ本講座につきまして仕様変更のためご連絡させていただきました。
今後ジクタス上の講座につきましては、8回分の授業をひとまとまりとしてパート分けされた状態で公開されてまいります。伴いまして、画面上の表示に一部変更がありますのでお知らせいたします。 ご登録いただいた各生徒の受講ペースに応じて公開パートが増えてまいります。 具体的な表示イメージは下記ページをご確認ください。
2024年8月末時点で情報Ⅰ本講座を受講していたアカウントにつきましては、 今まで公開していた第1~9回までの講座一覧に加え、パート分けされた講座が追加で公開されてまりいます。 第1~9回の表示はそのまま引き継がれますが、Webドリルの進捗表示はパート分けの講座には適用されません。ご了承くださいませ。 仕様変更に伴い、現在教室長もしくは講師に生徒アカウントログインをお願いしておりますが、今後は生徒自身にてログインをしていただいて問題ございません。
また、生徒が自宅等にてログインし復習に取り組むことも問題ございませんので、教室にてご指示いただければと存じます。 (実際にご指示いただくかは教室判断に委ねさせていただきます。)
受講ペースが変更したり、増コマが発生したりする場合などは、公開ペースを本部にて調整いたしますので、下記より必ずご連絡くださいませ。 また本件に関して不明な点がございましたら、同フォームよりお問い合わせください。 以上、引き続きよろしくお願い申し上げます。 情報Ⅰサポートチーム
"`; diff --git a/packages/render/src/browser/__snapshots__/render-web.spec.tsx.snap b/packages/render/src/browser/__snapshots__/render-web.spec.tsx.snap new file mode 100644 index 0000000000..2a39925b95 --- /dev/null +++ b/packages/render/src/browser/__snapshots__/render-web.spec.tsx.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`render on the browser environment > should handle characters with a higher byte count gracefully 1`] = `"Test Normal 情報Ⅰコース担当者様
平素よりお世話になっております。 情報Ⅰサポートチームです。 情報Ⅰ本講座につきまして仕様変更のためご連絡させていただきました。
今後ジクタス上の講座につきましては、8回分の授業をひとまとまりとしてパート分けされた状態で公開されてまいります。伴いまして、画面上の表示に一部変更がありますのでお知らせいたします。 ご登録いただいた各生徒の受講ペースに応じて公開パートが増えてまいります。 具体的な表示イメージは下記ページをご確認ください。
2024年8月末時点で情報Ⅰ本講座を受講していたアカウントにつきましては、 今まで公開していた第1~9回までの講座一覧に加え、パート分けされた講座が追加で公開されてまりいます。 第1~9回の表示はそのまま引き継がれますが、Webドリルの進捗表示はパート分けの講座には適用されません。ご了承くださいませ。 仕様変更に伴い、現在教室長もしくは講師に生徒アカウントログインをお願いしておりますが、今後は生徒自身にてログインをしていただいて問題ございません。
また、生徒が自宅等にてログインし復習に取り組むことも問題ございませんので、教室にてご指示いただければと存じます。 (実際にご指示いただくかは教室判断に委ねさせていただきます。)
受講ペースが変更したり、増コマが発生したりする場合などは、公開ペースを本部にて調整いたしますので、下記より必ずご連絡くださいませ。 また本件に関して不明な点がございましたら、同フォームよりお問い合わせください。 以上、引き続きよろしくお願い申し上げます。 情報Ⅰサポートチーム
"`; diff --git a/packages/render/src/browser/read-stream.ts b/packages/render/src/browser/read-stream.ts new file mode 100644 index 0000000000..fea97fc18a --- /dev/null +++ b/packages/render/src/browser/read-stream.ts @@ -0,0 +1,44 @@ +import { + PipeableStream, + ReactDOMServerReadableStream, +} from "react-dom/server.browser"; + +const decoder = new TextDecoder("utf-8"); + +export const readStream = async ( + stream: PipeableStream | ReactDOMServerReadableStream, +) => { + const chunks: Uint8Array[] = []; + + if ("pipeTo" in stream) { + // means it's a readable stream + const writableStream = new WritableStream({ + write(chunk: Uint8Array) { + chunks.push(chunk); + }, + }); + await stream.pipeTo(writableStream); + } else { + throw new Error( + "For some reason, the Node version of `react-dom/server` has been imported instead of the browser one.", + { + cause: { + stream, + }, + }, + ); + } + + let length = 0; + chunks.forEach((item) => { + length += item.length; + }); + const mergedChunks = new Uint8Array(length); + let offset = 0; + chunks.forEach((item) => { + mergedChunks.set(item, offset); + offset += item.length; + }); + + return decoder.decode(mergedChunks); +}; diff --git a/packages/render/src/browser/render-async-web.spec.tsx b/packages/render/src/browser/render-async-web.spec.tsx index b2f050338f..53aecebf6f 100644 --- a/packages/render/src/browser/render-async-web.spec.tsx +++ b/packages/render/src/browser/render-async-web.spec.tsx @@ -65,6 +65,42 @@ describe("renderAsync on the browser environment", () => { ); }); + // This is a test to ensure we have no regressions for https://github.com/resend/react-email/issues/1667 + it("should handle characters with a higher byte count gracefully", async () => { + const actualOutput = await renderAsync( + <> +Test Normal 情報Ⅰコース担当者様
++ 平素よりお世話になっております。 情報Ⅰサポートチームです。 + 情報Ⅰ本講座につきまして仕様変更のためご連絡させていただきました。{" "} +
+ 今後ジクタス上の講座につきましては、8回分の授業をひとまとまりとしてパート分けされた状態で公開されてまいります。 ++ 伴いまして、画面上の表示に一部変更がありますのでお知らせいたします。 + ご登録いただいた各生徒の受講ペースに応じて公開パートが増えてまいります。 + 具体的な表示イメージは下記ページをご確認ください。 +
++ 2024年8月末時点で情報Ⅰ本講座を受講していたアカウントにつきましては、 + 今まで公開していた第1~9回までの講座一覧に加え、パート分けされた講座が追加で公開されてまりいます。 + 第1~9回の表示はそのまま引き継がれますが、Webドリルの進捗表示はパート分けの講座には適用されません。ご了承くださいませ。 + 仕様変更に伴い、現在教室長もしくは講師に生徒アカウントログインをお願いしておりますが、今後は生徒自身にてログインをしていただいて問題ございません。 +
++ また、生徒が自宅等にてログインし復習に取り組むことも問題ございませんので、教室にてご指示いただければと存じます。 + (実際にご指示いただくかは教室判断に委ねさせていただきます。) +
++ 受講ペースが変更したり、増コマが発生したりする場合などは、公開ペースを本部にて調整いたしますので、下記より必ずご連絡くださいませ。 + また本件に関して不明な点がございましたら、同フォームよりお問い合わせください。 + 以上、引き続きよろしくお願い申し上げます。 情報Ⅰサポートチーム +
+ >, + ); + + expect(actualOutput).toMatchSnapshot(); + }); + it("converts a React component into PlainText", async () => { const actualOutput = await renderAsync(, { plainText: true, diff --git a/packages/render/src/browser/render-async.tsx b/packages/render/src/browser/render-async.tsx index 4e8a167b33..9795b5fcd9 100644 --- a/packages/render/src/browser/render-async.tsx +++ b/packages/render/src/browser/render-async.tsx @@ -1,41 +1,9 @@ import { convert } from "html-to-text"; -import type { - PipeableStream, - ReactDOMServerReadableStream, -} from "react-dom/server"; +import { Suspense } from "react"; import { pretty } from "../shared/utils/pretty"; import { plainTextSelectors } from "../shared/plain-text-selectors"; import type { Options } from "../shared/options"; -import { Suspense } from "react"; - -const decoder = new TextDecoder("utf-8"); - -const readStream = async ( - stream: PipeableStream | ReactDOMServerReadableStream, -) => { - let result = ""; - - if ("pipeTo" in stream) { - // means it's a readable stream - const writableStream = new WritableStream({ - write(chunk: BufferSource) { - result += decoder.decode(chunk); - }, - }); - await stream.pipeTo(writableStream); - } else { - throw new Error( - "For some reason, the Node version of `react-dom/server` has been imported instead of the browser one.", - { - cause: { - stream, - }, - }, - ); - } - - return result; -}; +import { readStream } from "./read-stream"; export const renderAsync = async ( element: React.ReactElement, diff --git a/packages/render/src/browser/render-web.spec.tsx b/packages/render/src/browser/render-web.spec.tsx index bb166498ec..a5ae0a372c 100644 --- a/packages/render/src/browser/render-web.spec.tsx +++ b/packages/render/src/browser/render-web.spec.tsx @@ -57,6 +57,42 @@ describe("render on the browser environment", () => { ); }); + // This is a test to ensure we have no regressions for https://github.com/resend/react-email/issues/1667 + it("should handle characters with a higher byte count gracefully", async () => { + const actualOutput = await render( + <> +Test Normal 情報Ⅰコース担当者様
++ 平素よりお世話になっております。 情報Ⅰサポートチームです。 + 情報Ⅰ本講座につきまして仕様変更のためご連絡させていただきました。{" "} +
+ 今後ジクタス上の講座につきましては、8回分の授業をひとまとまりとしてパート分けされた状態で公開されてまいります。 ++ 伴いまして、画面上の表示に一部変更がありますのでお知らせいたします。 + ご登録いただいた各生徒の受講ペースに応じて公開パートが増えてまいります。 + 具体的な表示イメージは下記ページをご確認ください。 +
++ 2024年8月末時点で情報Ⅰ本講座を受講していたアカウントにつきましては、 + 今まで公開していた第1~9回までの講座一覧に加え、パート分けされた講座が追加で公開されてまりいます。 + 第1~9回の表示はそのまま引き継がれますが、Webドリルの進捗表示はパート分けの講座には適用されません。ご了承くださいませ。 + 仕様変更に伴い、現在教室長もしくは講師に生徒アカウントログインをお願いしておりますが、今後は生徒自身にてログインをしていただいて問題ございません。 +
++ また、生徒が自宅等にてログインし復習に取り組むことも問題ございませんので、教室にてご指示いただければと存じます。 + (実際にご指示いただくかは教室判断に委ねさせていただきます。) +
++ 受講ペースが変更したり、増コマが発生したりする場合などは、公開ペースを本部にて調整いたしますので、下記より必ずご連絡くださいませ。 + また本件に関して不明な点がございましたら、同フォームよりお問い合わせください。 + 以上、引き続きよろしくお願い申し上げます。 情報Ⅰサポートチーム +
+ >, + ); + + expect(actualOutput).toMatchSnapshot(); + }); + it("converts a React component into HTML", async () => { const actualOutput = await render(); diff --git a/packages/render/src/browser/render.tsx b/packages/render/src/browser/render.tsx index edf98d2857..c05f7c242d 100644 --- a/packages/render/src/browser/render.tsx +++ b/packages/render/src/browser/render.tsx @@ -3,23 +3,23 @@ import type { PipeableStream, ReactDOMServerReadableStream, } from "react-dom/server"; +import { Suspense } from "react"; import { pretty } from "../shared/utils/pretty"; import { plainTextSelectors } from "../shared/plain-text-selectors"; import type { Options } from "../shared/options"; -import { Suspense } from "react"; const decoder = new TextDecoder("utf-8"); const readStream = async ( stream: PipeableStream | ReactDOMServerReadableStream, ) => { - let result = ""; + const chunks: Uint8Array[] = []; if ("pipeTo" in stream) { // means it's a readable stream const writableStream = new WritableStream({ - write(chunk: BufferSource) { - result += decoder.decode(chunk); + write(chunk: Uint8Array) { + chunks.push(chunk); }, }); await stream.pipeTo(writableStream); @@ -34,7 +34,18 @@ const readStream = async ( ); } - return result; + let length = 0; + chunks.forEach((item) => { + length += item.length; + }); + const mergedChunks = new Uint8Array(length); + let offset = 0; + chunks.forEach((item) => { + mergedChunks.set(item, offset); + offset += item.length; + }); + + return decoder.decode(mergedChunks); }; export const render = async ( diff --git a/packages/render/src/node/__snapshots__/render-async-edge.spec.tsx.snap b/packages/render/src/node/__snapshots__/render-async-edge.spec.tsx.snap new file mode 100644 index 0000000000..b022d5e92f --- /dev/null +++ b/packages/render/src/node/__snapshots__/render-async-edge.spec.tsx.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`renderAsync on the edge > should handle characters with a higher byte count gracefully in React 18 1`] = `"Test Normal 情報Ⅰコース担当者様
平素よりお世話になっております。 情報Ⅰサポートチームです。 情報Ⅰ本講座につきまして仕様変更のためご連絡させていただきました。
今後ジクタス上の講座につきましては、8回分の授業をひとまとまりとしてパート分けされた状態で公開されてまいります。伴いまして、画面上の表示に一部変更がありますのでお知らせいたします。 ご登録いただいた各生徒の受講ペースに応じて公開パートが増えてまいります。 具体的な表示イメージは下記ページをご確認ください。
2024年8月末時点で情報Ⅰ本講座を受講していたアカウントにつきましては、 今まで公開していた第1~9回までの講座一覧に加え、パート分けされた講座が追加で公開されてまりいます。 第1~9回の表示はそのまま引き継がれますが、Webドリルの進捗表示はパート分けの講座には適用されません。ご了承くださいませ。 仕様変更に伴い、現在教室長もしくは講師に生徒アカウントログインをお願いしておりますが、今後は生徒自身にてログインをしていただいて問題ございません。
また、生徒が自宅等にてログインし復習に取り組むことも問題ございませんので、教室にてご指示いただければと存じます。 (実際にご指示いただくかは教室判断に委ねさせていただきます。)
受講ペースが変更したり、増コマが発生したりする場合などは、公開ペースを本部にて調整いたしますので、下記より必ずご連絡くださいませ。 また本件に関して不明な点がございましたら、同フォームよりお問い合わせください。 以上、引き続きよろしくお願い申し上げます。 情報Ⅰサポートチーム
"`; diff --git a/packages/render/src/node/__snapshots__/render-async-node.spec.tsx.snap b/packages/render/src/node/__snapshots__/render-async-node.spec.tsx.snap index b4679db2e7..6228bbef16 100644 --- a/packages/render/src/node/__snapshots__/render-async-node.spec.tsx.snap +++ b/packages/render/src/node/__snapshots__/render-async-node.spec.tsx.snap @@ -1,5 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`renderAsync on node environments > should handle characters with a higher byte count gracefully in React 18 1`] = `"Test Normal 情報Ⅰコース担当者様
平素よりお世話になっております。 情報Ⅰサポートチームです。 情報Ⅰ本講座につきまして仕様変更のためご連絡させていただきました。
今後ジクタス上の講座につきましては、8回分の授業をひとまとまりとしてパート分けされた状態で公開されてまいります。伴いまして、画面上の表示に一部変更がありますのでお知らせいたします。 ご登録いただいた各生徒の受講ペースに応じて公開パートが増えてまいります。 具体的な表示イメージは下記ページをご確認ください。
2024年8月末時点で情報Ⅰ本講座を受講していたアカウントにつきましては、 今まで公開していた第1~9回までの講座一覧に加え、パート分けされた講座が追加で公開されてまりいます。 第1~9回の表示はそのまま引き継がれますが、Webドリルの進捗表示はパート分けの講座には適用されません。ご了承くださいませ。 仕様変更に伴い、現在教室長もしくは講師に生徒アカウントログインをお願いしておりますが、今後は生徒自身にてログインをしていただいて問題ございません。
また、生徒が自宅等にてログインし復習に取り組むことも問題ございませんので、教室にてご指示いただければと存じます。 (実際にご指示いただくかは教室判断に委ねさせていただきます。)
受講ペースが変更したり、増コマが発生したりする場合などは、公開ペースを本部にて調整いたしますので、下記より必ずご連絡くださいませ。 また本件に関して不明な点がございましたら、同フォームよりお問い合わせください。 以上、引き続きよろしくお願い申し上げます。 情報Ⅰサポートチーム
"`; + exports[`renderAsync on node environments > that it properly waits for Suepsense boundaries to resolve before resolving 1`] = ` "Test Normal 情報Ⅰコース担当者様
平素よりお世話になっております。 情報Ⅰサポートチームです。 情報Ⅰ本講座につきまして仕様変更のためご連絡させていただきました。
今後ジクタス上の講座につきましては、8回分の授業をひとまとまりとしてパート分けされた状態で公開されてまいります。伴いまして、画面上の表示に一部変更がありますのでお知らせいたします。 ご登録いただいた各生徒の受講ペースに応じて公開パートが増えてまいります。 具体的な表示イメージは下記ページをご確認ください。
2024年8月末時点で情報Ⅰ本講座を受講していたアカウントにつきましては、 今まで公開していた第1~9回までの講座一覧に加え、パート分けされた講座が追加で公開されてまりいます。 第1~9回の表示はそのまま引き継がれますが、Webドリルの進捗表示はパート分けの講座には適用されません。ご了承くださいませ。 仕様変更に伴い、現在教室長もしくは講師に生徒アカウントログインをお願いしておりますが、今後は生徒自身にてログインをしていただいて問題ございません。
また、生徒が自宅等にてログインし復習に取り組むことも問題ございませんので、教室にてご指示いただければと存じます。 (実際にご指示いただくかは教室判断に委ねさせていただきます。)
受講ペースが変更したり、増コマが発生したりする場合などは、公開ペースを本部にて調整いたしますので、下記より必ずご連絡くださいませ。 また本件に関して不明な点がございましたら、同フォームよりお問い合わせください。 以上、引き続きよろしくお願い申し上げます。 情報Ⅰサポートチーム
"`; diff --git a/packages/render/src/node/__snapshots__/render-node.spec.tsx.snap b/packages/render/src/node/__snapshots__/render-node.spec.tsx.snap index 56cb0f7460..c97cf084a3 100644 --- a/packages/render/src/node/__snapshots__/render-node.spec.tsx.snap +++ b/packages/render/src/node/__snapshots__/render-node.spec.tsx.snap @@ -1,5 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`render on node environments > should handle characters with a higher byte count gracefully in React 18 1`] = `"Test Normal 情報Ⅰコース担当者様
平素よりお世話になっております。 情報Ⅰサポートチームです。 情報Ⅰ本講座につきまして仕様変更のためご連絡させていただきました。
今後ジクタス上の講座につきましては、8回分の授業をひとまとまりとしてパート分けされた状態で公開されてまいります。伴いまして、画面上の表示に一部変更がありますのでお知らせいたします。 ご登録いただいた各生徒の受講ペースに応じて公開パートが増えてまいります。 具体的な表示イメージは下記ページをご確認ください。
2024年8月末時点で情報Ⅰ本講座を受講していたアカウントにつきましては、 今まで公開していた第1~9回までの講座一覧に加え、パート分けされた講座が追加で公開されてまりいます。 第1~9回の表示はそのまま引き継がれますが、Webドリルの進捗表示はパート分けの講座には適用されません。ご了承くださいませ。 仕様変更に伴い、現在教室長もしくは講師に生徒アカウントログインをお願いしておりますが、今後は生徒自身にてログインをしていただいて問題ございません。
また、生徒が自宅等にてログインし復習に取り組むことも問題ございませんので、教室にてご指示いただければと存じます。 (実際にご指示いただくかは教室判断に委ねさせていただきます。)
受講ペースが変更したり、増コマが発生したりする場合などは、公開ペースを本部にて調整いたしますので、下記より必ずご連絡くださいませ。 また本件に関して不明な点がございましたら、同フォームよりお問い合わせください。 以上、引き続きよろしくお願い申し上げます。 情報Ⅰサポートチーム
"`; + exports[`render on node environments > that it properly waits for Suepsense boundaries to resolve before resolving 1`] = ` "Test Normal 情報Ⅰコース担当者様
++ 平素よりお世話になっております。 情報Ⅰサポートチームです。 + 情報Ⅰ本講座につきまして仕様変更のためご連絡させていただきました。{" "} +
+ 今後ジクタス上の講座につきましては、8回分の授業をひとまとまりとしてパート分けされた状態で公開されてまいります。 ++ 伴いまして、画面上の表示に一部変更がありますのでお知らせいたします。 + ご登録いただいた各生徒の受講ペースに応じて公開パートが増えてまいります。 + 具体的な表示イメージは下記ページをご確認ください。 +
++ 2024年8月末時点で情報Ⅰ本講座を受講していたアカウントにつきましては、 + 今まで公開していた第1~9回までの講座一覧に加え、パート分けされた講座が追加で公開されてまりいます。 + 第1~9回の表示はそのまま引き継がれますが、Webドリルの進捗表示はパート分けの講座には適用されません。ご了承くださいませ。 + 仕様変更に伴い、現在教室長もしくは講師に生徒アカウントログインをお願いしておりますが、今後は生徒自身にてログインをしていただいて問題ございません。 +
++ また、生徒が自宅等にてログインし復習に取り組むことも問題ございませんので、教室にてご指示いただければと存じます。 + (実際にご指示いただくかは教室判断に委ねさせていただきます。) +
++ 受講ペースが変更したり、増コマが発生したりする場合などは、公開ペースを本部にて調整いたしますので、下記より必ずご連絡くださいませ。 + また本件に関して不明な点がございましたら、同フォームよりお問い合わせください。 + 以上、引き続きよろしくお願い申し上げます。 情報Ⅰサポートチーム +
+ >, + ); + + expect(actualOutput).toMatchSnapshot(); + }); + it("converts a React component into HTML", async () => { const actualOutput = await renderAsync(); diff --git a/packages/render/src/node/render-async-node.spec.tsx b/packages/render/src/node/render-async-node.spec.tsx index b7c905cae5..7a3d0b1c4c 100644 --- a/packages/render/src/node/render-async-node.spec.tsx +++ b/packages/render/src/node/render-async-node.spec.tsx @@ -39,6 +39,42 @@ describe("renderAsync on node environments", () => { vi.resetAllMocks(); }); + // This is a test to ensure we have no regressions for https://github.com/resend/react-email/issues/1667 + it("should handle characters with a higher byte count gracefully in React 18", async () => { + const actualOutput = await renderAsync( + <> +Test Normal 情報Ⅰコース担当者様
++ 平素よりお世話になっております。 情報Ⅰサポートチームです。 + 情報Ⅰ本講座につきまして仕様変更のためご連絡させていただきました。{" "} +
+ 今後ジクタス上の講座につきましては、8回分の授業をひとまとまりとしてパート分けされた状態で公開されてまいります。 ++ 伴いまして、画面上の表示に一部変更がありますのでお知らせいたします。 + ご登録いただいた各生徒の受講ペースに応じて公開パートが増えてまいります。 + 具体的な表示イメージは下記ページをご確認ください。 +
++ 2024年8月末時点で情報Ⅰ本講座を受講していたアカウントにつきましては、 + 今まで公開していた第1~9回までの講座一覧に加え、パート分けされた講座が追加で公開されてまりいます。 + 第1~9回の表示はそのまま引き継がれますが、Webドリルの進捗表示はパート分けの講座には適用されません。ご了承くださいませ。 + 仕様変更に伴い、現在教室長もしくは講師に生徒アカウントログインをお願いしておりますが、今後は生徒自身にてログインをしていただいて問題ございません。 +
++ また、生徒が自宅等にてログインし復習に取り組むことも問題ございませんので、教室にてご指示いただければと存じます。 + (実際にご指示いただくかは教室判断に委ねさせていただきます。) +
++ 受講ペースが変更したり、増コマが発生したりする場合などは、公開ペースを本部にて調整いたしますので、下記より必ずご連絡くださいませ。 + また本件に関して不明な点がございましたら、同フォームよりお問い合わせください。 + 以上、引き続きよろしくお願い申し上げます。 情報Ⅰサポートチーム +
+ >, + ); + + expect(actualOutput).toMatchSnapshot(); + }); + it("that it properly waits for Suepsense boundaries to resolve before resolving", async () => { const EmailTemplate = () => { const html = usePromise( diff --git a/packages/render/src/node/render-async.tsx b/packages/render/src/node/render-async.tsx index 32b2854f78..a81a6694ae 100644 --- a/packages/render/src/node/render-async.tsx +++ b/packages/render/src/node/render-async.tsx @@ -1,49 +1,9 @@ -import { Writable } from "node:stream"; import { convert } from "html-to-text"; -import type { - PipeableStream, - ReactDOMServerReadableStream, -} from "react-dom/server"; +import { Suspense } from "react"; import { pretty } from "../shared/utils/pretty"; import { plainTextSelectors } from "../shared/plain-text-selectors"; import type { Options } from "../shared/options"; -import { Suspense } from "react"; - -const decoder = new TextDecoder("utf-8"); - -const readStream = async ( - stream: PipeableStream | ReactDOMServerReadableStream, -) => { - let result = ""; - - if ("pipeTo" in stream) { - // means it's a readable stream - const writableStream = new WritableStream({ - write(chunk: BufferSource) { - result += decoder.decode(chunk); - }, - }); - await stream.pipeTo(writableStream); - } else { - const writable = new Writable({ - write(chunk: BufferSource, _encoding, callback) { - result += decoder.decode(chunk); - - callback(); - }, - }); - stream.pipe(writable); - - return new PromiseTest Normal 情報Ⅰコース担当者様
++ 平素よりお世話になっております。 情報Ⅰサポートチームです。 + 情報Ⅰ本講座につきまして仕様変更のためご連絡させていただきました。{" "} +
+ 今後ジクタス上の講座につきましては、8回分の授業をひとまとまりとしてパート分けされた状態で公開されてまいります。 ++ 伴いまして、画面上の表示に一部変更がありますのでお知らせいたします。 + ご登録いただいた各生徒の受講ペースに応じて公開パートが増えてまいります。 + 具体的な表示イメージは下記ページをご確認ください。 +
++ 2024年8月末時点で情報Ⅰ本講座を受講していたアカウントにつきましては、 + 今まで公開していた第1~9回までの講座一覧に加え、パート分けされた講座が追加で公開されてまりいます。 + 第1~9回の表示はそのまま引き継がれますが、Webドリルの進捗表示はパート分けの講座には適用されません。ご了承くださいませ。 + 仕様変更に伴い、現在教室長もしくは講師に生徒アカウントログインをお願いしておりますが、今後は生徒自身にてログインをしていただいて問題ございません。 +
++ また、生徒が自宅等にてログインし復習に取り組むことも問題ございませんので、教室にてご指示いただければと存じます。 + (実際にご指示いただくかは教室判断に委ねさせていただきます。) +
++ 受講ペースが変更したり、増コマが発生したりする場合などは、公開ペースを本部にて調整いたしますので、下記より必ずご連絡くださいませ。 + また本件に関して不明な点がございましたら、同フォームよりお問い合わせください。 + 以上、引き続きよろしくお願い申し上げます。 情報Ⅰサポートチーム +
+ >, + ); + + expect(actualOutput).toMatchSnapshot(); + }); + it("converts a React component into HTML", async () => { const actualOutput = await render(); diff --git a/packages/render/src/node/render-node.spec.tsx b/packages/render/src/node/render-node.spec.tsx index 5ef4fefeab..c30c8839f9 100644 --- a/packages/render/src/node/render-node.spec.tsx +++ b/packages/render/src/node/render-node.spec.tsx @@ -48,6 +48,43 @@ describe("render on node environments", () => { vi.resetAllMocks(); }); + // This is a test to ensure we have no regressions for https://github.com/resend/react-email/issues/1667 + // The error only happens with React 18, and thus is tested on React 18. + it("should handle characters with a higher byte count gracefully in React 18", async () => { + const actualOutput = await render( + <> +Test Normal 情報Ⅰコース担当者様
++ 平素よりお世話になっております。 情報Ⅰサポートチームです。 + 情報Ⅰ本講座につきまして仕様変更のためご連絡させていただきました。{" "} +
+ 今後ジクタス上の講座につきましては、8回分の授業をひとまとまりとしてパート分けされた状態で公開されてまいります。 ++ 伴いまして、画面上の表示に一部変更がありますのでお知らせいたします。 + ご登録いただいた各生徒の受講ペースに応じて公開パートが増えてまいります。 + 具体的な表示イメージは下記ページをご確認ください。 +
++ 2024年8月末時点で情報Ⅰ本講座を受講していたアカウントにつきましては、 + 今まで公開していた第1~9回までの講座一覧に加え、パート分けされた講座が追加で公開されてまりいます。 + 第1~9回の表示はそのまま引き継がれますが、Webドリルの進捗表示はパート分けの講座には適用されません。ご了承くださいませ。 + 仕様変更に伴い、現在教室長もしくは講師に生徒アカウントログインをお願いしておりますが、今後は生徒自身にてログインをしていただいて問題ございません。 +
++ また、生徒が自宅等にてログインし復習に取り組むことも問題ございませんので、教室にてご指示いただければと存じます。 + (実際にご指示いただくかは教室判断に委ねさせていただきます。) +
++ 受講ペースが変更したり、増コマが発生したりする場合などは、公開ペースを本部にて調整いたしますので、下記より必ずご連絡くださいませ。 + また本件に関して不明な点がございましたら、同フォームよりお問い合わせください。 + 以上、引き続きよろしくお願い申し上げます。 情報Ⅰサポートチーム +
+ >, + ); + + expect(actualOutput).toMatchSnapshot(); + }); + it("that it properly waits for Suepsense boundaries to resolve before resolving", async () => { const EmailTemplate = () => { const html = usePromise( diff --git a/packages/render/src/node/render.tsx b/packages/render/src/node/render.tsx index 3ff90bd9ae..693e4582f1 100644 --- a/packages/render/src/node/render.tsx +++ b/packages/render/src/node/render.tsx @@ -1,49 +1,9 @@ -import { Writable } from "node:stream"; import { convert } from "html-to-text"; -import type { - PipeableStream, - ReactDOMServerReadableStream, -} from "react-dom/server"; +import { Suspense } from "react"; import { pretty } from "../shared/utils/pretty"; import { plainTextSelectors } from "../shared/plain-text-selectors"; import type { Options } from "../shared/options"; -import { Suspense } from "react"; - -const decoder = new TextDecoder("utf-8"); - -const readStream = async ( - stream: PipeableStream | ReactDOMServerReadableStream, -) => { - let result = ""; - - if ("pipeTo" in stream) { - // means it's a readable stream - const writableStream = new WritableStream({ - write(chunk: BufferSource) { - result += decoder.decode(chunk); - }, - }); - await stream.pipeTo(writableStream); - } else { - const writable = new Writable({ - write(chunk: BufferSource, _encoding, callback) { - result += decoder.decode(chunk); - - callback(); - }, - }); - stream.pipe(writable); - - return new Promise