Skip to content

feat: add TSX parsing benchmark#122

Open
caffeinum wants to merge 3 commits intomainfrom
feat/tsx-parse-benchmark
Open

feat: add TSX parsing benchmark#122
caffeinum wants to merge 3 commits intomainfrom
feat/tsx-parse-benchmark

Conversation

@caffeinum
Copy link
Copy Markdown
Contributor

  • Add benchmark script to measure TSX component tree parsing speed
  • Includes simple (18 nodes) and complex (51 nodes) test cases
  • Results: ~0.005ms median, ~19K parses/sec for complex components
  • Linear scaling: ~0.001ms per node

- Add benchmark script to measure TSX component tree parsing speed
- Includes simple (18 nodes) and complex (51 nodes) test cases
- Results: ~0.005ms median, ~19K parses/sec for complex components
- Linear scaling: ~0.001ms per node
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 134026f and 8433589.

📒 Files selected for processing (2)
  • benchmarks/complex.tsx
  • benchmarks/simple.tsx

📝 Walkthrough

walkthrough

three new files added under benchmarks: two tsx video composition demos (simple and complex) and a bun-based parse benchmark that dynamically imports and analyzes tsx trees with warmup, per-run timings, node stats, and percentile/throughput metrics.

changes

Cohort / File(s) Summary
video composition demos
benchmarks/complex.tsx, benchmarks/simple.tsx
added default-exported vargai/jsx render compositions: complex.tsx builds a 1920×1080, 30fps multi-scene render with 12 clips, overlays, music, captions; simple.tsx provides a smaller 4-clip demo with overlays, music, and captions.
benchmarking script
benchmarks/parse.ts
added a bun-based parsing benchmark that dynamically imports a target tsx with cache-busting, walks the component tree to count node types, runs warmup and timed iterations, and outputs stats (min/max/avg/stddev/p50/p95/p99/throughput).

estimated code review effort

🎯 3 (moderate) | ⏱️ ~20 minutes

poem

🎬 a trio of benchmarks lands, neat and slow
simple clips hum, complex scenes glow
parse runs warm, timers start the show
node counts tumble, stats in a row
meow — let's watch the numbers grow 🐾

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed the title directly describes the main changeset: adding a tsx parsing benchmark script with test cases.
Description check ✅ Passed the description clearly relates to the changeset, detailing the benchmark script, test cases, and performance results.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/tsx-parse-benchmark

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
benchmarks/parse.ts (1)

12-12: use arrow functions — guideline

function countNodes, function getNodeTypes, and function benchmark are function declarations; guidelines prefer arrow functions

