@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.
package/README.md CHANGED
@@ -13,16 +13,18 @@ npm install @depup/react-email__render
13
13
 
14
14
  | Field | Value |
15
15
  |-------|-------|
16
- | Original | [@react-email/render](https://www.npmjs.com/package/@react-email/render) @ 2.0.5 |
17
- | Processed | 2026-04-01 |
16
+ | Original | [@react-email/render](https://www.npmjs.com/package/@react-email/render) @ 2.1.0 |
17
+ | Processed | 2026-07-21 |
18
18
  | Smoke test | failed |
19
- | Deps updated | 1 |
19
+ | Deps updated | 3 |
20
20
 
21
21
  ## Dependency Changes
22
22
 
23
23
  | Dependency | From | To |
24
24
  |------------|------|-----|
25
- | prettier | ^3.5.3 | ^3.8.1 |
25
+ | entities | ^4.5.0 | ^8.0.0 |
26
+ | html-to-text | ^9.0.5 | ^10.0.0 |
27
+ | prettier | ^3.5.3 | ^3.9.6 |
26
28
 
27
29
  ---
28
30
 
package/changes.json CHANGED
@@ -1,10 +1,18 @@
1
1
  {
2
2
  "bumped": {
3
+ "entities": {
4
+ "from": "^4.5.0",
5
+ "to": "^8.0.0"
6
+ },
7
+ "html-to-text": {
8
+ "from": "^9.0.5",
9
+ "to": "^10.0.0"
10
+ },
3
11
  "prettier": {
4
12
  "from": "^3.5.3",
5
- "to": "^3.8.1"
13
+ "to": "^3.9.6"
6
14
  }
7
15
  },
8
- "timestamp": "2026-04-01T00:43:16.039Z",
9
- "totalUpdated": 1
16
+ "timestamp": "2026-07-21T16:06:58.573Z",
17
+ "totalUpdated": 3
10
18
  }
@@ -1,4 +1,5 @@
1
- //#region rolldown:runtime
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
2
3
  var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -6,16 +7,12 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
6
7
  var __getProtoOf = Object.getPrototypeOf;
7
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
9
  var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") {
10
- for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
- key = keys[i];
12
- if (!__hasOwnProp.call(to, key) && key !== except) {
13
- __defProp(to, key, {
14
- get: ((k) => from[k]).bind(null, key),
15
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
- });
17
- }
18
- }
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
19
16
  }
20
17
  return to;
21
18
  };
@@ -23,7 +20,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
20
  value: mod,
24
21
  enumerable: true
25
22
  }) : target, mod));
26
-
27
23
  //#endregion
28
24
  let prettier_plugins_html = require("prettier/plugins/html");
29
25
  prettier_plugins_html = __toESM(prettier_plugins_html);
@@ -31,8 +27,9 @@ let prettier_standalone = require("prettier/standalone");
31
27
  let html_to_text = require("html-to-text");
32
28
  let react = require("react");
33
29
  react = __toESM(react);
30
+ let entities_lib_decode_js = require("entities/lib/decode.js");
31
+ let html5parser = require("html5parser");
34
32
  let react_jsx_runtime = require("react/jsx-runtime");
35
-
36
33
  //#region src/shared/utils/pretty.ts
37
34
  function getHtmlNode(path) {
38
35
  const topNode = path.node;
@@ -93,7 +90,6 @@ const pretty = (str, options = {}) => {
93
90
  ...options
94
91
  });
95
92
  };
96
-
97
93
  //#endregion
98
94
  //#region src/shared/utils/to-plain-text.ts
99
95
  const plainTextSelectors = [
@@ -111,6 +107,10 @@ const plainTextSelectors = [
111
107
  linkBrackets: false,
112
108
  hideLinkHrefIfSameAsText: true
113
109
  }
110
+ },
111
+ {
112
+ selector: "[data-text-format=\"dataTable\"]",
113
+ format: "dataTable"
114
114
  }
115
115
  ];
116
116
  function toPlainText(html, options) {
@@ -120,7 +120,278 @@ function toPlainText(html, options) {
120
120
  selectors: [...plainTextSelectors, ...options?.selectors ?? []]
121
121
  });
122
122
  }
