react-email 2.0.0-canary.0 → 2.0.0-canary.2
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 +18 -2
- package/cli/index.mjs +5 -2
- package/package.json +2 -2
- package/src/actions/render-email-by-slug.tsx +28 -15
- package/src/app/preview/[slug]/preview.tsx +0 -1
- package/src/components/sidebar/sidebar-directory-children.tsx +1 -0
- package/src/components/sidebar/sidebar-directory.tsx +29 -13
- package/src/components/sidebar/sidebar.tsx +2 -2
- package/src/utils/get-email-component.ts +13 -46
- package/src/utils/improve-error-with-sourcemap.ts +55 -0
- package/src/utils/types/error-object.ts +5 -0
package/cli/index.js
CHANGED
|
@@ -252,7 +252,7 @@ var import_extra_typings = require("@commander-js/extra-typings");
|
|
|
252
252
|
// package.json
|
|
253
253
|
var package_default = {
|
|
254
254
|
name: "react-email",
|
|
255
|
-
version: "2.0.0-canary.
|
|
255
|
+
version: "2.0.0-canary.2",
|
|
256
256
|
description: "A live preview of your emails right in your browser.",
|
|
257
257
|
bin: {
|
|
258
258
|
email: "./cli/index.js"
|
|
@@ -296,7 +296,7 @@ var package_default = {
|
|
|
296
296
|
clsx: "2.1.0",
|
|
297
297
|
commander: "9.4.1",
|
|
298
298
|
debounce: "2.0.0",
|
|
299
|
-
esbuild: "0.
|
|
299
|
+
esbuild: "0.19.11",
|
|
300
300
|
"eslint-config-prettier": "9.0.0",
|
|
301
301
|
"eslint-config-turbo": "1.10.12",
|
|
302
302
|
"fast-glob": "3.3.2",
|
|
@@ -571,6 +571,22 @@ var startDevServer = function() {
|
|
|
571
571
|
startDevServer(emailsDirRelativePath, staticBaseDirRelativePath, nextPortToTry)
|
|
572
572
|
];
|
|
573
573
|
}
|
|
574
|
+
devServer.on("close", /*#__PURE__*/ _async_to_generator(function() {
|
|
575
|
+
return _ts_generator(this, function(_state) {
|
|
576
|
+
switch(_state.label){
|
|
577
|
+
case 0:
|
|
578
|
+
return [
|
|
579
|
+
4,
|
|
580
|
+
app.close()
|
|
581
|
+
];
|
|
582
|
+
case 1:
|
|
583
|
+
_state.sent();
|
|
584
|
+
return [
|
|
585
|
+
2
|
|
586
|
+
];
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
}));
|
|
574
590
|
devServer.on("error", function(e) {
|
|
575
591
|
console.error(" ".concat(import_log_symbols.default.error, " preview server error: "), JSON.stringify(e));
|
|
576
592
|
process.exit(1);
|
package/cli/index.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { program } from "@commander-js/extra-typings";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "react-email",
|
|
9
|
-
version: "2.0.0-canary.
|
|
9
|
+
version: "2.0.0-canary.2",
|
|
10
10
|
description: "A live preview of your emails right in your browser.",
|
|
11
11
|
bin: {
|
|
12
12
|
email: "./cli/index.js"
|
|
@@ -50,7 +50,7 @@ var package_default = {
|
|
|
50
50
|
clsx: "2.1.0",
|
|
51
51
|
commander: "9.4.1",
|
|
52
52
|
debounce: "2.0.0",
|
|
53
|
-
esbuild: "0.
|
|
53
|
+
esbuild: "0.19.11",
|
|
54
54
|
"eslint-config-prettier": "9.0.0",
|
|
55
55
|
"eslint-config-turbo": "1.10.12",
|
|
56
56
|
"fast-glob": "3.3.2",
|
|
@@ -275,6 +275,9 @@ var startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath, po
|
|
|
275
275
|
nextPortToTry
|
|
276
276
|
);
|
|
277
277
|
}
|
|
278
|
+
devServer.on("close", async () => {
|
|
279
|
+
await app.close();
|
|
280
|
+
});
|
|
278
281
|
devServer.on("error", (e) => {
|
|
279
282
|
console.error(
|
|
280
283
|
` ${logSymbols.error} preview server error: `,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-email",
|
|
3
|
-
"version": "2.0.0-canary.
|
|
3
|
+
"version": "2.0.0-canary.2",
|
|
4
4
|
"description": "A live preview of your emails right in your browser.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"email": "./cli/index.js"
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"clsx": "2.1.0",
|
|
39
39
|
"commander": "9.4.1",
|
|
40
40
|
"debounce": "2.0.0",
|
|
41
|
-
"esbuild": "0.
|
|
41
|
+
"esbuild": "0.19.11",
|
|
42
42
|
"eslint-config-prettier": "9.0.0",
|
|
43
43
|
"eslint-config-turbo": "1.10.12",
|
|
44
44
|
"fast-glob": "3.3.2",
|
|
@@ -5,6 +5,7 @@ import { renderAsync } from '@react-email/render';
|
|
|
5
5
|
import { getEmailComponent } from '../utils/get-email-component';
|
|
6
6
|
import { emailsDirectoryAbsolutePath } from '../utils/emails-directory-absolute-path';
|
|
7
7
|
import type { ErrorObject } from '../utils/types/error-object';
|
|
8
|
+
import { improveErrorWithSourceMap } from '../utils/improve-error-with-sourcemap';
|
|
8
9
|
|
|
9
10
|
export interface RenderedEmailMetadata {
|
|
10
11
|
markup: string;
|
|
@@ -29,22 +30,34 @@ export const renderEmailBySlug = async (
|
|
|
29
30
|
return { error: result.error };
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
const Email = result
|
|
33
|
+
const { emailComponent: Email, sourceMapToOriginalFile } = result;
|
|
33
34
|
|
|
34
35
|
const previewProps = Email.PreviewProps || {};
|
|
35
36
|
const EmailComponent = Email as React.FC;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
plainText
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
37
|
+
try {
|
|
38
|
+
const markup = await renderAsync(<EmailComponent {...previewProps} />, {
|
|
39
|
+
pretty: true,
|
|
40
|
+
});
|
|
41
|
+
const plainText = await renderAsync(<EmailComponent {...previewProps} />, {
|
|
42
|
+
plainText: true,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const reactMarkup = await fs.readFile(emailPath, 'utf-8');
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
markup,
|
|
49
|
+
plainText,
|
|
50
|
+
reactMarkup,
|
|
51
|
+
};
|
|
52
|
+
} catch (exception) {
|
|
53
|
+
const error = exception as Error;
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
error: improveErrorWithSourceMap(
|
|
57
|
+
error,
|
|
58
|
+
emailPath,
|
|
59
|
+
sourceMapToOriginalFile,
|
|
60
|
+
),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
50
63
|
};
|
|
@@ -17,6 +17,7 @@ export const SidebarDirectoryChildren = (props: {
|
|
|
17
17
|
}) => {
|
|
18
18
|
const directoryPathRelativeToEmailsDirectory =
|
|
19
19
|
props.emailsDirectoryMetadata.absolutePath
|
|
20
|
+
.replace(`${emailsDirectoryAbsolutePath}${pathSeparator}`, '')
|
|
20
21
|
.replace(emailsDirectoryAbsolutePath, '')
|
|
21
22
|
.trim();
|
|
22
23
|
const isBaseEmailsDirectory =
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
import * as Collapsible from '@radix-ui/react-collapsible';
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import { cn } from '../../utils';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
emailsDirectoryAbsolutePath,
|
|
7
|
+
pathSeparator,
|
|
8
|
+
} from '../../utils/emails-directory-absolute-path';
|
|
6
9
|
import { type EmailsDirectory } from '../../actions/get-emails-directory-metadata';
|
|
7
10
|
import { Heading } from '../heading';
|
|
8
11
|
import { IconFolder } from '../icons/icon-folder';
|
|
@@ -15,34 +18,47 @@ interface SidebarDirectoryProps {
|
|
|
15
18
|
currentEmailOpenSlug?: string;
|
|
16
19
|
}
|
|
17
20
|
|
|
21
|
+
const persistedOpenDirectories = new Set<string>();
|
|
22
|
+
|
|
18
23
|
export const SidebarDirectory = ({
|
|
19
|
-
emailsDirectoryMetadata,
|
|
24
|
+
emailsDirectoryMetadata: directoryMetadata,
|
|
20
25
|
className,
|
|
21
26
|
currentEmailOpenSlug,
|
|
22
27
|
}: SidebarDirectoryProps) => {
|
|
23
28
|
const isBaseEmailsDirectory =
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
29
|
+
directoryMetadata.absolutePath === emailsDirectoryAbsolutePath;
|
|
30
|
+
const directoryPathRelativeToBaseEmailsDirectory =
|
|
31
|
+
directoryMetadata.absolutePath
|
|
32
|
+
.replace(`${emailsDirectoryAbsolutePath}${pathSeparator}`, '')
|
|
27
33
|
.replace(emailsDirectoryAbsolutePath, '')
|
|
28
34
|
.trim();
|
|
29
|
-
const
|
|
30
|
-
? currentEmailOpenSlug.includes(
|
|
35
|
+
const doesDirectoryContainCurrentEmailOpen = currentEmailOpenSlug
|
|
36
|
+
? currentEmailOpenSlug.includes(directoryPathRelativeToBaseEmailsDirectory)
|
|
31
37
|
: false;
|
|
32
38
|
|
|
33
39
|
const isEmpty =
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
directoryMetadata.emailFilenames.length > 0 ||
|
|
41
|
+
directoryMetadata.subDirectories.length > 0;
|
|
36
42
|
|
|
37
43
|
const [open, setOpen] = React.useState(
|
|
38
|
-
|
|
44
|
+
persistedOpenDirectories.has(directoryMetadata.absolutePath) ||
|
|
45
|
+
isBaseEmailsDirectory ||
|
|
46
|
+
doesDirectoryContainCurrentEmailOpen,
|
|
39
47
|
);
|
|
40
48
|
|
|
41
49
|
return (
|
|
42
50
|
<Collapsible.Root
|
|
43
51
|
className={cn('group', className)}
|
|
44
52
|
data-root={isBaseEmailsDirectory}
|
|
45
|
-
onOpenChange={
|
|
53
|
+
onOpenChange={(isOpening) => {
|
|
54
|
+
if (isOpening) {
|
|
55
|
+
persistedOpenDirectories.add(directoryMetadata.absolutePath);
|
|
56
|
+
} else {
|
|
57
|
+
persistedOpenDirectories.delete(directoryMetadata.absolutePath);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
setOpen(isOpening);
|
|
61
|
+
}}
|
|
46
62
|
open={open}
|
|
47
63
|
>
|
|
48
64
|
<Collapsible.Trigger
|
|
@@ -60,7 +76,7 @@ export const SidebarDirectory = ({
|
|
|
60
76
|
size="2"
|
|
61
77
|
weight="medium"
|
|
62
78
|
>
|
|
63
|
-
{
|
|
79
|
+
{directoryMetadata.directoryName}
|
|
64
80
|
</Heading>
|
|
65
81
|
{isEmpty ? (
|
|
66
82
|
<IconArrowDown
|
|
@@ -74,7 +90,7 @@ export const SidebarDirectory = ({
|
|
|
74
90
|
{isEmpty ? (
|
|
75
91
|
<SidebarDirectoryChildren
|
|
76
92
|
currentEmailOpenSlug={currentEmailOpenSlug}
|
|
77
|
-
emailsDirectoryMetadata={
|
|
93
|
+
emailsDirectoryMetadata={directoryMetadata}
|
|
78
94
|
open={open}
|
|
79
95
|
/>
|
|
80
96
|
) : null}
|
|
@@ -17,9 +17,9 @@ export const Sidebar = React.forwardRef<SidebarElement, Readonly<SidebarProps>>(
|
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
19
|
<aside className={className} ref={forwardedRef} {...props}>
|
|
20
|
-
<nav className="p-6 w-screen md:w-full md:min-w-[275px] md:max-w-[275px] flex flex-col gap-4 border-r border-slate-6">
|
|
20
|
+
<nav className="p-6 h-full w-screen md:w-full md:min-w-[275px] md:max-w-[275px] flex flex-col gap-4 border-r border-slate-6">
|
|
21
21
|
<SidebarDirectory
|
|
22
|
-
className="w-fit overflow-x-auto"
|
|
22
|
+
className="min-w-full w-fit overflow-x-auto"
|
|
23
23
|
currentEmailOpenSlug={currentEmailOpenSlug}
|
|
24
24
|
emailsDirectoryMetadata={emailsDirectoryMetadata}
|
|
25
25
|
/>
|
|
@@ -3,17 +3,19 @@
|
|
|
3
3
|
import vm from 'node:vm';
|
|
4
4
|
import stream from 'node:stream';
|
|
5
5
|
import util from 'node:util';
|
|
6
|
-
import
|
|
7
|
-
import { SourceMapConsumer, type RawSourceMap } from 'source-map-js';
|
|
6
|
+
import { type RawSourceMap } from 'source-map-js';
|
|
8
7
|
import { type OutputFile, build, type BuildFailure } from 'esbuild';
|
|
9
8
|
import type { EmailTemplate as EmailComponent } from './types/email-template';
|
|
10
9
|
import type { ErrorObject } from './types/error-object';
|
|
10
|
+
import { improveErrorWithSourceMap } from './improve-error-with-sourcemap';
|
|
11
11
|
|
|
12
12
|
export const getEmailComponent = async (
|
|
13
13
|
emailPath: string,
|
|
14
14
|
): Promise<
|
|
15
15
|
| {
|
|
16
16
|
emailComponent: EmailComponent;
|
|
17
|
+
|
|
18
|
+
sourceMapToOriginalFile: RawSourceMap;
|
|
17
19
|
}
|
|
18
20
|
| { error: ErrorObject }
|
|
19
21
|
> => {
|
|
@@ -25,7 +27,12 @@ export const getEmailComponent = async (
|
|
|
25
27
|
platform: 'node',
|
|
26
28
|
write: false,
|
|
27
29
|
format: 'cjs',
|
|
28
|
-
jsx: '
|
|
30
|
+
jsx: 'automatic',
|
|
31
|
+
logLevel: 'silent',
|
|
32
|
+
// allows for using jsx on a .js file
|
|
33
|
+
loader: {
|
|
34
|
+
'.js': 'jsx',
|
|
35
|
+
},
|
|
29
36
|
outdir: 'stdout', // just a stub for esbuild, it won't actually write to this folder
|
|
30
37
|
sourcemap: 'external',
|
|
31
38
|
});
|
|
@@ -75,59 +82,19 @@ export const getEmailComponent = async (
|
|
|
75
82
|
},
|
|
76
83
|
process,
|
|
77
84
|
};
|
|
85
|
+
const sourceMapToEmail = JSON.parse(sourceMapFile.text) as RawSourceMap;
|
|
78
86
|
try {
|
|
79
87
|
vm.runInNewContext(builtEmailCode, fakeContext, { filename: emailPath });
|
|
80
88
|
} catch (exception) {
|
|
81
89
|
const error = exception as Error;
|
|
82
|
-
let stack: string | undefined;
|
|
83
|
-
|
|
84
|
-
if (typeof error.stack !== 'undefined') {
|
|
85
|
-
const parsedStack = stackTraceParser.parse(error.stack);
|
|
86
|
-
const sourceMapConsumer = new SourceMapConsumer(
|
|
87
|
-
JSON.parse(sourceMapFile.text) as RawSourceMap,
|
|
88
|
-
);
|
|
89
|
-
const newStackLines = [] as string[];
|
|
90
|
-
for (const stackFrame of parsedStack) {
|
|
91
|
-
if (stackFrame.file === emailPath) {
|
|
92
|
-
if (stackFrame.column || stackFrame.lineNumber) {
|
|
93
|
-
const positionWithError = sourceMapConsumer.originalPositionFor({
|
|
94
|
-
column: stackFrame.column ?? 0,
|
|
95
|
-
line: stackFrame.lineNumber ?? 0,
|
|
96
|
-
});
|
|
97
|
-
const columnAndLine =
|
|
98
|
-
positionWithError.column && positionWithError.line
|
|
99
|
-
? `${positionWithError.line}:${positionWithError.column}`
|
|
100
|
-
: positionWithError.line;
|
|
101
|
-
newStackLines.push(
|
|
102
|
-
` at ${stackFrame.methodName} (${emailPath}:${columnAndLine})`,
|
|
103
|
-
);
|
|
104
|
-
} else {
|
|
105
|
-
newStackLines.push(` at ${stackFrame.methodName} (${emailPath})`);
|
|
106
|
-
}
|
|
107
|
-
} else {
|
|
108
|
-
const columnAndLine =
|
|
109
|
-
stackFrame.column && stackFrame.lineNumber
|
|
110
|
-
? `${stackFrame.lineNumber}:${stackFrame.column}`
|
|
111
|
-
: stackFrame.lineNumber;
|
|
112
|
-
newStackLines.push(
|
|
113
|
-
` at ${stackFrame.methodName} (${stackFrame.file}:${columnAndLine})`,
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
stack = newStackLines.join('\n');
|
|
118
|
-
}
|
|
119
90
|
|
|
120
91
|
return {
|
|
121
|
-
error:
|
|
122
|
-
name: error.name,
|
|
123
|
-
message: error.message,
|
|
124
|
-
cause: error.cause,
|
|
125
|
-
stack,
|
|
126
|
-
},
|
|
92
|
+
error: improveErrorWithSourceMap(error, emailPath, sourceMapToEmail),
|
|
127
93
|
};
|
|
128
94
|
}
|
|
129
95
|
|
|
130
96
|
return {
|
|
131
97
|
emailComponent: fakeContext.module.exports.default as EmailComponent,
|
|
98
|
+
sourceMapToOriginalFile: sourceMapToEmail,
|
|
132
99
|
};
|
|
133
100
|
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as stackTraceParser from 'stacktrace-parser';
|
|
2
|
+
import { SourceMapConsumer, type RawSourceMap } from 'source-map-js';
|
|
3
|
+
import type { ErrorObject } from './types/error-object';
|
|
4
|
+
|
|
5
|
+
export const improveErrorWithSourceMap = (
|
|
6
|
+
error: Error,
|
|
7
|
+
|
|
8
|
+
originalFilePath: string,
|
|
9
|
+
sourceMapToOriginalFile: RawSourceMap,
|
|
10
|
+
): ErrorObject => {
|
|
11
|
+
let stack: string | undefined;
|
|
12
|
+
|
|
13
|
+
if (typeof error.stack !== 'undefined') {
|
|
14
|
+
const parsedStack = stackTraceParser.parse(error.stack);
|
|
15
|
+
const sourceMapConsumer = new SourceMapConsumer(sourceMapToOriginalFile);
|
|
16
|
+
const newStackLines = [] as string[];
|
|
17
|
+
for (const stackFrame of parsedStack) {
|
|
18
|
+
if (stackFrame.file === originalFilePath) {
|
|
19
|
+
if (stackFrame.column || stackFrame.lineNumber) {
|
|
20
|
+
const positionWithError = sourceMapConsumer.originalPositionFor({
|
|
21
|
+
column: stackFrame.column ?? 0,
|
|
22
|
+
line: stackFrame.lineNumber ?? 0,
|
|
23
|
+
});
|
|
24
|
+
const columnAndLine =
|
|
25
|
+
positionWithError.column && positionWithError.line
|
|
26
|
+
? `${positionWithError.line}:${positionWithError.column}`
|
|
27
|
+
: positionWithError.line;
|
|
28
|
+
newStackLines.push(
|
|
29
|
+
` at ${stackFrame.methodName} (${originalFilePath}:${columnAndLine})`,
|
|
30
|
+
);
|
|
31
|
+
} else {
|
|
32
|
+
newStackLines.push(
|
|
33
|
+
` at ${stackFrame.methodName} (${originalFilePath})`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
const columnAndLine =
|
|
38
|
+
stackFrame.column && stackFrame.lineNumber
|
|
39
|
+
? `${stackFrame.lineNumber}:${stackFrame.column}`
|
|
40
|
+
: stackFrame.lineNumber;
|
|
41
|
+
newStackLines.push(
|
|
42
|
+
` at ${stackFrame.methodName} (${stackFrame.file}:${columnAndLine})`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
stack = newStackLines.join('\n');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
name: error.name,
|
|
51
|
+
message: error.message,
|
|
52
|
+
cause: error.cause,
|
|
53
|
+
stack,
|
|
54
|
+
};
|
|
55
|
+
};
|