|
| 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