@depup/react-email__render 2.0.6-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,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,17 +20,17 @@ 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);
30
26
  let prettier_standalone = require("prettier/standalone");
31
27
  let html_to_text = require("html-to-text");
28
+ let entities_lib_decode_js = require("entities/lib/decode.js");
29
+ let html5parser = require("html5parser");
32
30
  let react = require("react");
33
31
  react = __toESM(react);
34
32
  let react_jsx_runtime = require("react/jsx-runtime");
35
33
  let node_stream = require("node:stream");
36
-
37
34
  //#region src/shared/utils/pretty.ts
38
35
  function getHtmlNode(path) {
39
36
  const topNode = path.node;
@@ -94,7 +91,6 @@ const pretty = (str, options = {}) => {
94
91
  ...options
95
92
  });
96
93
  };
97
-
98
94
  //#endregion
99
95
  //#region src/shared/utils/to-plain-text.ts
100
96
  const plainTextSelectors = [
@@ -112,6 +108,10 @@ const plainTextSelectors = [
112
108
  linkBrackets: false,
113
109
  hideLinkHrefIfSameAsText: true
114
110
  }
111
+ },
112
+ {
113
+ selector: "[data-text-format=\"dataTable\"]",
114
+ format: "dataTable"
115
115
  }
116
116
  ];
117
117
  function toPlainText(html, options) {
@@ -121,7 +121,278 @@ function toPlainText(html, options) {
121
121
  selectors: [...plainTextSelectors, ...options?.selectors ?? []]
122
122
  });
123
123
  }
