UNPKG

@react-email/render

Version:

Transform React components into HTML email templates

148 lines (142 loc) 4.25 kB
import { Suspense } from "react"; import * as html from "prettier/plugins/html"; import { format } from "prettier/standalone"; import { convert } from "html-to-text"; import { Writable } from "node:stream"; import { jsx } from "react/jsx-runtime"; //#region src/shared/utils/pretty.ts function recursivelyMapDoc(doc, callback) { if (Array.isArray(doc)) return doc.map((innerDoc) => recursivelyMapDoc(innerDoc, callback)); if (typeof doc === "object") { if (doc.type === "group") return { ...doc, contents: recursivelyMapDoc(doc.contents, callback), expandedStates: recursivelyMapDoc(doc.expandedStates, callback) }; if ("contents" in doc) return { ...doc, contents: recursivelyMapDoc(doc.contents, callback) }; if ("parts" in doc) return { ...doc, parts: recursivelyMapDoc(doc.parts, callback) }; if (doc.type === "if-break") return { ...doc, breakContents: recursivelyMapDoc(doc.breakContents, callback), flatContents: recursivelyMapDoc(doc.flatContents, callback) }; } return callback(doc); } const modifiedHtml = { ...html }; if (modifiedHtml.printers) { const previousPrint = modifiedHtml.printers.html.print; modifiedHtml.printers.html.print = (path, options, print, args) => { const node = path.getNode(); const rawPrintingResult = previousPrint(path, options, print, args); if (node.type === "ieConditionalComment") return recursivelyMapDoc(rawPrintingResult, (doc) => { if (typeof doc === "object" && doc.type === "line") return doc.soft ? "" : " "; return doc; }); return rawPrintingResult; }; } const defaults = { endOfLine: "lf", tabWidth: 2, plugins: [modifiedHtml], bracketSameLine: true, parser: "html" }; const pretty = (str, options = {}) => { return format(str.replaceAll("\0", ""), { ...defaults, ...options }); }; //#endregion //#region src/shared/utils/to-plain-text.ts const plainTextSelectors = [ { selector: "img", format: "skip" }, { selector: "[data-skip-in-text=true]", format: "skip" }, { selector: "a", options: { linkBrackets: false, hideLinkHrefIfSameAsText: true } } ]; function toPlainText(html$1, options) { return convert(html$1, { selectors: plainTextSelectors, ...options }); } //#endregion //#region src/node/read-stream.ts const decoder = new TextDecoder("utf-8"); const readStream = async (stream) => { let result = ""; if ("pipeTo" in stream) { const writableStream = new WritableStream({ write(chunk) { result += decoder.decode(chunk); } }); await stream.pipeTo(writableStream); } else { const writable = new Writable({ write(chunk, _encoding, callback) { result += decoder.decode(chunk); callback(); } }); stream.pipe(writable); await new Promise((resolve, reject) => { writable.on("error", reject); writable.on("close", () => { resolve(); }); }); } return result; }; //#endregion //#region src/node/render.tsx const render = async (node, options) => { const suspendedElement = /* @__PURE__ */ jsx(Suspense, { children: node }); const reactDOMServer = await import("react-dom/server").then((m) => m.default); let html$1; if (Object.hasOwn(reactDOMServer, "renderToReadableStream")) html$1 = await readStream(await reactDOMServer.renderToReadableStream(suspendedElement, { progressiveChunkSize: Number.POSITIVE_INFINITY })); else await new Promise((resolve, reject) => { const stream = reactDOMServer.renderToPipeableStream(suspendedElement, { async onAllReady() { html$1 = await readStream(stream); resolve(); }, onError(error) { reject(error); }, progressiveChunkSize: Number.POSITIVE_INFINITY }); }); if (options?.plainText) return toPlainText(html$1, options.htmlToTextOptions); const document = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">${html$1.replace(/<!DOCTYPE.*?>/, "")}`; if (options?.pretty) return pretty(document); return document; }; //#endregion //#region src/node/index.ts /** * @deprecated use {@link render} */ const renderAsync = (element, options) => { return render(element, options); }; //#endregion export { plainTextSelectors, pretty, render, renderAsync, toPlainText }; //# sourceMappingURL=index.mjs.map