Skip to content

Instantly share code, notes, and snippets.

@balazsorban44
Last active September 12, 2024 11:12
Show Gist options
  • Save balazsorban44/41c2fe0a75b34171967ae7e6e75054ac to your computer and use it in GitHub Desktop.
Save balazsorban44/41c2fe0a75b34171967ae7e6e75054ac to your computer and use it in GitHub Desktop.
import fs from 'node:fs';
import path from 'node:path';
import { renderToStaticMarkup } from 'react-dom/server';
import { convert, type HtmlToTextOptions } from 'html-to-text';
const emailFiles = fs.readdirSync(path.resolve(__dirname, '../src/emails'));
interface EmailTemplateConfig {
default: React.ReactElement;
props: Record<string, string>;
subject: string;
}
const emailTemplates: Record<string, EmailTemplateConfig & { file: string }> =
{};
for (const file of emailFiles) {
if (file.endsWith('.tsx')) {
const emailModule = require(`../src/emails/${file}`) as EmailTemplateConfig;
const fileName = file.replace('.tsx', '');
const emailName = fileName
// eslint-disable-next-line prefer-named-capture-group
.replace(/-([a-z])/g, (_, letter) => (letter as string).toUpperCase());
emailTemplates[emailName] = {
file: fileName,
default: emailModule.default,
props: emailModule.props,
subject: emailModule.subject,
};
}
}
const htmlToTextOptions: HtmlToTextOptions = {
formatters: {
link(elem, _, builder) {
const href = (elem.attribs as Record<string, string>).href;
const content = elem.children[0].data!;
builder.addInline(href === content ? href : `${content} ${href}`);
},
},
selectors: [
{ selector: 'a', format: 'link' },
{ selector: 'img', format: 'skip' },
{ selector: 'h1', options: { uppercase: false } },
],
};
const imports = [
"import type { EmailContent } from './send-email';",
"import escapeHtml from 'escape-html';",
];
const content = [];
for (const [key, template] of Object.entries(emailTemplates)) {
const { file, props, subject } = template;
const Element = template.default;
const html = renderToStaticMarkup(<Element />);
const escapedHtml = html.replace(/\$\{(\w*)\}/g, '${escapeHtml($1)}');
const text = convert(html, htmlToTextOptions);
const typeName = `${key.charAt(0).toUpperCase() + key.slice(1)}Props`;
imports.push(
`import type { TemplateProps as ${typeName} } from '../emails/${file}';`,
);
content.push(`
export function ${key}Content(params: ${typeName}): EmailContent {
const { ${Object.keys(props).join(', ')} } = params;
return {
subject: \`${subject}\`,
body: {
html: \`${escapedHtml}\`,
text: \`${text}\`,
},
};
}`);
}
fs.writeFileSync(
path.resolve(__dirname, '../src/utils/emails.ts'),
`\
// Generated by \`pnpm build:email\`
${imports.join('\n')}
${content.join('\n')}`,
'utf8',
);
console.log('Email templates built successfully.');
import {
Body,
Container,
Head,
Html,
Section,
Text,
Tailwind,
} from '@react-email/components';
export interface TemplateProps {
name: string;
}
export const props: TemplateProps = {
name: '${name}',
};
export const subject = `Hi, ${props.name}`;
export default function Template(): React.JSX.Element {
return (
<Html>
<Tailwind>
<Head />
<Body>
<Container>
<Section>
<Text>{props.name}</Text>
</Section>
</Container>
</Body>
</Tailwind>
</Html>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment