Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions benchmarks/preview-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "@benchmarks/preview-server",
"private": true,
"version": "0.0.0",
"type": "module",
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting type: "module" makes this package ESM, but several scripts use __dirname, which is undefined in ESM. This risks runtime failures when running the benchmark scripts.

Prompt for AI agents
Address the following comment on benchmarks/preview-server/package.json at line 5:

<comment>Setting type: &quot;module&quot; makes this package ESM, but several scripts use __dirname, which is undefined in ESM. This risks runtime failures when running the benchmark scripts.</comment>

<file context>
@@ -2,6 +2,7 @@
   &quot;name&quot;: &quot;@benchmarks/preview-server&quot;,
   &quot;private&quot;: true,
   &quot;version&quot;: &quot;0.0.0&quot;,
+  &quot;type&quot;: &quot;module&quot;,
   &quot;scripts&quot;: {
     &quot;local-vs-2.1.7-canary.2&quot;: &quot;tsx ./src/local-vs-2.1.7-canary.2.ts&quot;,
</file context>
Fix with Cubic

"scripts": {
"local-vs-2.1.7-canary.2": "tsx ./src/local-vs-2.1.7-canary.2.ts",
"local-vs-2.1.7-canary.2-on-startup": "tsx ./src/local-vs-2.1.7-canary.2-on-startup.ts"
Expand Down
50 changes: 28 additions & 22 deletions benchmarks/preview-server/src/local-vs-2.1.7-canary.2-on-startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,45 @@ import { Bench } from 'tinybench';
import { runServer } from './utils/run-server';

const pathToCanaryCliScript = path.resolve(
__dirname,
import.meta.dirname,
'../',
'./node_modules/react-email-2.1.7-canary.2/cli/index.js',
);

const pathToLocalCliScript = path.resolve(
__dirname,
import.meta.dirname,
'../',
'./node_modules/react-email/dist/cli/index.js',
);

(async () => {
const bench = new Bench({
iterations: 30,
});
const bench = new Bench({
iterations: 30,
});

bench
.add('startup on local', async () => {
bench
.add('startup on local', async () => {
try {
const server = await runServer(pathToLocalCliScript);
await fetch(`${server.url}/preview/magic-links/notion-magic-link`);
server.subprocess.kill();
})
.add('startup on 2.1.7-canary.2', async () => {
const server = await runServer(pathToCanaryCliScript);
await fetch(`${server.url}/preview/magic-links/notion-magic-link`);
server.subprocess.kill();
});
if (!server.subprocess.kill()) {
throw new Error('could not close sub process for preview server');
}
} catch (err) {
console.error('Error starting local server:', err);
}
})
.add('startup on 2.1.7-canary.2', async () => {
const server = await runServer(pathToCanaryCliScript);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subprocess may be left running if fetch throws; wrap server lifecycle in a try/finally so kill() always runs for cleanup.

Prompt for AI agents
Address the following comment on benchmarks/preview-server/src/local-vs-2.1.7-canary.2-on-startup.ts at line 29:

<comment>Subprocess may be left running if fetch throws; wrap server lifecycle in a try/finally so kill() always runs for cleanup.</comment>

<file context>
@@ -15,28 +15,26 @@ const pathToLocalCliScript = path.resolve(
+    server.subprocess.kill();
+  })
+  .add(&#39;startup on 2.1.7-canary.2&#39;, async () =&gt; {
+    const server = await runServer(pathToCanaryCliScript);
+    await fetch(`${server.url}/preview/magic-links/notion-magic-link`);
+    server.subprocess.kill();
</file context>
Fix with Cubic

await fetch(`${server.url}/preview/magic-links/notion-magic-link`);
if (!server.subprocess.kill()) {
throw new Error('could not close sub process for preview server');
}
});

await bench.run();
await bench.run();

await fs.writeFile(
'startup-bench-results-30-iterations.json',
JSON.stringify(bench.results),
'utf8',
);
})();
await fs.writeFile(
'startup-bench-results-30-iterations.json',
JSON.stringify(bench.results),
'utf8',
);
50 changes: 23 additions & 27 deletions benchmarks/preview-server/src/local-vs-2.1.7-canary.2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,39 @@ import { Bench } from 'tinybench';
import { runServer } from './utils/run-server';

const pathToCanaryCliScript = path.resolve(
__dirname,
import.meta.dirname,
'../',
'./node_modules/react-email-2.1.7-canary.2/cli/index.js',
);

const pathToLocalCliScript = path.resolve(
__dirname,
import.meta.dirname,
'../',
'./node_modules/react-email/dist/cli/index.js',
);

(async () => {
const bench = new Bench({
iterations: 30,
const bench = new Bench({
iterations: 30,
warmupIterations: 5,
});

const localServer = await runServer(pathToLocalCliScript);
const canaryServer = await runServer(pathToCanaryCliScript);
bench
.add('local', async () => {
await fetch(`${localServer.url}/preview/magic-links/notion-magic-link`);
})
.add('2.1.7-canary.2', async () => {
await fetch(`${canaryServer.url}/preview/magic-links/notion-magic-link`);
});

const localServer = await runServer(pathToLocalCliScript);
const canaryServer = await runServer(pathToCanaryCliScript);
bench
.add('local', async () => {
await fetch(`${localServer.url}/preview/magic-links/notion-magic-link`);
})
.add('2.1.7-canary.2', async () => {
await fetch(`${canaryServer.url}/preview/magic-links/notion-magic-link`);
});
await bench.run();
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure server subprocesses are terminated via try/finally so they’re killed even if an await (fetch/run/writeFile) throws.

Prompt for AI agents
Address the following comment on benchmarks/preview-server/src/local-vs-2.1.7-canary.2.ts at line 35:

<comment>Ensure server subprocesses are terminated via try/finally so they’re killed even if an await (fetch/run/writeFile) throws.</comment>

<file context>
@@ -15,32 +15,30 @@ const pathToLocalCliScript = path.resolve(
+await fetch(`${localServer.url}/preview/magic-links/notion-magic-link`);
+await fetch(`${canaryServer.url}/preview/magic-links/notion-magic-link`);
+
+await bench.run();
+
+localServer.subprocess.kill();
</file context>
Fix with Cubic


await fetch(`${localServer.url}/preview/magic-links/notion-magic-link`);
await fetch(`${canaryServer.url}/preview/magic-links/notion-magic-link`);
localServer.subprocess.kill();
canaryServer.subprocess.kill();

await bench.run();

localServer.subprocess.kill();
canaryServer.subprocess.kill();

await fs.writeFile(
'bench-results-30-iterations.json',
JSON.stringify(bench.results),
'utf8',
);
})();
await fs.writeFile(
'bench-results-30-iterations.json',
JSON.stringify(bench.results),
'utf8',
);

This file was deleted.

20 changes: 19 additions & 1 deletion benchmarks/preview-server/src/utils/run-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,25 @@ export interface Server {
export function runServer(pathToCliScript: string) {
return new Promise<Server>((resolve, reject) => {
const node = spawn('node', [pathToCliScript, 'dev'], {
cwd: path.resolve(__dirname, '../../../../apps/demo'),
cwd: path.resolve(import.meta.dirname, '../../../../apps/demo'),
});

const kill = () => {
node.kill();
};

process.addListener('exit', kill);
process.addListener('SIGINT', kill);
process.addListener('SIGTERM', kill);
process.addListener('SIGUSR1', kill);
process.addListener('SIGUSR2', kill);

node.on('close', () => {
process.removeListener('exit', kill);
process.removeListener('SIGINT', kill);
process.removeListener('SIGTERM', kill);
process.removeListener('SIGUSR1', kill);
process.removeListener('SIGUSR2', kill);
});

node.stdout.on('data', (data) => {
Expand Down
16 changes: 16 additions & 0 deletions benchmarks/preview-server/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "tsconfig/react-library.json",
"include": ["src"],
"exclude": ["node_modules"],
"compilerOptions": {
"target": "esnext",
"noUncheckedIndexedAccess": true,
"resolveJsonModule": true,
"moduleResolution": "bundler",
"module": "esnext",
"declarationMap": false,
"declaration": false,
"outDir": "dist"
}
}
32 changes: 10 additions & 22 deletions benchmarks/tailwind-component/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ determining the performance hits that the Tailwind component causes to try impro
├── package.json
├── src
| ├── emails
| ├── benchmark-0.0.12-vs-local-version.ts
| ├── benchmark-with-vs-without.ts
| └── tailwind-render.ts
| ├── benchmark-0.0.12-vs-local-version.tsx
| ├── benchmark-0.0.17-vs-local-version.tsx
| ├── benchmark-with-vs-without.tsx
| └── tailwind-render.tsx
├── tailwind.config.js
└── tsconfig.json
```
Expand All @@ -25,26 +26,13 @@ The `emails` folder contains examples to be used across different benchmarks.

## Running benchmarks

To avoid ESM problems, these benchmarks need to be compiled using `tsup`,
which can be done by running `pnpm compile`, and then using `node` directly.
Something like the following if you want to run the `with-vs-without` benchmark:

```sh
pnpm compile && node ./dist/benchmark-with-vs-without.js
```

They are each compiled into a different entry on the `./dist` folder with their respective names.

We have scripts for each benchmark on our `./package.json` that you can try running:

```json
"scripts": {
"with-vs-without": "pnpm compile && node ./dist/benchmark-with-vs-without.js",
"before-perf-vs-after-perf": "pnpm compile && node ./dist/benchmark-0.0.12-vs-local-version",

"flamegraph-render-tailwind": "pnpm compile && node --prof ./dist/tailwind-render && node --prof-process --preprocess -j isolate*.log | flamebearer",

"compile": "tsup src/*.ts",
"lint": "eslint ."
},
"scripts": {
"with-vs-without": "tsx ./src/benchmark-with-vs-without",
"0.0.17-vs-local": "tsx --max-old-space-size=256 ./src/benchmark-0.0.17-vs-local-version",
"0.0.12-vs-local": "tsx ./src/benchmark-0.0.12-vs-local-version",
"flamegraph-render-tailwind": "tsx --prof ./src/tailwind-render && node --prof-process --preprocess -j isolate*.log | flamebearer"
},
```
2 changes: 1 addition & 1 deletion benchmarks/tailwind-component/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@benchmarks/tailwind-component",
"private": true,
"main": "dist/benchmark.js",
"type": "module",
"version": "0.0.0",
"scripts": {
"with-vs-without": "tsx ./src/benchmark-with-vs-without",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,24 @@ import { Tailwind as VersionTwelveTailwind } from 'tailwind-0.0.12';
import { Bench } from 'tinybench';
import EmailWithTailwind from './emails/with-tailwind.js';

const main = async () => {
const bench = new Bench({
iterations: 100,
});

bench
.add('local', async () => {
await render(<EmailWithTailwind Tailwind={LocalTailwind} />);
})
.add('0.0.12', async () => {
// @ts-expect-error
await render(<EmailWithTailwind Tailwind={VersionTwelveTailwind} />);
});
const bench = new Bench({
iterations: 100,
});

await bench.run();
bench
.add('local', async () => {
await render(<EmailWithTailwind Tailwind={LocalTailwind} />);
})
.add('0.0.12', async () => {
// @ts-expect-error
await render(<EmailWithTailwind Tailwind={VersionTwelveTailwind} />);
});

return bench;
};
await bench.run();

main()
.then((bench) => {
writeFileSync(
'bench-results-100-iterations.json',
JSON.stringify(bench.results),
'utf-8',
);
console.table(bench.table());
})
.catch(console.error);
writeFileSync(
'bench-results-100-iterations.json',
JSON.stringify(bench.results),
'utf-8',
);
console.table(bench.table());
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,26 @@ import { Tailwind as VersionSeventeenTailwind } from 'tailwind-0.0.17';
import { Bench } from 'tinybench';
import EmailWithTailwind from './emails/with-tailwind.js';

const main = async () => {
const bench = new Bench({
iterations: 100,
});

bench
.add('local', async () => {
await render(<EmailWithTailwind Tailwind={LocalTailwind} />);
})
.add('0.0.17', async () => {
await render(<EmailWithTailwind Tailwind={VersionSeventeenTailwind} />);
});
const bench = new Bench({
iterations: 100,
});

await bench.run();
bench
.add('local', async () => {
await render(<EmailWithTailwind Tailwind={LocalTailwind} />);
})
.add('0.0.17', async () => {
// Doing as any here because of the React types mismatch between versions, but things should be fine
await render(
<EmailWithTailwind Tailwind={VersionSeventeenTailwind as any} />,
);
});

return bench;
};
await bench.run();

main()
.then((bench) => {
writeFileSync(
'bench-results-100-iterations.json',
JSON.stringify(bench.results),
'utf-8',
);
console.table(bench.table());
})
.catch(console.error);
writeFileSync(
'bench-results-100-iterations.json',
JSON.stringify(bench.results),
'utf-8',
);
console.table(bench.table());
Loading
Loading