123
-
123
+ //#endregion
124
+ //#region src/shared/utils/unstable-to-plain-text.ts
125
+ const SKIPPED_TAGS = new Set([
126
+ "img",
127
+ "noscript",
128
+ "script",
129
+ "style",
130
+ "template"
131
+ ]);
132
+ const WHITESPACE_RUN = /([ \t\n\r\f\u200b]+)/;
133
+ const TAG_BLOCKS = {
134
+ article: {
135
+ open: 2,
136
+ close: 2
137
+ },
138
+ aside: {
139
+ open: 2,
140
+ close: 2
141
+ },
142
+ blockquote: {
143
+ open: 2,
144
+ close: 2,
145
+ prefix: {
146
+ first: "> ",
147
+ rest: "> "
148
+ }
149
+ },
150
+ div: {
151
+ open: 2,
152
+ close: 2
153
+ },
154
+ footer: {
155
+ open: 2,
156
+ close: 2
157
+ },
158
+ form: {
159
+ open: 2,
160
+ close: 2
161
+ },
162
+ h1: {
163
+ open: 3,
164
+ close: 2
165
+ },
166
+ h2: {
167
+ open: 3,
168
+ close: 2
169
+ },
170
+ h3: {
171
+ open: 3,
172
+ close: 2
173
+ },
174
+ h4: {
175
+ open: 3,
176
+ close: 2
177
+ },
178
+ h5: {
179
+ open: 3,
180
+ close: 2
181
+ },
182
+ h6: {
183
+ open: 3,
184
+ close: 2
185
+ },
186
+ header: {
187
+ open: 2,
188
+ close: 2
189
+ },
190
+ hr: {
191
+ open: 2,
192
+ close: 2
193
+ },
194
+ main: {
195
+ open: 2,
196
+ close: 2
197
+ },
198
+ nav: {
199
+ open: 2,
200
+ close: 2
201
+ },
202
+ p: {
203
+ open: 2,
204
+ close: 2
205
+ },
206
+ pre: {
207
+ open: 2,
208
+ close: 2
209
+ },
210
+ section: {
211
+ open: 2,
212
+ close: 2
213
+ },
214
+ table: {
215
+ open: 2,
216
+ close: 2
217
+ }
218
+ };
219
+ function unstableToPlainText(html) {
220
+ const tree = (0, html5parser.parse)(html, { setAttributeMap: true });
221
+ const body = findBody(tree);
222
+ const blocks = [{
223
+ block: {
224
+ open: 0,
225
+ close: 0
226
+ },
227
+ text: [],
228
+ leading: 0,
229
+ stash: 0,
230
+ space: false
231
+ }];
232
+ const stack = [{
233
+ parent: body,
234
+ children: body?.body ?? tree,
235
+ index: 0,
236
+ pre: false,
237
+ opened: false,
238
+ textFrom: 0,
239
+ orderedList: void 0
240
+ }];
241
+ while (stack.length > 0) {
242
+ const frame = stack[stack.length - 1];
243
+ const node = frame.children[frame.index];
244
+ if (node === void 0) {
245
+ stack.pop();
246
+ exitElement(frame.parent, frame);
247
+ continue;
248
+ }
249
+ frame.index += 1;
250
+ enterNode(node, frame);
251
+ }
252
+ function top() {
253
+ return blocks[blocks.length - 1];
254
+ }
255
+ function writeWord(value) {
256
+ const block = top();
257
+ if (block.stash > 0) block.text.push("\n".repeat(block.stash));
258
+ else if (block.space && block.text.length > 0) block.text.push(" ");
259
+ block.stash = 0;
260
+ block.space = false;
261
+ block.text.push(value);
262
+ }
263
+ function enterNode(node, frame) {
264
+ if (node.type === html5parser.SyntaxKind.Text) {
265
+ const value = (0, entities_lib_decode_js.decodeHTML)(node.value);
266
+ if (frame.pre) {
267
+ if (value.length > 0) writeWord(value);
268
+ } else {
269
+ const segments = value.split(WHITESPACE_RUN);
270
+ for (let i = 0; i < segments.length; i++) {
271
+ const segment = segments[i];
272
+ if (segment.length === 0) continue;
273
+ if (i % 2 === 1) top().space = true;
274
+ else writeWord(segment);
275
+ }
276
+ }
277
+ return;
278
+ }
279
+ if (SKIPPED_TAGS.has(node.name) || (0, entities_lib_decode_js.decodeHTMLAttribute)(node.attributeMap?.["data-skip-in-text"]?.value?.value ?? "") === "true") return;
280
+ const parentTag = frame.parent?.name;
281
+ let block = TAG_BLOCKS[node.name];
282
+ let orderedList;
283
+ if (node.name === "ul") {
284
+ const breaks = parentTag === "li" ? 1 : 2;
285
+ block = {
286
+ open: breaks,
287
+ close: breaks
288
+ };
289
+ } else if (node.name === "li" && parentTag === "ul") block = {
290
+ open: 1,
291
+ close: 1,
292
+ prefix: (stack[stack.length - 2]?.parent)?.name === "li" ? {
293
+ first: "* ",
294
+ rest: " "
295
+ } : {
296
+ first: " * ",
297
+ rest: " "
298
+ }
299
+ };
300
+ else if (node.name === "ol") {
301
+ const nested = parentTag === "li";
302
+ const parsedStart = Number.parseInt((0, entities_lib_decode_js.decodeHTMLAttribute)(node.attributeMap?.start?.value?.value ?? "1"), 10);
303
+ const start = Number.isNaN(parsedStart) ? 1 : parsedStart;
304
+ const itemCount = node.body?.filter((child) => child.type === html5parser.SyntaxKind.Tag && child.name === "li").length ?? 0;
305
+ let prefixLength = 0;
306
+ for (let index = start; index < start + itemCount; index++) {
307
+ const prefix = `${nested ? "" : " "}${index}. `;
308
+ prefixLength = Math.max(prefixLength, prefix.length);
309
+ }
310
+ const breaks = nested ? 1 : 2;
311
+ block = {
312
+ open: breaks,
313
+ close: breaks
314
+ };
315
+ orderedList = {
316
+ next: start,
317
+ prefixLength,
318
+ nested
319
+ };
320
+ } else if (node.name === "li" && parentTag === "ol" && frame.orderedList) {
321
+ const list = frame.orderedList;
322
+ block = {
323
+ open: 1,
324
+ close: 1,
325
+ prefix: {
326
+ first: `${list.nested ? "" : " "}${list.next++}. `.padEnd(list.prefixLength),
327
+ rest: " ".repeat(list.prefixLength)
328
+ }
329
+ };
330
+ }
331
+ if (block) blocks.push({
332
+ block,
333
+ text: [],
334
+ leading: block.open,
335
+ stash: 0,
336
+ space: false
337
+ });
338
+ if (node.name === "hr") writeWord("-".repeat(40));
339
+ else if (node.name === "br") {
340
+ top().space = false;
341
+ top().text.push("\n");
342
+ }
343
+ stack.push({
344
+ parent: node,
345
+ children: node.body ?? [],
346
+ index: 0,
347
+ pre: frame.pre || node.name === "pre",
348
+ opened: block !== void 0,
349
+ textFrom: top().text.length,
350
+ orderedList
351
+ });
352
+ }
353
+ function exitElement(element, frame) {
354
+ if (element === void 0) return;
355
+ if (element.name === "a") {
356
+ const href = (0, entities_lib_decode_js.decodeHTMLAttribute)(element.attributeMap?.href?.value?.value ?? "").replace(/^mailto:/, "");
357
+ if (href.length > 0 && !href.startsWith("#")) {
358
+ const anchorText = top().text.slice(frame.textFrom).join("");
359
+ if (anchorText !== href) {
360
+ if (anchorText.length > 0) top().space = true;
361
+ writeWord(href);
362
+ }
363
+ }
364
+ }
365
+ if (frame.opened) closeBlock();
366
+ }
367
+ function closeBlock() {
368
+ const child = blocks.pop();
369
+ if (child === void 0) return;
370
+ const parent = blocks[blocks.length - 1];
371
+ let content = child.text.join("");
372
+ const prefix = child.block.prefix;
373
+ if (prefix !== void 0) {
374
+ const trimmed = content.replace(/^\n+|\n+$/g, "");
375
+ content = prefix.first + trimmed.replaceAll("\n", `\n${prefix.rest}`);
376
+ }
377
+ const breaks = Math.max(parent.stash, child.leading);
378
+ if (parent.text.length > 0) {
379
+ parent.text.push("\n".repeat(breaks));
380
+ if (content.length > 0) parent.text.push(content);
381
+ } else {
382
+ if (content.length > 0) parent.text.push(content);
383
+ parent.leading = breaks;
384
+ }
385
+ parent.stash = Math.max(child.stash, child.block.close);
386
+ }
387
+ return blocks[0].text.join("");
388
+ }
389
+ function findBody(tree) {
390
+ for (const child of tree) {
391
+ if (child.type !== html5parser.SyntaxKind.Tag || child.name !== "html") continue;
392
+ for (const inner of child.body ?? []) if (inner.type === html5parser.SyntaxKind.Tag && inner.name === "body") return inner;
393
+ }
394
+ }
124
395
  //#endregion
125
396
  //#region src/shared/error-boundary.tsx
126
397
  function createErrorBoundary(reject) {
@@ -134,7 +405,43 @@ function createErrorBoundary(reject) {
134
405
  }
135
406
  };
136
407
  }
137
-
408
+ //#endregion
409
+ //#region src/shared/utils/strip-image-preload-links.ts
410
+ /**
411
+ * React injects `<link rel="preload" as="image" />` resource hints into the
412
+ * document `<head>` for every `<img>` it renders during server-side rendering.
413
+ *
414
+ * These hints are meant for browsers loading a web page, where they can speed
415
+ * up the initial paint. In an email they are dead weight: email clients ignore
416
+ * `<link rel="preload">`, and the tags only add noise to the rendered HTML.
417
+ *
418
+ * @see https://github.com/resend/react-email/issues/3034
419
+ *
420
+ * This removes only those auto-injected image preload links, leaving every
421
+ * other `<link>` (stylesheets, fonts, user-authored non-image preloads, ...)
422
+ * untouched. It parses each `<link>` tag's attributes instead of relying on a
423
+ * fixed string match, so it is not affected by attribute order or spacing.
424
+ */
425
+ const stripImagePreloadLinks = (html) => {
426
+ return html.replace(/<link\b[^>]*\/?>/gi, (tag) => isImagePreloadLink(tag) ? "" : tag);
427
+ };
428
+ const isImagePreloadLink = (tag) => {
429
+ const attributes = parseAttributes(tag);
430
+ return attributes.rel === "preload" && attributes.as === "image";
431
+ };
432
+ const ATTRIBUTE_PATTERN = /([a-z][a-z0-9-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+)))?/gi;
433
+ /**
434
+ * Parses the attributes of a single, already-isolated HTML tag string (e.g.
435
+ * `<link rel="preload" as="image" href="..." />`) into a name → value map.
436
+ * Attribute names are lower-cased; values are read from double, single, or
437
+ * unquoted forms.
438
+ */
439
+ const parseAttributes = (tag) => {
440
+ const attributeSection = tag.replace(/^<[a-z][a-z0-9-]*/i, "");
441
+ const attributes = {};
442
+ for (const [, name, doubleQuoted, singleQuoted, unquoted] of attributeSection.matchAll(ATTRIBUTE_PATTERN)) attributes[name.toLowerCase()] = doubleQuoted ?? singleQuoted ?? unquoted ?? "";
443
+ return attributes;
444
+ };
138
445
  //#endregion
