Skip to content

Commit c9b7433

Browse files
committed
fix(react-email): Dependents of dependents of email templates not causing hot reloads (#2083)
1 parent 4c50846 commit c9b7433

File tree

6 files changed

+126
-3
lines changed

6 files changed

+126
-3
lines changed

.changeset/eleven-wombats-make.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-email": patch
3+
---
4+
5+
Fix dependent of dependents not causing hot reloads

packages/react-email/src/cli/utils/preview/hot-reloading/setup-hot-reloading.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export const setupHotreloading = async (
7878

7979
const newFilesOutsideEmailsDirectory = getFilesOutsideEmailsDirectory();
8080
// updates the files outside of the user's emails directory by unwatching
81-
// the inexistant ones and watching the new ones
81+
// the inexistent ones and watching the new ones
8282
//
8383
// this is necessary to avoid the issue mentioned here https://github.com/resend/react-email/issues/1433#issuecomment-2177515290
8484
for (const p of filesOutsideEmailsDirectory) {

packages/react-email/src/hooks/use-email-rendering-result.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
renderEmailByPath,
66
} from '../actions/render-email-by-path';
77
import { isBuilding } from '../app/env';
8+
import { useEmails } from '../contexts/emails';
9+
import { containsEmailTemplate } from '../utils/contains-email-template';
810
import { useHotreload } from './use-hot-reload';
911

1012
export const useEmailRenderingResult = (
@@ -15,6 +17,8 @@ export const useEmailRenderingResult = (
1517
serverEmailRenderedResult,
1618
);
1719

20+
const { emailsDirectoryMetadata } = useEmails();
21+
1822
if (!isBuilding) {
1923
// eslint-disable-next-line react-hooks/rules-of-hooks
2024
useHotreload(async (changes) => {
@@ -25,10 +29,15 @@ export const useEmailRenderingResult = (
2529
// going to be equivalent to the slug
2630
change.filename;
2731

32+
if (
33+
containsEmailTemplate(slugForChangedEmail, emailsDirectoryMetadata)
34+
) {
35+
continue;
36+
}
37+
2838
const pathForChangedEmail =
2939
await getEmailPathFromSlug(slugForChangedEmail);
3040

31-
// We always render the email template here so that we can allow
3241
const newRenderingResult = await renderEmailByPath(
3342
pathForChangedEmail,
3443
true,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import path from 'node:path';
2+
import { containsEmailTemplate } from './contains-email-template';
3+
4+
test('containsEmailTemplate()', async () => {
5+
const emailsDirectoryPath = path.resolve(
6+
__dirname,
7+
'../../../../apps/demo/emails',
8+
);
9+
const directory = {
10+
absolutePath: emailsDirectoryPath,
11+
directoryName: 'emails',
12+
relativePath: '',
13+
emailFilenames: [],
14+
subDirectories: [
15+
{
16+
absolutePath: `${emailsDirectoryPath}/magic-links`,
17+
directoryName: 'magic-links',
18+
relativePath: 'magic-links',
19+
emailFilenames: [
20+
'aws-verify-email',
21+
'linear-login-code',
22+
'notion-magic-link',
23+
'plaid-verify-identity',
24+
'raycast-magic-link',
25+
'slack-confirm',
26+
],
27+
subDirectories: [],
28+
},
29+
{
30+
absolutePath: `${emailsDirectoryPath}/newsletters`,
31+
directoryName: 'newsletters',
32+
relativePath: 'newsletters',
33+
emailFilenames: [
34+
'codepen-challengers',
35+
'google-play-policy-update',
36+
'stack-overflow-tips',
37+
],
38+
subDirectories: [],
39+
},
40+
{
41+
absolutePath: `${emailsDirectoryPath}/notifications`,
42+
directoryName: 'notifications',
43+
relativePath: 'notifications',
44+
emailFilenames: [
45+
'github-access-token',
46+
'papermark-year-in-review',
47+
'vercel-invite-user',
48+
'yelp-recent-login',
49+
],
50+
subDirectories: [],
51+
},
52+
{
53+
absolutePath: `${emailsDirectoryPath}/receipts`,
54+
directoryName: 'receipts',
55+
relativePath: 'receipts',
56+
emailFilenames: ['apple-receipt', 'nike-receipt'],
57+
subDirectories: [],
58+
},
59+
{
60+
absolutePath: `${emailsDirectoryPath}/reset-password`,
61+
directoryName: 'reset-password',
62+
relativePath: 'reset-password',
63+
emailFilenames: ['dropbox-reset-password', 'twitch-reset-password'],
64+
subDirectories: [],
65+
},
66+
{
67+
absolutePath: `${emailsDirectoryPath}/reviews`,
68+
directoryName: 'reviews',
69+
relativePath: 'reviews',
70+
emailFilenames: ['airbnb-review', 'amazon-review'],
71+
subDirectories: [],
72+
},
73+
{
74+
absolutePath: `${emailsDirectoryPath}/welcome`,
75+
directoryName: 'welcome',
76+
relativePath: 'welcome',
77+
emailFilenames: ['koala-welcome', 'netlify-welcome', 'stripe-welcome'],
78+
subDirectories: [],
79+
},
80+
],
81+
};
82+
expect(containsEmailTemplate('welcome/koala-welcome', directory)).toBe(true);
83+
expect(containsEmailTemplate('welcome/missing-template', directory)).toBe(
84+
false,
85+
);
86+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { EmailsDirectory } from './get-emails-directory-metadata';
2+
3+
export const containsEmailTemplate = (
4+
relativeEmailPath: string,
5+
directory: EmailsDirectory,
6+
) => {
7+
const remainingSegments = relativeEmailPath
8+
.replace(directory.relativePath, '')
9+
.split('/')
10+
.filter(Boolean);
11+
if (remainingSegments.length === 1) {
12+
const emailFilename = remainingSegments[0]!;
13+
return directory.emailFilenames.includes(emailFilename);
14+
}
15+
const subDirectory = directory.subDirectories.find(
16+
(sub) => sub.relativePath === remainingSegments[0],
17+
);
18+
if (subDirectory === undefined) {
19+
return false;
20+
}
21+
22+
return containsEmailTemplate(relativeEmailPath, subDirectory);
23+
};

packages/react-email/src/utils/get-emails-directory-metadata.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const isFileAnEmail = (fullPath: string): boolean => {
1212
if (!['.js', '.tsx', '.jsx'].includes(ext)) return false;
1313

1414
// This is to avoid a possible race condition where the file doesn't exist anymore
15-
// once we are checking if it is an actual email, this couuld cause issues that
15+
// once we are checking if it is an actual email, this could cause issues that
1616
// would be very hard to debug and find out the why of it happening.
1717
if (!fs.existsSync(fullPath)) {
1818
return false;

0 commit comments

Comments
 (0)