react-email 6.10.0 → 6.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # react-email
2
2
 
3
+ ## 6.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0432f96: Use custom esbuild plugins by passing the new `--esbuild-plugins` flag to `email dev`, `email build` and `email export`, so templates that need a custom compile-time transform can be previewed and exported.
8
+
3
9
  ## 6.10.0
4
10
 
5
11
  ### Minor Changes
@@ -6,7 +6,7 @@ import * as fs$1 from "node:fs";
6
6
  import fs, { existsSync, promises, statSync } from "node:fs";
7
7
  import * as path$2 from "node:path";
8
8
  import path from "node:path";
9
- import url, { fileURLToPath } from "node:url";
9
+ import url, { fileURLToPath, pathToFileURL } from "node:url";
10
10
  import logSymbols from "log-symbols";
11
11
  import { addDevDependency, installDependencies, runScript } from "nypm";
12
12
  import * as fsp$1 from "node:fs/promises";
@@ -6533,7 +6533,7 @@ const getTracingRootDir = async (usersProjectLocation) => {
6533
6533
  //#region package.json
6534
6534
  var package_default = {
6535
6535
  name: "react-email",
6536
- version: "6.10.0",
6536
+ version: "6.11.0",
6537
6537
  description: "A live preview of your emails right in your browser.",
6538
6538
  bin: { "email": "./dist/cli/index.mjs" },
6539
6539
  type: "module",
@@ -6739,8 +6739,9 @@ const stopSpinnerAndPersist = (spinner, display) => {
6739
6739
  //#endregion
6740
6740
  //#region src/cli/commands/build.ts
6741
6741
  const isInReactEmailMonorepo = !path.dirname(fileURLToPath(import.meta.url)).includes("node_modules");
6742
- const setNextEnvironmentVariablesForBuild = async (emailsDirRelativePath, builtPreviewAppPath, usersProjectLocation) => {
6742
+ const setNextEnvironmentVariablesForBuild = async (emailsDirRelativePath, builtPreviewAppPath, usersProjectLocation, esbuildPluginsPath) => {
6743
6743
  const rootDir = await getTracingRootDir(usersProjectLocation);
6744
+ const esbuildPluginsAbsolutePath = esbuildPluginsPath ? path.resolve(usersProjectLocation, esbuildPluginsPath).replaceAll("\\", "/") : void 0;
6744
6745
  const nextConfigContents = `
6745
6746
  import path from 'path';
6746
6747
  const emailsDirRelativePath = path.normalize('${emailsDirRelativePath}');
@@ -6754,13 +6755,14 @@ const nextConfig = {
6754
6755
  REACT_EMAIL_INTERNAL_EMAILS_DIR_RELATIVE_PATH: emailsDirRelativePath,
6755
6756
  REACT_EMAIL_INTERNAL_EMAILS_DIR_ABSOLUTE_PATH: path.resolve(userProjectLocation, emailsDirRelativePath),
6756
6757
  REACT_EMAIL_INTERNAL_PREVIEW_SERVER_LOCATION: previewServerLocation,
6757
- REACT_EMAIL_INTERNAL_USER_PROJECT_LOCATION: userProjectLocation
6758
+ REACT_EMAIL_INTERNAL_USER_PROJECT_LOCATION: userProjectLocation,
6759
+ ${esbuildPluginsAbsolutePath ? `REACT_EMAIL_INTERNAL_ESBUILD_PLUGINS_PATH: '${esbuildPluginsAbsolutePath}',` : ""}
6758
6760
  },
6759
6761
  turbopack: {
6760
6762
  root: rootDir,
6761
6763
  },
6762
6764
  outputFileTracingRoot: rootDir,
6763
- serverExternalPackages: ['esbuild'],
6765
+ serverExternalPackages: ['esbuild', 'jiti'],
6764
6766
  typescript: {
6765
6767
  ignoreBuildErrors: true
6766
6768
  },
@@ -6803,7 +6805,7 @@ const updatePackageJson = async (builtUiPath) => {
6803
6805
  for (const [dependency, version] of Object.entries(packageJson.devDependencies)) packageJson.devDependencies[dependency] = version.replace("workspace:", "");
6804
6806
  await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageJson), "utf8");
6805
6807
  };
6806
- const build$1 = async ({ dir: emailsDirRelativePath, packageManager }) => {
6808
+ const build$1 = async ({ dir: emailsDirRelativePath, packageManager, esbuildPlugins }) => {
6807
6809
  if (packageManager) console.warn("The --packageManager option is deprecated and ignored. The build command now just uses npm.");
6808
6810
  try {
6809
6811
  const usersProjectLocation = process.cwd();
@@ -6837,7 +6839,7 @@ const build$1 = async ({ dir: emailsDirRelativePath, packageManager }) => {
6837
6839
  await fs.promises.cp(staticPath, builtStaticDirectory, { recursive: true });
6838
6840
  }
6839
6841
  spinner.setText("Setting Next environment variables for preview app to work properly");
6840
- await setNextEnvironmentVariablesForBuild(emailsDirRelativePath, builtPreviewAppPath, usersProjectLocation);
6842
+ await setNextEnvironmentVariablesForBuild(emailsDirRelativePath, builtPreviewAppPath, usersProjectLocation, esbuildPlugins);
6841
6843
  spinner.setText("Setting server side generation for the email preview pages");
6842
6844
  await forceSSGForEmailPreviews(emailsDirPath, builtPreviewAppPath);
6843
6845
  spinner.setText("Updating package.json's build and start scripts");
@@ -7232,14 +7234,15 @@ const conf = new Conf({
7232
7234
  const styleText = nodeUtil.styleText ? nodeUtil.styleText : (_, text) => text;
7233
7235
  //#endregion
7234
7236
  //#region src/cli/utils/preview/get-env-variables-for-preview-app.ts
7235
- const getEnvVariablesForPreviewApp = (relativePathToEmailsDirectory, previewServerLocation, cwd, resendApiKey, compatibilityClients) => {
7237
+ const getEnvVariablesForPreviewApp = (relativePathToEmailsDirectory, previewServerLocation, cwd, resendApiKey, compatibilityClients, esbuildPluginsPath) => {
7236
7238
  return {
7237
7239
  REACT_EMAIL_INTERNAL_EMAILS_DIR_RELATIVE_PATH: relativePathToEmailsDirectory,
7238
7240
  REACT_EMAIL_INTERNAL_EMAILS_DIR_ABSOLUTE_PATH: path.resolve(cwd, relativePathToEmailsDirectory),
7239
7241
  REACT_EMAIL_INTERNAL_PREVIEW_SERVER_LOCATION: previewServerLocation,
7240
7242
  REACT_EMAIL_INTERNAL_USER_PROJECT_LOCATION: cwd,
7241
7243
  REACT_EMAIL_INTERNAL_RESEND_API_KEY: resendApiKey,
7242
- ...compatibilityClients !== void 0 && { COMPATIBILITY_EMAIL_CLIENTS: compatibilityClients }
7244
+ ...compatibilityClients !== void 0 && { COMPATIBILITY_EMAIL_CLIENTS: compatibilityClients },
7245
+ ...esbuildPluginsPath !== void 0 && { REACT_EMAIL_INTERNAL_ESBUILD_PLUGINS_PATH: path.resolve(cwd, esbuildPluginsPath) }
7243
7246
  };
7244
7247
  };
7245
7248
  //#endregion
@@ -7294,7 +7297,7 @@ const safeAsyncServerListen = (server, port) => {
7294
7297
  });
7295
7298
  });
7296
7299
  };
7297
- const startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath, port, compatibilityClients) => {
7300
+ const startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath, port, compatibilityClients, esbuildPluginsPath) => {
7298
7301
  const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
7299
7302
  if (major < 20 || major === 20 && minor < 19) {
7300
7303
  console.error(` ${logSymbols.error} Node ${process.versions.node} is not supported. Please upgrade to Node 20.19 or higher.`);
@@ -7328,7 +7331,7 @@ const startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath,
7328
7331
  } else {
7329
7332
  const nextPortToTry = port + 1;
7330
7333
  console.warn(` ${logSymbols.warning} Port ${port} is already in use, trying ${nextPortToTry}`);
7331
- return startDevServer(emailsDirRelativePath, staticBaseDirRelativePath, nextPortToTry, compatibilityClients);
7334
+ return startDevServer(emailsDirRelativePath, staticBaseDirRelativePath, nextPortToTry, compatibilityClients, esbuildPluginsPath);
7332
7335
  }
7333
7336
  devServer.on("close", async () => {
7334
7337
  await app.close();
@@ -7350,7 +7353,7 @@ const startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath,
7350
7353
  process.env = {
7351
7354
  NODE_ENV: "development",
7352
7355
  ...process.env,
7353
- ...getEnvVariablesForPreviewApp(path.normalize(emailsDirRelativePath), previewServerLocation, process.cwd(), conf.get("resendApiKey"), compatibilityClients)
7356
+ ...getEnvVariablesForPreviewApp(path.normalize(emailsDirRelativePath), previewServerLocation, process.cwd(), conf.get("resendApiKey"), compatibilityClients, esbuildPluginsPath)
7354
7357
  };
7355
7358
  if (!process.env.ESBUILD_BINARY_PATH) try {
7356
7359
  const esbuild = createJiti(previewServer.esmResolve("esbuild"));
@@ -7449,13 +7452,13 @@ const tree = async (dirPath, depth) => {
7449
7452
  };
7450
7453
  //#endregion
7451
7454
  //#region src/cli/commands/dev.ts
7452
- const dev = async ({ dir: emailsDirRelativePath, port, clients }) => {
7455
+ const dev = async ({ dir: emailsDirRelativePath, port, clients, esbuildPlugins }) => {
7453
7456
  try {
7454
7457
  if (!fs.existsSync(emailsDirRelativePath)) {
7455
7458
  console.error(`Missing ${emailsDirRelativePath} folder`);
7456
7459
  process.exit(1);
7457
7460
  }
7458
- await setupHotreloading(await startDevServer(emailsDirRelativePath, emailsDirRelativePath, Number.parseInt(port, 10), clients), emailsDirRelativePath);
7461
+ await setupHotreloading(await startDevServer(emailsDirRelativePath, emailsDirRelativePath, Number.parseInt(port, 10), clients, esbuildPlugins), emailsDirRelativePath);
7459
7462
  } catch (error) {
7460
7463
  console.log(error);
7461
7464
  process.exit(1);
@@ -7489,12 +7492,26 @@ const inlineCssLoader = () => ({
7489
7492
  }
7490
7493
  });
7491
7494
  //#endregion
7495
+ //#region src/cli/utils/esbuild/load-esbuild-plugins.ts
7496
+ /**
7497
+ * Loads the esbuild plugins a user wants applied when their templates are
7498
+ * bundled. The module's default export is either an array of plugins or a
7499
+ * function returning one, so plugins that need setup can do it lazily.
7500
+ */
7501
+ const loadEsbuildPlugins = async (modulePath) => {
7502
+ const exported = await createJiti(pathToFileURL(modulePath).href).import(modulePath, { default: true });
7503
+ const plugins = typeof exported === "function" ? await exported() : exported;
7504
+ if (!Array.isArray(plugins)) throw new Error(`Expected the default export of ${modulePath} to be an array of esbuild plugins or a function returning one`);
7505
+ return plugins;
7506
+ };
7507
+ //#endregion
7492
7508
  //#region src/cli/utils/esbuild/escape-string-for-regex.ts
7493
7509
  function escapeStringForRegex(string) {
7494
7510
  return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d");
7495
7511
  }
7496
7512
  //#endregion
7497
7513
  //#region src/cli/utils/esbuild/renderring-utilities-exporter.ts
7514
+ const ENTRY_SUFFIX = "?react-email-entry";
7498
7515
  /**
7499
7516
  * Made to export the `render` function out of the user's email template
7500
7517
  * so that issues like https://github.com/resend/react-email/issues/649 don't
@@ -7505,17 +7522,39 @@ function escapeStringForRegex(string) {
7505
7522
  *
7506
7523
  * This avoids multiple versions of React being involved, i.e., the version
7507
7524
  * in the CLI vs. the version the user has on their emails.
7525
+ *
7526
+ * Each template entry point is replaced by a wrapper module that re-exports
7527
+ * it along with the utilities, instead of loading the template's source here.
7528
+ * The template itself then goes through the regular load pipeline, so other
7529
+ * plugins (for example the user's `--esbuild-plugins`) can still transform it.
7508
7530
  */
7509
7531
  const renderingUtilitiesExporter = (emailTemplates) => ({
7510
7532
  name: "rendering-utilities-exporter",
7511
- setup: (b) => {
7512
- b.onLoad({ filter: new RegExp(emailTemplates.map((emailPath) => escapeStringForRegex(emailPath)).join("|")) }, async ({ path: pathToFile }) => {
7533
+ setup: async (b) => {
7534
+ const templatePaths = await Promise.all(emailTemplates.map((emailPath) => promises.realpath(emailPath)));
7535
+ const templateFilter = new RegExp(templatePaths.map((templatePath) => escapeStringForRegex(templatePath)).join("|"));
7536
+ b.onResolve({ filter: /.*/ }, async (args) => {
7537
+ if (args.kind !== "entry-point") return null;
7538
+ const resolvedPath = path.isAbsolute(args.path) ? args.path : path.resolve(args.resolveDir, args.path);
7539
+ const realPath = await promises.realpath(resolvedPath);
7540
+ if (!templatePaths.includes(realPath)) return null;
7513
7541
  return {
7514
- contents: `${await promises.readFile(pathToFile, "utf8")};
7515
- export { render } from 'react-email-module-that-will-export-render'
7542
+ path: realPath,
7543
+ suffix: ENTRY_SUFFIX
7544
+ };
7545
+ });
7546
+ b.onLoad({ filter: templateFilter }, ({ path: pathToFile, suffix }) => {
7547
+ if (suffix !== ENTRY_SUFFIX) return null;
7548
+ const templateSpecifier = JSON.stringify(pathToFile);
7549
+ return {
7550
+ contents: `export * from ${templateSpecifier};
7551
+ import * as emailModule from ${templateSpecifier};
7552
+ export default emailModule.default;
7553
+ export { render } from 'react-email-module-that-will-export-render';
7516
7554
  export { createElement as reactEmailCreateReactElement } from 'react';
7517
7555
  `,
7518
- loader: path.extname(pathToFile).slice(1)
7556
+ loader: "js",
7557
+ resolveDir: path.dirname(pathToFile)
7519
7558
  };
7520
7559
  });
7521
7560
  b.onResolve({ filter: /^react-email-module-that-will-export-render$/ }, async (args) => {
@@ -7591,6 +7630,7 @@ const exportTemplates = async (pathToWhereEmailMarkupShouldBeDumped, emailsDirec
7591
7630
  if (fs.existsSync(pathToWhereEmailMarkupShouldBeDumped)) fs.rmSync(pathToWhereEmailMarkupShouldBeDumped, { recursive: true });
7592
7631
  const allTemplates = getEmailTemplatesFromDirectory(emailsDirectoryMetadata);
7593
7632
  try {
7633
+ const userPlugins = options.esbuildPlugins ? await loadEsbuildPlugins(path.resolve(process.cwd(), options.esbuildPlugins)) : [];
7594
7634
  for (let i = 0; i < allTemplates.length; i += BUILD_BATCH_SIZE) {
7595
7635
  const batch = allTemplates.slice(i, i + BUILD_BATCH_SIZE);
7596
7636
  await build({
@@ -7604,7 +7644,11 @@ const exportTemplates = async (pathToWhereEmailMarkupShouldBeDumped, emailsDirec
7604
7644
  outExtension: { ".js": ".cjs" },
7605
7645
  outdir: pathToWhereEmailMarkupShouldBeDumped,
7606
7646
  platform: "node",
7607
- plugins: [inlineCssLoader(), renderingUtilitiesExporter(batch)],
7647
+ plugins: [
7648
+ inlineCssLoader(),
7649
+ renderingUtilitiesExporter(batch),
7650
+ ...userPlugins
7651
+ ],
7608
7652
  write: true
7609
7653
  });
7610
7654
  await stop();
@@ -7788,14 +7832,15 @@ if (!requiredFlags.every((flag) => process.execArgv.includes(flag))) spawn(proce
7788
7832
  });
7789
7833
  else {
7790
7834
  program.name("react-email").description("A live preview of your emails right in your browser").version(package_default.version);
7791
- program.command("dev").description("Starts the preview email development app").option("-d, --dir <path>", "Directory with your email templates", "./emails").option("-p --port <port>", "Port to run dev server on", "3000").option("-c, --clients <clients>", "Comma-separated list of email clients to show compatibility warnings for (overrides COMPATIBILITY_EMAIL_CLIENTS)", parseClientsOption).action(dev);
7792
- program.command("build").description("Copies the preview app for onto .react-email and builds it").option("-d, --dir <path>", "Directory with your email templates", "./emails").addOption(new Option("-p, --packageManager <name>").hideHelp()).action(build$1);
7835
+ program.command("dev").description("Starts the preview email development app").option("-d, --dir <path>", "Directory with your email templates", "./emails").option("-p --port <port>", "Port to run dev server on", "3000").option("-c, --clients <clients>", "Comma-separated list of email clients to show compatibility warnings for (overrides COMPATIBILITY_EMAIL_CLIENTS)", parseClientsOption).option("--esbuild-plugins <path>", "Path to a module whose default export is an array of esbuild plugins (or a function returning one) applied when bundling email templates").action(dev);
7836
+ program.command("build").description("Copies the preview app for onto .react-email and builds it").option("-d, --dir <path>", "Directory with your email templates", "./emails").addOption(new Option("-p, --packageManager <name>").hideHelp()).option("--esbuild-plugins <path>", "Path to a module whose default export is an array of esbuild plugins (or a function returning one) applied when bundling email templates").action(build$1);
7793
7837
  program.command("start").description("Runs the built preview app that is inside of \".react-email\"").action(start);
7794
- program.command("export").description("Build the templates to the `out` directory").option("--outDir <path>", "Output directory", "out").option("-p, --pretty", "Pretty print the output", false).option("-t, --plainText", "Set output format as plain text", false).option("-d, --dir <path>", "Directory with your email templates", "./emails").option("-e, --extension <extension>", "Set a custom file extension for rendered emails (e.g. blade.php)").option("-s, --silent", "To, or not to show a spinner with process information", false).action(({ outDir, pretty, plainText, silent, dir: srcDir, extension }) => exportTemplates(outDir, srcDir, {
7838
+ program.command("export").description("Build the templates to the `out` directory").option("--outDir <path>", "Output directory", "out").option("-p, --pretty", "Pretty print the output", false).option("-t, --plainText", "Set output format as plain text", false).option("-d, --dir <path>", "Directory with your email templates", "./emails").option("-e, --extension <extension>", "Set a custom file extension for rendered emails (e.g. blade.php)").option("-s, --silent", "To, or not to show a spinner with process information", false).option("--esbuild-plugins <path>", "Path to a module whose default export is an array of esbuild plugins (or a function returning one) applied when bundling email templates").action(({ outDir, pretty, plainText, silent, dir: srcDir, extension, esbuildPlugins }) => exportTemplates(outDir, srcDir, {
7795
7839
  silent,
7796
7840
  plainText,
7797
7841
  pretty,
7798
- extension
7842
+ extension,
7843
+ esbuildPlugins
7799
7844
  }));
7800
7845
  const resend = program.command("resend");
7801
7846
  resend.command("setup").description("Sets up the integration between the React Email CLI, and your Resend account through an API Key").action(resendSetup);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-email",
3
- "version": "6.10.0",
3
+ "version": "6.11.0",
4
4
  "description": "A live preview of your emails right in your browser.",
5
5
  "bin": {
6
6
  "email": "./dist/cli/index.mjs"
package/readme.md CHANGED
@@ -30,6 +30,14 @@ Starts a local development server that will watch your files and automatically r
30
30
  npx react-email dev
31
31
  ```
32
32
 
33
+ Templates are bundled with [esbuild](https://esbuild.github.io/). To run additional esbuild plugins, for example a compile-time transform your templates depend on, pass `--esbuild-plugins` with the path to a module whose default export is an array of plugins (or a function returning one):
34
+
35
+ ```sh
36
+ npx react-email dev --esbuild-plugins ./esbuild-plugins.mjs
37
+ ```
38
+
39
+ The same option is accepted by `email build` and `email export`.
40
+
33
41
  ### `email export`
34
42
 
35
43
  Generates the plain HTML files of your emails into a `out` directory.