♻️ example
-function countNodes(element: any): number {
+const countNodes = (element: unknown): number => {

as per coding guidelines, "Use arrow functions instead of function expressions"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@benchmarks/parse.ts` at line 12, Convert the declared functions countNodes,
getNodeTypes, and benchmark into arrow functions per the style guide: replace
each "function name(...)" declaration with a const binding using an arrow (e.g.,
const countNodes = (element: any): number => { ... }), preserving the original
parameter types, return types, and body logic; update any internal references or
exports to use the new constant names (countNodes, getNodeTypes, benchmark) so
behavior remains identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@benchmarks/complex.tsx`:
- Line 3: Remove the unused named imports elevenlabs and fal from the import
statement that currently reads import { elevenlabs, fal } from "vargai/ai"; — if
that import line only contains those two names, delete the entire import; if it
also imports other used symbols, drop elevenlabs and fal from the specifier list
to avoid unused-import lint errors and ensure no side-effect-only import is
removed.

In `@benchmarks/parse.ts`:
- Around line 159-162: The current parseInt for iterations can produce 0 or NaN
which leads to an empty times array and downstream crashes (avg/p50 undefined);
validate and normalize the parsed iterations before calling benchmark: parse the
value from process.argv[3] into a number, check isFinite and greater than 0
(e.g., let iterations = Number.parseInt(...) ; if (!Number.isFinite(iterations)
|| iterations < 1) iterations = 50), then pass that safe iterations variable
into benchmark(file, iterations). Also consider logging or informing the user
when their input was invalid.
- Around line 12-36: The functions use the any type and extra props.children
checks; import VargElement from "src/react/types" and replace the parameter
types: change countNodes(element: any) to countNodes(element: VargElement),
change getNodeTypes(...) and the parameter component: any to use VargElement as
well, remove the redundant element.props?.children branch (since VargElement has
children directly) and iterate only over element.children (which may be array or
single child) to recursively call countNodes/getNodeTypes so types align with
the VargElement shape.

In `@benchmarks/simple.tsx`:
- Line 3: Remove the unused imports by deleting the named imports "elevenlabs"
and "fal" from the import statement that currently reads import { elevenlabs,
fal } from "vargai/ai"; so only the actually used symbols remain (or convert to
a default/import-less form if nothing from "vargai/ai" is used); update the
import at the top of benchmarks/simple.tsx to eliminate the unused "elevenlabs"
and "fal" identifiers.

---

Nitpick comments:
In `@benchmarks/parse.ts`:
- Line 12: Convert the declared functions countNodes, getNodeTypes, and
benchmark into arrow functions per the style guide: replace each "function
name(...)" declaration with a const binding using an arrow (e.g., const
countNodes = (element: any): number => { ... }), preserving the original
parameter types, return types, and body logic; update any internal references or
exports to use the new constant names (countNodes, getNodeTypes, benchmark) so
behavior remains identical.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f54c593 and 134026f.

📒 Files selected for processing (3)
  • benchmarks/complex.tsx
  • benchmarks/parse.ts
  • benchmarks/simple.tsx

Comment on lines +12 to +36
function countNodes(element: any): number {
if (!element || typeof element !== "object" || !("type" in element)) {
return 0;
}

let count = 1;

if (element.children && Array.isArray(element.children)) {
for (const child of element.children) {
count += countNodes(child);
}
}

if (element.props?.children) {
if (Array.isArray(element.props.children)) {
for (const child of element.props.children) {
count += countNodes(child);
}
} else {
count += countNodes(element.props.children);
}
}

return count;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n benchmarks/parse.ts | head -80

Repository: vargHQ/sdk

Length of output: 2507


🏁 Script executed:

# search for vargai jsx transform or createElement patterns in codebase
rg "vargai" --type ts -A 2 -B 2 | head -50

Repository: vargHQ/sdk

Length of output: 2854


🏁 Script executed:

# check if there's a vargai package or docs in node_modules
fd -t f "package.json" | xargs grep -l "vargai" 2>/dev/null | head -5

Repository: vargHQ/sdk

Length of output: 66


🏁 Script executed:

# look for how elements are actually created/structured
rg "props.*children|\.children" --type ts -C 2 | head -80

Repository: vargHQ/sdk

Length of output: 4435


🏁 Script executed:

find src -name "types.ts" -o -name "types.d.ts" | xargs cat -n 2>/dev/null | head -100

Repository: vargHQ/sdk

Length of output: 3803


🏁 Script executed:

# search for VargElement type definition
rg "type VargElement|interface VargElement" --type ts -A 10

Repository: vargHQ/sdk

Length of output: 855


🏁 Script executed:

# check the react.tsx or main jsx runtime file
fd -t f "react.tsx|jsx.ts|runtime" src/react/ | head -10

Repository: vargHQ/sdk

Length of output: 123


replace any types with VargElement interface

the element: any params violate the "avoid any type" guideline. import VargElement from src/react/types and use it instead. this also removes the unnecessary check for element.props?.children since VargElement structure has children as a direct property, not nested in props.

quick fix
+import type { VargElement } from "../react/types";
+
-function countNodes(element: any): number {
+function countNodes(element: VargElement): number {
   if (!element || typeof element !== "object" || !("type" in element)) {
     return 0;
   }

   let count = 1;

-  if (element.children && Array.isArray(element.children)) {
-    for (const child of element.children) {
+  for (const child of element.children) {
+    if (child && typeof child === "object" && "type" in child) {
       count += countNodes(child);
     }
   }
-
-  if (element.props?.children) {
-    if (Array.isArray(element.props.children)) {
-      for (const child of element.props.children) {
-        count += countNodes(child);
-      }
-    } else {
-      count += countNodes(element.props.children);
-    }
-  }

   return count;
 }

same applies to getNodeTypes at line 38 and component: any at line 78.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@benchmarks/parse.ts` around lines 12 - 36, The functions use the any type and
extra props.children checks; import VargElement from "src/react/types" and
replace the parameter types: change countNodes(element: any) to
countNodes(element: VargElement), change getNodeTypes(...) and the parameter
component: any to use VargElement as well, remove the redundant
element.props?.children branch (since VargElement has children directly) and
iterate only over element.children (which may be array or single child) to
recursively call countNodes/getNodeTypes so types align with the VargElement
shape.

Comment on lines +159 to +162
const file = process.argv[2] || "video.tsx";
const iterations = parseInt(process.argv[3] || "50", 10);

benchmark(file, iterations).catch(console.error);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

zero/NaN iterations crashes at runtime

parseInt can return 0 or NaN — either way times stays empty → avg = NaNp50.toFixed(3) throws TypeError: Cannot read properties of undefined 🙀

🛡️ proposed fix
 const iterations = parseInt(process.argv[3] || "50", 10);
+if (!Number.isFinite(iterations) || iterations < 1) {
+  console.error("iterations must be a positive integer");
+  process.exit(1);
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@benchmarks/parse.ts` around lines 159 - 162, The current parseInt for
iterations can produce 0 or NaN which leads to an empty times array and
downstream crashes (avg/p50 undefined); validate and normalize the parsed
iterations before calling benchmark: parse the value from process.argv[3] into a
number, check isFinite and greater than 0 (e.g., let iterations =
Number.parseInt(...) ; if (!Number.isFinite(iterations) || iterations < 1)
iterations = 50), then pass that safe iterations variable into benchmark(file,
iterations). Also consider logging or informing the user when their input was
invalid.

caffeinum and others added 2 commits February 24, 2026 17:18
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants