react-email 6.1.4 → 6.1.5

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.1.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 1a61cb0: Avoid OOM when running `email export` on projects with many templates. esbuild builds now run in batches of 10 entry points, and the render phase runs each batch of 25 templates inside a `worker_threads` worker so V8 isolate memory is reclaimed between batches.
8
+
3
9
  ## 6.1.4
4
10
 
5
11
  ### Patch Changes
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
3
3
  import { spawn } from "node:child_process";
4
4
  import { Option, program } from "commander";
5
5
  import * as fs$1 from "node:fs";
6
- import fs, { existsSync, promises, statSync, unlinkSync, writeFileSync } from "node:fs";
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
9
  import url, { fileURLToPath } from "node:url";
@@ -31,7 +31,8 @@ import os from "node:os";
31
31
  import Conf from "conf";
32
32
  import * as nodeUtil from "node:util";
33
33
  import { lookup } from "mime-types";
34
- import { build } from "esbuild";
34
+ import { Worker } from "node:worker_threads";
35
+ import { build, stop } from "esbuild";
35
36
  import { glob } from "glob";
36
37
  import normalize$1 from "normalize-path";
37
38
  //#region \0rolldown/runtime.js
@@ -6522,7 +6523,7 @@ const getEmailsDirectoryMetadata = async (absolutePathToEmailsDirectory, keepFil
6522
6523
  //#region package.json
6523
6524
  var package_default = {
6524
6525
  name: "react-email",
6525
- version: "6.1.4",
6526
+ version: "6.1.5",
6526
6527
  description: "A live preview of your emails right in your browser.",
6527
6528
  bin: { "email": "./dist/cli/index.mjs" },
6528
6529
  type: "module",
@@ -7403,7 +7404,40 @@ const getEmailTemplatesFromDirectory = (emailDirectory) => {
7403
7404
  for (const directory of emailDirectory.subDirectories) templatePaths.push(...getEmailTemplatesFromDirectory(directory));
7404
7405
  return templatePaths;
7405
7406
  };
7406
- const require$1 = createRequire(url.fileURLToPath(import.meta.url));
7407
+ const BUILD_BATCH_SIZE = 10;
7408
+ const RENDER_BATCH_SIZE = 25;
7409
+ const renderWorkerSource = `
7410
+ const { unlinkSync, writeFileSync } = require('node:fs');
7411
+ const { parentPort, workerData } = require('node:worker_threads');
7412
+
7413
+ const { templates, options } = workerData;
7414
+
7415
+ (async () => {
7416
+ for (const template of templates) {
7417
+ try {
7418
+ const emailModule = require(template);
7419
+ const rendered = await emailModule.render(
7420
+ emailModule.reactEmailCreateReactElement(emailModule.default, {}),
7421
+ options,
7422
+ );
7423
+ const htmlPath = template.replace(
7424
+ '.cjs',
7425
+ options.plainText ? '.txt' : '.html',
7426
+ );
7427
+ writeFileSync(htmlPath, rendered);
7428
+ unlinkSync(template);
7429
+ parentPort.postMessage({ type: 'progress', template });
7430
+ } catch (exception) {
7431
+ parentPort.postMessage({
7432
+ type: 'error',
7433
+ template,
7434
+ message: exception && exception.stack ? exception.stack : String(exception),
7435
+ });
7436
+ process.exit(1);
7437
+ }
7438
+ }
7439
+ })();
7440
+ `;
7407
7441
  const exportTemplates = async (pathToWhereEmailMarkupShouldBeDumped, emailsDirectoryPath, options) => {
7408
7442
  let spinner;
7409
7443
  if (!options.silent) {
@@ -7423,20 +7457,24 @@ const exportTemplates = async (pathToWhereEmailMarkupShouldBeDumped, emailsDirec
7423
7457
  if (fs.existsSync(pathToWhereEmailMarkupShouldBeDumped)) fs.rmSync(pathToWhereEmailMarkupShouldBeDumped, { recursive: true });
7424
7458
  const allTemplates = getEmailTemplatesFromDirectory(emailsDirectoryMetadata);
7425
7459
  try {
7426
- await build({
7427
- bundle: true,
7428
- entryPoints: allTemplates,
7429
- external: ["css-tree"],
7430
- format: "cjs",
7431
- jsx: "automatic",
7432
- loader: { ".js": "jsx" },
7433
- logLevel: "silent",
7434
- outExtension: { ".js": ".cjs" },
7435
- outdir: pathToWhereEmailMarkupShouldBeDumped,
7436
- platform: "node",
7437
- plugins: [renderingUtilitiesExporter(allTemplates)],
7438
- write: true
7439
- });
7460
+ for (let i = 0; i < allTemplates.length; i += BUILD_BATCH_SIZE) {
7461
+ const batch = allTemplates.slice(i, i + BUILD_BATCH_SIZE);
7462
+ await build({
7463
+ bundle: true,
7464
+ entryPoints: batch,
7465
+ external: ["css-tree"],
7466
+ format: "cjs",
7467
+ jsx: "automatic",
7468
+ loader: { ".js": "jsx" },
7469
+ logLevel: "silent",
7470
+ outExtension: { ".js": ".cjs" },
7471
+ outdir: pathToWhereEmailMarkupShouldBeDumped,
7472
+ platform: "node",
7473
+ plugins: [renderingUtilitiesExporter(batch)],
7474
+ write: true
7475
+ });
7476
+ await stop();
7477
+ }
7440
7478
  } catch (exception) {
7441
7479
  if (spinner) stopSpinnerAndPersist(spinner, {
7442
7480
  symbol: logSymbols.error,
@@ -7451,20 +7489,41 @@ const exportTemplates = async (pathToWhereEmailMarkupShouldBeDumped, emailsDirec
7451
7489
  spinner.setText(`rendering ${allBuiltTemplates[0]?.split("/").pop()}`);
7452
7490
  spinner.start();
7453
7491
  }
7454
- for await (const template of allBuiltTemplates) try {
7455
- if (spinner) spinner.setText(`rendering ${template.split("/").pop()}`);
7456
- delete require$1.cache[template];
7457
- const emailModule = require$1(template);
7458
- const rendered = await emailModule.render(emailModule.reactEmailCreateReactElement(emailModule.default, {}), options);
7459
- writeFileSync(template.replace(".cjs", options.plainText ? ".txt" : ".html"), rendered);
7460
- unlinkSync(template);
7461
- } catch (exception) {
7462
- if (spinner) stopSpinnerAndPersist(spinner, {
7463
- symbol: logSymbols.error,
7464
- text: `failed when rendering ${template.split("/").pop()}`
7465
- });
7466
- console.error(exception);
7467
- process.exit(1);
7492
+ for (let i = 0; i < allBuiltTemplates.length; i += RENDER_BATCH_SIZE) {
7493
+ const batch = allBuiltTemplates.slice(i, i + RENDER_BATCH_SIZE);
7494
+ let failedTemplate;
7495
+ let failureMessage;
7496
+ try {
7497
+ await new Promise((resolve, reject) => {
7498
+ const worker = new Worker(renderWorkerSource, {
7499
+ eval: true,
7500
+ workerData: {
7501
+ templates: batch,
7502
+ options
7503
+ }
7504
+ });
7505
+ worker.on("message", (msg) => {
7506
+ if (msg.type === "progress") {
7507
+ if (spinner) spinner.setText(`rendering ${msg.template.split("/").pop()}`);
7508
+ } else if (msg.type === "error") {
7509
+ failedTemplate = msg.template;
7510
+ failureMessage = msg.message;
7511
+ }
7512
+ });
7513
+ worker.on("error", reject);
7514
+ worker.on("exit", (code) => {
7515
+ if (code !== 0) reject(new Error(failureMessage ?? `Render worker exited with code ${code}`));
7516
+ else resolve();
7517
+ });
7518
+ });
7519
+ } catch (exception) {
7520
+ if (spinner) stopSpinnerAndPersist(spinner, {
7521
+ symbol: logSymbols.error,
7522
+ text: failedTemplate ? `failed when rendering ${failedTemplate.split("/").pop()}` : "failed when rendering"
7523
+ });
7524
+ console.error(exception);
7525
+ process.exit(1);
7526
+ }
7468
7527
  }
7469
7528
  if (spinner) {
7470
7529
  spinner.succeed("Rendered all files");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-email",
3
- "version": "6.1.4",
3
+ "version": "6.1.5",
4
4
  "description": "A live preview of your emails right in your browser.",
5
5
  "bin": {
6
6
  "email": "./dist/cli/index.mjs"