139
446
  //#region src/shared/read-stream.browser.ts
140
447
  const decoder = new TextDecoder("utf-8");
@@ -161,7 +468,6 @@ const readStream = async (stream) => {
161
468
  });
162
469
  return decoder.decode(mergedChunks);
163
470
  };
164
-
165
471
  //#endregion
166
472
  //#region src/browser/render.tsx
167
473
  const render = async (node, options) => {
@@ -179,16 +485,15 @@ const render = async (node, options) => {
179
485
  }).then(async (stream) => {
180
486
  await stream.allReady;
181
487
  return readStream(stream);
182
- }).then(resolve).catch(reject);
488
+ }).then((result) => resolve(stripImagePreloadLinks(result))).catch(reject);
183
489
  });
184
- if (options?.plainText) return toPlainText(html, options.htmlToTextOptions);
490
+ if (options?.plainText) return options.unstableTextConversion ? unstableToPlainText(html) : toPlainText(html, options.htmlToTextOptions);
185
491
  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.*?>/, "")}`;
186
492
  if (options?.pretty) return pretty(document);
187
493
  return document;
188
494
  };
189
-
190
495
  //#endregion
191
496
  exports.plainTextSelectors = plainTextSelectors;
192
497
  exports.pretty = pretty;
193
498
  exports.render = render;
194
- exports.toPlainText = toPlainText;
499
+ exports.toPlainText = toPlainText;
@@ -24,6 +24,16 @@ type Options = {
24
24
  * @see https://github.com/html-to-text/node-html-to-text
25
25
  */
26
26
  htmlToTextOptions?: HtmlToTextOptions;
27
+ unstableTextConversion?: false;
28
+ } | {
29
+ plainText?: true;
30
+ /**
31
+ * Converts to plain text with an in-house formatter instead of
32
+ * html-to-text, so it doesn't take `htmlToTextOptions`.
33
+ *
34
+ * @see {@link unstableToPlainText}
35
+ */
36
+ unstableTextConversion: true;
27
37
  });
28
38
  //#endregion
29
39
  //#region src/shared/utils/pretty.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/shared/options.ts","../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/browser/render.tsx"],"sourcesContent":[],"mappings":";;;;KAIY,OAAA;;;AAAZ;;;;ACyHA;;;;ECvHa;AASb;;;;ACTA;;;;;sBHqB0B;;;;cCkGb,gCAAgC,cAAY;;;cCvH5C,oBAAoB;iBASjB,WAAA,yBAAoC;;;cCTvC,eAAsB,KAAA,CAAM,qBAAqB,YAAO"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/shared/options.ts","../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/browser/render.tsx"],"mappings":";;;;KAKY,OAAA;;;AAAZ;EAIE,MAAA;AAAA;EAmByC;;;EAbrC,SAAA;AAAA;EAaoB;;;EAPpB,SAAA;EAkBsB;;;;;ACsF5B;EDjGM,iBAAA,GAAoB,iBAAA;EACpB,sBAAA;AAAA;EAGA,SAAA;EC6FuC;;;;;;EDtFvC,sBAAA;AAAA;;;cCsFO,MAAA,GAAU,GAAA,UAAa,OAAA,GAAS,SAAA,KAAY,OAAA;;;cCvH5C,kBAAA,EAAoB,kBAAA;AAAA,iBAUjB,WAAA,CAAY,IAAA,UAAc,OAAA,GAAU,iBAAA;;;cCTvC,MAAA,GAAgB,IAAA,EAAM,KAAA,CAAM,SAAA,EAAW,OAAA,GAAU,OAAA,KAAO,OAAA"}
@@ -24,6 +24,16 @@ type Options = {
24
24
  * @see https://github.com/html-to-text/node-html-to-text
25
25
  */
26
26
  htmlToTextOptions?: HtmlToTextOptions;
27
+ unstableTextConversion?: false;
28
+ } | {
29
+ plainText?: true;
30
+ /**
31
+ * Converts to plain text with an in-house formatter instead of
32
+ * html-to-text, so it doesn't take `htmlToTextOptions`.
33
+ *
34
+ * @see {@link unstableToPlainText}
35
+ */
36
+ unstableTextConversion: true;
27
37
  });
28
38
  //#endregion
29
39
  //#region src/shared/utils/pretty.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/shared/options.ts","../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/browser/render.tsx"],"sourcesContent":[],"mappings":";;;;KAIY,OAAA;;;AAAZ;;;;ACyHA;;;;ECvHa;AASb;;;;ACTA;;;;;sBHqB0B;;;;cCkGb,gCAAgC,cAAY;;;cCvH5C,oBAAoB;iBASjB,WAAA,yBAAoC;;;cCTvC,eAAsB,KAAA,CAAM,qBAAqB,YAAO"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/shared/options.ts","../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/browser/render.tsx"],"mappings":";;;;KAKY,OAAA;;;AAAZ;EAIE,MAAA;AAAA;EAmByC;;;EAbrC,SAAA;AAAA;EAaoB;;;EAPpB,SAAA;EAkBsB;;;;;ACsF5B;EDjGM,iBAAA,GAAoB,iBAAA;EACpB,sBAAA;AAAA;EAGA,SAAA;EC6FuC;;;;;;EDtFvC,sBAAA;AAAA;;;cCsFO,MAAA,GAAU,GAAA,UAAa,OAAA,GAAS,SAAA,KAAY,OAAA;;;cCvH5C,kBAAA,EAAoB,kBAAA;AAAA,iBAUjB,WAAA,CAAY,IAAA,UAAc,OAAA,GAAU,iBAAA;;;cCTvC,MAAA,GAAgB,IAAA,EAAM,KAAA,CAAM,SAAA,EAAW,OAAA,GAAU,OAAA,KAAO,OAAA"}