@depup/react-email__render 2.0.5-depup.0 → 2.1.0-depup.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,11 @@
1
1
  import * as html from "prettier/plugins/html";
2
2
  import { format } from "prettier/standalone";
3
3
  import { convert } from "html-to-text";
4
+ import { decodeHTML, decodeHTMLAttribute } from "entities/lib/decode.js";
5
+ import { SyntaxKind, parse } from "html5parser";
4
6
  import React, { Suspense } from "react";
5
7
  import { Fragment, jsx } from "react/jsx-runtime";
6
8
  import { Writable } from "node:stream";
7
-
8
9
  //#region src/shared/utils/pretty.ts
9
10
  function getHtmlNode(path) {
10
11
  const topNode = path.node;
@@ -65,7 +66,6 @@ const pretty = (str, options = {}) => {
65
66
  ...options
66
67
  });
67
68
  };
68
-
69
69
  //#endregion
70
70
  //#region src/shared/utils/to-plain-text.ts
71
71
  const plainTextSelectors = [
@@ -83,16 +83,291 @@ const plainTextSelectors = [
83
83
  linkBrackets: false,
84
84
  hideLinkHrefIfSameAsText: true
85
85
  }
86
+ },
87
+ {
88
+ selector: "[data-text-format=\"dataTable\"]",
89
+ format: "dataTable"
86
90
  }
87
91
  ];
