Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 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
42 changes: 20 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 @@ -15,28 +15,26 @@ const pathToLocalCliScript = path.resolve(
'./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 () => {
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();
});
bench
.add('startup on local', async () => {
const server = await runServer(pathToLocalCliScript);
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.

Awaiting runServer without a timeout can hang indefinitely if the child process fails to emit the expected stdout.

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

<comment>Awaiting runServer without a timeout can hang indefinitely if the child process fails to emit the expected stdout.</comment>

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

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

<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(
-    });
+bench
+  .add(&#39;startup on local&#39;, async () =&gt; {
+    const server = await runServer(pathToLocalCliScript);
+    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`);
server.subprocess.kill();
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.

kill() is asynchronous; await the child process 'close' event after sending the signal to ensure the server fully exits before the next iteration.

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

<comment>kill() is asynchronous; await the child process &#39;close&#39; event after sending the signal to ensure the server fully exits before the next iteration.</comment>

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

})
.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`);
server.subprocess.kill();
});

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',
);
52 changes: 25 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 @@ -15,32 +15,30 @@ const pathToLocalCliScript = path.resolve(
'./node_modules/react-email/dist/cli/index.js',
);

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

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 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();
canaryServer.subprocess.kill();

await fs.writeFile(
'bench-results-30-iterations.json',
JSON.stringify(bench.results),
'utf8',
);
})();
await fetch(`${localServer.url}/preview/magic-links/notion-magic-link`);
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


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

await fs.writeFile(
'bench-results-30-iterations.json',
JSON.stringify(bench.results),
'utf8',
);
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"
}
}
26 changes: 7 additions & 19 deletions benchmarks/tailwind-component/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ determining the performance hits that the Tailwind component causes to try impro
├── src
| ├── emails
| ├── benchmark-0.0.12-vs-local-version.ts
| ├── benchmark-0.0.17-vs-local-version.ts
| ├── benchmark-with-vs-without.ts
| └── tailwind-render.ts
├── tailwind.config.js
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());
28 changes: 10 additions & 18 deletions benchmarks/tailwind-component/src/benchmark-with-vs-without.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,16 @@ import EmailWithoutTailwind from './emails/without-tailwind.js';
// import like this instead of installing from the workspace
// to still be able to test versions that are already published

async function main() {
const bench = new Bench({ time: 100 });
const bench = new Bench({ time: 100 });

bench
.add('without tailwind', async () => {
await render(<EmailWithoutTailwind />);
})
.add('with current tailwind', async () => {
await render(<EmailWithTailwind Tailwind={CurrentTailwind} />);
});

await bench.run();
bench
.add('without tailwind', async () => {
await render(<EmailWithoutTailwind />);
})
.add('with current tailwind', async () => {
await render(<EmailWithTailwind Tailwind={CurrentTailwind} />);
});

return bench;
}
await bench.run();

main()
.then((bench) => {
console.table(bench.table());
})
.catch(console.error);
console.table(bench.table());
3 changes: 2 additions & 1 deletion benchmarks/tailwind-component/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"target": "esnext",
"noUncheckedIndexedAccess": true,
"resolveJsonModule": true,
"moduleResolution": "Node",
"moduleResolution": "bundler",
"module": "esnext",
"declarationMap": false,
"declaration": false,
"outDir": "dist"
Expand Down
Loading