124
-
124
+ //#endregion
125
+ //#region src/shared/utils/unstable-to-plain-text.ts
126
+ const SKIPPED_TAGS = new Set([
127
+ "img",
128
+ "noscript",
129
+ "script",
130
+ "style",
131
+ "template"
132
+ ]);
133
+ const WHITESPACE_RUN = /([ \t\n\r\f\u200b]+)/;
134
+ const TAG_BLOCKS = {
135
+ article: {
136
+ open: 2,
137
+ close: 2
138
+ },
139
+ aside: {
140
+ open: 2,
141
+ close: 2
142
+ },
143
+ blockquote: {
144
+ open: 2,
145
+ close: 2,
146
+ prefix: {
147
+ first: "> ",
148
+ rest: "> "
149
+ }
150
+ },
151
+ div: {
152
+ open: 2,
153
+ close: 2
154
+ },
155
+ footer: {
156
+ open: 2,
157
+ close: 2
158
+ },
159
+ form: {
160
+ open: 2,
161
+ close: 2
162
+ },
163
+ h1: {
164
+ open: 3,
165
+ close: 2
166
+ },
167
+ h2: {
168
+ open: 3,
169
+ close: 2
170
+ },
171
+ h3: {
172
+ open: 3,
173
+ close: 2
174
+ },
175
+ h4: {
176
+ open: 3,
177
+ close: 2
178
+ },
179
+ h5: {
180
+ open: 3,
181
+ close: 2
182
+ },
183
+ h6: {
184
+ open: 3,
185
+ close: 2
186
+ },
187
+ header: {
188
+ open: 2,
189
+ close: 2
190
+ },
191
+ hr: {
192
+ open: 2,
193
+ close: 2
194
+ },
195
+ main: {
196
+ open: 2,
197
+ close: 2
198
+ },
199
+ nav: {
200
+ open: 2,
201
+ close: 2
202
+ },
203
+ p: {
204
+ open: 2,
205
+ close: 2
206
+ },
207
+ pre: {
208
+ open: 2,
209
+ close: 2
210
+ },
211
+ section: {
212
+ open: 2,
213
+ close: 2
214
+ },
215
+ table: {
216
+ open: 2,
217
+ close: 2
218
+ }
219
+ };
220
+ function unstableToPlainText(html) {
221
+ const tree = (0, html5parser.parse)(html, { setAttributeMap: true });
222
+ const body = findBody(tree);
223
+ const blocks = [{
224
+ block: {
225
+ open: 0,
226
+ close: 0
227
+ },
228
+ text: [],
229
+ leading: 0,
230
+ stash: 0,
231
+ space: false
232
+ }];
233
+ const stack = [{
234
+ parent: body,
235
+ children: body?.body ?? tree,
236
+ index: 0,
237
+ pre: false,
238
+ opened: false,
239
+ textFrom: 0,
240
+ orderedList: void 0
241
+ }];
242
+ while (stack.length > 0) {
243
+ const frame = stack[stack.length - 1];
244
+ const node = frame.children[frame.index];
245
+ if (node === void 0) {
246
+ stack.pop();
247
+ exitElement(frame.parent, frame);
248
+ continue;
249
+ }
250
+ frame.index += 1;
251
+ enterNode(node, frame);
252
+ }
253
+ function top() {
254
+ return blocks[blocks.length - 1];
255
+ }
256
+ function writeWord(value) {
257
+ const block = top();
258
+ if (block.stash > 0) block.text.push("\n".repeat(block.stash));
259
+ else if (block.space && block.text.length > 0) block.text.push(" ");
260
+ block.stash = 0;
261
+ block.space = false;
262
+ block.text.push(value);
263
+ }
264
+ function enterNode(node, frame) {
265
+ if (node.type === html5parser.SyntaxKind.Text) {
266
+ const value = (0, entities_lib_decode_js.decodeHTML)(node.value);
267
+ if (frame.pre) {
268
+ if (value.length > 0) writeWord(value);
269
+ } else {
270
+ const segments = value.split(WHITESPACE_RUN);
271
+ for (let i = 0; i < segments.length; i++) {
272
+ const segment = segments[i];
273
+ if (segment.length === 0) continue;
274
+ if (i % 2 === 1) top().space = true;
275
+ else writeWord(segment);
276
+ }
277
+ }
278
+ return;
279
+ }
280
+ if (SKIPPED_TAGS.has(node.name) || (0, entities_lib_decode_js.decodeHTMLAttribute)(node.attributeMap?.["data-skip-in-text"]?.value?.value ?? "") === "true") return;
281
+ const parentTag = frame.parent?.name;
282
+ let block = TAG_BLOCKS[node.name];
283
+ let orderedList;
284
+ if (node.name === "ul") {
285
+ const breaks = parentTag === "li" ? 1 : 2;
286
+ block = {
287
+ open: breaks,
288
+ close: breaks
289
+ };
290
+ } else if (node.name === "li" && parentTag === "ul") block = {
291
+ open: 1,
292
+ close: 1,
293
+ prefix: (stack[stack.length - 2]?.parent)?.name === "li" ? {
294
+ first: "* ",
295
+ rest: " "
296
+ } : {
297
+ first: " * ",
298
+ rest: " "
299
+ }
300
+ };
301
+ else if (node.name === "ol") {
302
+ const nested = parentTag === "li";
303
+ const parsedStart = Number.parseInt((0, entities_lib_decode_js.decodeHTMLAttribute)(node.attributeMap?.start?.value?.value ?? "1"), 10);
304
+ const start = Number.isNaN(parsedStart) ? 1 : parsedStart;
305
+ const itemCount = node.body?.filter((child) => child.type === html5parser.SyntaxKind.Tag && child.name === "li").length ?? 0;
306
+ let prefixLength = 0;
307
+ for (let index = start; index < start + itemCount; index++) {
308
+ const prefix = `${nested ? "" : " "}${index}. `;
309
+ prefixLength = Math.max(prefixLength, prefix.length);
310
+ }
311
+ const breaks = nested ? 1 : 2;
312
+ block = {
313
+ open: breaks,
314
+ close: breaks
315
+ };
316
+ orderedList = {
317
+ next: start,
318
+ prefixLength,
319
+ nested
320
+ };
321
+ } else if (node.name === "li" && parentTag === "ol" && frame.orderedList) {
322
+ const list = frame.orderedList;
323
+ block = {
324
+ open: 1,
325
+ close: 1,
326
+ prefix: {
327
+ first: `${list.nested ? "" : " "}${list.next++}. `.padEnd(list.prefixLength),
328
+ rest: " ".repeat(list.prefixLength)
329
+ }
330
+ };
331
+ }
332
+ if (block) blocks.push({
333
+ block,
334
+ text: [],
335
+ leading: block.open,
336
+ stash: 0,
337
+ space: false
338
+ });
339
+ if (node.name === "hr") writeWord("-".repeat(40));
340
+ else if (node.name === "br") {
341
+ top().space = false;
342
+ top().text.push("\n");
343
+ }
344
+ stack.push({
345
+ parent: node,
346
+ children: node.body ?? [],
347
+ index: 0,
348
+ pre: frame.pre || node.name === "pre",
349
+ opened: block !== void 0,
350
+ textFrom: top().text.length,
351
+ orderedList
352
+ });
353
+ }
354
+ function exitElement(element, frame) {
355
+ if (element === void 0) return;
356
+ if (element.name === "a") {
357
+ const href = (0, entities_lib_decode_js.decodeHTMLAttribute)(element.attributeMap?.href?.value?.value ?? "").replace(/^mailto:/, "");
358
+ if (href.length > 0 && !href.startsWith("#")) {
359
+ const anchorText = top().text.slice(frame.textFrom).join("");
360
+ if (anchorText !== href) {
361
+ if (anchorText.length > 0) top().space = true;
362
+ writeWord(href);
363
+ }
364
+ }
365
+ }
366
+ if (frame.opened) closeBlock();
367
+ }
368
+ function closeBlock() {
369
+ const child = blocks.pop();
370
+ if (child === void 0) return;
371
+ const parent = blocks[blocks.length - 1];
372
+ let content = child.text.join("");
373
+ const prefix = child.block.prefix;
374
+ if (prefix !== void 0) {
375
+ const trimmed = content.replace(/^\n+|\n+$/g, "");
376
+ content = prefix.first + trimmed.replaceAll("\n", `\n${prefix.rest}`);
377
+ }
378
+ const breaks = Math.max(parent.stash, child.leading);
379
+ if (parent.text.length > 0) {
380
+ parent.text.push("\n".repeat(breaks));
381
+ if (content.length > 0) parent.text.push(content);
382
+ } else {
383
+ if (content.length > 0) parent.text.push(content);
384
+ parent.leading = breaks;
385
+ }
386
+ parent.stash = Math.max(child.stash, child.block.close);
387
+ }
388
+ return blocks[0].text.join("");
389
+ }
390
+ function findBody(tree) {
391
+ for (const child of tree) {
392
+ if (child.type !== html5parser.SyntaxKind.Tag || child.name !== "html") continue;
393
+ for (const inner of child.body ?? []) if (inner.type === html5parser.SyntaxKind.Tag && inner.name === "body") return inner;
394
+ }
395
+ }
125
396
  //#endregion
