react-email 6.10.0 → 6.11.1

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,18 @@
1
1
  # react-email
2
2
 
3
+ ## 6.11.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 54769fc: Prevent `email export` from deleting the project, the home directory, the email templates or the static assets when `--outDir` points at them.
8
+ - 6926bac: `<Markdown>` now applies `markdownCustomStyles.th` to table header cells. Header cells were previously styled with `td`, so any `th` styles were ignored.
9
+
10
+ ## 6.11.0
11
+
12
+ ### Minor Changes
13
+
14
+ - 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.
15
+
3
16
  ## 6.10.0
4
17
 
5
18
  ### 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.1",
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) => {
@@ -7541,6 +7580,19 @@ const getEmailTemplatesFromDirectory = (emailDirectory) => {
7541
7580
  for (const directory of emailDirectory.subDirectories) templatePaths.push(...getEmailTemplatesFromDirectory(directory));
7542
7581
  return templatePaths;
7543
7582
  };
7583
+ const isInside = (parent, child) => child.startsWith(`${parent}${path.sep}`);
7584
+ const getUnsafeOutDirMessage = (outDir, emailsDirectoryPath, { cwd, homeDirectory }) => {
7585
+ const resolvedOutDir = path.resolve(cwd, outDir);
7586
+ const resolvedEmailsDirectory = path.resolve(cwd, emailsDirectoryPath);
7587
+ const staticDirectory = path.join(resolvedEmailsDirectory, "static");
7588
+ if (resolvedOutDir === path.parse(resolvedOutDir).root) return "--outDir can't be the filesystem root.";
7589
+ if (resolvedOutDir === homeDirectory) return "--outDir can't be your home directory.";
7590
+ if (isInside(resolvedOutDir, homeDirectory)) return "--outDir can't contain your home directory.";
7591
+ if (resolvedOutDir === cwd) return "--outDir can't be the project directory.";
7592
+ if (isInside(resolvedOutDir, cwd)) return "--outDir can't contain the project directory.";
7593
+ if (resolvedOutDir === resolvedEmailsDirectory || isInside(resolvedOutDir, resolvedEmailsDirectory)) return "--outDir can't contain your email templates.";
7594
+ if (resolvedOutDir === staticDirectory || isInside(staticDirectory, resolvedOutDir)) return "--outDir can't be your static assets directory.";
7595
+ };
7544
7596
  const BUILD_BATCH_SIZE = 10;
7545
7597
  const RENDER_BATCH_SIZE = 25;
7546
7598
  const renderWorkerSource = `
@@ -7588,9 +7640,26 @@ const exportTemplates = async (pathToWhereEmailMarkupShouldBeDumped, emailsDirec
7588
7640
  else console.error(`Could not find the directory at ${emailsDirectoryPath}`);
7589
7641
  process.exit(1);
7590
7642
  }
7591
- if (fs.existsSync(pathToWhereEmailMarkupShouldBeDumped)) fs.rmSync(pathToWhereEmailMarkupShouldBeDumped, { recursive: true });
7643
+ const resolvedPathToWhereEmailMarkupShouldBeDumped = path.resolve(process.cwd(), pathToWhereEmailMarkupShouldBeDumped);
7644
+ if (fs.existsSync(resolvedPathToWhereEmailMarkupShouldBeDumped)) {
7645
+ const unsafeOutDirMessage = getUnsafeOutDirMessage(resolvedPathToWhereEmailMarkupShouldBeDumped, emailsDirectoryPath, {
7646
+ cwd: process.cwd(),
7647
+ homeDirectory: os.homedir()
7648
+ });
7649
+ if (unsafeOutDirMessage) {
7650
+ const message = `${unsafeOutDirMessage} Choose a different --outDir, such as "out".`;
7651
+ if (spinner) stopSpinnerAndPersist(spinner, {
7652
+ symbol: logSymbols.error,
7653
+ text: message
7654
+ });
7655
+ else console.error(message);
7656
+ process.exit(1);
7657
+ }
7658
+ fs.rmSync(resolvedPathToWhereEmailMarkupShouldBeDumped, { recursive: true });
7659
+ }
7592
7660
  const allTemplates = getEmailTemplatesFromDirectory(emailsDirectoryMetadata);
7593
7661
  try {
7662
+ const userPlugins = options.esbuildPlugins ? await loadEsbuildPlugins(path.resolve(process.cwd(), options.esbuildPlugins)) : [];
7594
7663
  for (let i = 0; i < allTemplates.length; i += BUILD_BATCH_SIZE) {
7595
7664
  const batch = allTemplates.slice(i, i + BUILD_BATCH_SIZE);
7596
7665
  await build({
@@ -7604,7 +7673,11 @@ const exportTemplates = async (pathToWhereEmailMarkupShouldBeDumped, emailsDirec
7604
7673
  outExtension: { ".js": ".cjs" },
7605
7674
  outdir: pathToWhereEmailMarkupShouldBeDumped,
7606
7675
  platform: "node",
7607
- plugins: [inlineCssLoader(), renderingUtilitiesExporter(batch)],
7676
+ plugins: [
7677
+ inlineCssLoader(),
7678
+ renderingUtilitiesExporter(batch),
7679
+ ...userPlugins
7680
+ ],
7608
7681
  write: true
7609
7682
  });
7610
7683
  await stop();
@@ -7788,14 +7861,15 @@ if (!requiredFlags.every((flag) => process.execArgv.includes(flag))) spawn(proce
7788
7861
  });
7789
7862
  else {
7790
7863
  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);
7864
+ 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);
7865
+ 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
7866
  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, {
7867
+ 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
7868
  silent,
7796
7869
  plainText,
7797
7870
  pretty,
7798
- extension
7871
+ extension,
7872
+ esbuildPlugins
7799
7873
  }));
7800
7874
  const resend = program.command("resend");
7801
7875
  resend.command("setup").description("Sets up the integration between the React Email CLI, and your Resend account through an API Key").action(resendSetup);
@@ -80,7 +80,8 @@ const Markdown = react.forwardRef(({ children, markdownContainerStyles, markdown
80
80
  renderer.tablecell = ({ tokens, align, header }) => {
81
81
  const text = renderer.parser.parseInline(tokens);
82
82
  const type = header ? "th" : "td";
83
- return `${align ? `<${type} align="${align}"${require_parse_css_in_js_to_inline_css.parseCssInJsToInlineCss(finalStyles.td) !== "" ? ` style="${require_parse_css_in_js_to_inline_css.parseCssInJsToInlineCss(finalStyles.td)}"` : ""}>` : `<${type}${require_parse_css_in_js_to_inline_css.parseCssInJsToInlineCss(finalStyles.td) !== "" ? ` style="${require_parse_css_in_js_to_inline_css.parseCssInJsToInlineCss(finalStyles.td)}"` : ""}>`}${text}</${type}>\n`;
83
+ const style = require_parse_css_in_js_to_inline_css.parseCssInJsToInlineCss(finalStyles[type]);
84
+ return `${`<${type}${align ? ` align="${align}"` : ""}${style !== "" ? ` style="${style}"` : ""}>`}${text}</${type}>\n`;
84
85
  };
85
86
  renderer.tablerow = ({ text }) => {
86
87
  return `<tr${require_parse_css_in_js_to_inline_css.parseCssInJsToInlineCss(finalStyles.tr) !== "" ? ` style="${require_parse_css_in_js_to_inline_css.parseCssInJsToInlineCss(finalStyles.tr)}"` : ""}>\n${text}</tr>\n`;
@@ -78,7 +78,8 @@ const Markdown = React$1.forwardRef(({ children, markdownContainerStyles, markdo
78
78
  renderer.tablecell = ({ tokens, align, header }) => {
79
79
  const text = renderer.parser.parseInline(tokens);
80
80
  const type = header ? "th" : "td";
81
- return `${align ? `<${type} align="${align}"${parseCssInJsToInlineCss(finalStyles.td) !== "" ? ` style="${parseCssInJsToInlineCss(finalStyles.td)}"` : ""}>` : `<${type}${parseCssInJsToInlineCss(finalStyles.td) !== "" ? ` style="${parseCssInJsToInlineCss(finalStyles.td)}"` : ""}>`}${text}</${type}>\n`;
81
+ const style = parseCssInJsToInlineCss(finalStyles[type]);
82
+ return `${`<${type}${align ? ` align="${align}"` : ""}${style !== "" ? ` style="${style}"` : ""}>`}${text}</${type}>\n`;
82
83
  };
83
84
  renderer.tablerow = ({ text }) => {
84
85
  return `<tr${parseCssInJsToInlineCss(finalStyles.tr) !== "" ? ` style="${parseCssInJsToInlineCss(finalStyles.tr)}"` : ""}>\n${text}</tr>\n`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-email",
3
- "version": "6.10.0",
3
+ "version": "6.11.1",
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.
@@ -218,4 +218,33 @@ console.log(\`Hello, $\{name}!\`);
218
218
  </div><!--/$-->"
219
219
  `);
220
220
  });
221
+ it('applies th styles to table header cells and td styles to body cells', async () => {
222
+ const actualOutput = await render(
223
+ <Markdown
224
+ markdownCustomStyles={{
225
+ th: { fontWeight: 'bold' },
226
+ td: { color: 'gray' },
227
+ }}
228
+ >
229
+ {`| Name | Qty |
230
+ | ---- | --: |
231
+ | Apples | 3 |`}
232
+ </Markdown>,
233
+ );
234
+ expect(actualOutput).toMatchInlineSnapshot(`
235
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><div data-id="react-email-markdown"><table role="presentation">
236
+ <thead>
237
+ <tr>
238
+ <th style="font-weight:bold">Name</th>
239
+ <th align="right" style="font-weight:bold">Qty</th>
240
+ </tr>
241
+ </thead>
242
+ <tbody><tr>
243
+ <td style="color:gray">Apples</td>
244
+ <td align="right" style="color:gray">3</td>
245
+ </tr>
246
+ </tbody></table>
247
+ </div><!--/$-->"
248
+ `);
249
+ });
221
250
  });
@@ -197,17 +197,10 @@ export const Markdown = React.forwardRef<HTMLDivElement, MarkdownProps>(
197
197
  renderer.tablecell = ({ tokens, align, header }) => {
198
198
  const text = renderer.parser.parseInline(tokens);
199
199
  const type = header ? 'th' : 'td';
200
- const tag = align
201
- ? `<${type} align="${align}"${
202
- parseCssInJsToInlineCss(finalStyles.td) !== ''
203
- ? ` style="${parseCssInJsToInlineCss(finalStyles.td)}"`
204
- : ''
205
- }>`
206
- : `<${type}${
207
- parseCssInJsToInlineCss(finalStyles.td) !== ''
208
- ? ` style="${parseCssInJsToInlineCss(finalStyles.td)}"`
209
- : ''
210
- }>`;
200
+ const style = parseCssInJsToInlineCss(finalStyles[type]);
201
+ const tag = `<${type}${align ? ` align="${align}"` : ''}${
202
+ style !== '' ? ` style="${style}"` : ''
203
+ }>`;
211
204
  return `${tag}${text}</${type}>\n`;
212
205
  };
213
206