88
- function toPlainText(html$1, options) {
89
- return convert(html$1, {
92
+ function toPlainText(html, options) {
93
+ return convert(html, {
90
94
  wordwrap: false,
91
95
  ...options,
92
96
  selectors: [...plainTextSelectors, ...options?.selectors ?? []]
93
97
  });
94
98
  }
95
-
99
+ //#endregion
100
+ //#region src/shared/utils/unstable-to-plain-text.ts
101
+ const SKIPPED_TAGS = new Set([
102
+ "img",
103
+ "noscript",
104
+ "script",
105
+ "style",
106
+ "template"
107
+ ]);
108
+ const WHITESPACE_RUN = /([ \t\n\r\f\u200b]+)/;
109
+ const TAG_BLOCKS = {
110
+ article: {
111
+ open: 2,
112
+ close: 2
113
+ },
114
+ aside: {
115
+ open: 2,
116
+ close: 2
117
+ },
118
+ blockquote: {
119
+ open: 2,
120
+ close: 2,
121
+ prefix: {
122
+ first: "> ",
123
+ rest: "> "
124
+ }
125
+ },
126
+ div: {
127
+ open: 2,
128
+ close: 2
129
+ },
130
+ footer: {
131
+ open: 2,
132
+ close: 2
133
+ },
134
+ form: {
135
+ open: 2,
136
+ close: 2
137
+ },
138
+ h1: {
139
+ open: 3,
140
+ close: 2
141
+ },
142
+ h2: {
143
+ open: 3,
144
+ close: 2
145
+ },
146
+ h3: {
147
+ open: 3,
148
+ close: 2
149
+ },
150
+ h4: {
151
+ open: 3,
152
+ close: 2
153
+ },
154
+ h5: {
155
+ open: 3,
156
+ close: 2
157
+ },
158
+ h6: {
159
+ open: 3,
160
+ close: 2
161
+ },
162
+ header: {
163
+ open: 2,
164
+ close: 2
165
+ },
166
+ hr: {
167
+ open: 2,
168
+ close: 2
169
+ },
170
+ main: {
171
+ open: 2,
172
+ close: 2
173
+ },
174
+ nav: {
175
+ open: 2,
176
+ close: 2
177
+ },
178
+ p: {
179
+ open: 2,
180
+ close: 2
181
+ },
182
+ pre: {
183
+ open: 2,
184
+ close: 2
185
+ },
186
+ section: {
187
+ open: 2,
188
+ close: 2
189
+ },
190
+ table: {
191
+ open: 2,
192
+ close: 2
193
+ }
194
+ };
195
+ function unstableToPlainText(html) {
196
+ const tree = parse(html, { setAttributeMap: true });
197
+ const body = findBody(tree);
198
+ const blocks = [{
199
+ block: {
200
+ open: 0,
201
+ close: 0
202
+ },
203
+ text: [],
204
+ leading: 0,
205
+ stash: 0,
206
+ space: false
207
+ }];
208
+ const stack = [{
209
+ parent: body,
210
+ children: body?.body ?? tree,
211
+ index: 0,
212
+ pre: false,
213
+ opened: false,
214
+ textFrom: 0,
215
+ orderedList: void 0
216
+ }];
217
+ while (stack.length > 0) {
218
+ const frame = stack[stack.length - 1];
219
+ const node = frame.children[frame.index];
220
+ if (node === void 0) {
221
+ stack.pop();
222
+ exitElement(frame.parent, frame);
223
+ continue;
224
+ }
225
+ frame.index += 1;
226
+ enterNode(node, frame);
227
+ }
228
+ function top() {
229
+ return blocks[blocks.length - 1];
230
+ }
231
+ function writeWord(value) {
232
+ const block = top();
233
+ if (block.stash > 0) block.text.push("\n".repeat(block.stash));
234
+ else if (block.space && block.text.length > 0) block.text.push(" ");
235
+ block.stash = 0;
236
+ block.space = false;
237
+ block.text.push(value);
238
+ }
239
+ function enterNode(node, frame) {
240
+ if (node.type === SyntaxKind.Text) {
241
+ const value = decodeHTML(node.value);
242
+ if (frame.pre) {
243
+ if (value.length > 0) writeWord(value);
244
+ } else {
245
+ const segments = value.split(WHITESPACE_RUN);
246
+ for (let i = 0; i < segments.length; i++) {
247
+ const segment = segments[i];
248
+ if (segment.length === 0) continue;
249
+ if (i % 2 === 1) top().space = true;
250
+ else writeWord(segment);
251
+ }
252
+ }
253
+ return;
254
+ }
255
+ if (SKIPPED_TAGS.has(node.name) || decodeHTMLAttribute(node.attributeMap?.["data-skip-in-text"]?.value?.value ?? "") === "true") return;
256
+ const parentTag = frame.parent?.name;
257
+ let block = TAG_BLOCKS[node.name];
258
+ let orderedList;
259
+ if (node.name === "ul") {
260
+ const breaks = parentTag === "li" ? 1 : 2;
261
+ block = {
262
+ open: breaks,
263
+ close: breaks
264
+ };
265
+ } else if (node.name === "li" && parentTag === "ul") block = {
266
+ open: 1,
267
+ close: 1,
268
+ prefix: (stack[stack.length - 2]?.parent)?.name === "li" ? {
269
+ first: "* ",
270
+ rest: " "
271
+ } : {
272
+ first: " * ",
273
+ rest: " "
274
+ }
275
+ };
276
+ else if (node.name === "ol") {
277
+ const nested = parentTag === "li";
278
+ const parsedStart = Number.parseInt(decodeHTMLAttribute(node.attributeMap?.start?.value?.value ?? "1"), 10);
279
+ const start = Number.isNaN(parsedStart) ? 1 : parsedStart;
280
+ const itemCount = node.body?.filter((child) => child.type === SyntaxKind.Tag && child.name === "li").length ?? 0;
281
+ let prefixLength = 0;
282
+ for (let index = start; index < start + itemCount; index++) {
283
+ const prefix = `${nested ? "" : " "}${index}. `;
284
+ prefixLength = Math.max(prefixLength, prefix.length);
285
+ }
286
+ const breaks = nested ? 1 : 2;
287
+ block = {
288
+ open: breaks,
289
+ close: breaks
290
+ };
291
+ orderedList = {
292
+ next: start,
293
+ prefixLength,
294
+ nested
295
+ };
296
+ } else if (node.name === "li" && parentTag === "ol" && frame.orderedList) {
297
+ const list = frame.orderedList;
298
+ block = {
299
+ open: 1,
300
+ close: 1,
301
+ prefix: {
302
+ first: `${list.nested ? "" : " "}${list.next++}. `.padEnd(list.prefixLength),
303
+ rest: " ".repeat(list.prefixLength)
304
+ }
305
+ };
306
+ }
307
+ if (block) blocks.push({
308
+ block,
309
+ text: [],
310
+ leading: block.open,
311
+ stash: 0,
312
+ space: false
313
+ });
314
+ if (node.name === "hr") writeWord("-".repeat(40));
315
+ else if (node.name === "br") {
316
+ top().space = false;
317
+ top().text.push("\n");
318
+ }
319
+ stack.push({
320
+ parent: node,
321
+ children: node.body ?? [],
322
+ index: 0,
323
+ pre: frame.pre || node.name === "pre",
324
+ opened: block !== void 0,
325
+ textFrom: top().text.length,
326
+ orderedList
327
+ });
328
+ }
329
+ function exitElement(element, frame) {
330
+ if (element === void 0) return;
331
+ if (element.name === "a") {
332
+ const href = decodeHTMLAttribute(element.attributeMap?.href?.value?.value ?? "").replace(/^mailto:/, "");
333
+ if (href.length > 0 && !href.startsWith("#")) {
334
+ const anchorText = top().text.slice(frame.textFrom).join("");
335
+ if (anchorText !== href) {
336
+ if (anchorText.length > 0) top().space = true;
337
+ writeWord(href);
338
+ }
339
+ }
340
+ }
341
+ if (frame.opened) closeBlock();
342
+ }
343
+ function closeBlock() {
344
+ const child = blocks.pop();
345
+ if (child === void 0) return;
346
+ const parent = blocks[blocks.length - 1];
347
+ let content = child.text.join("");
348
+ const prefix = child.block.prefix;
349
+ if (prefix !== void 0) {
350
+ const trimmed = content.replace(/^\n+|\n+$/g, "");
351
+ content = prefix.first + trimmed.replaceAll("\n", `\n${prefix.rest}`);
352
+ }
353
+ const breaks = Math.max(parent.stash, child.leading);
354
+ if (parent.text.length > 0) {
355
+ parent.text.push("\n".repeat(breaks));
356
+ if (content.length > 0) parent.text.push(content);
357
+ } else {
358
+ if (content.length > 0) parent.text.push(content);
359
+ parent.leading = breaks;
360
+ }
361
+ parent.stash = Math.max(child.stash, child.block.close);
362
+ }
363
+ return blocks[0].text.join("");
364
+ }
365
+ function findBody(tree) {
366
+ for (const child of tree) {
367
+ if (child.type !== SyntaxKind.Tag || child.name !== "html") continue;
368
+ for (const inner of child.body ?? []) if (inner.type === SyntaxKind.Tag && inner.name === "body") return inner;
369
+ }
370
+ }
96
371
  //#endregion
97
372
  //#region src/shared/error-boundary.tsx
98
373
  function createErrorBoundary(reject) {
@@ -106,7 +381,43 @@ function createErrorBoundary(reject) {
106
381
  }
107
382
  };
108
383
  }
109
-
384
+ //#endregion
385
+ //#region src/shared/utils/strip-image-preload-links.ts
386
+ /**
387
+ * React injects `<link rel="preload" as="image" />` resource hints into the
388
+ * document `<head>` for every `<img>` it renders during server-side rendering.
389
+ *
390
+ * These hints are meant for browsers loading a web page, where they can speed
391
+ * up the initial paint. In an email they are dead weight: email clients ignore
392
+ * `<link rel="preload">`, and the tags only add noise to the rendered HTML.
393
+ *
394
+ * @see https://github.com/resend/react-email/issues/3034
395
+ *
396
+ * This removes only those auto-injected image preload links, leaving every
397
+ * other `<link>` (stylesheets, fonts, user-authored non-image preloads, ...)
398
+ * untouched. It parses each `<link>` tag's attributes instead of relying on a
399
+ * fixed string match, so it is not affected by attribute order or spacing.
400
+ */
401
+ const stripImagePreloadLinks = (html) => {
402
+ return html.replace(/<link\b[^>]*\/?>/gi, (tag) => isImagePreloadLink(tag) ? "" : tag);
403
+ };
404
+ const isImagePreloadLink = (tag) => {
405
+ const attributes = parseAttributes(tag);
406
+ return attributes.rel === "preload" && attributes.as === "image";
407
+ };
408
+ const ATTRIBUTE_PATTERN = /([a-z][a-z0-9-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+)))?/gi;
409
+ /**
410
+ * Parses the attributes of a single, already-isolated HTML tag string (e.g.
411
+ * `<link rel="preload" as="image" href="..." />`) into a name → value map.
412
+ * Attribute names are lower-cased; values are read from double, single, or
413
+ * unquoted forms.
414
+ */
415
+ const parseAttributes = (tag) => {
416
+ const attributeSection = tag.replace(/^<[a-z][a-z0-9-]*/i, "");
417
+ const attributes = {};
418
+ for (const [, name, doubleQuoted, singleQuoted, unquoted] of attributeSection.matchAll(ATTRIBUTE_PATTERN)) attributes[name.toLowerCase()] = doubleQuoted ?? singleQuoted ?? unquoted ?? "";
419
+ return attributes;
420
+ };
110
421
  //#endregion
111
422
  //#region src/node/read-stream.ts
112
423
  const readStream = async (stream) => {
@@ -148,7 +459,6 @@ const readStream = async (stream) => {
148
459
  }
149
460
  return result;
150
461
  };
151
-
152
462
  //#endregion
153
463
  //#region src/node/render.tsx
154
464
  const render = async (node, options) => {
@@ -156,7 +466,7 @@ const render = async (node, options) => {
156
466
  if ("default" in m) return m.default;
157
467
  return m;
158
468
  });
159
- let html$1;
469
+ let html;
160
470
  await new Promise((resolve, reject) => {
161
471
  if (Object.hasOwn(reactDOMServer, "renderToReadableStream") && typeof WritableStream !== "undefined") {
162
472
  const ErrorBoundary = createErrorBoundary(reject);
@@ -169,14 +479,16 @@ const render = async (node, options) => {
169
479
  await stream.allReady;
170
480
  return readStream(stream);
171
481
  }).then((result) => {
172
- html$1 = result;
482
+ html = result;
173
483
  resolve();
174
484
  }).catch(reject);
175
485
  } else {
176
486
  const ErrorBoundary = createErrorBoundary(reject);
177
487
  const stream = reactDOMServer.renderToPipeableStream(/* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(Suspense, { children: node }) }), {
178
488
  async onAllReady() {
179
- html$1 = await readStream(stream);
489
+ html = await readStream(stream).then((s) => {
490
+ return s.replaceAll("\0", "");
491
+ });
180
492
  resolve();
181
493
  },
182
494
  onError(error) {
@@ -186,12 +498,13 @@ const render = async (node, options) => {
186
498
  });
187
499
  }
188
500
  });
189
- if (options?.plainText) return toPlainText(html$1, options.htmlToTextOptions);
190
- 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.*?>/, "")}`;
501
+ html = stripImagePreloadLinks(html);
502
+ if (options?.plainText) return options.unstableTextConversion ? unstableToPlainText(html) : toPlainText(html, options.htmlToTextOptions);
503
+ const document = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">${html.replace(/<!DOCTYPE.*?>/, "")}`;
191
504
  if (options?.pretty) return pretty(document);
192
505
  return document;
193
506
  };
194
-
195
507
  //#endregion
196
- export { plainTextSelectors, pretty, render, toPlainText };
508
+ export { plainTextSelectors, pretty, render, toPlainText, unstableToPlainText };
509
+
197
510
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["html","html"],"sources":["../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/shared/error-boundary.tsx","../../src/node/read-stream.ts","../../src/node/render.tsx"],"sourcesContent":["import type { Options, Plugin } from 'prettier';\nimport type { builders } from 'prettier/doc';\nimport * as html from 'prettier/plugins/html';\nimport { format } from 'prettier/standalone';\n\ninterface HtmlNode {\n type?: 'element' | 'text' | 'ieConditionalComment';\n kind?: 'element' | 'text' | 'ieConditionalComment' | 'root';\n name?: string;\n sourceSpan: {\n start: { file: unknown[]; offset: number; line: number; col: number };\n end: { file: unknown[]; offset: number; line: number; col: number };\n details: null;\n };\n parent?: HtmlNode;\n}\n\nfunction getHtmlNode(path: {\n node?: HtmlNode;\n stack?: Array<Record<string, unknown>>;\n}) {\n const topNode = path.node;\n if (topNode) {\n return topNode;\n }\n\n return path.stack?.[path.stack.length - 1] as HtmlNode;\n}\n\nfunction recursivelyMapDoc(\n doc: builders.Doc,\n callback: (innerDoc: string | builders.DocCommand) => builders.Doc,\n): builders.Doc {\n if (Array.isArray(doc)) {\n return doc.map((innerDoc) => recursivelyMapDoc(innerDoc, callback));\n }\n\n if (typeof doc === 'object') {\n if (doc.type === 'line') {\n return callback(doc.soft ? '' : ' ');\n }\n\n if (doc.type === 'group') {\n return {\n ...doc,\n contents: recursivelyMapDoc(doc.contents, callback),\n expandedStates: recursivelyMapDoc(\n doc.expandedStates,\n callback,\n ) as builders.Doc[],\n };\n }\n\n if ('contents' in doc) {\n return {\n ...doc,\n contents: recursivelyMapDoc(doc.contents, callback),\n };\n }\n\n if ('parts' in doc) {\n return {\n ...doc,\n parts: recursivelyMapDoc(doc.parts, callback) as builders.Doc[],\n };\n }\n\n if (doc.type === 'if-break') {\n return {\n ...doc,\n breakContents: recursivelyMapDoc(doc.breakContents, callback),\n flatContents: recursivelyMapDoc(doc.flatContents, callback),\n };\n }\n\n const nextDoc = { ...doc } as Record<string, unknown>;\n for (const [key, value] of Object.entries(nextDoc)) {\n if (value && typeof value === 'object') {\n nextDoc[key] = recursivelyMapDoc(value as builders.Doc, callback);\n }\n }\n\n return nextDoc as builders.Doc;\n }\n\n return callback(doc);\n}\n\nconst modifiedHtml = { ...html } as Plugin;\nif (modifiedHtml.printers) {\n const previousPrint = modifiedHtml.printers.html.print;\n modifiedHtml.printers.html.print = (path, options, print, args) => {\n const node = getHtmlNode(\n path as Parameters<Plugin['printers']['html']['print']>[0],\n );\n\n const rawPrintingResult = previousPrint(path, options, print, args);\n\n if (\n node?.type === 'ieConditionalComment' ||\n node?.kind === 'ieConditionalComment'\n ) {\n const printingResult = recursivelyMapDoc(rawPrintingResult, (doc) => {\n if (typeof doc === 'object' && doc.type === 'line') {\n return doc.soft ? '' : ' ';\n }\n\n return doc;\n });\n\n return printingResult;\n }\n\n return rawPrintingResult;\n };\n}\n\nconst defaults: Options = {\n endOfLine: 'lf',\n tabWidth: 2,\n plugins: [modifiedHtml],\n bracketSameLine: true,\n parser: 'html',\n};\n\nexport const pretty = (str: string, options: Options = {}) => {\n return format(str.replaceAll('\\0', ''), {\n ...defaults,\n ...options,\n });\n};\n","import {\n convert,\n type HtmlToTextOptions,\n type SelectorDefinition,\n} from 'html-to-text';\n\nexport const plainTextSelectors: SelectorDefinition[] = [\n { selector: 'img', format: 'skip' },\n { selector: '[data-skip-in-text=true]', format: 'skip' },\n {\n selector: 'a',\n options: { linkBrackets: false, hideLinkHrefIfSameAsText: true },\n },\n];\n\nexport function toPlainText(html: string, options?: HtmlToTextOptions) {\n return convert(html, {\n wordwrap: false,\n ...options,\n selectors: [...plainTextSelectors, ...(options?.selectors ?? [])],\n });\n}\n","import React from 'react';\n\nexport function createErrorBoundary(reject: (error: unknown) => void) {\n // React Server Components don't support React.Component, so it's just not defined here\n if (!React.Component) {\n return (props: { children?: React.ReactNode }) => <>{props.children}</>;\n }\n\n return class ErrorBoundary extends React.Component<{\n children: React.ReactNode;\n }> {\n componentDidCatch(error: unknown) {\n reject(error);\n }\n render() {\n return this.props.children;\n }\n };\n}\n","import { Writable } from 'node:stream';\nimport type {\n PipeableStream,\n ReactDOMServerReadableStream,\n} from 'react-dom/server.browser';\n\nexport const readStream = async (\n stream: PipeableStream | ReactDOMServerReadableStream,\n) => {\n let result = '';\n // Create a single TextDecoder instance to handle streaming properly\n // This fixes issues with multi-byte characters (e.g., CJK) being split across chunks\n const decoder = new TextDecoder('utf-8');\n\n if ('pipeTo' in stream) {\n // means it's a readable stream\n const writableStream = new WritableStream({\n write(chunk: BufferSource) {\n // Use stream: true to handle multi-byte characters split across chunks\n result += decoder.decode(chunk, { stream: true });\n },\n close() {\n // Flush any remaining bytes\n result += decoder.decode();\n },\n });\n await stream.pipeTo(writableStream);\n } else {\n const writable = new Writable({\n write(chunk: BufferSource, _encoding, callback) {\n // Use stream: true to handle multi-byte characters split across chunks\n result += decoder.decode(chunk, { stream: true });\n\n callback();\n },\n final(callback) {\n // Flush any remaining bytes\n result += decoder.decode();\n callback();\n },\n });\n await new Promise<void>((resolve, reject) => {\n writable.on('pipe', (source) => {\n source.on('error', (err: Error) => {\n writable.destroy(err);\n });\n });\n writable.on('error', reject);\n writable.on('close', () => {\n resolve();\n });\n\n stream.pipe(writable);\n });\n }\n\n return result;\n};\n","import { Suspense } from 'react';\nimport { createErrorBoundary } from '../shared/error-boundary';\nimport type { Options } from '../shared/options';\nimport { pretty } from '../shared/utils/pretty';\nimport { toPlainText } from '../shared/utils/to-plain-text';\nimport { readStream } from './read-stream';\n\nexport const render = async (node: React.ReactNode, options?: Options) => {\n const reactDOMServer = await import('react-dom/server').then((m) => {\n if ('default' in m) {\n return m.default;\n }\n return m;\n });\n\n let html!: string;\n await new Promise<void>((resolve, reject) => {\n if (\n Object.hasOwn(reactDOMServer, 'renderToReadableStream') &&\n typeof WritableStream !== 'undefined'\n ) {\n const ErrorBoundary = createErrorBoundary(reject);\n reactDOMServer\n .renderToReadableStream(\n <ErrorBoundary>\n <Suspense>{node}</Suspense>\n </ErrorBoundary>,\n {\n progressiveChunkSize: Number.POSITIVE_INFINITY,\n onError(error) {\n // Throw immediately when an error occurs to prevent CSR fallback\n reject(error);\n },\n },\n )\n .then(async (stream) => {\n await stream.allReady;\n return readStream(stream);\n })\n .then((result) => {\n html = result;\n resolve();\n })\n .catch(reject);\n } else {\n const ErrorBoundary = createErrorBoundary(reject);\n const stream = reactDOMServer.renderToPipeableStream(\n <ErrorBoundary>\n <Suspense>{node}</Suspense>\n </ErrorBoundary>,\n {\n async onAllReady() {\n html = await readStream(stream);\n resolve();\n },\n onError(error) {\n reject(error);\n },\n progressiveChunkSize: Number.POSITIVE_INFINITY,\n },\n );\n }\n });\n\n if (options?.plainText) {\n return toPlainText(html, options.htmlToTextOptions);\n }\n\n const doctype =\n '<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">';\n\n const document = `${doctype}${html.replace(/<!DOCTYPE.*?>/, '')}`;\n\n if (options?.pretty) {\n return pretty(document);\n }\n\n return document;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/shared/utils/unstable-to-plain-text.ts","../../src/shared/error-boundary.tsx","../../src/shared/utils/strip-image-preload-links.ts","../../src/node/read-stream.ts","../../src/node/render.tsx"],"sourcesContent":["import type { Options, Plugin } from 'prettier';\nimport type { builders } from 'prettier/doc';\nimport * as html from 'prettier/plugins/html';\nimport { format } from 'prettier/standalone';\n\ninterface HtmlNode {\n type?: 'element' | 'text' | 'ieConditionalComment';\n kind?: 'element' | 'text' | 'ieConditionalComment' | 'root';\n name?: string;\n sourceSpan: {\n start: { file: unknown[]; offset: number; line: number; col: number };\n end: { file: unknown[]; offset: number; line: number; col: number };\n details: null;\n };\n parent?: HtmlNode;\n}\n\nfunction getHtmlNode(path: {\n node?: HtmlNode;\n stack?: Array<Record<string, unknown>>;\n}) {\n const topNode = path.node;\n if (topNode) {\n return topNode;\n }\n\n return path.stack?.[path.stack.length - 1] as unknown as HtmlNode;\n}\n\nfunction recursivelyMapDoc(\n doc: builders.Doc,\n callback: (innerDoc: string | builders.DocCommand) => builders.Doc,\n): builders.Doc {\n if (Array.isArray(doc)) {\n return doc.map((innerDoc) => recursivelyMapDoc(innerDoc, callback));\n }\n\n if (typeof doc === 'object') {\n if (doc.type === 'line') {\n return callback(doc.soft ? '' : ' ');\n }\n\n if (doc.type === 'group') {\n return {\n ...doc,\n contents: recursivelyMapDoc(doc.contents, callback),\n expandedStates: recursivelyMapDoc(\n doc.expandedStates,\n callback,\n ) as builders.Doc[],\n };\n }\n\n if ('contents' in doc) {\n return {\n ...doc,\n contents: recursivelyMapDoc(doc.contents, callback),\n };\n }\n\n if ('parts' in doc) {\n return {\n ...doc,\n parts: recursivelyMapDoc(doc.parts, callback) as builders.Doc[],\n };\n }\n\n if (doc.type === 'if-break') {\n return {\n ...doc,\n breakContents: recursivelyMapDoc(doc.breakContents, callback),\n flatContents: recursivelyMapDoc(doc.flatContents, callback),\n };\n }\n\n const nextDoc = { ...doc } as Record<string, unknown>;\n for (const [key, value] of Object.entries(nextDoc)) {\n if (value && typeof value === 'object') {\n nextDoc[key] = recursivelyMapDoc(value as builders.Doc, callback);\n }\n }\n\n return nextDoc as unknown as builders.Doc;\n }\n\n return callback(doc);\n}\n\nconst modifiedHtml = { ...html } as Plugin;\nif (modifiedHtml.printers) {\n const previousPrint = modifiedHtml.printers.html.print;\n modifiedHtml.printers.html.print = (path, options, print, args) => {\n const node = getHtmlNode(\n path as Parameters<NonNullable<Plugin['printers']>['html']['print']>[0],\n );\n\n const rawPrintingResult = previousPrint(path, options, print, args);\n\n if (\n node?.type === 'ieConditionalComment' ||\n node?.kind === 'ieConditionalComment'\n ) {\n const printingResult = recursivelyMapDoc(rawPrintingResult, (doc) => {\n if (typeof doc === 'object' && doc.type === 'line') {\n return doc.soft ? '' : ' ';\n }\n\n return doc;\n });\n\n return printingResult;\n }\n\n return rawPrintingResult;\n };\n}\n\nconst defaults: Options = {\n endOfLine: 'lf',\n tabWidth: 2,\n plugins: [modifiedHtml],\n bracketSameLine: true,\n parser: 'html',\n};\n\nexport const pretty = (str: string, options: Options = {}) => {\n return format(str.replaceAll('\\0', ''), {\n ...defaults,\n ...options,\n });\n};\n","import {\n convert,\n type HtmlToTextOptions,\n type SelectorDefinition,\n} from 'html-to-text';\n\nexport const plainTextSelectors: SelectorDefinition[] = [\n { selector: 'img', format: 'skip' },\n { selector: '[data-skip-in-text=true]', format: 'skip' },\n {\n selector: 'a',\n options: { linkBrackets: false, hideLinkHrefIfSameAsText: true },\n },\n { selector: '[data-text-format=\"dataTable\"]', format: 'dataTable' },\n];\n\nexport function toPlainText(html: string, options?: HtmlToTextOptions) {\n return convert(html, {\n wordwrap: false,\n ...options,\n selectors: [...plainTextSelectors, ...(options?.selectors ?? [])],\n });\n}\n","import { decodeHTML, decodeHTMLAttribute } from 'entities/lib/decode.js';\nimport { type INode, type ITag, parse, SyntaxKind } from 'html5parser';\n\nconst SKIPPED_TAGS = new Set([\n 'img',\n 'noscript',\n 'script',\n 'style',\n 'template',\n]);\n\nconst WHITESPACE_RUN = /([ \\t\\n\\r\\f\\u200b]+)/;\n\n// A block prefix puts `first` before the block's content and `rest` at the\n// start of every following line: blockquote marks every line (\"> \"), a list\n// item marks the first (\" * \") and indents the rest to align nested content.\ntype BlockPrefix = { first: string; rest: string };\n\n// Everything tag-specific about how a block separates from its surroundings.\ntype Block = { open: number; close: number; prefix?: BlockPrefix };\n\n// html-to-text's default separation for the block tags implemented so far.\n// Tags absent here (and not handled in `enterNode`) are treated as inline\n// and contribute no separation — notably td/tr, which is why table cells run\n// together, matching `toPlainText` today. Only extend this alongside a spec\n// case verifying the behavior against `toPlainText`.\nconst TAG_BLOCKS: Record<string, Block> = {\n article: { open: 2, close: 2 },\n aside: { open: 2, close: 2 },\n blockquote: { open: 2, close: 2, prefix: { first: '> ', rest: '> ' } },\n div: { open: 2, close: 2 },\n footer: { open: 2, close: 2 },\n form: { open: 2, close: 2 },\n h1: { open: 3, close: 2 },\n h2: { open: 3, close: 2 },\n h3: { open: 3, close: 2 },\n h4: { open: 3, close: 2 },\n h5: { open: 3, close: 2 },\n h6: { open: 3, close: 2 },\n header: { open: 2, close: 2 },\n hr: { open: 2, close: 2 },\n main: { open: 2, close: 2 },\n nav: { open: 2, close: 2 },\n p: { open: 2, close: 2 },\n pre: { open: 2, close: 2 },\n section: { open: 2, close: 2 },\n table: { open: 2, close: 2 },\n};\n\ninterface OpenBlock {\n block: Block;\n text: string[];\n leading: number;\n stash: number;\n space: boolean;\n}\n\ninterface OrderedList {\n next: number;\n prefixLength: number;\n nested: boolean;\n}\n\ninterface WalkFrame {\n // the element whose children this frame is iterating, or undefined at root\n parent: ITag | undefined;\n children: INode[];\n index: number;\n pre: boolean;\n // whether this element opened a block, to be closed back on exit\n opened: boolean;\n // content length of the enclosing block when this element was entered;\n // exit of an <a> reads back the text written since\n textFrom: number;\n orderedList: OrderedList | undefined;\n}\n\nexport function unstableToPlainText(html: string): string {\n const tree = parse(html, { setAttributeMap: true });\n const body = findBody(tree);\n\n const blocks: OpenBlock[] = [\n {\n block: { open: 0, close: 0 },\n text: [],\n leading: 0,\n stash: 0,\n space: false,\n },\n ];\n const stack: WalkFrame[] = [\n {\n parent: body,\n children: body?.body ?? tree,\n index: 0,\n pre: false,\n opened: false,\n textFrom: 0,\n orderedList: undefined,\n },\n ];\n\n while (stack.length > 0) {\n const frame = stack[stack.length - 1];\n const node = frame.children[frame.index];\n if (node === undefined) {\n // children exhausted: the element ends here, the only moment its\n // exit effects can go out — they must follow the subtree's content\n stack.pop();\n exitElement(frame.parent, frame);\n continue;\n }\n frame.index += 1;\n enterNode(node, frame);\n }\n\n function top(): OpenBlock {\n return blocks[blocks.length - 1];\n }\n\n function writeWord(value: string) {\n const block = top();\n if (block.stash > 0) block.text.push('\\n'.repeat(block.stash));\n else if (block.space && block.text.length > 0) block.text.push(' ');\n block.stash = 0;\n block.space = false;\n block.text.push(value);\n }\n\n // enterNode descends into an element by pushing its frame; skipped\n // subtrees are skipped by simply not pushing\n function enterNode(node: INode, frame: WalkFrame) {\n if (node.type === SyntaxKind.Text) {\n const value = decodeHTML(node.value);\n if (frame.pre) {\n // whitespace is significant inside <pre>: one verbatim word,\n // newlines and all, so it never goes through collapsing\n if (value.length > 0) {\n writeWord(value);\n }\n } else {\n // splitting on a capturing group alternates strictly: even indices\n // are the words, odd indices are the whitespace runs between them —\n // so parity alone says which, no need to re-match each segment\n const segments = value.split(WHITESPACE_RUN);\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i];\n if (segment.length === 0) continue;\n if (i % 2 === 1) {\n top().space = true;\n } else {\n writeWord(segment);\n }\n }\n }\n return;\n }\n if (\n SKIPPED_TAGS.has(node.name) ||\n decodeHTMLAttribute(\n node.attributeMap?.['data-skip-in-text']?.value?.value ?? '',\n ) === 'true'\n ) {\n return;\n }\n\n const parentTag = frame.parent?.name;\n\n let block: Block | undefined = TAG_BLOCKS[node.name];\n let orderedList: OrderedList | undefined;\n if (node.name === 'ul') {\n // a list directly inside an <li> sits closer to its parent item:\n // single line breaks both ways (html-to-text's isNestedList)\n const breaks = parentTag === 'li' ? 1 : 2;\n block = { open: breaks, close: breaks };\n } else if (node.name === 'li' && parentTag === 'ul') {\n // whether the enclosing list is itself nested decides the item's\n // prefix; `frame` belongs to that list, so the list's own parent is\n // one frame below the top of the stack\n const grandparent = stack[stack.length - 2]?.parent;\n const inNestedList = grandparent?.name === 'li';\n block = {\n open: 1,\n close: 1,\n prefix: inNestedList\n ? { first: '* ', rest: ' ' }\n : { first: ' * ', rest: ' ' },\n };\n } else if (node.name === 'ol') {\n const nested = parentTag === 'li';\n const parsedStart = Number.parseInt(\n decodeHTMLAttribute(node.attributeMap?.start?.value?.value ?? '1'),\n 10,\n );\n const start = Number.isNaN(parsedStart) ? 1 : parsedStart;\n const itemCount =\n node.body?.filter(\n (child) => child.type === SyntaxKind.Tag && child.name === 'li',\n ).length ?? 0;\n let prefixLength = 0;\n for (let index = start; index < start + itemCount; index++) {\n const prefix = `${nested ? '' : ' '}${index}. `;\n prefixLength = Math.max(prefixLength, prefix.length);\n }\n const breaks = nested ? 1 : 2;\n block = { open: breaks, close: breaks };\n orderedList = { next: start, prefixLength, nested };\n } else if (node.name === 'li' && parentTag === 'ol' && frame.orderedList) {\n const list = frame.orderedList;\n const prefix = `${list.nested ? '' : ' '}${list.next++}. `.padEnd(\n list.prefixLength,\n );\n block = {\n open: 1,\n close: 1,\n prefix: { first: prefix, rest: ' '.repeat(list.prefixLength) },\n };\n }\n\n if (block) {\n blocks.push({\n block,\n text: [],\n leading: block.open,\n stash: 0,\n space: false,\n });\n }\n\n if (node.name === 'hr') {\n writeWord('-'.repeat(40));\n } else if (node.name === 'br') {\n top().space = false;\n top().text.push('\\n');\n }\n\n stack.push({\n parent: node,\n children: node.body ?? [],\n index: 0,\n pre: frame.pre || node.name === 'pre',\n opened: block !== undefined,\n textFrom: top().text.length,\n orderedList,\n });\n }\n\n function exitElement(element: ITag | undefined, frame: WalkFrame) {\n if (element === undefined) return;\n\n if (element.name === 'a') {\n const href = decodeHTMLAttribute(\n element.attributeMap?.href?.value?.value ?? '',\n ).replace(/^mailto:/, '');\n // fragment-only hrefs are suppressed (html-to-text's noAnchorUrl)\n if (href.length > 0 && !href.startsWith('#')) {\n // the enclosing block's content doubles as the buffer of the\n // anchor's text: what was written since the anchor opened is\n // compared against the href to decide whether to append it\n // (html-to-text's hideLinkHrefIfSameAsText, which `toPlainText`\n // enables)\n const anchorText = top().text.slice(frame.textFrom).join('');\n if (anchorText !== href) {\n if (anchorText.length > 0) top().space = true;\n writeWord(href);\n }\n }\n }\n\n if (frame.opened) {\n closeBlock();\n }\n }\n\n function closeBlock() {\n const child = blocks.pop();\n if (child === undefined) return;\n const parent = blocks[blocks.length - 1];\n\n let content = child.text.join('');\n const prefix = child.block.prefix;\n if (prefix !== undefined) {\n const trimmed = content.replace(/^\\n+|\\n+$/g, '');\n content = prefix.first + trimmed.replaceAll('\\n', `\\n${prefix.rest}`);\n }\n\n const breaks = Math.max(parent.stash, child.leading);\n if (parent.text.length > 0) {\n parent.text.push('\\n'.repeat(breaks));\n if (content.length > 0) parent.text.push(content);\n } else {\n // nothing to separate from yet: the separation propagates upward\n // instead of materializing — this is edge suppression, and also how\n // empty blocks nested in empty blocks still commit their breaks\n if (content.length > 0) parent.text.push(content);\n parent.leading = breaks;\n }\n parent.stash = Math.max(child.stash, child.block.close);\n }\n\n return blocks[0].text.join('');\n}\n\nfunction findBody(tree: INode[]): ITag | undefined {\n for (const child of tree) {\n if (child.type !== SyntaxKind.Tag || child.name !== 'html') continue;\n for (const inner of child.body ?? []) {\n if (inner.type === SyntaxKind.Tag && inner.name === 'body') return inner;\n }\n }\n return undefined;\n}\n","import React from 'react';\n\nexport function createErrorBoundary(reject: (error: unknown) => void) {\n // React Server Components don't support React.Component, so it's just not defined here\n if (!React.Component) {\n return (props: { children?: React.ReactNode }) => <>{props.children}</>;\n }\n\n return class ErrorBoundary extends React.Component<{\n children: React.ReactNode;\n }> {\n componentDidCatch(error: unknown) {\n reject(error);\n }\n render() {\n return this.props.children;\n }\n };\n}\n","/**\n * React injects `<link rel=\"preload\" as=\"image\" />` resource hints into the\n * document `<head>` for every `<img>` it renders during server-side rendering.\n *\n * These hints are meant for browsers loading a web page, where they can speed\n * up the initial paint. In an email they are dead weight: email clients ignore\n * `<link rel=\"preload\">`, and the tags only add noise to the rendered HTML.\n *\n * @see https://github.com/resend/react-email/issues/3034\n *\n * This removes only those auto-injected image preload links, leaving every\n * other `<link>` (stylesheets, fonts, user-authored non-image preloads, ...)\n * untouched. It parses each `<link>` tag's attributes instead of relying on a\n * fixed string match, so it is not affected by attribute order or spacing.\n */\nexport const stripImagePreloadLinks = (html: string): string => {\n return html.replace(/<link\\b[^>]*\\/?>/gi, (tag) =>\n isImagePreloadLink(tag) ? '' : tag,\n );\n};\n\nconst isImagePreloadLink = (tag: string): boolean => {\n const attributes = parseAttributes(tag);\n return attributes.rel === 'preload' && attributes.as === 'image';\n};\n\nconst ATTRIBUTE_PATTERN =\n /([a-z][a-z0-9-]*)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'>]+)))?/gi;\n\n/**\n * Parses the attributes of a single, already-isolated HTML tag string (e.g.\n * `<link rel=\"preload\" as=\"image\" href=\"...\" />`) into a name → value map.\n * Attribute names are lower-cased; values are read from double, single, or\n * unquoted forms.\n */\nconst parseAttributes = (tag: string): Record<string, string> => {\n // Skip the tag name (`<link`) so it is not read as an attribute.\n const attributeSection = tag.replace(/^<[a-z][a-z0-9-]*/i, '');\n\n const attributes: Record<string, string> = {};\n for (const [\n ,\n name,\n doubleQuoted,\n singleQuoted,\n unquoted,\n ] of attributeSection.matchAll(ATTRIBUTE_PATTERN)) {\n attributes[name.toLowerCase()] =\n doubleQuoted ?? singleQuoted ?? unquoted ?? '';\n }\n\n return attributes;\n};\n","import { Writable } from 'node:stream';\nimport type {\n PipeableStream,\n ReactDOMServerReadableStream,\n} from 'react-dom/server.browser';\n\nexport const readStream = async (\n stream: PipeableStream | ReactDOMServerReadableStream,\n) => {\n let result = '';\n // Create a single TextDecoder instance to handle streaming properly\n // This fixes issues with multi-byte characters (e.g., CJK) being split across chunks\n const decoder = new TextDecoder('utf-8');\n\n if ('pipeTo' in stream) {\n // means it's a readable stream\n const writableStream = new WritableStream({\n write(chunk: BufferSource) {\n // Use stream: true to handle multi-byte characters split across chunks\n result += decoder.decode(chunk, { stream: true });\n },\n close() {\n // Flush any remaining bytes\n result += decoder.decode();\n },\n });\n await stream.pipeTo(writableStream);\n } else {\n const writable = new Writable({\n write(chunk: BufferSource, _encoding, callback) {\n // Use stream: true to handle multi-byte characters split across chunks\n result += decoder.decode(chunk, { stream: true });\n\n callback();\n },\n final(callback) {\n // Flush any remaining bytes\n result += decoder.decode();\n callback();\n },\n });\n await new Promise<void>((resolve, reject) => {\n writable.on('pipe', (source) => {\n source.on('error', (err: Error) => {\n writable.destroy(err);\n });\n });\n writable.on('error', reject);\n writable.on('close', () => {\n resolve();\n });\n\n stream.pipe(writable);\n });\n }\n\n return result;\n};\n","import { Suspense } from 'react';\nimport { createErrorBoundary } from '../shared/error-boundary';\nimport type { Options } from '../shared/options';\nimport { pretty } from '../shared/utils/pretty';\nimport { stripImagePreloadLinks } from '../shared/utils/strip-image-preload-links';\nimport { toPlainText } from '../shared/utils/to-plain-text';\nimport { unstableToPlainText } from '../shared/utils/unstable-to-plain-text';\nimport { readStream } from './read-stream';\n\nexport const render = async (node: React.ReactNode, options?: Options) => {\n const reactDOMServer = await import('react-dom/server').then((m) => {\n if ('default' in m) {\n return m.default;\n }\n return m;\n });\n\n let html!: string;\n await new Promise<void>((resolve, reject) => {\n if (\n Object.hasOwn(reactDOMServer, 'renderToReadableStream') &&\n typeof WritableStream !== 'undefined'\n ) {\n const ErrorBoundary = createErrorBoundary(reject);\n reactDOMServer\n .renderToReadableStream(\n <ErrorBoundary>\n <Suspense>{node}</Suspense>\n </ErrorBoundary>,\n {\n progressiveChunkSize: Number.POSITIVE_INFINITY,\n onError(error) {\n // Throw immediately when an error occurs to prevent CSR fallback\n reject(error);\n },\n },\n )\n .then(async (stream) => {\n await stream.allReady;\n return readStream(stream);\n })\n .then((result) => {\n html = result;\n resolve();\n })\n .catch(reject);\n } else {\n const ErrorBoundary = createErrorBoundary(reject);\n const stream = reactDOMServer.renderToPipeableStream(\n <ErrorBoundary>\n <Suspense>{node}</Suspense>\n </ErrorBoundary>,\n {\n async onAllReady() {\n html = await readStream(stream).then((s: string) => {\n // Workaround for https://github.com/facebook/react/pull/26228\n // (fixed in React 19, not backported to 18)\n return s.replaceAll('\\0', '');\n });\n resolve();\n },\n onError(error) {\n reject(error);\n },\n progressiveChunkSize: Number.POSITIVE_INFINITY,\n },\n );\n }\n });\n\n html = stripImagePreloadLinks(html);\n\n if (options?.plainText) {\n return options.unstableTextConversion\n ? unstableToPlainText(html)\n : toPlainText(html, options.htmlToTextOptions);\n }\n\n const doctype =\n '<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">';\n\n const document = `${doctype}${html.replace(/<!DOCTYPE.*?>/, '')}`;\n\n if (options?.pretty) {\n return pretty(document);\n }\n\n return document;\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@depup/react-email__render",
3
- "version": "2.0.5-depup.0",
3
+ "version": "2.1.0-depup.4",
4
4
  "description": "Transform React components into HTML email templates (with updated dependencies)",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/browser/index.cjs",
@@ -53,16 +53,6 @@
53
53
  "default": "./dist/edge/index.cjs"
54
54
  }
55
55
  },
56
- "convex": {
57
- "import": {
58
- "types": "./dist/edge/index.d.mts",
59
- "default": "./dist/edge/index.mjs"
60
- },
61
- "require": {
62
- "types": "./dist/edge/index.d.cts",
63
- "default": "./dist/edge/index.cjs"
64
- }
65
- },
66
56
  "node": {
67
57
  "import": {
68
58
  "types": "./dist/node/index.d.mts",
@@ -73,6 +63,16 @@
73
63
  "default": "./dist/node/index.cjs"
74
64
  }
75
65
  },
66
+ "convex": {
67
+ "import": {
68
+ "types": "./dist/edge/index.d.mts",
69
+ "default": "./dist/edge/index.mjs"
70
+ },
71
+ "require": {
72
+ "types": "./dist/edge/index.d.cts",
73
+ "default": "./dist/edge/index.cjs"
74
+ }
75
+ },
76
76
  "browser": {
77
77
  "import": {
78
78
  "types": "./dist/browser/index.d.mts",
@@ -101,6 +101,10 @@
101
101
  "url": "https://github.com/resend/react-email.git",
102
102
  "directory": "packages/render"
103
103
  },
104
+ "bugs": {
105
+ "url": "https://github.com/resend/react-email/issues"
106
+ },
107
+ "homepage": "https://react.email",
104
108
  "keywords": [
105
109
  "@react-email/render",
106
110
  "depup",
@@ -115,8 +119,10 @@
115
119
  "node": ">=20.0.0"
116
120
  },
117
121
  "dependencies": {
118
- "html-to-text": "^9.0.5",
119
- "prettier": "^3.8.1"
122
+ "entities": "^8.0.0",
123
+ "html-to-text": "^10.0.0",
124
+ "html5parser": "^3.0.0",
125
+ "prettier": "^3.9.6"
120
126
  },
121
127
  "peerDependencies": {
122
128
  "react": "^18.0 || ^19.0 || ^19.0.0-rc",
@@ -125,8 +131,14 @@
125
131
  "devDependencies": {
126
132
  "@edge-runtime/vm": "5.0.0",
127
133
  "@types/html-to-text": "9.0.4",
134
+ "@types/react": "19.2.14",
135
+ "@types/react-dom": "19.2.3",
136
+ "@types/shelljs": "0.10.0",
128
137
  "jsdom": "26.1.0",
138
+ "playwright": "1.59.1",
139
+ "shelljs": "0.10.0",
129
140
  "typescript": "5.9.3",
141
+ "yalc": "1.0.0-pre.53",
130
142
  "tsconfig": "0.0.0"
131
143
  },
132
144
  "scripts": {
@@ -134,19 +146,28 @@
134
146
  "build:watch": "tsdown --watch",
135
147
  "clean": "rm -rf dist",
136
148
  "test": "vitest run",
149
+ "test:e2e": "vitest run --config vitest.e2e.config.ts",
137
150
  "test:watch": "vitest"
138
151
  },
139
152
  "depup": {
140
153
  "changes": {
154
+ "entities": {
155
+ "from": "^4.5.0",
156
+ "to": "^8.0.0"
157
+ },
158
+ "html-to-text": {
159
+ "from": "^9.0.5",
160
+ "to": "^10.0.0"
161
+ },
141
162
  "prettier": {
142
163
  "from": "^3.5.3",
143
- "to": "^3.8.1"
164
+ "to": "^3.9.6"
144
165
  }
145
166
  },
146
- "depsUpdated": 1,
167
+ "depsUpdated": 3,
147
168
  "originalPackage": "@react-email/render",
148
- "originalVersion": "2.0.5",
149
- "processedAt": "2026-04-01T00:43:21.325Z",
169
+ "originalVersion": "2.1.0",
170
+ "processedAt": "2026-07-21T16:07:09.034Z",
150
171
  "smokeTest": "failed"
151
172
  }
152
173
  }