react-email 2.1.2 → 2.1.3-canary.0

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/cli/index.js CHANGED
@@ -340,7 +340,7 @@ var import_commander = require("commander");
340
340
  // package.json
341
341
  var package_default = {
342
342
  name: "react-email",
343
- version: "2.1.2",
343
+ version: "2.1.3-canary.0",
344
344
  description: "A live preview of your emails right in your browser.",
345
345
  bin: {
346
346
  email: "./cli/index.js"
@@ -373,9 +373,8 @@ var package_default = {
373
373
  "@radix-ui/react-popover": "1.0.7",
374
374
  "@radix-ui/react-slot": "1.0.2",
375
375
  "@radix-ui/react-toggle-group": "1.0.4",
376
- "@radix-ui/react-tooltip": "1.0.6",
377
- "@react-email/components": "0.0.17",
378
- "@react-email/render": "0.0.13",
376
+ "@radix-ui/react-tooltip": "1.0.7",
377
+ "@react-email/render": "0.0.14-canary.0",
379
378
  "@swc/core": "1.3.101",
380
379
  "@types/react": "^18.2.0",
381
380
  "@types/react-dom": "^18.2.0",
@@ -394,10 +393,10 @@ var package_default = {
394
393
  glob: "10.3.4",
395
394
  "log-symbols": "4.1.0",
396
395
  "mime-types": "2.1.35",
397
- next: "14.1.0",
396
+ next: "14.1.4",
398
397
  "normalize-path": "3.0.0",
399
398
  ora: "5.4.1",
400
- postcss: "8.4.35",
399
+ postcss: "8.4.38",
401
400
  "prism-react-renderer": "2.1.0",
402
401
  react: "^18.2.0",
403
402
  "react-dom": "^18.2.0",
@@ -420,9 +419,8 @@ var package_default = {
420
419
  "@vercel/style-guide": "5.1.0",
421
420
  eslint: "8.50.0",
422
421
  tsup: "7.2.0",
423
- tsx: "4.7.1",
424
- vitest: "1.1.3",
425
- watch: "1.0.2"
422
+ tsx: "4.9.0",
423
+ vitest: "1.1.3"
426
424
  }
427
425
  };
428
426
  // src/cli/commands/dev.ts
@@ -1405,7 +1403,7 @@ var import_node_fs3 = require("fs");
1405
1403
  var import_mime_types = require("mime-types");
1406
1404
  var serveStaticFile = function() {
1407
1405
  var _ref = _async_to_generator(function(res, parsedUrl, staticDirRelativePath) {
1408
- var staticBaseDir, pathname, ext, fileAbsolutePath, doesFileExist, fileStat, fileData;
1406
+ var staticBaseDir, pathname, ext, fileAbsolutePath, fileHandle, fileData, exception;
1409
1407
  return _ts_generator(this, function(_state) {
1410
1408
  switch(_state.label){
1411
1409
  case 0:
@@ -1413,37 +1411,47 @@ var serveStaticFile = function() {
1413
1411
  pathname = parsedUrl.pathname;
1414
1412
  ext = import_node_path4.default.parse(pathname).ext;
1415
1413
  fileAbsolutePath = import_node_path4.default.join(staticBaseDir, pathname);
1416
- doesFileExist = (0, import_node_fs3.existsSync)(fileAbsolutePath);
1417
- if (!!doesFileExist) return [
1418
- 3,
1419
- 1
1420
- ];
1421
- res.statusCode = 404;
1422
- res.end("File ".concat(pathname, " not found!"));
1423
- return [
1424
- 3,
1425
- 4
1426
- ];
1427
- case 1:
1428
1414
  return [
1429
1415
  4,
1430
- import_node_fs3.promises.stat(fileAbsolutePath)
1416
+ import_node_fs3.promises.open(fileAbsolutePath, "r")
1431
1417
  ];
1418
+ case 1:
1419
+ fileHandle = _state.sent();
1420
+ _state.label = 2;
1432
1421
  case 2:
1433
- fileStat = _state.sent();
1434
- if (fileStat.isDirectory()) {
1435
- fileAbsolutePath += "/index".concat(ext);
1436
- }
1422
+ _state.trys.push([
1423
+ 2,
1424
+ 4,
1425
+ 5,
1426
+ 6
1427
+ ]);
1437
1428
  return [
1438
1429
  4,
1439
- import_node_fs3.promises.readFile(fileAbsolutePath)
1430
+ import_node_fs3.promises.readFile(fileHandle)
1440
1431
  ];
1441
1432
  case 3:
1442
1433
  fileData = _state.sent();
1443
1434
  res.setHeader("Content-type", (0, import_mime_types.lookup)(ext) || "text/plain");
1444
1435
  res.end(fileData);
1445
- _state.label = 4;
1436
+ return [
1437
+ 3,
1438
+ 6
1439
+ ];
1446
1440
  case 4:
1441
+ exception = _state.sent();
1442
+ console.error("Could not read file at ".concat(fileAbsolutePath, " to be served, here's the exception:"), exception);
1443
+ res.statusCode = 500;
1444
+ res.end("Could not read file to be served! Check your terminal for more information.");
1445
+ return [
1446
+ 3,
1447
+ 6
1448
+ ];
1449
+ case 5:
1450
+ fileHandle.close();
1451
+ return [
1452
+ 7
1453
+ ];
1454
+ case 6:
1447
1455
  return [
1448
1456
  2
1449
1457
  ];
@@ -1699,6 +1707,9 @@ var isFileAnEmail = function(fullPath) {
1699
1707
  ".tsx",
1700
1708
  ".jsx"
1701
1709
  ].includes(ext)) return false;
1710
+ if (!import_node_fs5.default.existsSync(fullPath)) {
1711
+ return false;
1712
+ }
1702
1713
  var fileContents = import_node_fs5.default.readFileSync(fullPath, "utf8");
1703
1714
  return /\bexport\s+default\b/gm.test(fileContents);
1704
1715
  };
@@ -1968,7 +1979,7 @@ var setNextEnvironmentVariablesForBuild = function() {
1968
1979
  import_node_path8.default.normalize(emailsDirRelativePath), "PLACEHOLDER", "PLACEHOLDER")), {
1969
1980
  NEXT_PUBLIC_IS_BUILDING: "true"
1970
1981
  });
1971
- nextConfigContents = "\nconst path = require('path');\n/** @type {import('next').NextConfig} */\nmodule.exports = {\n env: {\n ...".concat(JSON.stringify(envVariables), ",\n NEXT_PUBLIC_USER_PROJECT_LOCATION: path.resolve(process.cwd(), '../'),\n NEXT_PUBLIC_CLI_PACKAGE_LOCATION: process.cwd(),\n },\n // this is needed so that the code for building emails works properly\n webpack: (\n /** @type {import('webpack').Configuration & { externals: string[] }} */\n config,\n { isServer }\n ) => {\n if (isServer) {\n config.externals.push('esbuild');\n }\n\n return config;\n },\n typescript: {\n ignoreBuildErrors: true\n },\n eslint: {\n ignoreDuringBuilds: true\n },\n experimental: {\n webpackBuildWorker: true,\n serverComponentsExternalPackages: [\n '@react-email/components',\n '@react-email/render',\n '@react-email/tailwind',\n ],\n },\n}");
1982
+ nextConfigContents = "\nconst path = require('path');\n/** @type {import('next').NextConfig} */\nmodule.exports = {\n env: {\n ...".concat(JSON.stringify(envVariables), ",\n NEXT_PUBLIC_USER_PROJECT_LOCATION: path.resolve(process.cwd(), '../'),\n NEXT_PUBLIC_CLI_PACKAGE_LOCATION: process.cwd(),\n },\n // this is needed so that the code for building emails works properly\n webpack: (\n /** @type {import('webpack').Configuration & { externals: string[] }} */\n config,\n { isServer }\n ) => {\n if (isServer) {\n config.externals.push('esbuild');\n }\n\n return config;\n },\n typescript: {\n ignoreBuildErrors: true\n },\n eslint: {\n ignoreDuringBuilds: true\n },\n experimental: {\n webpackBuildWorker: true\n },\n}");
1972
1983
  return [
1973
1984
  4,
1974
1985
  import_node_fs7.default.promises.writeFile(import_node_path8.default.resolve(builtPreviewAppPath, "./next.config.js"), nextConfigContents, "utf8")
@@ -2019,7 +2030,7 @@ var forceSSGForEmailPreviews = function() {
2019
2030
  });
2020
2031
  return [
2021
2032
  4,
2022
- import_node_fs7.default.promises.appendFile(import_node_path8.default.resolve(builtPreviewAppPath, "./src/app/preview/[...slug]/page.tsx"), "\n\nexport async function generateStaticParams() { \n return ".concat(JSON.stringify(parameters), ";\n}"), "utf8")
2033
+ import_node_fs7.default.promises.appendFile(import_node_path8.default.resolve(builtPreviewAppPath, "./src/app/preview/[...slug]/page.tsx"), "\n\nexport function generateStaticParams() { \n return Promise.resolve(\n ".concat(JSON.stringify(parameters), "\n );\n}"), "utf8")
2023
2034
  ];
2024
2035
  case 2:
2025
2036
  _state.sent();
@@ -2051,6 +2062,8 @@ var updatePackageJson = function() {
2051
2062
  ]);
2052
2063
  packageJson.scripts.build = "next build";
2053
2064
  packageJson.scripts.start = "next start";
2065
+ packageJson.name = "preview-server";
2066
+ delete packageJson.dependencies["@react-email/render"];
2054
2067
  packageJson.dependencies.sharp = "0.33.2";
2055
2068
  return [
2056
2069
  4,
package/cli/index.mjs CHANGED
@@ -13,7 +13,7 @@ import { program } from "commander";
13
13
  // package.json
14
14
  var package_default = {
15
15
  name: "react-email",
16
- version: "2.1.2",
16
+ version: "2.1.3-canary.0",
17
17
  description: "A live preview of your emails right in your browser.",
18
18
  bin: {
19
19
  email: "./cli/index.js"
@@ -46,9 +46,8 @@ var package_default = {
46
46
  "@radix-ui/react-popover": "1.0.7",
47
47
  "@radix-ui/react-slot": "1.0.2",
48
48
  "@radix-ui/react-toggle-group": "1.0.4",
49
- "@radix-ui/react-tooltip": "1.0.6",
50
- "@react-email/components": "0.0.17",
51
- "@react-email/render": "0.0.13",
49
+ "@radix-ui/react-tooltip": "1.0.7",
50
+ "@react-email/render": "0.0.14-canary.0",
52
51
  "@swc/core": "1.3.101",
53
52
  "@types/react": "^18.2.0",
54
53
  "@types/react-dom": "^18.2.0",
@@ -67,10 +66,10 @@ var package_default = {
67
66
  glob: "10.3.4",
68
67
  "log-symbols": "4.1.0",
69
68
  "mime-types": "2.1.35",
70
- next: "14.1.0",
69
+ next: "14.1.4",
71
70
  "normalize-path": "3.0.0",
72
71
  ora: "5.4.1",
73
- postcss: "8.4.35",
72
+ postcss: "8.4.38",
74
73
  "prism-react-renderer": "2.1.0",
75
74
  react: "^18.2.0",
76
75
  "react-dom": "^18.2.0",
@@ -93,9 +92,8 @@ var package_default = {
93
92
  "@vercel/style-guide": "5.1.0",
94
93
  eslint: "8.50.0",
95
94
  tsup: "7.2.0",
96
- tsx: "4.7.1",
97
- vitest: "1.1.3",
98
- watch: "1.0.2"
95
+ tsx: "4.9.0",
96
+ vitest: "1.1.3"
99
97
  }
100
98
  };
101
99
 
@@ -444,25 +442,29 @@ var closeOraOnSIGNIT = (spinner) => {
444
442
 
445
443
  // src/cli/utils/preview/serve-static-file.ts
446
444
  import path4 from "path";
447
- import { promises as fs3, existsSync } from "fs";
445
+ import { promises as fs3 } from "fs";
448
446
  import { lookup } from "mime-types";
449
447
  var serveStaticFile = async (res, parsedUrl, staticDirRelativePath) => {
450
448
  const staticBaseDir = path4.join(process.cwd(), staticDirRelativePath);
451
449
  const pathname = parsedUrl.pathname;
452
450
  const ext = path4.parse(pathname).ext;
453
451
  let fileAbsolutePath = path4.join(staticBaseDir, pathname);
454
- const doesFileExist = existsSync(fileAbsolutePath);
455
- if (!doesFileExist) {
456
- res.statusCode = 404;
457
- res.end(`File ${pathname} not found!`);
458
- } else {
459
- const fileStat = await fs3.stat(fileAbsolutePath);
460
- if (fileStat.isDirectory()) {
461
- fileAbsolutePath += `/index${ext}`;
462
- }
463
- const fileData = await fs3.readFile(fileAbsolutePath);
452
+ const fileHandle = await fs3.open(fileAbsolutePath, "r");
453
+ try {
454
+ const fileData = await fs3.readFile(fileHandle);
464
455
  res.setHeader("Content-type", lookup(ext) || "text/plain");
465
456
  res.end(fileData);
457
+ } catch (exception) {
458
+ console.error(
459
+ `Could not read file at ${fileAbsolutePath} to be served, here's the exception:`,
460
+ exception
461
+ );
462
+ res.statusCode = 500;
463
+ res.end(
464
+ `Could not read file to be served! Check your terminal for more information.`
465
+ );
466
+ } finally {
467
+ fileHandle.close();
466
468
  }
467
469
  };
468
470
 
@@ -651,6 +653,9 @@ var isFileAnEmail = (fullPath) => {
651
653
  const { ext } = path7.parse(fullPath);
652
654
  if (![".js", ".tsx", ".jsx"].includes(ext))
653
655
  return false;
656
+ if (!fs5.existsSync(fullPath)) {
657
+ return false;
658
+ }
654
659
  const fileContents = fs5.readFileSync(fullPath, "utf8");
655
660
  return /\bexport\s+default\b/gm.test(fileContents);
656
661
  };
@@ -883,12 +888,7 @@ module.exports = {
883
888
  ignoreDuringBuilds: true
884
889
  },
885
890
  experimental: {
886
- webpackBuildWorker: true,
887
- serverComponentsExternalPackages: [
888
- '@react-email/components',
889
- '@react-email/render',
890
- '@react-email/tailwind',
891
- ],
891
+ webpackBuildWorker: true
892
892
  },
893
893
  }`;
894
894
  await fs7.promises.writeFile(
@@ -928,8 +928,10 @@ var forceSSGForEmailPreviews = async (emailsDirPath, builtPreviewAppPath) => {
928
928
  path9.resolve(builtPreviewAppPath, "./src/app/preview/[...slug]/page.tsx"),
929
929
  `
930
930
 
931
- export async function generateStaticParams() {
932
- return ${JSON.stringify(parameters)};
931
+ export function generateStaticParams() {
932
+ return Promise.resolve(
933
+ ${JSON.stringify(parameters)}
934
+ );
933
935
  }`,
934
936
  "utf8"
935
937
  );
@@ -941,6 +943,8 @@ var updatePackageJson = async (builtPreviewAppPath) => {
941
943
  );
942
944
  packageJson.scripts.build = "next build";
943
945
  packageJson.scripts.start = "next start";
946
+ packageJson.name = "preview-server";
947
+ delete packageJson.dependencies["@react-email/render"];
944
948
  packageJson.dependencies.sharp = "0.33.2";
945
949
  await fs7.promises.writeFile(
946
950
  packageJsonPath,
package/next.config.js CHANGED
@@ -11,12 +11,5 @@ module.exports = {
11
11
  }
12
12
 
13
13
  return config;
14
- },
15
- experimental: {
16
- serverComponentsExternalPackages: [
17
- '@react-email/components',
18
- '@react-email/render',
19
- '@react-email/tailwind',
20
- ],
21
- },
14
+ }
22
15
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-email",
3
- "version": "2.1.2",
3
+ "version": "2.1.3-canary.0",
4
4
  "description": "A live preview of your emails right in your browser.",
5
5
  "bin": {
6
6
  "email": "./cli/index.js"
@@ -25,9 +25,8 @@
25
25
  "@radix-ui/react-popover": "1.0.7",
26
26
  "@radix-ui/react-slot": "1.0.2",
27
27
  "@radix-ui/react-toggle-group": "1.0.4",
28
- "@radix-ui/react-tooltip": "1.0.6",
29
- "@react-email/components": "0.0.17",
30
- "@react-email/render": "0.0.13",
28
+ "@radix-ui/react-tooltip": "1.0.7",
29
+ "@react-email/render": "0.0.14-canary.0",
31
30
  "@swc/core": "1.3.101",
32
31
  "@types/react": "^18.2.0",
33
32
  "@types/react-dom": "^18.2.0",
@@ -46,10 +45,10 @@
46
45
  "glob": "10.3.4",
47
46
  "log-symbols": "4.1.0",
48
47
  "mime-types": "2.1.35",
49
- "next": "14.1.0",
48
+ "next": "14.1.4",
50
49
  "normalize-path": "3.0.0",
51
50
  "ora": "5.4.1",
52
- "postcss": "8.4.35",
51
+ "postcss": "8.4.38",
53
52
  "prism-react-renderer": "2.1.0",
54
53
  "react": "^18.2.0",
55
54
  "react-dom": "^18.2.0",
@@ -72,9 +71,8 @@
72
71
  "@vercel/style-guide": "5.1.0",
73
72
  "eslint": "8.50.0",
74
73
  "tsup": "7.2.0",
75
- "tsx": "4.7.1",
76
- "vitest": "1.1.3",
77
- "watch": "1.0.2"
74
+ "tsx": "4.9.0",
75
+ "vitest": "1.1.3"
78
76
  },
79
77
  "scripts": {
80
78
  "build": "tsup",
@@ -19,7 +19,7 @@ export const getEmailPathFromSlug = async (slug: string) => {
19
19
  }
20
20
 
21
21
  throw new Error(
22
- `Could not find your email file based on the slug by guessing the file extension. Tried .tsx, .jsx and .js.
22
+ `Could not find your email file based on the slug (${slug}) by guessing the file extension. Tried .tsx, .jsx and .js.
23
23
 
24
24
  This is most likely not an issue with the preview server. It most likely is that the email doesn't exist.`,
25
25
  );
@@ -12,6 +12,13 @@ const isFileAnEmail = (fullPath: string): boolean => {
12
12
 
13
13
  if (!['.js', '.tsx', '.jsx'].includes(ext)) return false;
14
14
 
15
+ // This is to avoid a possible race condition where the file doesn't exist anymore
16
+ // once we are checking if it is an actual email, this couuld cause issues that
17
+ // would be very hard to debug and find out the why of it happening.
18
+ if (!fs.existsSync(fullPath)) {
19
+ return false;
20
+ }
21
+
15
22
  // check with a heuristic to see if the file has at least
16
23
  // a default export
17
24
  const fileContents = fs.readFileSync(fullPath, 'utf8');
@@ -1,6 +1,5 @@
1
1
  'use server';
2
2
  import fs from 'node:fs';
3
- import { renderAsync } from '@react-email/render';
4
3
  import { getEmailComponent } from '../utils/get-email-component';
5
4
  import type { ErrorObject } from '../utils/types/error-object';
6
5
  import { improveErrorWithSourceMap } from '../utils/improve-error-with-sourcemap';
@@ -26,7 +25,11 @@ export const renderEmailByPath = async (
26
25
  return { error: result.error };
27
26
  }
28
27
 
29
- const { emailComponent: Email, sourceMapToOriginalFile } = result;
28
+ const {
29
+ emailComponent: Email,
30
+ renderAsync,
31
+ sourceMapToOriginalFile,
32
+ } = result;
30
33
 
31
34
  const previewProps = Email.PreviewProps || {};
32
35
  const EmailComponent = Email as React.FC;
@@ -1,4 +1,6 @@
1
1
  import { Suspense } from 'react';
2
+ import path from 'node:path';
3
+ import { redirect } from 'next/navigation';
2
4
  import { getEmailPathFromSlug } from '../../../actions/get-email-path-from-slug';
3
5
  import { getEmailsDirectoryMetadata } from '../../../actions/get-emails-directory-metadata';
4
6
  import { renderEmailByPath } from '../../../actions/render-email-by-path';
@@ -12,7 +14,7 @@ export interface PreviewParams {
12
14
  slug: string[];
13
15
  }
14
16
 
15
- export default async function Page({ params }: { params: PreviewParams }) {
17
+ const Page = async ({ params }: { params: PreviewParams }) => {
16
18
  // will come in here as segments of a relative path to the email
17
19
  // ex: ['authentication', 'verify-password.tsx']
18
20
  const slug = params.slug.join('/');
@@ -28,7 +30,16 @@ This is most likely not an issue with the preview server. Maybe there was a typo
28
30
  );
29
31
  }
30
32
 
31
- const emailPath = await getEmailPathFromSlug(slug);
33
+ let emailPath: string;
34
+ try {
35
+ emailPath = await getEmailPathFromSlug(slug);
36
+ } catch (exception) {
37
+ if (exception instanceof Error) {
38
+ console.warn(exception.message);
39
+ redirect('/');
40
+ }
41
+ throw exception;
42
+ }
32
43
 
33
44
  const emailRenderingResult = await renderEmailByPath(emailPath);
34
45
 
@@ -53,8 +64,10 @@ This is most likely not an issue with the preview server. Maybe there was a typo
53
64
  />
54
65
  </Suspense>
55
66
  );
56
- }
67
+ };
57
68
 
58
69
  export function generateMetadata({ params }: { params: PreviewParams }) {
59
- return { title: `${params.slug.join('/')} — React Email` };
70
+ return { title: `${path.basename(params.slug.join('/'))} — React Email` };
60
71
  }
72
+
73
+ export default Page;
@@ -11,6 +11,7 @@ import { IconPhone } from './icons/icon-phone';
11
11
  import { IconSource } from './icons/icon-source';
12
12
  import { Send } from './send';
13
13
  import { Tooltip } from './tooltip';
14
+ import { pathSeparator } from '../utils/emails-directory-absolute-path';
14
15
 
15
16
  interface TopbarProps {
16
17
  currentEmailOpenSlug: string;
@@ -49,7 +50,7 @@ export const Topbar: React.FC<Readonly<TopbarProps>> = ({
49
50
 
50
51
  <div className="items-center overflow-hidden hidden lg:flex text-center absolute left-1/2 transform -translate-x-1/2 top-1/2 -translate-y-1/2">
51
52
  <Heading as="h2" className="truncate" size="2" weight="medium">
52
- {currentEmailOpenSlug}
53
+ {currentEmailOpenSlug.split(pathSeparator).pop()}
53
54
  </Heading>
54
55
  </div>
55
56
 
@@ -0,0 +1,3 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`getEmailComponent() with a demo email template 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><div>Hello <!-- -->Gabriel<!-- -->!</div>"`;
@@ -0,0 +1,25 @@
1
+ import path from 'node:path';
2
+ import * as React from 'react';
3
+ import { getEmailComponent } from './get-email-component';
4
+
5
+ test('getEmailComponent() with a demo email template', async () => {
6
+ const result = await getEmailComponent(
7
+ path.resolve(__dirname, './testing/email-template.tsx'),
8
+ );
9
+
10
+ if ('error' in result) {
11
+ console.log(result.error);
12
+ expect('error' in result).toBe(false);
13
+ } else {
14
+ expect(result.emailComponent).toBeTruthy();
15
+ expect(result.sourceMapToOriginalFile).toBeTruthy();
16
+
17
+ const emailHtml = await result.renderAsync(
18
+ React.createElement(
19
+ result.emailComponent,
20
+ result.emailComponent.PreviewProps,
21
+ ),
22
+ );
23
+ expect(emailHtml).toMatchSnapshot();
24
+ }
25
+ });
@@ -1,8 +1,16 @@
1
1
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
2
  import path from 'node:path';
3
3
  import vm from 'node:vm';
4
+ import fs from 'node:fs/promises';
4
5
  import { type RawSourceMap } from 'source-map-js';
5
- import { type OutputFile, build, type BuildFailure } from 'esbuild';
6
+ import {
7
+ type OutputFile,
8
+ build,
9
+ type ResolveOptions,
10
+ type BuildFailure,
11
+ type Loader,
12
+ } from 'esbuild';
13
+ import type { renderAsync } from '@react-email/render';
6
14
  import type { EmailTemplate as EmailComponent } from './types/email-template';
7
15
  import type { ErrorObject } from './types/error-object';
8
16
  import { improveErrorWithSourceMap } from './improve-error-with-sourcemap';
@@ -14,6 +22,8 @@ export const getEmailComponent = async (
14
22
  | {
15
23
  emailComponent: EmailComponent;
16
24
 
25
+ renderAsync: typeof renderAsync;
26
+
17
27
  sourceMapToOriginalFile: RawSourceMap;
18
28
  }
19
29
  | { error: ErrorObject }
@@ -23,8 +33,49 @@ export const getEmailComponent = async (
23
33
  const buildData = await build({
24
34
  bundle: true,
25
35
  entryPoints: [emailPath],
36
+ plugins: [
37
+ {
38
+ name: 'add-export-for-render-async',
39
+ setup(b) {
40
+ b.onLoad(
41
+ { filter: new RegExp(path.basename(emailPath)) },
42
+ async () => ({
43
+ contents: `${await fs.readFile(emailPath, 'utf8')};
44
+ export { renderAsync } from 'react-email-module-that-will-export-render'
45
+ `,
46
+ loader: path.extname(emailPath).slice(1) as Loader,
47
+ }),
48
+ );
49
+
50
+ b.onResolve(
51
+ { filter: /^react-email-module-that-will-export-render$/ },
52
+ async (args) => {
53
+ const options: ResolveOptions = {
54
+ kind: 'import-statement',
55
+ importer: args.importer,
56
+ resolveDir: args.resolveDir,
57
+ namespace: args.namespace,
58
+ };
59
+ let result = await b.resolve('@react-email/render', options);
60
+ if (result.errors.length === 0) {
61
+ return result;
62
+ }
63
+
64
+ // If @react-email/render does not exist, resolve to @react-email/components
65
+ result = await b.resolve('@react-email/components', options);
66
+ if (result.errors.length > 0) {
67
+ result.errors[0]!.text =
68
+ "Failed trying to import `renderAsync` from either `@react-email/render` or `@react-email/components` to be able to render your email template.\n Maybe you don't have either of them installed?";
69
+ }
70
+ return result;
71
+ },
72
+ );
73
+ },
74
+ },
75
+ ],
26
76
  platform: 'node',
27
77
  write: false,
78
+
28
79
  format: 'cjs',
29
80
  jsx: 'automatic',
30
81
  logLevel: 'silent',
@@ -62,8 +113,15 @@ export const getEmailComponent = async (
62
113
  TextEncoderStream,
63
114
  ReadableStream,
64
115
  URL,
65
- module: { exports: { default: undefined as unknown } },
66
- __filanem: emailPath,
116
+ URLSearchParams,
117
+ Headers,
118
+ module: {
119
+ exports: {
120
+ default: undefined as unknown,
121
+ renderAsync: undefined as unknown,
122
+ },
123
+ },
124
+ __filename: emailPath,
67
125
  __dirname: path.dirname(emailPath),
68
126
  require: (module: string) => {
69
127
  if (module in staticNodeModulesForVM) {
@@ -71,7 +129,7 @@ export const getEmailComponent = async (
71
129
  return staticNodeModulesForVM[module];
72
130
  }
73
131
 
74
- // eslint-disable-next-line @typescript-eslint/no-var-requires
132
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-useless-template-literals
75
133
  return require(`${module}`) as unknown;
76
134
  // this stupid string templating was necessary to not have
77
135
  // webpack warnings like:
@@ -85,11 +143,18 @@ export const getEmailComponent = async (
85
143
  process,
86
144
  };
87
145
  const sourceMapToEmail = JSON.parse(sourceMapFile.text) as RawSourceMap;
146
+ // because it will have a path like <tsconfigLocation>/stdout/email.js.map
147
+ sourceMapToEmail.sourceRoot = path.resolve(sourceMapFile.path, '../..');
148
+ sourceMapToEmail.sources = sourceMapToEmail.sources.map((source) =>
149
+ path.resolve(sourceMapFile.path, '..', source),
150
+ );
88
151
  try {
89
152
  vm.runInNewContext(builtEmailCode, fakeContext, { filename: emailPath });
90
153
  } catch (exception) {
91
154
  const error = exception as Error;
92
155
 
156
+ error.stack &&= error.stack.split('at Script.runInContext (node:vm')[0];
157
+
93
158
  return {
94
159
  error: improveErrorWithSourceMap(error, emailPath, sourceMapToEmail),
95
160
  };
@@ -109,6 +174,8 @@ export const getEmailComponent = async (
109
174
 
110
175
  return {
111
176
  emailComponent: fakeContext.module.exports.default as EmailComponent,
177
+ renderAsync: fakeContext.module.exports.renderAsync as typeof renderAsync,
178
+
112
179
  sourceMapToOriginalFile: sourceMapToEmail,
113
180
  };
114
181
  };