Skip to content

fix(react-email): Dependents of dependents of email templates not causing hot reloads #2083

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eleven-wombats-make.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-email": patch
---

Fix dependent of dependents not causing hot reloads
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export const setupHotreloading = async (

const newFilesOutsideEmailsDirectory = getFilesOutsideEmailsDirectory();
// updates the files outside of the user's emails directory by unwatching
// the inexistant ones and watching the new ones
// the inexistent ones and watching the new ones
//
// this is necessary to avoid the issue mentioned here https://github.com/resend/react-email/issues/1433#issuecomment-2177515290
for (const p of filesOutsideEmailsDirectory) {
Expand Down
11 changes: 10 additions & 1 deletion packages/react-email/src/hooks/use-email-rendering-result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
renderEmailByPath,
} from '../actions/render-email-by-path';
import { isBuilding } from '../app/env';
import { useEmails } from '../contexts/emails';
import { containsEmailTemplate } from '../utils/contains-email-template';
import { useHotreload } from './use-hot-reload';

export const useEmailRenderingResult = (
Expand All @@ -15,6 +17,8 @@ export const useEmailRenderingResult = (
serverEmailRenderedResult,
);

const { emailsDirectoryMetadata } = useEmails();

if (!isBuilding) {
// eslint-disable-next-line react-hooks/rules-of-hooks
useHotreload(async (changes) => {
Expand All @@ -25,10 +29,15 @@ export const useEmailRenderingResult = (
// going to be equivalent to the slug
change.filename;

if (
containsEmailTemplate(slugForChangedEmail, emailsDirectoryMetadata)
) {
continue;
}

const pathForChangedEmail =
await getEmailPathFromSlug(slugForChangedEmail);

// We always render the email template here so that we can allow
const newRenderingResult = await renderEmailByPath(
pathForChangedEmail,
true,
Expand Down
86 changes: 86 additions & 0 deletions packages/react-email/src/utils/contains-email-template.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import path from 'node:path';
import { containsEmailTemplate } from './contains-email-template';

test('containsEmailTemplate()', async () => {
const emailsDirectoryPath = path.resolve(
__dirname,
'../../../../apps/demo/emails',
);
const directory = {
absolutePath: emailsDirectoryPath,
directoryName: 'emails',
relativePath: '',
emailFilenames: [],
subDirectories: [
{
absolutePath: `${emailsDirectoryPath}/magic-links`,
directoryName: 'magic-links',
relativePath: 'magic-links',
emailFilenames: [
'aws-verify-email',
'linear-login-code',
'notion-magic-link',
'plaid-verify-identity',
'raycast-magic-link',
'slack-confirm',
],
subDirectories: [],
},
{
absolutePath: `${emailsDirectoryPath}/newsletters`,
directoryName: 'newsletters',
relativePath: 'newsletters',
emailFilenames: [
'codepen-challengers',
'google-play-policy-update',
'stack-overflow-tips',
],
subDirectories: [],
},
{
absolutePath: `${emailsDirectoryPath}/notifications`,
directoryName: 'notifications',
relativePath: 'notifications',
emailFilenames: [
'github-access-token',
'papermark-year-in-review',
'vercel-invite-user',
'yelp-recent-login',
],
subDirectories: [],
},
{
absolutePath: `${emailsDirectoryPath}/receipts`,
directoryName: 'receipts',
relativePath: 'receipts',
emailFilenames: ['apple-receipt', 'nike-receipt'],
subDirectories: [],
},
{
absolutePath: `${emailsDirectoryPath}/reset-password`,
directoryName: 'reset-password',
relativePath: 'reset-password',
emailFilenames: ['dropbox-reset-password', 'twitch-reset-password'],
subDirectories: [],
},
{
absolutePath: `${emailsDirectoryPath}/reviews`,
directoryName: 'reviews',
relativePath: 'reviews',
emailFilenames: ['airbnb-review', 'amazon-review'],
subDirectories: [],
},
{
absolutePath: `${emailsDirectoryPath}/welcome`,
directoryName: 'welcome',
relativePath: 'welcome',
emailFilenames: ['koala-welcome', 'netlify-welcome', 'stripe-welcome'],
subDirectories: [],
},
],
};
expect(containsEmailTemplate('welcome/koala-welcome', directory)).toBe(true);
expect(containsEmailTemplate('welcome/missing-template', directory)).toBe(
false,
);
});
23 changes: 23 additions & 0 deletions packages/react-email/src/utils/contains-email-template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { EmailsDirectory } from './get-emails-directory-metadata';

export const containsEmailTemplate = (
relativeEmailPath: string,
directory: EmailsDirectory,
) => {
const remainingSegments = relativeEmailPath
.replace(directory.relativePath, '')
.split('/')
.filter(Boolean);
if (remainingSegments.length === 1) {
const emailFilename = remainingSegments[0]!;
return directory.emailFilenames.includes(emailFilename);
}
const subDirectory = directory.subDirectories.find(
(sub) => sub.relativePath === remainingSegments[0],
);
if (subDirectory === undefined) {
return false;
}

return containsEmailTemplate(relativeEmailPath, subDirectory);
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const isFileAnEmail = (fullPath: string): boolean => {
if (!['.js', '.tsx', '.jsx'].includes(ext)) return false;

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