Skip to content

Commit 39d2cb8

Browse files
dielduarteclaude
andcommitted
docs: document the --esbuild-plugins option
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 1bfa0f4 commit 39d2cb8

3 files changed

Lines changed: 126 additions & 1 deletion

File tree

‎apps/docs/cli.mdx‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ your email template when you make changes.
1919
<ResponseField name="--port" type="string" default="3000">
2020
Port to run dev server on
2121
</ResponseField>
22+
<ResponseField name="--esbuild-plugins" type="string">
23+
Path to a module whose default export is an array of [esbuild plugins](/guides/esbuild-plugins), or a function returning one, to run when bundling your templates.
24+
</ResponseField>
2225

2326
**F.A.Q**
2427

@@ -147,6 +150,9 @@ Copies the preview app for onto `.react-email` and builds it.
147150
<ResponseField name="--packageManager" type="string" default="npm">
148151
Package manager to use on the installation of `.react-email`.
149152
</ResponseField>
153+
<ResponseField name="--esbuild-plugins" type="string">
154+
Path to a module whose default export is an array of [esbuild plugins](/guides/esbuild-plugins), or a function returning one, to run when bundling your templates.
155+
</ResponseField>
150156

151157
## `email start`
152158

@@ -194,6 +200,9 @@ Generates the plain HTML files of your emails into a `out` directory.
194200
Set a custom file extension for rendered templates (for example, `blade.php`). When omitted,
195201
the extension defaults to `.html`, or `.txt` when `--plainText` is enabled.
196202
</ResponseField>
203+
<ResponseField name="--esbuild-plugins" type="string">
204+
Path to a module whose default export is an array of [esbuild plugins](/guides/esbuild-plugins), or a function returning one, to run when bundling your templates.
205+
</ResponseField>
197206

198207
## `email help <cmd>`
199208

‎apps/docs/docs.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@
7575
"guides/internationalization/react-i18next",
7676
"guides/internationalization/react-intl"
7777
]
78-
}
78+
},
79+
"guides/esbuild-plugins"
7980
]
8081
},
8182
{
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
---
2+
title: 'Custom esbuild plugins'
3+
sidebarTitle: 'esbuild plugins'
4+
description: 'Run your own esbuild plugins when React Email bundles your templates'
5+
'og:image': 'https://react.email/static/covers/react-email.png'
6+
icon: 'puzzle-piece'
7+
---
8+
9+
To preview, build, and export your emails, React Email bundles each template with [esbuild](https://esbuild.github.io/).
10+
The `--esbuild-plugins` option adds your own [esbuild plugins](https://esbuild.github.io/plugins/) to that bundling step.
11+
12+
Use it when a template depends on something only a bundler plugin can provide, such as:
13+
14+
- A compile-time transform
15+
- Imports that need a custom resolver, like virtual modules or data fetched at build time
16+
- File types esbuild doesn't load on its own
17+
18+
## 1. Create a plugins module
19+
20+
Create a module whose default export is an array of esbuild plugins.
21+
It can also export a function that returns the array, sync or async, for plugins that need setup.
22+
The module can be written in JavaScript or TypeScript.
23+
24+
This example lets templates import the latest releases of any GitHub repository, fetched while the template is bundled:
25+
26+
```js esbuild-plugins.mjs
27+
const cache = new Map();
28+
29+
export default [
30+
{
31+
name: 'github-releases',
32+
setup(build) {
33+
build.onResolve({ filter: /^github-releases:/ }, (args) => ({
34+
path: args.path.slice('github-releases:'.length),
35+
namespace: 'github-releases',
36+
}));
37+
38+
build.onLoad(
39+
{ filter: /.*/, namespace: 'github-releases' },
40+
async ({ path: repo }) => {
41+
if (!cache.has(repo)) {
42+
const response = await fetch(
43+
`https://api.github.com/repos/${repo}/releases?per_page=3`,
44+
);
45+
if (!response.ok) {
46+
throw new Error(`Could not fetch releases for ${repo}`);
47+
}
48+
const releases = await response.json();
49+
cache.set(
50+
repo,
51+
releases.map((release) => ({
52+
name: release.name || release.tag_name,
53+
url: release.html_url,
54+
body: release.body ?? '',
55+
})),
56+
);
57+
}
58+
59+
return { contents: JSON.stringify(cache.get(repo)), loader: 'json' };
60+
},
61+
);
62+
},
63+
},
64+
];
65+
```
66+
67+
<Info>
68+
The preview server bundles a template again every time it renders it, so
69+
cache anything expensive your plugins do, like the network request above.
70+
</Info>
71+
72+
## 2. Use it in a template
73+
74+
With the plugin in place, a template can import the releases like any other module:
75+
76+
```jsx emails/whats-new.jsx
77+
import { Button, Heading, Html, Markdown, Section } from 'react-email';
78+
import releases from 'github-releases:resend/react-email';
79+
80+
export default function WhatsNew() {
81+
return (
82+
<Html>
83+
{releases.map((release) => (
84+
<Section key={release.url}>
85+
<Heading as="h2">{release.name}</Heading>
86+
<Markdown>{release.body}</Markdown>
87+
<Button href={release.url}>View on GitHub</Button>
88+
</Section>
89+
))}
90+
</Html>
91+
);
92+
}
93+
```
94+
95+
## 3. Pass the plugins module to the CLI
96+
97+
Pass the module's path to every command that bundles your templates. The path is relative to the directory you run the command from.
98+
99+
```json package.json
100+
{
101+
"scripts": {
102+
"dev": "email dev --esbuild-plugins ./esbuild-plugins.mjs",
103+
"build": "email build --esbuild-plugins ./esbuild-plugins.mjs",
104+
"export": "email export --esbuild-plugins ./esbuild-plugins.mjs"
105+
}
106+
}
107+
```
108+
109+
`email start` doesn't take the option, since it serves the app `email build` already bundled with your plugins.
110+
111+
## Things to know
112+
113+
- **Plugins are loaded once per process.** After editing a working plugins module, restart `email dev` for the changes to apply.
114+
- **Loading errors show in the preview.** If the plugins module fails to load, the preview shows the error, and picks up your fix on the next render without a restart.
115+
- **React Email's own plugins run first.**

0 commit comments

Comments
 (0)