126
397
  //#region src/shared/error-boundary.tsx
127
398
  function createErrorBoundary(reject) {
@@ -135,7 +406,43 @@ function createErrorBoundary(reject) {
135
406
  }
136
407
  };
137
408
  }
138
-
409
+ //#endregion
410
+ //#region src/shared/utils/strip-image-preload-links.ts
411
+ /**
412
+ * React injects `<link rel="preload" as="image" />` resource hints into the
413
+ * document `<head>` for every `<img>` it renders during server-side rendering.
414
+ *
415
+ * These hints are meant for browsers loading a web page, where they can speed
416
+ * up the initial paint. In an email they are dead weight: email clients ignore
417
+ * `<link rel="preload">`, and the tags only add noise to the rendered HTML.
418
+ *
419
+ * @see https://github.com/resend/react-email/issues/3034
420
+ *
421
+ * This removes only those auto-injected image preload links, leaving every
422
+ * other `<link>` (stylesheets, fonts, user-authored non-image preloads, ...)
423
+ * untouched. It parses each `<link>` tag's attributes instead of relying on a
424
+ * fixed string match, so it is not affected by attribute order or spacing.
425
+ */
426
+ const stripImagePreloadLinks = (html) => {
427
+ return html.replace(/<link\b[^>]*\/?>/gi, (tag) => isImagePreloadLink(tag) ? "" : tag);
428
+ };
429
+ const isImagePreloadLink = (tag) => {
430
+ const attributes = parseAttributes(tag);
431
+ return attributes.rel === "preload" && attributes.as === "image";
432
+ };
433
+ const ATTRIBUTE_PATTERN = /([a-z][a-z0-9-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+)))?/gi;
434
+ /**
435
+ * Parses the attributes of a single, already-isolated HTML tag string (e.g.
436
+ * `<link rel="preload" as="image" href="..." />`) into a name → value map.
437
+ * Attribute names are lower-cased; values are read from double, single, or
438
+ * unquoted forms.
439
+ */
440
+ const parseAttributes = (tag) => {
441
+ const attributeSection = tag.replace(/^<[a-z][a-z0-9-]*/i, "");
442
+ const attributes = {};
443
+ for (const [, name, doubleQuoted, singleQuoted, unquoted] of attributeSection.matchAll(ATTRIBUTE_PATTERN)) attributes[name.toLowerCase()] = doubleQuoted ?? singleQuoted ?? unquoted ?? "";
444
+ return attributes;
445
+ };
139
446
  //#endregion
140
447
  //#region src/node/read-stream.ts
141
448
  const readStream = async (stream) => {
@@ -177,7 +484,6 @@ const readStream = async (stream) => {
177
484
  }
178
485
  return result;
179
486
  };
180
-
181
487
  //#endregion
182
488
  //#region src/node/render.tsx
183
489
  const render = async (node, options) => {
@@ -205,7 +511,9 @@ const render = async (node, options) => {
205
511
  const ErrorBoundary = createErrorBoundary(reject);
206
512
  const stream = reactDOMServer.renderToPipeableStream(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBoundary, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Suspense, { children: node }) }), {
207
513
  async onAllReady() {
208
- html = await readStream(stream);
514
+ html = await readStream(stream).then((s) => {
515
+ return s.replaceAll("\0", "");
516
+ });
209
517
  resolve();
210
518
  },
211
519
  onError(error) {
@@ -215,14 +523,15 @@ const render = async (node, options) => {
215
523
  });
216
524
  }
217
525
  });
218
- if (options?.plainText) return toPlainText(html, options.htmlToTextOptions);
526
+ html = stripImagePreloadLinks(html);
527
+ if (options?.plainText) return options.unstableTextConversion ? unstableToPlainText(html) : toPlainText(html, options.htmlToTextOptions);
219
528
  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.*?>/, "")}`;
220
529
  if (options?.pretty) return pretty(document);
221
530
  return document;
222
531
  };
223
-
224
532
  //#endregion
225
533
  exports.plainTextSelectors = plainTextSelectors;
226
534
  exports.pretty = pretty;
227
535
  exports.render = render;
228
- exports.toPlainText = toPlainText;
536
+ exports.toPlainText = toPlainText;
537
+ exports.unstableToPlainText = unstableToPlainText;
@@ -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
@@ -33,8 +43,11 @@ declare const pretty: (str: string, options?: Options$1) => Promise<string>;
33
43
  declare const plainTextSelectors: SelectorDefinition[];
34
44
  declare function toPlainText(html: string, options?: HtmlToTextOptions): string;
35
45
  //#endregion
46
+ //#region src/shared/utils/unstable-to-plain-text.d.ts
47
+ declare function unstableToPlainText(html: string): string;
48
+ //#endregion
36
49
  //#region src/node/render.d.ts
37
50
  declare const render: (node: React.ReactNode, options?: Options) => Promise<string>;
38
51
  //#endregion
39
- export { Options, plainTextSelectors, pretty, render, toPlainText };
52
+ export { Options, plainTextSelectors, pretty, render, toPlainText, unstableToPlainText };
40
53
  //# sourceMappingURL=index.d.cts.map
@@ -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/node/render.tsx"],"sourcesContent":[],"mappings":";;;;KAIY,OAAA;;;AAAZ;;;;ACyHA;;;;ECvHa;AASb;;;;ACRA;;;;;sBHoB0B;;;;cCkGb,gCAAgC,cAAY;;;cCvH5C,oBAAoB;iBASjB,WAAA,yBAAoC;;;cCRvC,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/shared/utils/unstable-to-plain-text.ts","../../src/node/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;;;iBC6DpC,mBAAA,CAAoB,IAAA;;;cCpEvB,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
@@ -33,8 +43,11 @@ declare const pretty: (str: string, options?: Options$1) => Promise<string>;
33
43
  declare const plainTextSelectors: SelectorDefinition[];
34
44
  declare function toPlainText(html: string, options?: HtmlToTextOptions): string;
35
45
  //#endregion
46
+ //#region src/shared/utils/unstable-to-plain-text.d.ts
47
+ declare function unstableToPlainText(html: string): string;
48
+ //#endregion
36
49
  //#region src/node/render.d.ts
37
50
  declare const render: (node: React.ReactNode, options?: Options) => Promise<string>;
38
51
  //#endregion
39
- export { Options, plainTextSelectors, pretty, render, toPlainText };
52
+ export { Options, plainTextSelectors, pretty, render, toPlainText, unstableToPlainText };
40
53
  //# sourceMappingURL=index.d.mts.map
@@ -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/node/render.tsx"],"sourcesContent":[],"mappings":";;;;KAIY,OAAA;;;AAAZ;;;;ACyHA;;;;ECvHa;AASb;;;;ACRA;;;;;sBHoB0B;;;;cCkGb,gCAAgC,cAAY;;;cCvH5C,oBAAoB;iBASjB,WAAA,yBAAoC;;;cCRvC,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/shared/utils/unstable-to-plain-text.ts","../../src/node/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;;;iBC6DpC,mBAAA,CAAoB,IAAA;;;cCpEvB,MAAA,GAAgB,IAAA,EAAM,KAAA,CAAM,SAAA,EAAW,OAAA,GAAU,OAAA,KAAO,OAAA"}